/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.healthconnect; import android.annotation.Nullable; import android.content.Context; import android.health.connect.ratelimiter.RateLimiter; import android.os.Process; import android.os.UserHandle; import android.os.UserManager; import android.util.Slog; import com.android.healthfitness.flags.Flags; import com.android.server.SystemService; import com.android.server.healthconnect.exportimport.ExportImportJobs; import com.android.server.healthconnect.injector.HealthConnectInjector; import com.android.server.healthconnect.injector.HealthConnectInjectorImpl; import com.android.server.healthconnect.migration.MigratorPackageChangesReceiver; import com.android.server.healthconnect.storage.HealthConnectContext; import java.util.Objects; /** * HealthConnect system service scaffold. * * @hide */ public class HealthConnectManagerService extends SystemService { private static final String TAG = "HealthConnectManagerService"; private final Context mContext; private final HealthConnectServiceImpl mHealthConnectService; private final UserManager mUserManager; private final HealthConnectInjector mHealthConnectInjector; private final RateLimiter mRateLimiter; private UserHandle mCurrentForegroundUser; public HealthConnectManagerService(Context context) { super(context); mRateLimiter = new RateLimiter(); mContext = context; mCurrentForegroundUser = context.getUser(); mUserManager = context.getSystemService(UserManager.class); HealthConnectInjector.setInstance(new HealthConnectInjectorImpl(context)); mHealthConnectInjector = HealthConnectInjector.getInstance(); mHealthConnectService = new HealthConnectServiceImpl( mContext, mHealthConnectInjector.getTimeSource(), mHealthConnectInjector.getInternalHealthConnectMappings(), mHealthConnectInjector.getTransactionManager(), mHealthConnectInjector.getHealthConnectPermissionHelper(), mHealthConnectInjector.getFirstGrantTimeManager(), mHealthConnectInjector.getMigrationEntityHelper(), mHealthConnectInjector.getMigrationStateManager(), mHealthConnectInjector.getMigrationUiStateManager(), mHealthConnectInjector.getMigrationCleaner(), mHealthConnectInjector.getFitnessRecordUpsertHelper(), mHealthConnectInjector.getFitnessRecordReadHelper(), mHealthConnectInjector.getFitnessRecordDeleteHelper(), mHealthConnectInjector.getFitnessRecordAggregateHelper(), mHealthConnectInjector.getMedicalResourceHelper(), mHealthConnectInjector.getMedicalDataSourceHelper(), mHealthConnectInjector.getExportManager(), mHealthConnectInjector.getExportImportSettingsStorage(), mHealthConnectInjector.getExportImportNotificationSender(), mHealthConnectInjector.getBackupRestore(), mHealthConnectInjector.getAccessLogsHelper(), mHealthConnectInjector.getHealthDataCategoryPriorityHelper(), mHealthConnectInjector.getActivityDateHelper(), mHealthConnectInjector.getChangeLogsHelper(), mHealthConnectInjector.getChangeLogsRequestHelper(), mHealthConnectInjector.getPriorityMigrationHelper(), mHealthConnectInjector.getAppInfoHelper(), mHealthConnectInjector.getDeviceInfoHelper(), mHealthConnectInjector.getPreferenceHelper(), mHealthConnectInjector.getDatabaseHelpers(), mHealthConnectInjector.getPreferencesManager(), mHealthConnectInjector.getReadAccessLogsHelper(), mHealthConnectInjector.getAppOpsManagerLocal(), mHealthConnectInjector.getThreadScheduler(), mRateLimiter, mHealthConnectInjector.getEnvironmentDataDirectory(), mHealthConnectInjector.getExportImportLogger(), mHealthConnectInjector.getHealthFitnessStatsLog(), mHealthConnectInjector.getBackupRestoreLogger()); } @Override public void onStart() { mHealthConnectInjector .getPermissionPackageChangesOrchestrator() .registerBroadcastReceiver(mContext); new MigratorPackageChangesReceiver(mHealthConnectInjector.getMigrationStateManager()) .registerBroadcastReceiver(mContext); publishBinderService(Context.HEALTHCONNECT_SERVICE, mHealthConnectService); } /** * NOTE: Don't put any code that uses DB in onUserSwitching, such code should be part of * switchToSetupForUser which is only called once DB is in usable state. */ @Override public void onUserSwitching(@Nullable TargetUser from, TargetUser to) { if (from != null && mUserManager.isUserUnlocked(from.getUserHandle())) { // We need to cancel any pending timers for the foreground user before it goes into the // background. mHealthConnectService.cancelBackupRestoreTimeouts(); } HealthConnectThreadScheduler threadScheduler = mHealthConnectInjector.getThreadScheduler(); threadScheduler.shutdownThreadPools(); mRateLimiter.clearCache(); HealthConnectDailyJobs.cancelAllJobs(mContext); mHealthConnectInjector.getDatabaseHelpers().clearAllCache(); mHealthConnectInjector.getTransactionManager().shutDownCurrentUser(); mHealthConnectInjector.getMigrationStateManager().shutDownCurrentUser(mContext); threadScheduler.resetThreadPools(); mCurrentForegroundUser = to.getUserHandle(); if (mUserManager.isUserUnlocked(to.getUserHandle())) { // The user is already in unlocked state, so we should proceed with our setup right now, // as we won't be getting a onUserUnlocked callback setupForCurrentForegroundUser(); } } // NOTE: The only scenario in which onUserUnlocked's code should be triggered is if the // foreground user is unlocked. If {@code user} is not a foreground user, the following // code should only be triggered when the {@code user} actually gets unlocked. And in // such cases onUserSwitching will be triggered for {@code user} and this code will be // triggered then. @Override public void onUserUnlocked(TargetUser user) { Objects.requireNonNull(user); if (!user.getUserHandle().equals(mCurrentForegroundUser)) { // Ignore unlocking requests for non-foreground users return; } setupForCurrentForegroundUser(); } @Override public boolean isUserSupported(TargetUser user) { UserManager userManager = getUserContext(mContext, user.getUserHandle()).getSystemService(UserManager.class); return !(Objects.requireNonNull(userManager).isProfile()); } private void setupForCurrentForegroundUser() { Slog.d(TAG, "setupForCurrentForegroundUser: " + mCurrentForegroundUser); HealthConnectContext hcContext = HealthConnectContext.create( mContext, mCurrentForegroundUser, /* databaseDirName= */ null, mHealthConnectInjector.getEnvironmentDataDirectory()); mHealthConnectService.setupForUser(mCurrentForegroundUser); mHealthConnectInjector.getTransactionManager().setupForUser(hcContext); mHealthConnectInjector.getMigrationStateManager().setupForUser(mCurrentForegroundUser); mHealthConnectInjector .getMigrationBroadcastScheduler() .setupForUser(mCurrentForegroundUser); mHealthConnectInjector.getMigrationUiStateManager().setupForUser(mCurrentForegroundUser); mHealthConnectInjector .getPermissionPackageChangesOrchestrator() .setupForUser(mCurrentForegroundUser); mHealthConnectInjector .getHealthPermissionIntentAppsTracker() .setupForUser(mCurrentForegroundUser); mHealthConnectInjector.getBackupRestore().setupForUser(mCurrentForegroundUser); mHealthConnectInjector.getAppInfoHelper().setupForUser(hcContext); mHealthConnectInjector.getHealthDataCategoryPriorityHelper().setupForUser(hcContext); if (Flags.clearCachesAfterSwitchingUser()) { // Clear preferences cache again after the user switching is done as there's a race // condition with tasks re-populating the preferences cache between clearing the cache // and TransactionManager switching user, see b/355426144. mHealthConnectInjector.getPreferenceHelper().clearCache(); } HealthConnectThreadScheduler threadScheduler = mHealthConnectInjector.getThreadScheduler(); threadScheduler.scheduleInternalTask( () -> { try { HealthConnectDailyJobs.schedule(mContext, mCurrentForegroundUser); } catch (Exception e) { Slog.e(TAG, "Failed to schedule Health Connect daily service.", e); } }); threadScheduler.scheduleInternalTask( () -> { try { mHealthConnectInjector .getMigrationBroadcastScheduler() .scheduleNewJobs( mContext, mHealthConnectInjector.getMigrationStateManager()); } catch (Exception e) { Slog.e(TAG, "Migration broadcast schedule failed", e); } }); threadScheduler.scheduleInternalTask( () -> { try { mHealthConnectInjector .getMigrationStateManager() .switchToSetupForUser(mContext); } catch (Exception e) { Slog.e(TAG, "Failed to start user unlocked state changes actions", e); } }); threadScheduler.scheduleInternalTask( () -> { try { mHealthConnectInjector.getPreferenceHelper().initializePreferences(); } catch (Exception e) { Slog.e(TAG, "Failed to initialize preferences cache", e); } }); threadScheduler.scheduleInternalTask( () -> { try { ExportImportJobs.schedulePeriodicJobIfNotScheduled( mCurrentForegroundUser, mContext, mHealthConnectInjector.getExportImportSettingsStorage(), mHealthConnectInjector.getExportManager()); } catch (Exception e) { Slog.e(TAG, "Failed to schedule periodic export job.", e); } }); if (Flags.stepTrackingEnabled()) { threadScheduler.scheduleInternalTask( () -> { try { mHealthConnectInjector.getTrackerManager().initialize(); } catch (Exception e) { Slog.e(TAG, "Failed to initialize steps tracker.", e); } }); } } private static Context getUserContext(Context context, UserHandle user) { if (Process.myUserHandle().equals(user)) { return context; } else { return context.createContextAsUser(user, 0); } } }