1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.healthconnect; 18 19 import android.annotation.Nullable; 20 import android.content.Context; 21 import android.health.connect.ratelimiter.RateLimiter; 22 import android.os.Process; 23 import android.os.UserHandle; 24 import android.os.UserManager; 25 import android.util.Slog; 26 27 import com.android.healthfitness.flags.Flags; 28 import com.android.server.SystemService; 29 import com.android.server.healthconnect.exportimport.ExportImportJobs; 30 import com.android.server.healthconnect.injector.HealthConnectInjector; 31 import com.android.server.healthconnect.injector.HealthConnectInjectorImpl; 32 import com.android.server.healthconnect.migration.MigratorPackageChangesReceiver; 33 import com.android.server.healthconnect.storage.HealthConnectContext; 34 35 import java.util.Objects; 36 37 /** 38 * HealthConnect system service scaffold. 39 * 40 * @hide 41 */ 42 public class HealthConnectManagerService extends SystemService { 43 private static final String TAG = "HealthConnectManagerService"; 44 private final Context mContext; 45 private final HealthConnectServiceImpl mHealthConnectService; 46 private final UserManager mUserManager; 47 private final HealthConnectInjector mHealthConnectInjector; 48 private final RateLimiter mRateLimiter; 49 50 private UserHandle mCurrentForegroundUser; 51 HealthConnectManagerService(Context context)52 public HealthConnectManagerService(Context context) { 53 super(context); 54 mRateLimiter = new RateLimiter(); 55 mContext = context; 56 mCurrentForegroundUser = context.getUser(); 57 mUserManager = context.getSystemService(UserManager.class); 58 59 HealthConnectInjector.setInstance(new HealthConnectInjectorImpl(context)); 60 mHealthConnectInjector = HealthConnectInjector.getInstance(); 61 mHealthConnectService = 62 new HealthConnectServiceImpl( 63 mContext, 64 mHealthConnectInjector.getTimeSource(), 65 mHealthConnectInjector.getInternalHealthConnectMappings(), 66 mHealthConnectInjector.getTransactionManager(), 67 mHealthConnectInjector.getHealthConnectPermissionHelper(), 68 mHealthConnectInjector.getFirstGrantTimeManager(), 69 mHealthConnectInjector.getMigrationEntityHelper(), 70 mHealthConnectInjector.getMigrationStateManager(), 71 mHealthConnectInjector.getMigrationUiStateManager(), 72 mHealthConnectInjector.getMigrationCleaner(), 73 mHealthConnectInjector.getFitnessRecordUpsertHelper(), 74 mHealthConnectInjector.getFitnessRecordReadHelper(), 75 mHealthConnectInjector.getFitnessRecordDeleteHelper(), 76 mHealthConnectInjector.getFitnessRecordAggregateHelper(), 77 mHealthConnectInjector.getMedicalResourceHelper(), 78 mHealthConnectInjector.getMedicalDataSourceHelper(), 79 mHealthConnectInjector.getExportManager(), 80 mHealthConnectInjector.getExportImportSettingsStorage(), 81 mHealthConnectInjector.getExportImportNotificationSender(), 82 mHealthConnectInjector.getBackupRestore(), 83 mHealthConnectInjector.getAccessLogsHelper(), 84 mHealthConnectInjector.getHealthDataCategoryPriorityHelper(), 85 mHealthConnectInjector.getActivityDateHelper(), 86 mHealthConnectInjector.getChangeLogsHelper(), 87 mHealthConnectInjector.getChangeLogsRequestHelper(), 88 mHealthConnectInjector.getPriorityMigrationHelper(), 89 mHealthConnectInjector.getAppInfoHelper(), 90 mHealthConnectInjector.getDeviceInfoHelper(), 91 mHealthConnectInjector.getPreferenceHelper(), 92 mHealthConnectInjector.getDatabaseHelpers(), 93 mHealthConnectInjector.getPreferencesManager(), 94 mHealthConnectInjector.getReadAccessLogsHelper(), 95 mHealthConnectInjector.getAppOpsManagerLocal(), 96 mHealthConnectInjector.getThreadScheduler(), 97 mRateLimiter, 98 mHealthConnectInjector.getEnvironmentDataDirectory(), 99 mHealthConnectInjector.getExportImportLogger(), 100 mHealthConnectInjector.getHealthFitnessStatsLog(), 101 mHealthConnectInjector.getBackupRestoreLogger()); 102 } 103 104 @Override onStart()105 public void onStart() { 106 mHealthConnectInjector 107 .getPermissionPackageChangesOrchestrator() 108 .registerBroadcastReceiver(mContext); 109 new MigratorPackageChangesReceiver(mHealthConnectInjector.getMigrationStateManager()) 110 .registerBroadcastReceiver(mContext); 111 publishBinderService(Context.HEALTHCONNECT_SERVICE, mHealthConnectService); 112 } 113 114 /** 115 * NOTE: Don't put any code that uses DB in onUserSwitching, such code should be part of 116 * switchToSetupForUser which is only called once DB is in usable state. 117 */ 118 @Override onUserSwitching(@ullable TargetUser from, TargetUser to)119 public void onUserSwitching(@Nullable TargetUser from, TargetUser to) { 120 if (from != null && mUserManager.isUserUnlocked(from.getUserHandle())) { 121 // We need to cancel any pending timers for the foreground user before it goes into the 122 // background. 123 mHealthConnectService.cancelBackupRestoreTimeouts(); 124 } 125 126 HealthConnectThreadScheduler threadScheduler = mHealthConnectInjector.getThreadScheduler(); 127 threadScheduler.shutdownThreadPools(); 128 mRateLimiter.clearCache(); 129 HealthConnectDailyJobs.cancelAllJobs(mContext); 130 mHealthConnectInjector.getDatabaseHelpers().clearAllCache(); 131 mHealthConnectInjector.getTransactionManager().shutDownCurrentUser(); 132 mHealthConnectInjector.getMigrationStateManager().shutDownCurrentUser(mContext); 133 threadScheduler.resetThreadPools(); 134 135 mCurrentForegroundUser = to.getUserHandle(); 136 137 if (mUserManager.isUserUnlocked(to.getUserHandle())) { 138 // The user is already in unlocked state, so we should proceed with our setup right now, 139 // as we won't be getting a onUserUnlocked callback 140 setupForCurrentForegroundUser(); 141 } 142 } 143 144 // NOTE: The only scenario in which onUserUnlocked's code should be triggered is if the 145 // foreground user is unlocked. If {@code user} is not a foreground user, the following 146 // code should only be triggered when the {@code user} actually gets unlocked. And in 147 // such cases onUserSwitching will be triggered for {@code user} and this code will be 148 // triggered then. 149 @Override onUserUnlocked(TargetUser user)150 public void onUserUnlocked(TargetUser user) { 151 Objects.requireNonNull(user); 152 if (!user.getUserHandle().equals(mCurrentForegroundUser)) { 153 // Ignore unlocking requests for non-foreground users 154 return; 155 } 156 157 setupForCurrentForegroundUser(); 158 } 159 160 @Override isUserSupported(TargetUser user)161 public boolean isUserSupported(TargetUser user) { 162 UserManager userManager = 163 getUserContext(mContext, user.getUserHandle()).getSystemService(UserManager.class); 164 return !(Objects.requireNonNull(userManager).isProfile()); 165 } 166 setupForCurrentForegroundUser()167 private void setupForCurrentForegroundUser() { 168 Slog.d(TAG, "setupForCurrentForegroundUser: " + mCurrentForegroundUser); 169 HealthConnectContext hcContext = 170 HealthConnectContext.create( 171 mContext, 172 mCurrentForegroundUser, 173 /* databaseDirName= */ null, 174 mHealthConnectInjector.getEnvironmentDataDirectory()); 175 176 mHealthConnectService.setupForUser(mCurrentForegroundUser); 177 mHealthConnectInjector.getTransactionManager().setupForUser(hcContext); 178 mHealthConnectInjector.getMigrationStateManager().setupForUser(mCurrentForegroundUser); 179 mHealthConnectInjector 180 .getMigrationBroadcastScheduler() 181 .setupForUser(mCurrentForegroundUser); 182 mHealthConnectInjector.getMigrationUiStateManager().setupForUser(mCurrentForegroundUser); 183 mHealthConnectInjector 184 .getPermissionPackageChangesOrchestrator() 185 .setupForUser(mCurrentForegroundUser); 186 mHealthConnectInjector 187 .getHealthPermissionIntentAppsTracker() 188 .setupForUser(mCurrentForegroundUser); 189 mHealthConnectInjector.getBackupRestore().setupForUser(mCurrentForegroundUser); 190 mHealthConnectInjector.getAppInfoHelper().setupForUser(hcContext); 191 mHealthConnectInjector.getHealthDataCategoryPriorityHelper().setupForUser(hcContext); 192 193 if (Flags.clearCachesAfterSwitchingUser()) { 194 // Clear preferences cache again after the user switching is done as there's a race 195 // condition with tasks re-populating the preferences cache between clearing the cache 196 // and TransactionManager switching user, see b/355426144. 197 mHealthConnectInjector.getPreferenceHelper().clearCache(); 198 } 199 200 HealthConnectThreadScheduler threadScheduler = mHealthConnectInjector.getThreadScheduler(); 201 threadScheduler.scheduleInternalTask( 202 () -> { 203 try { 204 HealthConnectDailyJobs.schedule(mContext, mCurrentForegroundUser); 205 } catch (Exception e) { 206 Slog.e(TAG, "Failed to schedule Health Connect daily service.", e); 207 } 208 }); 209 210 threadScheduler.scheduleInternalTask( 211 () -> { 212 try { 213 mHealthConnectInjector 214 .getMigrationBroadcastScheduler() 215 .scheduleNewJobs( 216 mContext, 217 mHealthConnectInjector.getMigrationStateManager()); 218 } catch (Exception e) { 219 Slog.e(TAG, "Migration broadcast schedule failed", e); 220 } 221 }); 222 223 threadScheduler.scheduleInternalTask( 224 () -> { 225 try { 226 mHealthConnectInjector 227 .getMigrationStateManager() 228 .switchToSetupForUser(mContext); 229 } catch (Exception e) { 230 Slog.e(TAG, "Failed to start user unlocked state changes actions", e); 231 } 232 }); 233 threadScheduler.scheduleInternalTask( 234 () -> { 235 try { 236 mHealthConnectInjector.getPreferenceHelper().initializePreferences(); 237 } catch (Exception e) { 238 Slog.e(TAG, "Failed to initialize preferences cache", e); 239 } 240 }); 241 242 threadScheduler.scheduleInternalTask( 243 () -> { 244 try { 245 ExportImportJobs.schedulePeriodicJobIfNotScheduled( 246 mCurrentForegroundUser, 247 mContext, 248 mHealthConnectInjector.getExportImportSettingsStorage(), 249 mHealthConnectInjector.getExportManager()); 250 } catch (Exception e) { 251 Slog.e(TAG, "Failed to schedule periodic export job.", e); 252 } 253 }); 254 255 if (Flags.stepTrackingEnabled()) { 256 threadScheduler.scheduleInternalTask( 257 () -> { 258 try { 259 mHealthConnectInjector.getTrackerManager().initialize(); 260 } catch (Exception e) { 261 Slog.e(TAG, "Failed to initialize steps tracker.", e); 262 } 263 }); 264 } 265 } 266 getUserContext(Context context, UserHandle user)267 private static Context getUserContext(Context context, UserHandle user) { 268 if (Process.myUserHandle().equals(user)) { 269 return context; 270 } else { 271 return context.createContextAsUser(user, 0); 272 } 273 } 274 } 275