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