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.NonNull; 20 import android.annotation.Nullable; 21 import android.content.Context; 22 import android.health.connect.HealthConnectManager; 23 import android.health.connect.ratelimiter.RateLimiter; 24 import android.os.Process; 25 import android.os.UserHandle; 26 import android.os.UserManager; 27 import android.util.Slog; 28 29 import com.android.server.SystemService; 30 import com.android.server.healthconnect.exportimport.ExportImportJobs; 31 import com.android.server.healthconnect.migration.MigrationBroadcastScheduler; 32 import com.android.server.healthconnect.migration.MigrationCleaner; 33 import com.android.server.healthconnect.migration.MigrationStateManager; 34 import com.android.server.healthconnect.migration.MigrationUiStateManager; 35 import com.android.server.healthconnect.migration.MigratorPackageChangesReceiver; 36 import com.android.server.healthconnect.migration.PriorityMigrationHelper; 37 import com.android.server.healthconnect.migration.notification.MigrationNotificationSender; 38 import com.android.server.healthconnect.permission.FirstGrantTimeDatastore; 39 import com.android.server.healthconnect.permission.FirstGrantTimeManager; 40 import com.android.server.healthconnect.permission.HealthConnectPermissionHelper; 41 import com.android.server.healthconnect.permission.HealthPermissionIntentAppsTracker; 42 import com.android.server.healthconnect.permission.PermissionPackageChangesOrchestrator; 43 import com.android.server.healthconnect.storage.TransactionManager; 44 import com.android.server.healthconnect.storage.datatypehelpers.DatabaseHelper; 45 import com.android.server.healthconnect.storage.datatypehelpers.PreferenceHelper; 46 47 import java.util.Objects; 48 49 /** 50 * HealthConnect system service scaffold. 51 * 52 * @hide 53 */ 54 public class HealthConnectManagerService extends SystemService { 55 private static final String TAG = "HealthConnectManagerService"; 56 private final Context mContext; 57 private final PermissionPackageChangesOrchestrator mPermissionPackageChangesOrchestrator; 58 private final HealthConnectServiceImpl mHealthConnectService; 59 private final TransactionManager mTransactionManager; 60 private final UserManager mUserManager; 61 private final MigrationBroadcastScheduler mMigrationBroadcastScheduler; 62 private UserHandle mCurrentForegroundUser; 63 private MigrationUiStateManager mMigrationUiStateManager; 64 private final MigrationNotificationSender mMigrationNotificationSender; 65 HealthConnectManagerService(Context context)66 public HealthConnectManagerService(Context context) { 67 super(context); 68 HealthPermissionIntentAppsTracker permissionIntentTracker = 69 new HealthPermissionIntentAppsTracker(context); 70 FirstGrantTimeManager firstGrantTimeManager = 71 new FirstGrantTimeManager( 72 context, permissionIntentTracker, FirstGrantTimeDatastore.createInstance()); 73 HealthConnectPermissionHelper permissionHelper = 74 new HealthConnectPermissionHelper( 75 context, 76 context.getPackageManager(), 77 HealthConnectManager.getHealthPermissions(context), 78 permissionIntentTracker, 79 firstGrantTimeManager); 80 mCurrentForegroundUser = context.getUser(); 81 mContext = context; 82 mPermissionPackageChangesOrchestrator = 83 new PermissionPackageChangesOrchestrator( 84 permissionIntentTracker, 85 firstGrantTimeManager, 86 permissionHelper, 87 mCurrentForegroundUser); 88 mUserManager = context.getSystemService(UserManager.class); 89 mTransactionManager = 90 TransactionManager.getInstance( 91 new HealthConnectUserContext(mContext, mCurrentForegroundUser)); 92 HealthConnectDeviceConfigManager.initializeInstance(context); 93 mMigrationBroadcastScheduler = 94 new MigrationBroadcastScheduler(mCurrentForegroundUser.getIdentifier()); 95 final MigrationStateManager migrationStateManager = 96 MigrationStateManager.initializeInstance(mCurrentForegroundUser.getIdentifier()); 97 migrationStateManager.setMigrationBroadcastScheduler(mMigrationBroadcastScheduler); 98 final MigrationCleaner migrationCleaner = 99 new MigrationCleaner(mTransactionManager, PriorityMigrationHelper.getInstance()); 100 mMigrationNotificationSender = new MigrationNotificationSender(context); 101 mMigrationUiStateManager = 102 new MigrationUiStateManager( 103 mContext, 104 mCurrentForegroundUser, 105 migrationStateManager, 106 mMigrationNotificationSender); 107 mHealthConnectService = 108 new HealthConnectServiceImpl( 109 mTransactionManager, 110 HealthConnectDeviceConfigManager.getInitialisedInstance(), 111 permissionHelper, 112 migrationCleaner, 113 firstGrantTimeManager, 114 migrationStateManager, 115 mMigrationUiStateManager, 116 mContext); 117 } 118 119 @Override onStart()120 public void onStart() { 121 mPermissionPackageChangesOrchestrator.registerBroadcastReceiver(mContext); 122 new MigratorPackageChangesReceiver(MigrationStateManager.getInitialisedInstance()) 123 .registerBroadcastReceiver(mContext); 124 publishBinderService(Context.HEALTHCONNECT_SERVICE, mHealthConnectService); 125 HealthConnectDeviceConfigManager.getInitialisedInstance().updateRateLimiterValues(); 126 } 127 128 /** 129 * NOTE: Don't put any code that uses DB in onUserSwitching, such code should be part of 130 * switchToSetupForUser which is only called once DB is in usable state. 131 */ 132 @Override onUserSwitching(@ullable TargetUser from, @NonNull TargetUser to)133 public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) { 134 if (from != null && mUserManager.isUserUnlocked(from.getUserHandle())) { 135 // We need to cancel any pending timers for the foreground user before it goes into the 136 // background. 137 mHealthConnectService.cancelBackupRestoreTimeouts(); 138 } 139 140 HealthConnectThreadScheduler.shutdownThreadPools(); 141 DatabaseHelper.clearAllCache(); 142 mTransactionManager.onUserSwitching(); 143 RateLimiter.clearCache(); 144 HealthConnectThreadScheduler.resetThreadPools(); 145 MigrationStateManager migrationStateManager = 146 MigrationStateManager.getInitialisedInstance(); 147 migrationStateManager.onUserSwitching(mContext, to.getUserHandle().getIdentifier()); 148 149 mCurrentForegroundUser = to.getUserHandle(); 150 151 if (mUserManager.isUserUnlocked(to.getUserHandle())) { 152 // The user is already in unlocked state, so we should proceed with our setup right now, 153 // as we won't be getting a onUserUnlocked callback 154 switchToSetupForUser(to.getUserHandle()); 155 } 156 } 157 158 // NOTE: The only scenario in which onUserUnlocked's code should be triggered is if the 159 // foreground user is unlocked. If {@code user} is not a foreground user, the following 160 // code should only be triggered when the {@code user} actually gets unlocked. And in 161 // such cases onUserSwitching will be triggered for {@code user} and this code will be 162 // triggered then. 163 @Override onUserUnlocked(@onNull TargetUser user)164 public void onUserUnlocked(@NonNull TargetUser user) { 165 Objects.requireNonNull(user); 166 if (!user.getUserHandle().equals(mCurrentForegroundUser)) { 167 // Ignore unlocking requests for non-foreground users 168 return; 169 } 170 171 switchToSetupForUser(user.getUserHandle()); 172 } 173 174 @Override isUserSupported(@onNull TargetUser user)175 public boolean isUserSupported(@NonNull TargetUser user) { 176 UserManager userManager = 177 getUserContext(mContext, user.getUserHandle()).getSystemService(UserManager.class); 178 return !(Objects.requireNonNull(userManager).isProfile()); 179 } 180 switchToSetupForUser(UserHandle user)181 private void switchToSetupForUser(UserHandle user) { 182 // Note: This is for test setup debugging, please don't surround with DEBUG flag 183 Slog.d(TAG, "switchToSetupForUser: " + user); 184 mTransactionManager.onUserUnlocked( 185 new HealthConnectUserContext(mContext, mCurrentForegroundUser)); 186 mHealthConnectService.onUserSwitching(mCurrentForegroundUser); 187 mMigrationBroadcastScheduler.setUserId(mCurrentForegroundUser.getIdentifier()); 188 mMigrationUiStateManager.setUserHandle(mCurrentForegroundUser); 189 mPermissionPackageChangesOrchestrator.setUserHandle(mCurrentForegroundUser); 190 191 HealthConnectDailyJobs.cancelAllJobs(mContext); 192 193 HealthConnectThreadScheduler.scheduleInternalTask( 194 () -> { 195 try { 196 HealthConnectDailyJobs.schedule( 197 mContext, mCurrentForegroundUser.getIdentifier()); 198 } catch (Exception e) { 199 Slog.e(TAG, "Failed to schedule Health Connect daily service.", e); 200 } 201 }); 202 203 HealthConnectThreadScheduler.scheduleInternalTask( 204 () -> { 205 try { 206 mMigrationBroadcastScheduler.scheduleNewJobs(mContext); 207 } catch (Exception e) { 208 Slog.e(TAG, "Migration broadcast schedule failed", e); 209 } 210 }); 211 212 HealthConnectThreadScheduler.scheduleInternalTask( 213 () -> { 214 try { 215 MigrationStateManager.getInitialisedInstance() 216 .switchToSetupForUser(mContext); 217 } catch (Exception e) { 218 Slog.e(TAG, "Failed to start user unlocked state changes actions", e); 219 } 220 }); 221 HealthConnectThreadScheduler.scheduleInternalTask( 222 () -> { 223 try { 224 PreferenceHelper.getInstance().initializePreferences(); 225 } catch (Exception e) { 226 Slog.e(TAG, "Failed to initialize preferences cache", e); 227 } 228 }); 229 230 HealthConnectThreadScheduler.scheduleInternalTask( 231 () -> { 232 try { 233 ExportImportJobs.schedulePeriodicExportJob( 234 mContext, mCurrentForegroundUser.getIdentifier()); 235 } catch (Exception e) { 236 Slog.e(TAG, "Failed to schedule periodic export job.", e); 237 } 238 }); 239 } 240 241 @NonNull getUserContext(@onNull Context context, @NonNull UserHandle user)242 private static Context getUserContext(@NonNull Context context, @NonNull UserHandle user) { 243 if (Process.myUserHandle().equals(user)) { 244 return context; 245 } else { 246 return context.createContextAsUser(user, 0); 247 } 248 } 249 } 250