• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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 static android.Manifest.permission.BACKUP_HEALTH_CONNECT_DATA_AND_SETTINGS;
20 import static android.Manifest.permission.MIGRATE_HEALTH_CONNECT_DATA;
21 import static android.Manifest.permission.RESTORE_HEALTH_CONNECT_DATA_AND_SETTINGS;
22 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
23 import static android.health.connect.Constants.DEFAULT_LONG;
24 import static android.health.connect.Constants.MAXIMUM_PAGE_SIZE;
25 import static android.health.connect.Constants.READ;
26 import static android.health.connect.HealthConnectException.ERROR_INTERNAL;
27 import static android.health.connect.HealthConnectException.ERROR_INVALID_ARGUMENT;
28 import static android.health.connect.HealthConnectException.ERROR_IO;
29 import static android.health.connect.HealthConnectException.ERROR_SECURITY;
30 import static android.health.connect.HealthConnectException.ERROR_UNKNOWN;
31 import static android.health.connect.HealthConnectException.ERROR_UNSUPPORTED_OPERATION;
32 import static android.health.connect.HealthPermissions.MANAGE_HEALTH_DATA_PERMISSION;
33 import static android.health.connect.HealthPermissions.READ_HEALTH_DATA_HISTORY;
34 import static android.health.connect.HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND;
35 import static android.health.connect.HealthPermissions.WRITE_MEDICAL_DATA;
36 import static android.health.connect.datatypes.MedicalDataSource.validateMedicalDataSourceIds;
37 
38 import static com.android.healthfitness.flags.AconfigFlagHelper.isCloudBackupRestoreEnabled;
39 import static com.android.healthfitness.flags.AconfigFlagHelper.isPersonalHealthRecordEnabled;
40 import static com.android.healthfitness.flags.Flags.personalHealthRecordTelemetry;
41 import static com.android.server.healthconnect.logging.HealthConnectServiceLogger.ApiMethods.CREATE_MEDICAL_DATA_SOURCE;
42 import static com.android.server.healthconnect.logging.HealthConnectServiceLogger.ApiMethods.DELETE_DATA;
43 import static com.android.server.healthconnect.logging.HealthConnectServiceLogger.ApiMethods.DELETE_MEDICAL_DATA_SOURCE_WITH_DATA;
44 import static com.android.server.healthconnect.logging.HealthConnectServiceLogger.ApiMethods.DELETE_MEDICAL_RESOURCES_BY_IDS;
45 import static com.android.server.healthconnect.logging.HealthConnectServiceLogger.ApiMethods.DELETE_MEDICAL_RESOURCES_BY_REQUESTS;
46 import static com.android.server.healthconnect.logging.HealthConnectServiceLogger.ApiMethods.GET_CHANGES;
47 import static com.android.server.healthconnect.logging.HealthConnectServiceLogger.ApiMethods.GET_CHANGES_TOKEN;
48 import static com.android.server.healthconnect.logging.HealthConnectServiceLogger.ApiMethods.GET_MEDICAL_DATA_SOURCES_BY_IDS;
49 import static com.android.server.healthconnect.logging.HealthConnectServiceLogger.ApiMethods.GET_MEDICAL_DATA_SOURCES_BY_REQUESTS;
50 import static com.android.server.healthconnect.logging.HealthConnectServiceLogger.ApiMethods.INSERT_DATA;
51 import static com.android.server.healthconnect.logging.HealthConnectServiceLogger.ApiMethods.READ_AGGREGATED_DATA;
52 import static com.android.server.healthconnect.logging.HealthConnectServiceLogger.ApiMethods.READ_DATA;
53 import static com.android.server.healthconnect.logging.HealthConnectServiceLogger.ApiMethods.READ_MEDICAL_RESOURCES_BY_IDS;
54 import static com.android.server.healthconnect.logging.HealthConnectServiceLogger.ApiMethods.READ_MEDICAL_RESOURCES_BY_REQUESTS;
55 import static com.android.server.healthconnect.logging.HealthConnectServiceLogger.ApiMethods.UPDATE_DATA;
56 import static com.android.server.healthconnect.logging.HealthConnectServiceLogger.ApiMethods.UPSERT_MEDICAL_RESOURCES;
57 
58 import static java.util.Collections.singletonList;
59 import static java.util.stream.Collectors.toList;
60 import static java.util.stream.Collectors.toSet;
61 
62 import android.Manifest;
63 import android.annotation.Nullable;
64 import android.annotation.RequiresApi;
65 import android.content.AttributionSource;
66 import android.content.Context;
67 import android.content.Intent;
68 import android.content.pm.PackageManager;
69 import android.content.pm.ResolveInfo;
70 import android.database.sqlite.SQLiteException;
71 import android.health.HealthFitnessStatsLog;
72 import android.health.connect.Constants;
73 import android.health.connect.CreateMedicalDataSourceRequest;
74 import android.health.connect.DeleteMedicalResourcesRequest;
75 import android.health.connect.FetchDataOriginsPriorityOrderResponse;
76 import android.health.connect.GetMedicalDataSourcesRequest;
77 import android.health.connect.HealthConnectDataState;
78 import android.health.connect.HealthConnectException;
79 import android.health.connect.HealthConnectManager;
80 import android.health.connect.HealthConnectManager.DataDownloadState;
81 import android.health.connect.HealthDataCategory;
82 import android.health.connect.MedicalResourceId;
83 import android.health.connect.MedicalResourceTypeInfo;
84 import android.health.connect.PageTokenWrapper;
85 import android.health.connect.ReadMedicalResourcesResponse;
86 import android.health.connect.UpsertMedicalResourceRequest;
87 import android.health.connect.accesslog.AccessLog;
88 import android.health.connect.accesslog.AccessLogsResponseParcel;
89 import android.health.connect.aidl.ActivityDatesRequestParcel;
90 import android.health.connect.aidl.ActivityDatesResponseParcel;
91 import android.health.connect.aidl.AggregateDataRequestParcel;
92 import android.health.connect.aidl.ApplicationInfoResponseParcel;
93 import android.health.connect.aidl.DeleteUsingFiltersRequestParcel;
94 import android.health.connect.aidl.GetPriorityResponseParcel;
95 import android.health.connect.aidl.HealthConnectExceptionParcel;
96 import android.health.connect.aidl.IAccessLogsResponseCallback;
97 import android.health.connect.aidl.IActivityDatesResponseCallback;
98 import android.health.connect.aidl.IAggregateRecordsResponseCallback;
99 import android.health.connect.aidl.IApplicationInfoResponseCallback;
100 import android.health.connect.aidl.ICanRestoreResponseCallback;
101 import android.health.connect.aidl.IChangeLogsResponseCallback;
102 import android.health.connect.aidl.IDataStagingFinishedCallback;
103 import android.health.connect.aidl.IEmptyResponseCallback;
104 import android.health.connect.aidl.IGetChangeLogTokenCallback;
105 import android.health.connect.aidl.IGetChangesForBackupResponseCallback;
106 import android.health.connect.aidl.IGetHealthConnectDataStateCallback;
107 import android.health.connect.aidl.IGetHealthConnectMigrationUiStateCallback;
108 import android.health.connect.aidl.IGetLatestMetadataForBackupResponseCallback;
109 import android.health.connect.aidl.IGetPriorityResponseCallback;
110 import android.health.connect.aidl.IHealthConnectService;
111 import android.health.connect.aidl.IInsertRecordsResponseCallback;
112 import android.health.connect.aidl.IMedicalDataSourceResponseCallback;
113 import android.health.connect.aidl.IMedicalDataSourcesResponseCallback;
114 import android.health.connect.aidl.IMedicalResourceListParcelResponseCallback;
115 import android.health.connect.aidl.IMedicalResourceTypeInfosCallback;
116 import android.health.connect.aidl.IMedicalResourcesResponseCallback;
117 import android.health.connect.aidl.IMigrationCallback;
118 import android.health.connect.aidl.IReadMedicalResourcesResponseCallback;
119 import android.health.connect.aidl.IReadRecordsResponseCallback;
120 import android.health.connect.aidl.IRecordTypeInfoResponseCallback;
121 import android.health.connect.aidl.InsertRecordsResponseParcel;
122 import android.health.connect.aidl.MedicalResourceListParcel;
123 import android.health.connect.aidl.ReadMedicalResourcesRequestParcel;
124 import android.health.connect.aidl.ReadRecordsRequestParcel;
125 import android.health.connect.aidl.ReadRecordsResponseParcel;
126 import android.health.connect.aidl.RecordTypeInfoResponseParcel;
127 import android.health.connect.aidl.RecordsParcel;
128 import android.health.connect.aidl.UpdatePriorityRequestParcel;
129 import android.health.connect.aidl.UpsertMedicalResourceRequestsParcel;
130 import android.health.connect.backuprestore.BackupMetadata;
131 import android.health.connect.backuprestore.RestoreChange;
132 import android.health.connect.changelog.ChangeLogTokenRequest;
133 import android.health.connect.changelog.ChangeLogTokenResponse;
134 import android.health.connect.changelog.ChangeLogsRequest;
135 import android.health.connect.changelog.ChangeLogsResponse;
136 import android.health.connect.changelog.ChangeLogsResponse.DeletedLog;
137 import android.health.connect.datatypes.AppInfo;
138 import android.health.connect.datatypes.DataOrigin;
139 import android.health.connect.datatypes.MedicalDataSource;
140 import android.health.connect.datatypes.MedicalResource;
141 import android.health.connect.datatypes.Record;
142 import android.health.connect.exportimport.ExportImportDocumentProvider;
143 import android.health.connect.exportimport.IImportStatusCallback;
144 import android.health.connect.exportimport.IQueryDocumentProvidersCallback;
145 import android.health.connect.exportimport.IScheduledExportStatusCallback;
146 import android.health.connect.exportimport.ImportStatus;
147 import android.health.connect.exportimport.ScheduledExportSettings;
148 import android.health.connect.exportimport.ScheduledExportStatus;
149 import android.health.connect.internal.datatypes.RecordInternal;
150 import android.health.connect.internal.datatypes.utils.AggregationTypeIdMapper;
151 import android.health.connect.internal.datatypes.utils.HealthConnectMappings;
152 import android.health.connect.internal.datatypes.utils.MedicalResourceTypePermissionMapper;
153 import android.health.connect.migration.HealthConnectMigrationUiState;
154 import android.health.connect.migration.MigrationEntityParcel;
155 import android.health.connect.migration.MigrationException;
156 import android.health.connect.ratelimiter.RateLimiter;
157 import android.health.connect.ratelimiter.RateLimiter.QuotaCategory;
158 import android.health.connect.ratelimiter.RateLimiterException;
159 import android.health.connect.restore.BackupFileNamesSet;
160 import android.health.connect.restore.StageRemoteDataException;
161 import android.health.connect.restore.StageRemoteDataRequest;
162 import android.net.Uri;
163 import android.os.Binder;
164 import android.os.Build;
165 import android.os.ParcelFileDescriptor;
166 import android.os.Process;
167 import android.os.RemoteException;
168 import android.os.UserHandle;
169 import android.permission.PermissionManager;
170 import android.util.ArrayMap;
171 import android.util.Log;
172 import android.util.Pair;
173 import android.util.Slog;
174 
175 import com.android.healthfitness.flags.Flags;
176 import com.android.server.appop.AppOpsManagerLocal;
177 import com.android.server.healthconnect.backuprestore.BackupRestore;
178 import com.android.server.healthconnect.backuprestore.CloudBackupManager;
179 import com.android.server.healthconnect.backuprestore.CloudRestoreManager;
180 import com.android.server.healthconnect.common.RequestContext;
181 import com.android.server.healthconnect.exportimport.DocumentProvidersManager;
182 import com.android.server.healthconnect.exportimport.ExportImportJobs;
183 import com.android.server.healthconnect.exportimport.ExportManager;
184 import com.android.server.healthconnect.exportimport.ImportManager;
185 import com.android.server.healthconnect.fitness.FitnessRecordDeleteHelper;
186 import com.android.server.healthconnect.fitness.FitnessRecordReadHelper;
187 import com.android.server.healthconnect.fitness.FitnessRecordUpsertHelper;
188 import com.android.server.healthconnect.fitness.aggregation.FitnessRecordAggregateHelper;
189 import com.android.server.healthconnect.logging.BackupRestoreLogger;
190 import com.android.server.healthconnect.logging.ExportImportLogger;
191 import com.android.server.healthconnect.logging.HealthConnectServiceLogger;
192 import com.android.server.healthconnect.migration.DataMigrationManager;
193 import com.android.server.healthconnect.migration.MigrationCleaner;
194 import com.android.server.healthconnect.migration.MigrationStateManager;
195 import com.android.server.healthconnect.migration.MigrationUiStateManager;
196 import com.android.server.healthconnect.migration.PriorityMigrationHelper;
197 import com.android.server.healthconnect.notifications.HealthConnectNotificationSender;
198 import com.android.server.healthconnect.permission.DataPermissionEnforcer;
199 import com.android.server.healthconnect.permission.FirstGrantTimeManager;
200 import com.android.server.healthconnect.permission.HealthConnectPermissionHelper;
201 import com.android.server.healthconnect.permission.MedicalDataPermissionEnforcer;
202 import com.android.server.healthconnect.permission.PackageInfoUtils;
203 import com.android.server.healthconnect.phr.PhrPageTokenWrapper;
204 import com.android.server.healthconnect.phr.ReadMedicalResourcesInternalResponse;
205 import com.android.server.healthconnect.phr.validations.FhirResourceValidator;
206 import com.android.server.healthconnect.phr.validations.MedicalResourceValidator;
207 import com.android.server.healthconnect.storage.ExportImportSettingsStorage;
208 import com.android.server.healthconnect.storage.TransactionManager;
209 import com.android.server.healthconnect.storage.datatypehelpers.AccessLogsHelper;
210 import com.android.server.healthconnect.storage.datatypehelpers.ActivityDateHelper;
211 import com.android.server.healthconnect.storage.datatypehelpers.AppInfoHelper;
212 import com.android.server.healthconnect.storage.datatypehelpers.ChangeLogsHelper;
213 import com.android.server.healthconnect.storage.datatypehelpers.ChangeLogsRequestHelper;
214 import com.android.server.healthconnect.storage.datatypehelpers.DatabaseHelper.DatabaseHelpers;
215 import com.android.server.healthconnect.storage.datatypehelpers.DeviceInfoHelper;
216 import com.android.server.healthconnect.storage.datatypehelpers.HealthDataCategoryPriorityHelper;
217 import com.android.server.healthconnect.storage.datatypehelpers.MedicalDataSourceHelper;
218 import com.android.server.healthconnect.storage.datatypehelpers.MedicalResourceHelper;
219 import com.android.server.healthconnect.storage.datatypehelpers.MigrationEntityHelper;
220 import com.android.server.healthconnect.storage.datatypehelpers.PreferenceHelper;
221 import com.android.server.healthconnect.storage.datatypehelpers.ReadAccessLogsHelper;
222 import com.android.server.healthconnect.storage.datatypehelpers.RecordHelper;
223 import com.android.server.healthconnect.storage.request.UpsertMedicalResourceInternalRequest;
224 import com.android.server.healthconnect.storage.utils.InternalHealthConnectMappings;
225 import com.android.server.healthconnect.storage.utils.PreferencesManager;
226 import com.android.server.healthconnect.storage.utils.StorageUtils;
227 import com.android.server.healthconnect.utils.TimeSource;
228 
229 import org.json.JSONException;
230 
231 import java.io.File;
232 import java.io.IOException;
233 import java.io.PrintWriter;
234 import java.io.StringWriter;
235 import java.lang.ref.WeakReference;
236 import java.time.Clock;
237 import java.time.Instant;
238 import java.time.LocalDate;
239 import java.util.ArrayList;
240 import java.util.Arrays;
241 import java.util.Collections;
242 import java.util.HashSet;
243 import java.util.List;
244 import java.util.Map;
245 import java.util.Map.Entry;
246 import java.util.Objects;
247 import java.util.Optional;
248 import java.util.Set;
249 import java.util.UUID;
250 import java.util.stream.Collectors;
251 
252 /**
253  * IHealthConnectService's implementation
254  *
255  * @hide
256  */
257 final class HealthConnectServiceImpl extends IHealthConnectService.Stub {
258     private static final String TAG = "HealthConnectService";
259     // Permission for test api for deleting staged data
260     private static final String DELETE_STAGED_HEALTH_CONNECT_REMOTE_DATA_PERMISSION =
261             "android.permission.DELETE_STAGED_HEALTH_CONNECT_REMOTE_DATA";
262     // Allows an application to act as a backup inter-agent to send and receive HealthConnect data
263     private static final String HEALTH_CONNECT_BACKUP_INTER_AGENT_PERMISSION =
264             "android.permission.HEALTH_CONNECT_BACKUP_INTER_AGENT";
265 
266     @Nullable private final ImportManager mImportManager;
267 
268     private final TransactionManager mTransactionManager;
269     @Nullable private final CloudBackupManager mCloudBackupManager;
270     @Nullable private final CloudRestoreManager mCloudRestoreManager;
271     private final HealthConnectPermissionHelper mPermissionHelper;
272     private final FirstGrantTimeManager mFirstGrantTimeManager;
273     private final Context mContext;
274     private final PermissionManager mPermissionManager;
275 
276     private final BackupRestore mBackupRestore;
277     private final MigrationStateManager mMigrationStateManager;
278 
279     private final DataPermissionEnforcer mDataPermissionEnforcer;
280 
281     private final MedicalDataPermissionEnforcer mMedicalDataPermissionEnforcer;
282 
283     private final AppOpsManagerLocal mAppOpsManagerLocal;
284     private final MigrationUiStateManager mMigrationUiStateManager;
285 
286     private final HealthDataCategoryPriorityHelper mHealthDataCategoryPriorityHelper;
287     private final AppInfoHelper mAppInfoHelper;
288     private final PriorityMigrationHelper mPriorityMigrationHelper;
289     private final AggregationTypeIdMapper mAggregationTypeIdMapper;
290     private final DeviceInfoHelper mDeviceInfoHelper;
291     private final ExportImportSettingsStorage mExportImportSettingsStorage;
292     private final PreferenceHelper mPreferenceHelper;
293     private final FitnessRecordUpsertHelper mFitnessRecordUpsertHelper;
294     private final FitnessRecordReadHelper mFitnessRecordReadHelper;
295     private final FitnessRecordDeleteHelper mFitnessRecordDeleteHelper;
296     private final FitnessRecordAggregateHelper mFitnessRecordAggregateHelper;
297     private final MedicalResourceHelper mMedicalResourceHelper;
298     private final MedicalDataSourceHelper mMedicalDataSourceHelper;
299     private final ExportManager mExportManager;
300     private final AccessLogsHelper mAccessLogsHelper;
301     private final ActivityDateHelper mActivityDateHelper;
302     private final ChangeLogsHelper mChangeLogsHelper;
303     private final ChangeLogsRequestHelper mChangeLogsRequestHelper;
304     private final MigrationEntityHelper mMigrationEntityHelper;
305     private final InternalHealthConnectMappings mInternalHealthConnectMappings;
306     private final HealthConnectMappings mHealthConnectMappings;
307     private final TimeSource mTimeSource;
308     private final DatabaseHelpers mDatabaseHelpers;
309     private final PreferencesManager mPreferencesManager;
310     private final ReadAccessLogsHelper mReadAccessLogsHelper;
311     private final RateLimiter mRateLimiter;
312     // Used if PHR_FHIR_RESOURCE_VALIDATOR_USE_WEAK_REFERENCE is false.
313     @Nullable private FhirResourceValidator mFhirResourceValidator;
314     // Used if PHR_FHIR_RESOURCE_VALIDATOR_USE_WEAK_REFERENCE is true.
315     WeakReference<FhirResourceValidator> mFhirResourceValidatorWeakReference =
316             new WeakReference<>(null);
317     private final HealthConnectThreadScheduler mThreadScheduler;
318     private final HealthFitnessStatsLog mStatsLog;
319 
320     private volatile UserHandle mCurrentForegroundUser;
321 
HealthConnectServiceImpl( Context context, TimeSource timeSource, InternalHealthConnectMappings internalHealthConnectMappings, TransactionManager transactionManager, HealthConnectPermissionHelper permissionHelper, FirstGrantTimeManager firstGrantTimeManager, MigrationEntityHelper migrationEntityHelper, MigrationStateManager migrationStateManager, MigrationUiStateManager migrationUiStateManager, MigrationCleaner migrationCleaner, FitnessRecordUpsertHelper fitnessRecordUpsertHelper, FitnessRecordReadHelper fitnessRecordReadHelper, FitnessRecordDeleteHelper fitnessRecordDeleteHelper, FitnessRecordAggregateHelper fitnessRecordAggregateHelper, MedicalResourceHelper medicalResourceHelper, MedicalDataSourceHelper medicalDataSourceHelper, ExportManager exportManager, ExportImportSettingsStorage exportImportSettingsStorage, HealthConnectNotificationSender exportImportNotificationSender, BackupRestore backupRestore, AccessLogsHelper accessLogsHelper, HealthDataCategoryPriorityHelper healthDataCategoryPriorityHelper, ActivityDateHelper activityDateHelper, ChangeLogsHelper changeLogsHelper, ChangeLogsRequestHelper changeLogsRequestHelper, PriorityMigrationHelper priorityMigrationHelper, AppInfoHelper appInfoHelper, DeviceInfoHelper deviceInfoHelper, PreferenceHelper preferenceHelper, DatabaseHelpers databaseHelpers, PreferencesManager preferencesManager, ReadAccessLogsHelper readAccessLogsHelper, AppOpsManagerLocal appOpsManagerLocal, HealthConnectThreadScheduler threadScheduler, RateLimiter rateLimiter, File environmentDataDirectory, ExportImportLogger exportImportLogger, HealthFitnessStatsLog statsLog, BackupRestoreLogger backupRestoreLogger)322     HealthConnectServiceImpl(
323             Context context,
324             TimeSource timeSource,
325             InternalHealthConnectMappings internalHealthConnectMappings,
326             TransactionManager transactionManager,
327             HealthConnectPermissionHelper permissionHelper,
328             FirstGrantTimeManager firstGrantTimeManager,
329             MigrationEntityHelper migrationEntityHelper,
330             MigrationStateManager migrationStateManager,
331             MigrationUiStateManager migrationUiStateManager,
332             MigrationCleaner migrationCleaner,
333             FitnessRecordUpsertHelper fitnessRecordUpsertHelper,
334             FitnessRecordReadHelper fitnessRecordReadHelper,
335             FitnessRecordDeleteHelper fitnessRecordDeleteHelper,
336             FitnessRecordAggregateHelper fitnessRecordAggregateHelper,
337             MedicalResourceHelper medicalResourceHelper,
338             MedicalDataSourceHelper medicalDataSourceHelper,
339             ExportManager exportManager,
340             ExportImportSettingsStorage exportImportSettingsStorage,
341             HealthConnectNotificationSender exportImportNotificationSender,
342             BackupRestore backupRestore,
343             AccessLogsHelper accessLogsHelper,
344             HealthDataCategoryPriorityHelper healthDataCategoryPriorityHelper,
345             ActivityDateHelper activityDateHelper,
346             ChangeLogsHelper changeLogsHelper,
347             ChangeLogsRequestHelper changeLogsRequestHelper,
348             PriorityMigrationHelper priorityMigrationHelper,
349             AppInfoHelper appInfoHelper,
350             DeviceInfoHelper deviceInfoHelper,
351             PreferenceHelper preferenceHelper,
352             DatabaseHelpers databaseHelpers,
353             PreferencesManager preferencesManager,
354             ReadAccessLogsHelper readAccessLogsHelper,
355             AppOpsManagerLocal appOpsManagerLocal,
356             HealthConnectThreadScheduler threadScheduler,
357             RateLimiter rateLimiter,
358             File environmentDataDirectory,
359             ExportImportLogger exportImportLogger,
360             HealthFitnessStatsLog statsLog,
361             BackupRestoreLogger backupRestoreLogger) {
362         mContext = context;
363         mCurrentForegroundUser = context.getUser();
364         mTimeSource = timeSource;
365 
366         mInternalHealthConnectMappings = internalHealthConnectMappings;
367         mHealthConnectMappings = internalHealthConnectMappings.getExternalMappings();
368         mAggregationTypeIdMapper = AggregationTypeIdMapper.getInstance();
369 
370         mTransactionManager = transactionManager;
371         mPermissionHelper = permissionHelper;
372         mFirstGrantTimeManager = firstGrantTimeManager;
373 
374         mMigrationEntityHelper = migrationEntityHelper;
375         mMigrationStateManager = migrationStateManager;
376         mMigrationUiStateManager = migrationUiStateManager;
377         mMigrationUiStateManager.attachTo(migrationStateManager);
378         migrationCleaner.attachTo(migrationStateManager);
379 
380         mFitnessRecordUpsertHelper = fitnessRecordUpsertHelper;
381         mFitnessRecordReadHelper = fitnessRecordReadHelper;
382         mFitnessRecordDeleteHelper = fitnessRecordDeleteHelper;
383         mFitnessRecordAggregateHelper = fitnessRecordAggregateHelper;
384         mMedicalResourceHelper = medicalResourceHelper;
385         mMedicalDataSourceHelper = medicalDataSourceHelper;
386 
387         mExportManager = exportManager;
388         mExportImportSettingsStorage = exportImportSettingsStorage;
389         mBackupRestore = backupRestore;
390 
391         mAccessLogsHelper = accessLogsHelper;
392         mHealthDataCategoryPriorityHelper = healthDataCategoryPriorityHelper;
393         mActivityDateHelper = activityDateHelper;
394         mChangeLogsHelper = changeLogsHelper;
395         mChangeLogsRequestHelper = changeLogsRequestHelper;
396         mPriorityMigrationHelper = priorityMigrationHelper;
397         mAppInfoHelper = appInfoHelper;
398         mDeviceInfoHelper = deviceInfoHelper;
399         mPreferenceHelper = preferenceHelper;
400         mDatabaseHelpers = databaseHelpers;
401         mPreferencesManager = preferencesManager;
402         mReadAccessLogsHelper = readAccessLogsHelper;
403         mThreadScheduler = threadScheduler;
404         mRateLimiter = rateLimiter;
405 
406         mPermissionManager = mContext.getSystemService(PermissionManager.class);
407         mAppOpsManagerLocal = appOpsManagerLocal;
408         mMedicalDataPermissionEnforcer = new MedicalDataPermissionEnforcer(mPermissionManager);
409         mDataPermissionEnforcer =
410                 new DataPermissionEnforcer(
411                         mPermissionManager, mContext, internalHealthConnectMappings);
412         Clock clockForLogging = Clock.systemUTC();
413         mImportManager =
414                 new ImportManager(
415                         mAppInfoHelper,
416                         mContext,
417                         mExportImportSettingsStorage,
418                         mTransactionManager,
419                         mFitnessRecordUpsertHelper,
420                         mFitnessRecordReadHelper,
421                         mDeviceInfoHelper,
422                         mHealthDataCategoryPriorityHelper,
423                         Flags.exportImportFastFollow() ? clockForLogging : null,
424                         exportImportNotificationSender,
425                         environmentDataDirectory,
426                         exportImportLogger);
427 
428         mCloudBackupManager =
429                 // TODO(b/400105647): Remove duplicate flag check once excess code size is resolved.
430                 Flags.cloudBackupAndRestore() && isCloudBackupRestoreEnabled()
431                         ? new CloudBackupManager(
432                                 mTransactionManager,
433                                 mFitnessRecordReadHelper,
434                                 mAppInfoHelper,
435                                 mDeviceInfoHelper,
436                                 mHealthConnectMappings,
437                                 mInternalHealthConnectMappings,
438                                 mChangeLogsHelper,
439                                 mChangeLogsRequestHelper,
440                                 mHealthDataCategoryPriorityHelper,
441                                 mPreferenceHelper,
442                                 clockForLogging,
443                                 backupRestoreLogger)
444                         : null;
445         mCloudRestoreManager =
446                 // TODO(b/400105647): Remove duplicate flag check once excess code size is resolved.
447                 Flags.cloudBackupAndRestore() && isCloudBackupRestoreEnabled()
448                         ? new CloudRestoreManager(
449                                 mTransactionManager,
450                                 mFitnessRecordUpsertHelper,
451                                 mFitnessRecordReadHelper,
452                                 mInternalHealthConnectMappings,
453                                 mDeviceInfoHelper,
454                                 mAppInfoHelper,
455                                 mHealthDataCategoryPriorityHelper,
456                                 mPreferenceHelper,
457                                 clockForLogging,
458                                 backupRestoreLogger)
459                         : null;
460         mStatsLog = statsLog;
461     }
462 
setupForUser(UserHandle currentForegroundUser)463     public void setupForUser(UserHandle currentForegroundUser) {
464         mCurrentForegroundUser = currentForegroundUser;
465     }
466 
467     @Override
grantHealthPermission(String packageName, String permissionName, UserHandle user)468     public void grantHealthPermission(String packageName, String permissionName, UserHandle user) {
469         checkParamsNonNull(packageName, permissionName, user);
470 
471         throwIllegalStateExceptionIfDataSyncInProgress();
472         mPermissionHelper.grantHealthPermission(packageName, permissionName, user);
473     }
474 
475     @Override
revokeHealthPermission( String packageName, String permissionName, @Nullable String reason, UserHandle user)476     public void revokeHealthPermission(
477             String packageName, String permissionName, @Nullable String reason, UserHandle user) {
478         checkParamsNonNull(packageName, permissionName, user);
479 
480         throwIllegalStateExceptionIfDataSyncInProgress();
481         mPermissionHelper.revokeHealthPermission(packageName, permissionName, reason, user);
482     }
483 
484     @Override
revokeAllHealthPermissions( String packageName, @Nullable String reason, UserHandle user)485     public void revokeAllHealthPermissions(
486             String packageName, @Nullable String reason, UserHandle user) {
487         checkParamsNonNull(packageName, user);
488 
489         throwIllegalStateExceptionIfDataSyncInProgress();
490         mPermissionHelper.revokeAllHealthPermissions(packageName, reason, user);
491     }
492 
493     @Override
getGrantedHealthPermissions(String packageName, UserHandle user)494     public List<String> getGrantedHealthPermissions(String packageName, UserHandle user) {
495         checkParamsNonNull(packageName, user);
496 
497         throwIllegalStateExceptionIfDataSyncInProgress();
498         List<String> grantedPermissions =
499                 mPermissionHelper.getGrantedHealthPermissions(packageName, user);
500         return grantedPermissions;
501     }
502 
503     @Override
getHealthPermissionsFlags( String packageName, UserHandle user, List<String> permissions)504     public Map<String, Integer> getHealthPermissionsFlags(
505             String packageName, UserHandle user, List<String> permissions) {
506         checkParamsNonNull(packageName, user);
507         throwIllegalStateExceptionIfDataSyncInProgress();
508 
509         Map<String, Integer> response =
510                 mPermissionHelper.getHealthPermissionsFlags(packageName, user, permissions);
511         return response;
512     }
513 
514     @Override
setHealthPermissionsUserFixedFlagValue( String packageName, UserHandle user, List<String> permissions, boolean value)515     public void setHealthPermissionsUserFixedFlagValue(
516             String packageName, UserHandle user, List<String> permissions, boolean value) {
517         checkParamsNonNull(packageName, user);
518         throwIllegalStateExceptionIfDataSyncInProgress();
519 
520         mPermissionHelper.setHealthPermissionsUserFixedFlagValue(
521                 packageName, user, permissions, value);
522     }
523 
524     @Override
getHistoricalAccessStartDateInMilliseconds( String packageName, UserHandle userHandle)525     public long getHistoricalAccessStartDateInMilliseconds(
526             String packageName, UserHandle userHandle) {
527         checkParamsNonNull(packageName, userHandle);
528 
529         throwIllegalStateExceptionIfDataSyncInProgress();
530         Optional<Instant> date =
531                 mPermissionHelper.getHealthDataStartDateAccess(packageName, userHandle);
532         return date.map(Instant::toEpochMilli).orElse(Constants.DEFAULT_LONG);
533     }
534 
535     /**
536      * Inserts {@code recordsParcel} into the HealthConnect database.
537      *
538      * @param recordsParcel parcel for list of records to be inserted.
539      * @param callback Callback to receive result of performing this operation. The keys returned in
540      *     {@link InsertRecordsResponseParcel} are the unique IDs of the input records. The values
541      *     are in same order as {@code record}. In case of an error or a permission failure the
542      *     HealthConnect service, {@link IInsertRecordsResponseCallback#onError} will be invoked
543      *     with a {@link HealthConnectExceptionParcel}.
544      */
545     @Override
insertRecords( AttributionSource attributionSource, RecordsParcel recordsParcel, IInsertRecordsResponseCallback callback)546     public void insertRecords(
547             AttributionSource attributionSource,
548             RecordsParcel recordsParcel,
549             IInsertRecordsResponseCallback callback) {
550         checkParamsNonNull(attributionSource, recordsParcel, callback);
551 
552         final int uid = Binder.getCallingUid();
553         final int pid = Binder.getCallingPid();
554         final UserHandle userHandle = Binder.getCallingUserHandle();
555         final HealthConnectServiceLogger.Builder logger =
556                 new HealthConnectServiceLogger.Builder(false, INSERT_DATA)
557                         .setHealthFitnessStatsLog(mStatsLog)
558                         .setPackageName(attributionSource.getPackageName());
559 
560         ErrorCallback errorCallback = callback::onError;
561 
562         scheduleLoggingHealthDataApiErrors(
563                 () -> {
564                     enforceIsForegroundUser(userHandle);
565                     verifyPackageNameFromUid(uid, attributionSource);
566                     if (hasDataManagementPermission(uid, pid)) {
567                         throw new SecurityException(
568                                 "Apps with android.permission.MANAGE_HEALTH_DATA permission are"
569                                         + " not allowed to insert records");
570                     }
571                     enforceMemoryRateLimit(
572                             recordsParcel.getRecordsSize(), recordsParcel.getRecordsChunkSize());
573                     final List<RecordInternal<?>> recordInternals = recordsParcel.getRecords();
574                     logger.setNumberOfRecords(recordInternals.size());
575                     throwExceptionIfDataSyncInProgress();
576                     boolean isInForeground = mAppOpsManagerLocal.isUidInForeground(uid);
577                     tryAcquireApiCallQuota(
578                             uid,
579                             QuotaCategory.QUOTA_CATEGORY_WRITE,
580                             isInForeground,
581                             logger,
582                             recordsParcel.getRecordsChunkSize());
583                     mDataPermissionEnforcer.enforceRecordsWritePermissions(
584                             recordInternals, attributionSource);
585                     List<String> uuids =
586                             mFitnessRecordUpsertHelper.insertRecords(
587                                     Objects.requireNonNull(attributionSource.getPackageName()),
588                                     recordInternals,
589                                     mDataPermissionEnforcer.collectExtraWritePermissionStateMapping(
590                                             recordInternals, attributionSource));
591                     tryAndReturnResult(callback, uuids, logger);
592 
593                     mThreadScheduler.scheduleInternalTask(
594                             () -> postInsertTasks(attributionSource, recordsParcel));
595 
596                     logRecordTypeSpecificUpsertMetrics(
597                             recordInternals, attributionSource.getPackageName());
598                     logger.setDataTypesFromRecordInternals(recordInternals);
599                 },
600                 logger,
601                 errorCallback,
602                 uid,
603                 /* isController= */ false);
604     }
605 
postInsertTasks(AttributionSource attributionSource, RecordsParcel recordsParcel)606     private void postInsertTasks(AttributionSource attributionSource, RecordsParcel recordsParcel) {
607         mActivityDateHelper.insertRecordDate(recordsParcel.getRecords());
608         Set<Integer> recordsTypesInsertedSet =
609                 recordsParcel.getRecords().stream()
610                         .map(RecordInternal::getRecordType)
611                         .collect(toSet());
612         // Update AppInfo table with the record types of records inserted in the request for the
613         // current package.
614         mAppInfoHelper.updateAppInfoRecordTypesUsedOnInsert(
615                 recordsTypesInsertedSet, attributionSource.getPackageName());
616     }
617 
618     /**
619      * Returns aggregation results based on the {@code request} into the HealthConnect database.
620      *
621      * @param request represents the request using which the aggregation is to be performed.
622      * @param callback Callback to receive result of performing this operation.
623      */
aggregateRecords( AttributionSource attributionSource, AggregateDataRequestParcel request, IAggregateRecordsResponseCallback callback)624     public void aggregateRecords(
625             AttributionSource attributionSource,
626             AggregateDataRequestParcel request,
627             IAggregateRecordsResponseCallback callback) {
628         checkParamsNonNull(attributionSource, request, callback);
629 
630         final int uid = Binder.getCallingUid();
631         final int pid = Binder.getCallingPid();
632         final UserHandle userHandle = Binder.getCallingUserHandle();
633         final boolean holdsDataManagementPermission = hasDataManagementPermission(uid, pid);
634         final HealthConnectServiceLogger.Builder logger =
635                 new HealthConnectServiceLogger.Builder(
636                                 holdsDataManagementPermission, READ_AGGREGATED_DATA)
637                         .setHealthFitnessStatsLog(mStatsLog)
638                         .setPackageName(attributionSource.getPackageName());
639 
640         ErrorCallback errorCallback = callback::onError;
641         scheduleLoggingHealthDataApiErrors(
642                 () -> {
643                     enforceIsForegroundUser(userHandle);
644                     verifyPackageNameFromUid(uid, attributionSource);
645                     logger.setNumberOfRecords(request.getAggregateIds().length);
646                     throwExceptionIfDataSyncInProgress();
647                     List<Integer> recordTypesToTest = new ArrayList<>();
648                     for (int aggregateId : request.getAggregateIds()) {
649                         recordTypesToTest.add(
650                                 mAggregationTypeIdMapper
651                                         .getAggregationTypeFor(aggregateId)
652                                         .getApplicableRecordTypeId());
653                     }
654 
655                     long startDateAccess = request.getStartTime();
656                     // TODO(b/309776578): Consider making background reads possible for
657                     // aggregations when only using own data
658                     if (!holdsDataManagementPermission) {
659                         boolean isInForeground = mAppOpsManagerLocal.isUidInForeground(uid);
660                         logger.setCallerForegroundState(isInForeground);
661 
662                         if (!isInForeground) {
663                             mContext.enforcePermission(
664                                     READ_HEALTH_DATA_IN_BACKGROUND,
665                                     pid,
666                                     uid,
667                                     attributionSource.getPackageName()
668                                             + "must be in foreground to call aggregate method");
669                             if (isBackgroundPermissionFromSplit(attributionSource)) {
670                                 throw new SecurityException(
671                                         "READ_HEALTH_DATA_IN_BACKGROUND is from split permission,"
672                                                 + " must be explicitly requested and granted");
673                             }
674                         }
675                         tryAcquireApiCallQuota(
676                                 uid,
677                                 RateLimiter.QuotaCategory.QUOTA_CATEGORY_READ,
678                                 isInForeground,
679                                 logger);
680                         boolean enforceSelfRead =
681                                 mDataPermissionEnforcer.enforceReadAccessAndGetEnforceSelfRead(
682                                         recordTypesToTest, attributionSource);
683                         if (!isPermissionGranted(READ_HEALTH_DATA_HISTORY, uid, pid)) {
684                             startDateAccess =
685                                     mPermissionHelper
686                                             .getHealthDataStartDateAccessOrThrow(
687                                                     attributionSource.getPackageName(), userHandle)
688                                             .toEpochMilli();
689                         }
690                         maybeEnforceOnlyCallingPackageDataRequested(
691                                 request.getPackageFilters(),
692                                 attributionSource.getPackageName(),
693                                 enforceSelfRead,
694                                 "aggregationTypes: "
695                                         + Arrays.stream(request.getAggregateIds())
696                                                 .mapToObj(
697                                                         mAggregationTypeIdMapper
698                                                                 ::getAggregationTypeFor)
699                                                 .collect(Collectors.toList()));
700                     }
701                     boolean shouldRecordAccessLog = !holdsDataManagementPermission;
702                     callback.onResult(
703                             mFitnessRecordAggregateHelper.aggregateRecords(
704                                     attributionSource.getPackageName(),
705                                     request,
706                                     startDateAccess,
707                                     shouldRecordAccessLog));
708                     logger.setDataTypesFromRecordTypes(recordTypesToTest)
709                             .setHealthDataServiceApiStatusSuccess();
710                 },
711                 logger,
712                 errorCallback,
713                 uid,
714                 /* isController= */ holdsDataManagementPermission);
715     }
716 
717     /**
718      * Read records {@code recordsParcel} from HealthConnect database.
719      *
720      * @param request ReadRecordsRequestParcel is parcel for the request object containing {@link
721      *     RecordIdFiltersParcel}.
722      * @param callback Callback to receive result of performing this operation. The records are
723      *     returned in {@link RecordsParcel} . In case of an error or a permission failure the
724      *     HealthConnect service, {@link IReadRecordsResponseCallback#onError} will be invoked with
725      *     a {@link HealthConnectExceptionParcel}.
726      */
727     @Override
readRecords( AttributionSource attributionSource, ReadRecordsRequestParcel request, IReadRecordsResponseCallback callback)728     public void readRecords(
729             AttributionSource attributionSource,
730             ReadRecordsRequestParcel request,
731             IReadRecordsResponseCallback callback) {
732         checkParamsNonNull(attributionSource, request, callback);
733 
734         ErrorCallback errorCallback = error -> callback.onError(error);
735 
736         final int uid = Binder.getCallingUid();
737         final int pid = Binder.getCallingPid();
738         final UserHandle userHandle = Binder.getCallingUserHandle();
739         final boolean holdsDataManagementPermission = hasDataManagementPermission(uid, pid);
740         final String callingPackageName =
741                 Objects.requireNonNull(attributionSource.getPackageName());
742         final HealthConnectServiceLogger.Builder logger =
743                 new HealthConnectServiceLogger.Builder(holdsDataManagementPermission, READ_DATA)
744                         .setHealthFitnessStatsLog(mStatsLog)
745                         .setPackageName(callingPackageName);
746 
747         scheduleLoggingHealthDataApiErrors(
748                 () -> {
749                     enforceIsForegroundUser(userHandle);
750                     verifyPackageNameFromUid(uid, attributionSource);
751                     throwExceptionIfDataSyncInProgress();
752 
753                     boolean enforceSelfRead = false;
754 
755                     final boolean isInForeground = mAppOpsManagerLocal.isUidInForeground(uid);
756 
757                     if (!holdsDataManagementPermission) {
758                         logger.setCallerForegroundState(isInForeground);
759 
760                         tryAcquireApiCallQuota(
761                                 uid, QuotaCategory.QUOTA_CATEGORY_READ, isInForeground, logger);
762 
763                         if (mDataPermissionEnforcer.enforceReadAccessAndGetEnforceSelfRead(
764                                 request.getRecordType(), attributionSource)) {
765                             // If read permission is missing but write permission is granted,
766                             // then enforce self read
767                             enforceSelfRead = true;
768                         } else if (!isInForeground) {
769                             // If READ_HEALTH_DATA_IN_BACKGROUND permission is not granted,
770                             // then enforce self read
771                             enforceSelfRead = shouldEnforceSelfRead(uid, pid, attributionSource);
772                         }
773                         if (request.getRecordIdFiltersParcel() == null) {
774                             // Only enforce requested packages if this is a
775                             // ReadRecordsByRequest using filters. Reading by IDs does not have
776                             // data origins specified.
777                             // TODO(b/309778116): Consider throwing an error when reading by Id
778                             maybeEnforceOnlyCallingPackageDataRequested(
779                                     request.getPackageFilters(),
780                                     callingPackageName,
781                                     enforceSelfRead,
782                                     "recordType: "
783                                             + mHealthConnectMappings
784                                                     .getRecordIdToExternalRecordClassMap()
785                                                     .get(request.getRecordType()));
786                         }
787 
788                         if (Constants.DEBUG) {
789                             Slog.d(
790                                     TAG,
791                                     "Enforce self read for package "
792                                             + callingPackageName
793                                             + ":"
794                                             + enforceSelfRead);
795                         }
796                     }
797                     final Set<String> grantedExtraReadPermissions =
798                             mDataPermissionEnforcer.collectGrantedExtraReadPermissions(
799                                     Set.of(request.getRecordType()), attributionSource);
800 
801                     try {
802                         long startDateAccessEpochMilli = request.getStartTime();
803 
804                         if (!holdsDataManagementPermission
805                                 && !isPermissionGranted(READ_HEALTH_DATA_HISTORY, uid, pid)) {
806                             Instant startDateAccessInstant =
807                                     mPermissionHelper.getHealthDataStartDateAccessOrThrow(
808                                             callingPackageName, userHandle);
809 
810                             // Always set the startDateAccess for local time filter, as for
811                             // local date time we use it in conjunction with the time filter
812                             // start-time
813                             if (request.usesLocalTimeFilter()
814                                     || startDateAccessInstant.toEpochMilli()
815                                             > startDateAccessEpochMilli) {
816                                 startDateAccessEpochMilli = startDateAccessInstant.toEpochMilli();
817                             }
818                         }
819 
820                         boolean shouldRecordAccessLog =
821                                 !holdsDataManagementPermission && !enforceSelfRead;
822                         Pair<List<RecordInternal<?>>, PageTokenWrapper> readRecordsResponse =
823                                 mFitnessRecordReadHelper.readRecords(
824                                         mTransactionManager,
825                                         callingPackageName,
826                                         request,
827                                         grantedExtraReadPermissions,
828                                         startDateAccessEpochMilli,
829                                         isInForeground,
830                                         shouldRecordAccessLog,
831                                         enforceSelfRead,
832                                         /* packageNamesByAppIds= */ null);
833                         List<RecordInternal<?>> records = readRecordsResponse.first;
834                         long pageToken = readRecordsResponse.second.encode();
835 
836                         logger.setNumberOfRecords(records.size());
837 
838                         if (Constants.DEBUG) {
839                             Slog.d(TAG, "pageToken: " + pageToken);
840                         }
841 
842                         if (!Flags.addMissingAccessLogs()) {
843                             // Calls from controller APK should not be recorded in access logs
844                             // If an app is reading only its own data then it is not recorded in
845                             // access logs.
846                             if (!holdsDataManagementPermission && !enforceSelfRead) {
847                                 final List<Integer> recordTypes =
848                                         singletonList(request.getRecordType());
849                                 mAccessLogsHelper.addAccessLog(
850                                         callingPackageName, recordTypes, READ);
851                             }
852                         }
853 
854                         callback.onResult(
855                                 new ReadRecordsResponseParcel(
856                                         new RecordsParcel(records), pageToken));
857                         logRecordTypeSpecificReadMetrics(records, callingPackageName);
858                         logger.setDataTypesFromRecordInternals(records)
859                                 .setHealthDataServiceApiStatusSuccess();
860                     } catch (TypeNotPresentException exception) {
861                         // All the requested package names are not present, so simply
862                         // return an empty list
863                         if (FitnessRecordReadHelper.TYPE_NOT_PRESENT_PACKAGE_NAME.equals(
864                                 exception.typeName())) {
865                             if (Constants.DEBUG) {
866                                 Slog.d(TAG, "No app info recorded for " + callingPackageName);
867                             }
868                             callback.onResult(
869                                     new ReadRecordsResponseParcel(
870                                             new RecordsParcel(new ArrayList<>()), DEFAULT_LONG));
871                             logger.setHealthDataServiceApiStatusSuccess();
872                         } else {
873                             logger.setHealthDataServiceApiStatusError(
874                                     HealthConnectException.ERROR_UNKNOWN);
875                             throw exception;
876                         }
877                     }
878                 },
879                 logger,
880                 errorCallback,
881                 uid,
882                 /* isController= */ holdsDataManagementPermission);
883     }
884 
maybeEnforceOnlyCallingPackageDataRequested( List<String> packageFilters, String callingPackageName, boolean enforceSelfRead, String entityFailureMessage)885     private void maybeEnforceOnlyCallingPackageDataRequested(
886             List<String> packageFilters,
887             String callingPackageName,
888             boolean enforceSelfRead,
889             String entityFailureMessage) {
890         if (enforceSelfRead
891                 && (packageFilters.size() != 1
892                         || !packageFilters.get(0).equals(callingPackageName))) {
893             throw new SecurityException(
894                     "Caller does not have permission to read data for the following ("
895                             + entityFailureMessage
896                             + ") from other applications.");
897         }
898     }
899 
900     /**
901      * Updates {@code recordsParcel} into the HealthConnect database.
902      *
903      * @param recordsParcel parcel for list of records to be updated.
904      * @param callback Callback to receive result of performing this operation. In case of an error
905      *     or a permission failure the HealthConnect service, {@link IEmptyResponseCallback#onError}
906      *     will be invoked with a {@link HealthConnectException}.
907      */
908     @Override
updateRecords( AttributionSource attributionSource, RecordsParcel recordsParcel, IEmptyResponseCallback callback)909     public void updateRecords(
910             AttributionSource attributionSource,
911             RecordsParcel recordsParcel,
912             IEmptyResponseCallback callback) {
913         checkParamsNonNull(attributionSource, recordsParcel, callback);
914         ErrorCallback errorCallback = callback::onError;
915 
916         final int uid = Binder.getCallingUid();
917         final int pid = Binder.getCallingPid();
918         final UserHandle userHandle = Binder.getCallingUserHandle();
919         final HealthConnectServiceLogger.Builder logger =
920                 new HealthConnectServiceLogger.Builder(false, UPDATE_DATA)
921                         .setHealthFitnessStatsLog(mStatsLog)
922                         .setPackageName(attributionSource.getPackageName());
923         scheduleLoggingHealthDataApiErrors(
924                 () -> {
925                     enforceIsForegroundUser(userHandle);
926                     verifyPackageNameFromUid(uid, attributionSource);
927                     if (hasDataManagementPermission(uid, pid)) {
928                         throw new SecurityException(
929                                 "Apps with android.permission.MANAGE_HEALTH_DATA permission are"
930                                         + " not allowed to insert records");
931                     }
932                     enforceMemoryRateLimit(
933                             recordsParcel.getRecordsSize(), recordsParcel.getRecordsChunkSize());
934                     final List<RecordInternal<?>> recordInternals = recordsParcel.getRecords();
935                     logger.setNumberOfRecords(recordInternals.size());
936                     throwExceptionIfDataSyncInProgress();
937                     boolean isInForeground = mAppOpsManagerLocal.isUidInForeground(uid);
938                     tryAcquireApiCallQuota(
939                             uid,
940                             QuotaCategory.QUOTA_CATEGORY_WRITE,
941                             isInForeground,
942                             logger,
943                             recordsParcel.getRecordsChunkSize());
944                     mDataPermissionEnforcer.enforceRecordsWritePermissions(
945                             recordInternals, attributionSource);
946                     mFitnessRecordUpsertHelper.updateRecords(
947                             Objects.requireNonNull(attributionSource.getPackageName()),
948                             recordInternals,
949                             mDataPermissionEnforcer.collectExtraWritePermissionStateMapping(
950                                     recordInternals, attributionSource));
951                     tryAndReturnResult(callback, logger);
952                     logRecordTypeSpecificUpsertMetrics(
953                             recordInternals, attributionSource.getPackageName());
954                     logger.setDataTypesFromRecordInternals(recordInternals);
955                     // Update activity dates table
956                     mThreadScheduler.scheduleInternalTask(
957                             () ->
958                                     mActivityDateHelper.reSyncByRecordTypeIds(
959                                             recordInternals.stream()
960                                                     .map(RecordInternal::getRecordType)
961                                                     .toList()));
962                 },
963                 logger,
964                 errorCallback,
965                 uid,
966                 /* isController= */ false);
967     }
968 
969     /**
970      * @see HealthConnectManager#getChangeLogToken
971      */
972     @Override
getChangeLogToken( AttributionSource attributionSource, ChangeLogTokenRequest request, IGetChangeLogTokenCallback callback)973     public void getChangeLogToken(
974             AttributionSource attributionSource,
975             ChangeLogTokenRequest request,
976             IGetChangeLogTokenCallback callback) {
977         checkParamsNonNull(attributionSource, request, callback);
978 
979         ErrorCallback errorCallback = callback::onError;
980 
981         final int uid = Binder.getCallingUid();
982         final UserHandle userHandle = Binder.getCallingUserHandle();
983         final HealthConnectServiceLogger.Builder logger =
984                 new HealthConnectServiceLogger.Builder(false, GET_CHANGES_TOKEN)
985                         .setHealthFitnessStatsLog(mStatsLog)
986                         .setPackageName(attributionSource.getPackageName());
987         scheduleLoggingHealthDataApiErrors(
988                 () -> {
989                     enforceIsForegroundUser(userHandle);
990                     verifyPackageNameFromUid(uid, attributionSource);
991                     tryAcquireApiCallQuota(
992                             uid,
993                             QuotaCategory.QUOTA_CATEGORY_READ,
994                             mAppOpsManagerLocal.isUidInForeground(uid),
995                             logger);
996                     throwExceptionIfDataSyncInProgress();
997                     if (request.getRecordTypes().isEmpty()) {
998                         throw new IllegalArgumentException(
999                                 "Requested record types must not be empty.");
1000                     }
1001                     mDataPermissionEnforcer.enforceRecordIdsReadPermissions(
1002                             request.getRecordTypesList(), attributionSource);
1003                     callback.onResult(
1004                             new ChangeLogTokenResponse(
1005                                     mChangeLogsRequestHelper.getToken(
1006                                             mChangeLogsHelper.getLatestRowId(),
1007                                             attributionSource.getPackageName(),
1008                                             request)));
1009                     logger.setHealthDataServiceApiStatusSuccess();
1010                 },
1011                 logger,
1012                 errorCallback,
1013                 uid,
1014                 /* isController= */ false);
1015     }
1016 
1017     /**
1018      * @hide
1019      * @see HealthConnectManager#getChangeLogs
1020      */
1021     @Override
getChangeLogs( AttributionSource attributionSource, ChangeLogsRequest request, IChangeLogsResponseCallback callback)1022     public void getChangeLogs(
1023             AttributionSource attributionSource,
1024             ChangeLogsRequest request,
1025             IChangeLogsResponseCallback callback) {
1026         checkParamsNonNull(attributionSource, request, callback);
1027 
1028         ErrorCallback errorCallback = callback::onError;
1029 
1030         final int uid = Binder.getCallingUid();
1031         final int pid = Binder.getCallingPid();
1032         final UserHandle userHandle = Binder.getCallingUserHandle();
1033         final String callerPackageName = Objects.requireNonNull(attributionSource.getPackageName());
1034         final HealthConnectServiceLogger.Builder logger =
1035                 new HealthConnectServiceLogger.Builder(false, GET_CHANGES)
1036                         .setHealthFitnessStatsLog(mStatsLog)
1037                         .setPackageName(callerPackageName);
1038 
1039         scheduleLoggingHealthDataApiErrors(
1040                 () -> {
1041                     enforceIsForegroundUser(userHandle);
1042                     verifyPackageNameFromUid(uid, attributionSource);
1043                     throwExceptionIfDataSyncInProgress();
1044 
1045                     boolean isInForeground = mAppOpsManagerLocal.isUidInForeground(uid);
1046                     logger.setCallerForegroundState(isInForeground);
1047 
1048                     if (!isInForeground) {
1049                         mContext.enforcePermission(
1050                                 READ_HEALTH_DATA_IN_BACKGROUND,
1051                                 pid,
1052                                 uid,
1053                                 callerPackageName
1054                                         + "must be in foreground to call getChangeLogs method");
1055                         if (isBackgroundPermissionFromSplit(attributionSource)) {
1056                             throw new SecurityException(
1057                                     "READ_HEALTH_DATA_IN_BACKGROUND is from split permission,"
1058                                             + " must be explicitly requested and granted");
1059                         }
1060                     }
1061 
1062                     tryAcquireApiCallQuota(
1063                             uid, QuotaCategory.QUOTA_CATEGORY_READ, isInForeground, logger);
1064 
1065                     ChangeLogsRequestHelper.TokenRequest changeLogsTokenRequest =
1066                             mChangeLogsRequestHelper.getRequest(
1067                                     callerPackageName, request.getToken());
1068                     if (changeLogsTokenRequest.getRecordTypes().isEmpty()) {
1069                         throw new IllegalArgumentException(
1070                                 "Requested record types must not be empty.");
1071                     }
1072                     // This API doesn't support reading own data without read permissions.
1073                     mDataPermissionEnforcer.enforceRecordIdsReadPermissions(
1074                             changeLogsTokenRequest.getRecordTypes(), attributionSource);
1075                     long startDateAccessEpochMilli = DEFAULT_LONG;
1076                     if (!isPermissionGranted(READ_HEALTH_DATA_HISTORY, uid, pid)) {
1077                         startDateAccessEpochMilli =
1078                                 mPermissionHelper
1079                                         .getHealthDataStartDateAccessOrThrow(
1080                                                 callerPackageName, userHandle)
1081                                         .toEpochMilli();
1082                     }
1083                     final ChangeLogsHelper.ChangeLogsResponse changeLogsResponse =
1084                             mChangeLogsHelper.getChangeLogs(
1085                                     mAppInfoHelper,
1086                                     changeLogsTokenRequest,
1087                                     request,
1088                                     mChangeLogsRequestHelper);
1089 
1090                     Map<Integer, List<UUID>> recordTypeToInsertedUuids =
1091                             ChangeLogsHelper.getRecordTypeToInsertedUuids(
1092                                     changeLogsResponse.getChangeLogsMap());
1093 
1094                     Set<String> grantedExtraReadPermissions =
1095                             mDataPermissionEnforcer.collectGrantedExtraReadPermissions(
1096                                     recordTypeToInsertedUuids.keySet(), attributionSource);
1097 
1098                     List<RecordInternal<?>> recordInternals =
1099                             mFitnessRecordReadHelper.readRecords(
1100                                     mTransactionManager,
1101                                     callerPackageName,
1102                                     recordTypeToInsertedUuids,
1103                                     grantedExtraReadPermissions,
1104                                     startDateAccessEpochMilli,
1105                                     isInForeground,
1106                                     /* shouldRecordAccessLog= */ true);
1107                     List<DeletedLog> deletedLogs =
1108                             ChangeLogsHelper.getDeletedLogs(changeLogsResponse.getChangeLogsMap());
1109 
1110                     callback.onResult(
1111                             new ChangeLogsResponse(
1112                                     new RecordsParcel(recordInternals),
1113                                     deletedLogs,
1114                                     changeLogsResponse.getNextPageToken(),
1115                                     changeLogsResponse.hasMorePages()));
1116                     logger.setHealthDataServiceApiStatusSuccess()
1117                             .setNumberOfRecords(recordInternals.size() + deletedLogs.size())
1118                             .setDataTypesFromRecordInternals(recordInternals);
1119                 },
1120                 logger,
1121                 errorCallback,
1122                 uid,
1123                 /* isController= */ false);
1124     }
1125 
1126     /** API to delete records based on {@code request}. */
1127     @Override
deleteUsingFilters( AttributionSource attributionSource, DeleteUsingFiltersRequestParcel request, IEmptyResponseCallback callback)1128     public void deleteUsingFilters(
1129             AttributionSource attributionSource,
1130             DeleteUsingFiltersRequestParcel request,
1131             IEmptyResponseCallback callback) {
1132         checkParamsNonNull(attributionSource, request, callback);
1133         ErrorCallback errorCallback = callback::onError;
1134 
1135         final int uid = Binder.getCallingUid();
1136         final int pid = Binder.getCallingPid();
1137         final boolean holdsDataManagementPermission = hasDataManagementPermission(uid, pid);
1138         final HealthConnectServiceLogger.Builder logger =
1139                 new HealthConnectServiceLogger.Builder(holdsDataManagementPermission, DELETE_DATA)
1140                         .setHealthFitnessStatsLog(mStatsLog)
1141                         .setPackageName(attributionSource.getPackageName());
1142 
1143         final RequestContext requestContext = RequestContext.create();
1144 
1145         scheduleLoggingHealthDataApiErrors(
1146                 () -> {
1147                     enforceIsForegroundUser(requestContext.getCallingUser());
1148                     verifyPackageNameFromUid(uid, attributionSource);
1149                     throwExceptionIfDataSyncInProgress();
1150                     List<Integer> recordTypeIdsToDelete =
1151                             (!request.getRecordTypeFilters().isEmpty())
1152                                     ? request.getRecordTypeFilters()
1153                                     : new ArrayList<>(
1154                                             mHealthConnectMappings
1155                                                     .getRecordIdToExternalRecordClassMap()
1156                                                     .keySet());
1157 
1158                     if (!holdsDataManagementPermission) {
1159                         tryAcquireApiCallQuota(
1160                                 uid,
1161                                 QuotaCategory.QUOTA_CATEGORY_WRITE,
1162                                 mAppOpsManagerLocal.isUidInForeground(uid),
1163                                 logger);
1164                         mDataPermissionEnforcer.enforceRecordIdsWritePermissions(
1165                                 recordTypeIdsToDelete, attributionSource);
1166                     }
1167 
1168                     int numberOfRecordsDeleted =
1169                             mFitnessRecordDeleteHelper.deleteRecords(
1170                                     Objects.requireNonNull(attributionSource.getPackageName()),
1171                                     request,
1172                                     /* enforceSelfDelete= */ !holdsDataManagementPermission,
1173                                     /* shouldRecordAccessLog= */ !holdsDataManagementPermission);
1174                     tryAndReturnResult(callback, logger);
1175                     mThreadScheduler.scheduleInternalTask(
1176                             () -> postDeleteTasks(recordTypeIdsToDelete));
1177                     logger.setNumberOfRecords(numberOfRecordsDeleted)
1178                             .setDataTypesFromRecordTypes(recordTypeIdsToDelete);
1179                 },
1180                 logger,
1181                 errorCallback,
1182                 uid,
1183                 /* isController= */ holdsDataManagementPermission);
1184     }
1185 
1186     /** API to get Priority for {@code dataCategory} */
1187     @Override
getCurrentPriority( @ealthDataCategory.Type int dataCategory, IGetPriorityResponseCallback callback)1188     public void getCurrentPriority(
1189             @HealthDataCategory.Type int dataCategory, IGetPriorityResponseCallback callback) {
1190         checkParamsNonNull(callback);
1191         ErrorCallback errorCallback = callback::onError;
1192 
1193         final int uid = Binder.getCallingUid();
1194         final int pid = Binder.getCallingPid();
1195         final UserHandle userHandle = Binder.getCallingUserHandle();
1196         mThreadScheduler.scheduleControllerTask(
1197                 () -> {
1198                     try {
1199                         enforceIsForegroundUser(userHandle);
1200                         mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null);
1201                         throwExceptionIfDataSyncInProgress();
1202                         List<DataOrigin> dataOriginInPriorityOrder =
1203                                 mHealthDataCategoryPriorityHelper
1204                                         .syncAndGetPriorityOrder(dataCategory)
1205                                         .stream()
1206                                         .map(
1207                                                 (name) ->
1208                                                         new DataOrigin.Builder()
1209                                                                 .setPackageName(name)
1210                                                                 .build())
1211                                         .collect(toList());
1212                         callback.onResult(
1213                                 new GetPriorityResponseParcel(
1214                                         new FetchDataOriginsPriorityOrderResponse(
1215                                                 dataOriginInPriorityOrder)));
1216                     } catch (SQLiteException sqLiteException) {
1217                         Slog.e(TAG, "SQLiteException: ", sqLiteException);
1218                         tryAndThrowException(errorCallback, sqLiteException, ERROR_IO);
1219                     } catch (SecurityException securityException) {
1220                         Slog.e(TAG, "SecurityException: ", securityException);
1221                         tryAndThrowException(errorCallback, securityException, ERROR_SECURITY);
1222                     } catch (HealthConnectException healthConnectException) {
1223                         Slog.e(TAG, "HealthConnectException: ", healthConnectException);
1224                         tryAndThrowException(
1225                                 errorCallback,
1226                                 healthConnectException,
1227                                 healthConnectException.getErrorCode());
1228                     } catch (Exception exception) {
1229                         Slog.e(TAG, "Exception: ", exception);
1230                         tryAndThrowException(errorCallback, exception, ERROR_INTERNAL);
1231                     }
1232                 });
1233     }
1234 
1235     /** API to update priority for permission category(ies) */
1236     @Override
updatePriority( UpdatePriorityRequestParcel updatePriorityRequest, IEmptyResponseCallback callback)1237     public void updatePriority(
1238             UpdatePriorityRequestParcel updatePriorityRequest, IEmptyResponseCallback callback) {
1239         checkParamsNonNull(updatePriorityRequest, callback);
1240         ErrorCallback errorCallback = callback::onError;
1241 
1242         final int uid = Binder.getCallingUid();
1243         final int pid = Binder.getCallingPid();
1244         final UserHandle userHandle = Binder.getCallingUserHandle();
1245         mThreadScheduler.scheduleControllerTask(
1246                 () -> {
1247                     try {
1248                         enforceIsForegroundUser(userHandle);
1249                         mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null);
1250                         throwExceptionIfDataSyncInProgress();
1251                         mHealthDataCategoryPriorityHelper.setPriorityOrder(
1252                                 updatePriorityRequest.getDataCategory(),
1253                                 updatePriorityRequest.getPackagePriorityOrder());
1254                         callback.onResult();
1255                     } catch (SQLiteException sqLiteException) {
1256                         Slog.e(TAG, "SQLiteException: ", sqLiteException);
1257                         tryAndThrowException(errorCallback, sqLiteException, ERROR_IO);
1258                     } catch (SecurityException securityException) {
1259                         Slog.e(TAG, "SecurityException: ", securityException);
1260                         tryAndThrowException(errorCallback, securityException, ERROR_SECURITY);
1261                     } catch (HealthConnectException healthConnectException) {
1262                         Slog.e(TAG, "HealthConnectException: ", healthConnectException);
1263                         tryAndThrowException(
1264                                 errorCallback,
1265                                 healthConnectException,
1266                                 healthConnectException.getErrorCode());
1267                     } catch (Exception exception) {
1268                         Slog.e(TAG, "Exception: ", exception);
1269                         tryAndThrowException(errorCallback, exception, ERROR_INTERNAL);
1270                     }
1271                 });
1272     }
1273 
1274     @Override
setRecordRetentionPeriodInDays( int days, UserHandle user, IEmptyResponseCallback callback)1275     public void setRecordRetentionPeriodInDays(
1276             int days, UserHandle user, IEmptyResponseCallback callback) {
1277         checkParamsNonNull(user, callback);
1278         ErrorCallback wrappedCallback = callback::onError;
1279 
1280         final int uid = Binder.getCallingUid();
1281         final int pid = Binder.getCallingPid();
1282         final UserHandle userHandle = Binder.getCallingUserHandle();
1283         mThreadScheduler.scheduleControllerTask(
1284                 () -> {
1285                     try {
1286                         enforceIsForegroundUser(userHandle);
1287                         mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null);
1288                         throwExceptionIfDataSyncInProgress();
1289                         mPreferencesManager.setRecordRetentionPeriodInDays(days);
1290                         callback.onResult();
1291                     } catch (SQLiteException sqLiteException) {
1292                         Slog.e(TAG, "SQLiteException: ", sqLiteException);
1293                         tryAndThrowException(wrappedCallback, sqLiteException, ERROR_IO);
1294                     } catch (SecurityException securityException) {
1295                         Slog.e(TAG, "SecurityException: ", securityException);
1296                         tryAndThrowException(wrappedCallback, securityException, ERROR_SECURITY);
1297                     } catch (HealthConnectException healthConnectException) {
1298                         Slog.e(TAG, "HealthConnectException: ", healthConnectException);
1299                         tryAndThrowException(
1300                                 wrappedCallback,
1301                                 healthConnectException,
1302                                 healthConnectException.getErrorCode());
1303                     } catch (Exception exception) {
1304                         Slog.e(TAG, "Exception: ", exception);
1305                         tryAndThrowException(wrappedCallback, exception, ERROR_INTERNAL);
1306                     }
1307                 });
1308     }
1309 
1310     @Override
getRecordRetentionPeriodInDays(UserHandle user)1311     public int getRecordRetentionPeriodInDays(UserHandle user) {
1312         checkParamsNonNull(user);
1313 
1314         enforceIsForegroundUser(getCallingUserHandle());
1315         throwExceptionIfDataSyncInProgress();
1316         try {
1317             mContext.enforceCallingPermission(MANAGE_HEALTH_DATA_PERMISSION, null);
1318             return mPreferencesManager.getRecordRetentionPeriodInDays();
1319         } catch (Exception e) {
1320             if (e instanceof SecurityException) {
1321                 throw e;
1322             }
1323             Slog.e(TAG, "Unable to get record retention period for " + user);
1324         }
1325 
1326         throw new RuntimeException();
1327     }
1328 
1329     /**
1330      * Returns information, represented by {@code ApplicationInfoResponse}, for all the packages
1331      * that have contributed to the health connect DB.
1332      *
1333      * @param callback Callback to receive result of performing this operation. In case of an error
1334      *     or a permission failure the HealthConnect service, {@link IEmptyResponseCallback#onError}
1335      *     will be invoked with a {@link HealthConnectException}.
1336      */
1337     @Override
getContributorApplicationsInfo(IApplicationInfoResponseCallback callback)1338     public void getContributorApplicationsInfo(IApplicationInfoResponseCallback callback) {
1339         checkParamsNonNull(callback);
1340         ErrorCallback errorCallback = callback::onError;
1341 
1342         final int uid = Binder.getCallingUid();
1343         final int pid = Binder.getCallingPid();
1344         final UserHandle userHandle = Binder.getCallingUserHandle();
1345         mThreadScheduler.scheduleControllerTask(
1346                 () -> {
1347                     try {
1348                         enforceIsForegroundUser(userHandle);
1349                         mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null);
1350                         throwExceptionIfDataSyncInProgress();
1351                         // Get AppInfo IDs which has PHR data.
1352                         Set<Long> appIdsWithPhrData = Set.of();
1353                         if (isPersonalHealthRecordEnabled()) {
1354                             appIdsWithPhrData =
1355                                     mMedicalDataSourceHelper.getAllContributorAppInfoIds();
1356                         }
1357                         // Get all AppInfos which has either Fitness data or PHR data.
1358                         List<AppInfo> applicationInfosWithData =
1359                                 mAppInfoHelper.getApplicationInfosWithRecordTypesOrInIdsList(
1360                                         appIdsWithPhrData);
1361                         callback.onResult(
1362                                 new ApplicationInfoResponseParcel(applicationInfosWithData));
1363                     } catch (SQLiteException sqLiteException) {
1364                         Slog.e(TAG, "SqlException: ", sqLiteException);
1365                         tryAndThrowException(errorCallback, sqLiteException, ERROR_IO);
1366                     } catch (SecurityException securityException) {
1367                         Slog.e(TAG, "SecurityException: ", securityException);
1368                         tryAndThrowException(errorCallback, securityException, ERROR_SECURITY);
1369                     } catch (HealthConnectException healthConnectException) {
1370                         Slog.e(TAG, "HealthConnectException: ", healthConnectException);
1371                         tryAndThrowException(
1372                                 errorCallback,
1373                                 healthConnectException,
1374                                 healthConnectException.getErrorCode());
1375                     } catch (Exception e) {
1376                         Slog.e(TAG, "Exception: ", e);
1377                         tryAndThrowException(errorCallback, e, ERROR_INTERNAL);
1378                     }
1379                 });
1380     }
1381 
1382     /** Retrieves {@link RecordTypeInfoResponse} for each RecordType. */
1383     @Override
queryAllRecordTypesInfo(IRecordTypeInfoResponseCallback callback)1384     public void queryAllRecordTypesInfo(IRecordTypeInfoResponseCallback callback) {
1385         checkParamsNonNull(callback);
1386         ErrorCallback errorCallback = callback::onError;
1387 
1388         final int uid = Binder.getCallingUid();
1389         final int pid = Binder.getCallingPid();
1390         final UserHandle userHandle = Binder.getCallingUserHandle();
1391         mThreadScheduler.scheduleControllerTask(
1392                 () -> {
1393                     try {
1394                         enforceIsForegroundUser(userHandle);
1395                         mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null);
1396                         throwExceptionIfDataSyncInProgress();
1397                         callback.onResult(
1398                                 new RecordTypeInfoResponseParcel(
1399                                         getPopulatedRecordTypeInfoResponses()));
1400                     } catch (SQLiteException sqLiteException) {
1401                         tryAndThrowException(errorCallback, sqLiteException, ERROR_IO);
1402                     } catch (SecurityException securityException) {
1403                         Slog.e(TAG, "SecurityException: ", securityException);
1404                         tryAndThrowException(errorCallback, securityException, ERROR_SECURITY);
1405                     } catch (HealthConnectException healthConnectException) {
1406                         Slog.e(TAG, "HealthConnectException: ", healthConnectException);
1407                         tryAndThrowException(
1408                                 errorCallback,
1409                                 healthConnectException,
1410                                 healthConnectException.getErrorCode());
1411                     } catch (Exception exception) {
1412                         tryAndThrowException(errorCallback, exception, ERROR_INTERNAL);
1413                     }
1414                 });
1415     }
1416 
1417     /**
1418      * @see HealthConnectManager#queryAccessLogs
1419      */
1420     @Override
queryAccessLogs(String packageName, IAccessLogsResponseCallback callback)1421     public void queryAccessLogs(String packageName, IAccessLogsResponseCallback callback) {
1422         checkParamsNonNull(packageName, callback);
1423         ErrorCallback errorCallback = callback::onError;
1424 
1425         final int uid = Binder.getCallingUid();
1426         final int pid = Binder.getCallingPid();
1427         final UserHandle userHandle = Binder.getCallingUserHandle();
1428 
1429         mThreadScheduler.scheduleControllerTask(
1430                 () -> {
1431                     try {
1432                         enforceIsForegroundUser(userHandle);
1433                         mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null);
1434                         throwExceptionIfDataSyncInProgress();
1435                         final List<AccessLog> accessLogsList =
1436                                 mAccessLogsHelper.queryAccessLogs(userHandle);
1437                         callback.onResult(new AccessLogsResponseParcel(accessLogsList));
1438                     } catch (SecurityException securityException) {
1439                         Slog.e(TAG, "SecurityException: ", securityException);
1440                         tryAndThrowException(errorCallback, securityException, ERROR_SECURITY);
1441                     } catch (HealthConnectException healthConnectException) {
1442                         Slog.e(TAG, "HealthConnectException: ", healthConnectException);
1443                         tryAndThrowException(
1444                                 errorCallback,
1445                                 healthConnectException,
1446                                 healthConnectException.getErrorCode());
1447                     } catch (Exception exception) {
1448                         Slog.e(TAG, "Exception: ", exception);
1449                         tryAndThrowException(errorCallback, exception, ERROR_INTERNAL);
1450                     }
1451                 });
1452     }
1453 
1454     /**
1455      * Returns a list of unique dates for which the database has at least one entry
1456      *
1457      * @param activityDatesRequestParcel Parcel request containing records classes
1458      * @param callback Callback to receive result of performing this operation. The results are
1459      *     returned in {@link List<LocalDate>} . In case of an error or a permission failure the
1460      *     HealthConnect service, {@link IActivityDatesResponseCallback#onError} will be invoked
1461      *     with a {@link HealthConnectExceptionParcel}.
1462      */
1463     @Override
getActivityDates( ActivityDatesRequestParcel activityDatesRequestParcel, IActivityDatesResponseCallback callback)1464     public void getActivityDates(
1465             ActivityDatesRequestParcel activityDatesRequestParcel,
1466             IActivityDatesResponseCallback callback) {
1467         checkParamsNonNull(activityDatesRequestParcel, callback);
1468         ErrorCallback errorCallback = callback::onError;
1469 
1470         final int uid = Binder.getCallingUid();
1471         final int pid = Binder.getCallingPid();
1472         final UserHandle userHandle = Binder.getCallingUserHandle();
1473 
1474         mThreadScheduler.scheduleControllerTask(
1475                 () -> {
1476                     try {
1477                         enforceIsForegroundUser(userHandle);
1478                         mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null);
1479                         throwExceptionIfDataSyncInProgress();
1480                         List<LocalDate> localDates =
1481                                 mActivityDateHelper.getActivityDates(
1482                                         activityDatesRequestParcel.getRecordTypes());
1483 
1484                         callback.onResult(new ActivityDatesResponseParcel(localDates));
1485                     } catch (SQLiteException sqLiteException) {
1486                         Slog.e(TAG, "SqlException: ", sqLiteException);
1487                         tryAndThrowException(errorCallback, sqLiteException, ERROR_IO);
1488                     } catch (SecurityException securityException) {
1489                         Slog.e(TAG, "SecurityException: ", securityException);
1490                         tryAndThrowException(errorCallback, securityException, ERROR_SECURITY);
1491                     } catch (HealthConnectException healthConnectException) {
1492                         Slog.e(TAG, "HealthConnectException: ", healthConnectException);
1493                         tryAndThrowException(
1494                                 errorCallback,
1495                                 healthConnectException,
1496                                 healthConnectException.getErrorCode());
1497                     } catch (Exception e) {
1498                         Slog.e(TAG, "Exception: ", e);
1499                         tryAndThrowException(errorCallback, e, ERROR_INTERNAL);
1500                     }
1501                 });
1502     }
1503 
1504     /**
1505      * Changes migration state to {@link HealthConnectDataState#MIGRATION_STATE_IN_PROGRESS} if the
1506      * current state allows migration to be started.
1507      *
1508      * @param packageName calling package name
1509      * @param callback Callback to receive a result or an error encountered while performing this
1510      *     operation.
1511      */
1512     @Override
startMigration(String packageName, IMigrationCallback callback)1513     public void startMigration(String packageName, IMigrationCallback callback) {
1514         checkParamsNonNull(packageName, callback);
1515 
1516         int uid = Binder.getCallingUid();
1517         int pid = Binder.getCallingPid();
1518         final UserHandle userHandle = Binder.getCallingUserHandle();
1519 
1520         mThreadScheduler.scheduleInternalTask(
1521                 () -> {
1522                     try {
1523                         enforceIsForegroundUser(userHandle);
1524                         mContext.enforcePermission(
1525                                 MIGRATE_HEALTH_CONNECT_DATA,
1526                                 pid,
1527                                 uid,
1528                                 "Caller does not have " + MIGRATE_HEALTH_CONNECT_DATA);
1529                         enforceShowMigrationInfoIntent(packageName, uid);
1530                         mBackupRestore.runWithStatesReadLock(
1531                                 () -> {
1532                                     if (mBackupRestore.isRestoreMergingInProgress()) {
1533                                         throw new MigrationException(
1534                                                 "Cannot start data migration. Backup and restore in"
1535                                                         + " progress.",
1536                                                 MigrationException.ERROR_INTERNAL,
1537                                                 null);
1538                                     }
1539                                     mMigrationStateManager.startMigration(mContext);
1540                                 });
1541                         mPriorityMigrationHelper.populatePreMigrationPriority();
1542                         callback.onSuccess();
1543                     } catch (Exception e) {
1544                         Slog.e(TAG, "Exception: ", e);
1545                         tryAndThrowException(callback, e, MigrationException.ERROR_INTERNAL, null);
1546                     }
1547                 });
1548     }
1549 
1550     /**
1551      * Changes migration state to {@link HealthConnectDataState#MIGRATION_STATE_COMPLETE} if
1552      * migration is not already complete.
1553      *
1554      * @param packageName calling package name
1555      * @param callback Callback to receive a result or an error encountered while performing this
1556      *     operation.
1557      */
1558     @Override
finishMigration(String packageName, IMigrationCallback callback)1559     public void finishMigration(String packageName, IMigrationCallback callback) {
1560         checkParamsNonNull(packageName, callback);
1561 
1562         int uid = Binder.getCallingUid();
1563         int pid = Binder.getCallingPid();
1564         final UserHandle userHandle = Binder.getCallingUserHandle();
1565 
1566         mThreadScheduler.scheduleInternalTask(
1567                 () -> {
1568                     try {
1569                         enforceIsForegroundUser(userHandle);
1570                         mContext.enforcePermission(
1571                                 MIGRATE_HEALTH_CONNECT_DATA,
1572                                 pid,
1573                                 uid,
1574                                 "Caller does not have " + MIGRATE_HEALTH_CONNECT_DATA);
1575                         enforceShowMigrationInfoIntent(packageName, uid);
1576                         mMigrationStateManager.finishMigration(mContext);
1577                         mAppInfoHelper.syncAppInfoRecordTypesUsed();
1578                         callback.onSuccess();
1579                     } catch (Exception e) {
1580                         Slog.e(TAG, "Exception: ", e);
1581                         tryAndThrowException(callback, e, MigrationException.ERROR_INTERNAL, null);
1582                     }
1583                 });
1584     }
1585 
1586     /**
1587      * Write data to module storage. The migration state must be {@link
1588      * HealthConnectDataState#MIGRATION_STATE_IN_PROGRESS} to be able to write data.
1589      *
1590      * @param packageName calling package name
1591      * @param parcel Migration entity containing the data being migrated.
1592      * @param callback Callback to receive a result or an error encountered while performing this
1593      *     operation.
1594      */
1595     @Override
writeMigrationData( String packageName, MigrationEntityParcel parcel, IMigrationCallback callback)1596     public void writeMigrationData(
1597             String packageName, MigrationEntityParcel parcel, IMigrationCallback callback) {
1598         checkParamsNonNull(packageName, parcel, callback);
1599 
1600         int uid = Binder.getCallingUid();
1601         int pid = Binder.getCallingPid();
1602         UserHandle callingUserHandle = getCallingUserHandle();
1603 
1604         mThreadScheduler.scheduleInternalTask(
1605                 () -> {
1606                     try {
1607                         enforceIsForegroundUser(callingUserHandle);
1608                         mContext.enforcePermission(
1609                                 MIGRATE_HEALTH_CONNECT_DATA,
1610                                 pid,
1611                                 uid,
1612                                 "Caller does not have " + MIGRATE_HEALTH_CONNECT_DATA);
1613                         enforceShowMigrationInfoIntent(packageName, uid);
1614                         mMigrationStateManager.validateWriteMigrationData();
1615                         getDataMigrationManager(callingUserHandle)
1616                                 .apply(parcel.getMigrationEntities());
1617                         callback.onSuccess();
1618                     } catch (DataMigrationManager.EntityWriteException e) {
1619                         Slog.e(TAG, "Exception: ", e);
1620                         tryAndThrowException(
1621                                 callback,
1622                                 e,
1623                                 MigrationException.ERROR_MIGRATE_ENTITY,
1624                                 e.getEntityId());
1625                     } catch (Exception e) {
1626                         Slog.e(TAG, "Exception: ", e);
1627                         tryAndThrowException(callback, e, MigrationException.ERROR_INTERNAL, null);
1628                     }
1629                 });
1630     }
1631 
1632     /**
1633      * @param packageName calling package name
1634      * @param requiredSdkExtension The minimum sdk extension version for module to be ready for data
1635      *     migration from the apk.
1636      * @param callback Callback to receive a result or an error encountered while performing this
1637      *     operation.
1638      */
insertMinDataMigrationSdkExtensionVersion( String packageName, int requiredSdkExtension, IMigrationCallback callback)1639     public void insertMinDataMigrationSdkExtensionVersion(
1640             String packageName, int requiredSdkExtension, IMigrationCallback callback) {
1641         checkParamsNonNull(packageName, callback);
1642 
1643         int uid = Binder.getCallingUid();
1644         int pid = Binder.getCallingPid();
1645         final UserHandle userHandle = Binder.getCallingUserHandle();
1646 
1647         mThreadScheduler.scheduleInternalTask(
1648                 () -> {
1649                     try {
1650                         enforceIsForegroundUser(userHandle);
1651                         mContext.enforcePermission(
1652                                 MIGRATE_HEALTH_CONNECT_DATA,
1653                                 pid,
1654                                 uid,
1655                                 "Caller does not have " + MIGRATE_HEALTH_CONNECT_DATA);
1656                         enforceShowMigrationInfoIntent(packageName, uid);
1657                         mMigrationStateManager.validateSetMinSdkVersion();
1658                         mMigrationStateManager.setMinDataMigrationSdkExtensionVersion(
1659                                 mContext, requiredSdkExtension);
1660 
1661                         callback.onSuccess();
1662                     } catch (Exception e) {
1663                         Slog.e(TAG, "Exception: ", e);
1664                         tryAndThrowException(callback, e, MigrationException.ERROR_INTERNAL, null);
1665                     }
1666                 });
1667     }
1668 
1669     /**
1670      * @see HealthConnectManager#stageAllHealthConnectRemoteData
1671      */
1672     @Override
stageAllHealthConnectRemoteData( StageRemoteDataRequest stageRemoteDataRequest, UserHandle userHandle, IDataStagingFinishedCallback callback)1673     public void stageAllHealthConnectRemoteData(
1674             StageRemoteDataRequest stageRemoteDataRequest,
1675             UserHandle userHandle,
1676             IDataStagingFinishedCallback callback) {
1677         checkParamsNonNull(stageRemoteDataRequest, userHandle, callback);
1678 
1679         Map<String, ParcelFileDescriptor> origPfdsByFileName =
1680                 stageRemoteDataRequest.getPfdsByFileName();
1681         Map<String, HealthConnectException> exceptionsByFileName =
1682                 new ArrayMap<>(origPfdsByFileName.size());
1683         Map<String, ParcelFileDescriptor> pfdsByFileName =
1684                 new ArrayMap<>(origPfdsByFileName.size());
1685 
1686         try {
1687             mDataPermissionEnforcer.enforceAnyOfPermissions(
1688                     Manifest.permission.STAGE_HEALTH_CONNECT_REMOTE_DATA,
1689                     HEALTH_CONNECT_BACKUP_INTER_AGENT_PERMISSION);
1690 
1691             enforceIsForegroundUser(Binder.getCallingUserHandle());
1692 
1693             for (Entry<String, ParcelFileDescriptor> entry : origPfdsByFileName.entrySet()) {
1694                 try {
1695                     pfdsByFileName.put(entry.getKey(), entry.getValue().dup());
1696                 } catch (IOException e) {
1697                     Slog.e(TAG, "IOException: ", e);
1698                     exceptionsByFileName.put(
1699                             entry.getKey(), new HealthConnectException(ERROR_IO, e.getMessage()));
1700                 }
1701             }
1702 
1703             mThreadScheduler.scheduleInternalTask(
1704                     () -> {
1705                         if (!mBackupRestore.prepForStagingIfNotAlreadyDone()) {
1706                             try {
1707                                 callback.onResult();
1708                             } catch (RemoteException e) {
1709                                 Log.e(TAG, "Restore response could not be sent to the caller.", e);
1710                             }
1711                             return;
1712                         }
1713                         mBackupRestore.stageAllHealthConnectRemoteData(
1714                                 pfdsByFileName, exceptionsByFileName, userHandle, callback);
1715                     });
1716         } catch (SecurityException | IllegalStateException e) {
1717             Log.e(TAG, "Exception encountered while staging", e);
1718             try {
1719                 @HealthConnectException.ErrorCode
1720                 int errorCode = (e instanceof SecurityException) ? ERROR_SECURITY : ERROR_INTERNAL;
1721                 exceptionsByFileName.put("", new HealthConnectException(errorCode, e.getMessage()));
1722 
1723                 callback.onError(new StageRemoteDataException(exceptionsByFileName));
1724             } catch (RemoteException remoteException) {
1725                 Log.e(TAG, "Stage data response could not be sent to the caller.", e);
1726             }
1727         }
1728     }
1729 
1730     /**
1731      * @see HealthConnectManager#getAllDataForBackup
1732      */
1733     @Override
getAllDataForBackup( StageRemoteDataRequest stageRemoteDataRequest, UserHandle userHandle)1734     public void getAllDataForBackup(
1735             StageRemoteDataRequest stageRemoteDataRequest, UserHandle userHandle) {
1736         checkParamsNonNull(stageRemoteDataRequest, userHandle);
1737 
1738         mContext.enforceCallingPermission(HEALTH_CONNECT_BACKUP_INTER_AGENT_PERMISSION, null);
1739         final long token = Binder.clearCallingIdentity();
1740         try {
1741             mBackupRestore.getAllDataForBackup(stageRemoteDataRequest, userHandle);
1742         } finally {
1743             Binder.restoreCallingIdentity(token);
1744         }
1745     }
1746 
1747     /**
1748      * @see HealthConnectManager#getAllBackupFileNames
1749      */
1750     @Override
getAllBackupFileNames(boolean forDeviceToDevice)1751     public BackupFileNamesSet getAllBackupFileNames(boolean forDeviceToDevice) {
1752         mContext.enforceCallingPermission(HEALTH_CONNECT_BACKUP_INTER_AGENT_PERMISSION, null);
1753         return mBackupRestore.getAllBackupFileNames(forDeviceToDevice);
1754     }
1755 
1756     /**
1757      * @see HealthConnectManager#deleteAllStagedRemoteData
1758      */
1759     @Override
deleteAllStagedRemoteData(UserHandle userHandle)1760     public void deleteAllStagedRemoteData(UserHandle userHandle) {
1761         checkParamsNonNull(userHandle);
1762 
1763         mContext.enforceCallingPermission(
1764                 DELETE_STAGED_HEALTH_CONNECT_REMOTE_DATA_PERMISSION, null);
1765 
1766         int uid = Binder.getCallingUid();
1767         long token = Binder.clearCallingIdentity();
1768         try {
1769             mBackupRestore.deleteAndResetEverything(userHandle);
1770             mMigrationStateManager.clearCaches(mContext);
1771             mDatabaseHelpers.clearAllData(mTransactionManager);
1772             mRateLimiter.clearCache();
1773             String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
1774             for (String packageName : packageNames) {
1775                 mFirstGrantTimeManager.setFirstGrantTime(packageName, Instant.now(), userHandle);
1776             }
1777         } finally {
1778             Binder.restoreCallingIdentity(token);
1779         }
1780     }
1781 
1782     /**
1783      * @see HealthConnectManager#setLowerRateLimitsForTesting
1784      */
1785     @Override
setLowerRateLimitsForTesting(boolean enabled)1786     public void setLowerRateLimitsForTesting(boolean enabled) {
1787         // Continue using the existing test permission because we can't grant new permissions
1788         // to shell in a mainline update.
1789         mContext.enforceCallingPermission(
1790                 DELETE_STAGED_HEALTH_CONNECT_REMOTE_DATA_PERMISSION, null);
1791         mRateLimiter.setLowerRateLimitsForTesting(enabled);
1792     }
1793 
1794     /**
1795      * @see HealthConnectManager#updateDataDownloadState
1796      */
1797     @Override
updateDataDownloadState(@ataDownloadState int downloadState)1798     public void updateDataDownloadState(@DataDownloadState int downloadState) {
1799         mContext.enforceCallingPermission(
1800                 Manifest.permission.STAGE_HEALTH_CONNECT_REMOTE_DATA, null);
1801         enforceIsForegroundUser(getCallingUserHandle());
1802         mBackupRestore.updateDataDownloadState(downloadState);
1803     }
1804 
1805     /**
1806      * @see HealthConnectManager#getHealthConnectDataState
1807      */
1808     @Override
getHealthConnectDataState(IGetHealthConnectDataStateCallback callback)1809     public void getHealthConnectDataState(IGetHealthConnectDataStateCallback callback) {
1810         checkParamsNonNull(callback);
1811         final int uid = Binder.getCallingUid();
1812         final int pid = Binder.getCallingPid();
1813         final boolean holdsDataManagementPermission = hasDataManagementPermission(uid, pid);
1814 
1815         try {
1816             mDataPermissionEnforcer.enforceAnyOfPermissions(
1817                     MANAGE_HEALTH_DATA_PERMISSION, Manifest.permission.MIGRATE_HEALTH_CONNECT_DATA);
1818             final UserHandle userHandle = Binder.getCallingUserHandle();
1819             enforceIsForegroundUser(userHandle);
1820             mThreadScheduler.schedule(
1821                     mContext,
1822                     () -> {
1823                         try {
1824                             @HealthConnectDataState.DataRestoreError
1825                             int dataRestoreError = mBackupRestore.getDataRestoreError();
1826                             @HealthConnectDataState.DataRestoreState
1827                             int dataRestoreState = mBackupRestore.getDataRestoreState();
1828 
1829                             try {
1830                                 callback.onResult(
1831                                         new HealthConnectDataState(
1832                                                 dataRestoreState,
1833                                                 dataRestoreError,
1834                                                 mMigrationStateManager.getMigrationState()));
1835                             } catch (RemoteException remoteException) {
1836                                 Log.e(
1837                                         TAG,
1838                                         "HealthConnectDataState could not be sent to the caller.",
1839                                         remoteException);
1840                             }
1841                         } catch (RuntimeException e) {
1842                             // exception getting the state from the disk
1843                             try {
1844                                 callback.onError(
1845                                         new HealthConnectExceptionParcel(
1846                                                 new HealthConnectException(
1847                                                         ERROR_IO, e.getMessage())));
1848                             } catch (RemoteException remoteException) {
1849                                 Log.e(
1850                                         TAG,
1851                                         "Exception for getHealthConnectDataState could not be sent"
1852                                                 + " to the caller.",
1853                                         remoteException);
1854                             }
1855                         }
1856                     },
1857                     uid,
1858                     holdsDataManagementPermission);
1859         } catch (SecurityException | IllegalStateException e) {
1860             Log.e(TAG, "getHealthConnectDataState: Exception encountered", e);
1861             @HealthConnectException.ErrorCode
1862             int errorCode = (e instanceof SecurityException) ? ERROR_SECURITY : ERROR_INTERNAL;
1863             try {
1864                 callback.onError(
1865                         new HealthConnectExceptionParcel(
1866                                 new HealthConnectException(errorCode, e.getMessage())));
1867             } catch (RemoteException remoteException) {
1868                 Log.e(TAG, "getHealthConnectDataState error could not be sent", e);
1869             }
1870         }
1871     }
1872 
1873     /**
1874      * @see HealthConnectManager#getHealthConnectMigrationUiState
1875      */
1876     @Override
getHealthConnectMigrationUiState( IGetHealthConnectMigrationUiStateCallback callback)1877     public void getHealthConnectMigrationUiState(
1878             IGetHealthConnectMigrationUiStateCallback callback) {
1879         checkParamsNonNull(callback);
1880 
1881         final int uid = Binder.getCallingUid();
1882         final int pid = Binder.getCallingPid();
1883         final UserHandle userHandle = Binder.getCallingUserHandle();
1884         mThreadScheduler.scheduleControllerTask(
1885                 () -> {
1886                     try {
1887                         enforceIsForegroundUser(userHandle);
1888                         mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null);
1889 
1890                         try {
1891                             callback.onResult(
1892                                     new HealthConnectMigrationUiState(
1893                                             mMigrationUiStateManager
1894                                                     .getHealthConnectMigrationUiState()));
1895                         } catch (RemoteException remoteException) {
1896                             Log.e(
1897                                     TAG,
1898                                     "HealthConnectMigrationUiState could not be sent to the"
1899                                             + " caller.",
1900                                     remoteException);
1901                         }
1902                     } catch (SecurityException securityException) {
1903                         try {
1904                             callback.onError(
1905                                     new HealthConnectExceptionParcel(
1906                                             new HealthConnectException(
1907                                                     ERROR_SECURITY,
1908                                                     securityException.getMessage())));
1909                         } catch (RemoteException remoteException) {
1910                             Log.e(
1911                                     TAG,
1912                                     "Exception for HealthConnectMigrationUiState could not be sent"
1913                                             + " to the caller.",
1914                                     remoteException);
1915                         }
1916                     } catch (RuntimeException e) {
1917                         // exception getting the state from the disk
1918                         try {
1919                             callback.onError(
1920                                     new HealthConnectExceptionParcel(
1921                                             new HealthConnectException(ERROR_IO, e.getMessage())));
1922                         } catch (RemoteException remoteException) {
1923                             Log.e(
1924                                     TAG,
1925                                     "Exception for HealthConnectMigrationUiState could not be sent"
1926                                             + " to the caller.",
1927                                     remoteException);
1928                         }
1929                     }
1930                 });
1931     }
1932 
1933     @Override
configureScheduledExport( @ullable ScheduledExportSettings settings, UserHandle user)1934     public void configureScheduledExport(
1935             @Nullable ScheduledExportSettings settings, UserHandle user) {
1936         checkParamsNonNull(user);
1937 
1938         UserHandle userHandle = Binder.getCallingUserHandle();
1939         enforceIsForegroundUser(userHandle);
1940         throwExceptionIfDataSyncInProgress();
1941 
1942         try {
1943             mContext.enforceCallingPermission(MANAGE_HEALTH_DATA_PERMISSION, null);
1944             mExportImportSettingsStorage.configure(settings);
1945 
1946             // Trigger a one time immediate export when periodic export is scheduled.
1947             if (Flags.immediateExport() && settings != null && settings.getPeriodInDays() > 0) {
1948                 mThreadScheduler.scheduleInternalTask(
1949                         () -> {
1950                             try {
1951                                 final int uid = Binder.getCallingUid();
1952                                 final int pid = Binder.getCallingPid();
1953 
1954                                 mContext.enforcePermission(
1955                                         MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null);
1956                                 mExportManager.runExport(userHandle);
1957                             } catch (Exception e) {
1958                                 Slog.e(TAG, "Failed to trigger a one time immediate export.", e);
1959                             }
1960                         });
1961             }
1962 
1963             mThreadScheduler.scheduleInternalTask(
1964                     () -> {
1965                         try {
1966                             ExportImportJobs.schedulePeriodicExportJob(
1967                                     userHandle,
1968                                     mContext,
1969                                     mExportImportSettingsStorage,
1970                                     mExportManager);
1971                         } catch (Exception e) {
1972                             Slog.e(TAG, "Failed to schedule periodic export job.", e);
1973                         }
1974                     });
1975         } catch (SQLiteException sqLiteException) {
1976             Slog.e(TAG, "SQLiteException: ", sqLiteException);
1977             throw new HealthConnectException(ERROR_IO, sqLiteException.toString());
1978         } catch (SecurityException securityException) {
1979             Slog.e(TAG, "SecurityException: ", securityException);
1980             throw new HealthConnectException(
1981                     HealthConnectException.ERROR_SECURITY, securityException.toString());
1982         } catch (HealthConnectException healthConnectException) {
1983             Slog.e(TAG, "HealthConnectException: ", healthConnectException);
1984             throw new HealthConnectException(
1985                     healthConnectException.getErrorCode(), healthConnectException.toString());
1986         } catch (Exception exception) {
1987             Slog.e(TAG, "Exception: ", exception);
1988             throw new HealthConnectException(ERROR_INTERNAL, exception.toString());
1989         }
1990     }
1991 
1992     /** Queries status for a scheduled export */
1993     @Override
getScheduledExportStatus(UserHandle user, IScheduledExportStatusCallback callback)1994     public void getScheduledExportStatus(UserHandle user, IScheduledExportStatusCallback callback) {
1995         checkParamsNonNull(user, callback);
1996         ErrorCallback errorCallback = callback::onError;
1997         final int uid = Binder.getCallingUid();
1998         final int pid = Binder.getCallingPid();
1999         final UserHandle userHandle = Binder.getCallingUserHandle();
2000         mThreadScheduler.scheduleControllerTask(
2001                 () -> {
2002                     try {
2003                         enforceIsForegroundUser(userHandle);
2004                         mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null);
2005                         final Context userContext = mContext.createContextAsUser(userHandle, 0);
2006                         ScheduledExportStatus status =
2007                                 mExportImportSettingsStorage.getScheduledExportStatus(userContext);
2008                         callback.onResult(status);
2009                     } catch (HealthConnectException healthConnectException) {
2010                         Slog.e(TAG, "HealthConnectException: ", healthConnectException);
2011                         tryAndThrowException(
2012                                 errorCallback,
2013                                 healthConnectException,
2014                                 healthConnectException.getErrorCode());
2015                     } catch (Exception exception) {
2016                         Slog.e(TAG, "Exception: ", exception);
2017                         tryAndThrowException(errorCallback, exception, ERROR_INTERNAL);
2018                     }
2019                 });
2020     }
2021 
2022     @Override
getScheduledExportPeriodInDays(UserHandle user)2023     public int getScheduledExportPeriodInDays(UserHandle user) {
2024         checkParamsNonNull(user);
2025 
2026         enforceIsForegroundUser(getCallingUserHandle());
2027         throwExceptionIfDataSyncInProgress();
2028         try {
2029             mContext.enforceCallingPermission(MANAGE_HEALTH_DATA_PERMISSION, null);
2030             return mExportImportSettingsStorage.getScheduledExportPeriodInDays();
2031         } catch (Exception e) {
2032             if (e instanceof SecurityException) {
2033                 throw e;
2034             }
2035             Slog.e(TAG, "Unable to get period between scheduled exports for " + user);
2036         }
2037 
2038         throw new RuntimeException();
2039     }
2040 
2041     /** Queries the status for a data import */
2042     @Override
getImportStatus(UserHandle user, IImportStatusCallback callback)2043     public void getImportStatus(UserHandle user, IImportStatusCallback callback) {
2044         checkParamsNonNull(user, callback);
2045         ErrorCallback errorCallback = callback::onError;
2046 
2047         final int uid = Binder.getCallingUid();
2048         final int pid = Binder.getCallingPid();
2049         final UserHandle userHandle = Binder.getCallingUserHandle();
2050         mThreadScheduler.scheduleControllerTask(
2051                 () -> {
2052                     try {
2053                         enforceIsForegroundUser(userHandle);
2054                         mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null);
2055                         ImportStatus status = mExportImportSettingsStorage.getImportStatus();
2056                         callback.onResult(status);
2057                     } catch (HealthConnectException healthConnectException) {
2058                         Slog.e(TAG, "HealthConnectException: ", healthConnectException);
2059                         tryAndThrowException(
2060                                 errorCallback,
2061                                 healthConnectException,
2062                                 healthConnectException.getErrorCode());
2063                     } catch (Exception exception) {
2064                         Slog.e(TAG, "Exception: ", exception);
2065                         tryAndThrowException(errorCallback, exception, ERROR_INTERNAL);
2066                     }
2067                 });
2068     }
2069 
2070     @Override
runImport(UserHandle user, Uri file, IEmptyResponseCallback callback)2071     public void runImport(UserHandle user, Uri file, IEmptyResponseCallback callback) {
2072         if (mImportManager == null) return;
2073         checkParamsNonNull(file);
2074 
2075         final int uid = Binder.getCallingUid();
2076         final int pid = Binder.getCallingPid();
2077         final UserHandle userHandle = Binder.getCallingUserHandle();
2078         mThreadScheduler.scheduleControllerTask(
2079                 () -> {
2080                     try {
2081                         enforceIsForegroundUser(userHandle);
2082                         mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null);
2083                         mImportManager.runImport(userHandle, file);
2084                         callback.onResult();
2085                     } catch (Exception exception) {
2086                         throw new HealthConnectException(ERROR_IO, exception.toString());
2087                     }
2088                 });
2089     }
2090 
2091     @Override
runImmediateExport(Uri file, IEmptyResponseCallback callback)2092     public void runImmediateExport(Uri file, IEmptyResponseCallback callback) {
2093         checkParamsNonNull(file);
2094 
2095         final int uid = Binder.getCallingUid();
2096         final int pid = Binder.getCallingPid();
2097         final UserHandle userHandle = Binder.getCallingUserHandle();
2098         mThreadScheduler.scheduleControllerTask(
2099                 () -> {
2100                     try {
2101                         enforceIsForegroundUser(userHandle);
2102                         mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null);
2103                         // TODO(b/370954019): Modify runExport to use specific file.
2104                         mExportManager.runExport(userHandle);
2105                         callback.onResult();
2106                     } catch (Exception exception) {
2107                         throw new HealthConnectException(ERROR_IO, exception.toString());
2108                     }
2109                 });
2110     }
2111 
2112     /** Queries the document providers available to be used for export/import. */
2113     @Override
queryDocumentProviders(UserHandle user, IQueryDocumentProvidersCallback callback)2114     public void queryDocumentProviders(UserHandle user, IQueryDocumentProvidersCallback callback) {
2115         checkParamsNonNull(user, callback);
2116         ErrorCallback errorCallback = callback::onError;
2117 
2118         final int uid = Binder.getCallingUid();
2119         final int pid = Binder.getCallingPid();
2120         final UserHandle userHandle = Binder.getCallingUserHandle();
2121 
2122         mThreadScheduler.scheduleControllerTask(
2123                 () -> {
2124                     try {
2125                         enforceIsForegroundUser(userHandle);
2126                         mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null);
2127                         final Context userContext = mContext.createContextAsUser(userHandle, 0);
2128                         final List<ExportImportDocumentProvider> providers =
2129                                 DocumentProvidersManager.queryDocumentProviders(userContext);
2130                         callback.onResult(providers);
2131                     } catch (SecurityException securityException) {
2132                         Slog.e(TAG, "SecurityException: ", securityException);
2133                         throw new HealthConnectException(
2134                                 HealthConnectException.ERROR_SECURITY,
2135                                 securityException.toString());
2136                     } catch (HealthConnectException healthConnectException) {
2137                         Slog.e(TAG, "HealthConnectException: ", healthConnectException);
2138                         tryAndThrowException(
2139                                 errorCallback,
2140                                 healthConnectException,
2141                                 healthConnectException.getErrorCode());
2142                     } catch (Exception exception) {
2143                         Slog.e(TAG, "Exception: ", exception);
2144                         tryAndThrowException(errorCallback, exception, ERROR_INTERNAL);
2145                     }
2146                 });
2147     }
2148 
2149     /** Service implementation of {@link HealthConnectManager#createMedicalDataSource} */
2150     @Override
createMedicalDataSource( AttributionSource attributionSource, CreateMedicalDataSourceRequest request, IMedicalDataSourceResponseCallback callback)2151     public void createMedicalDataSource(
2152             AttributionSource attributionSource,
2153             CreateMedicalDataSourceRequest request,
2154             IMedicalDataSourceResponseCallback callback) {
2155         checkParamsNonNull(attributionSource, request, callback);
2156         ErrorCallback errorCallback = callback::onError;
2157         int uid = Binder.getCallingUid();
2158         int pid = Binder.getCallingPid();
2159         UserHandle userHandle = Binder.getCallingUserHandle();
2160         boolean holdsDataManagementPermission = hasDataManagementPermission(uid, pid);
2161         String packageName = attributionSource.getPackageName();
2162         HealthConnectServiceLogger.Builder logger =
2163                 new HealthConnectServiceLogger.Builder(
2164                                 holdsDataManagementPermission, CREATE_MEDICAL_DATA_SOURCE)
2165                         .setHealthFitnessStatsLog(mStatsLog)
2166                         .setPackageName(packageName);
2167 
2168         scheduleLoggingHealthDataApiErrors(
2169                 () -> {
2170                     if (!isPersonalHealthRecordEnabled()) {
2171                         HealthConnectException unsupportedException =
2172                                 new HealthConnectException(
2173                                         ERROR_UNSUPPORTED_OPERATION,
2174                                         "Creating MedicalDataSource is not supported.");
2175                         Slog.e(TAG, "HealthConnectException: ", unsupportedException);
2176                         tryAndThrowException(
2177                                 errorCallback,
2178                                 unsupportedException,
2179                                 unsupportedException.getErrorCode());
2180                         return;
2181                     }
2182 
2183                     enforceIsForegroundUser(userHandle);
2184                     verifyPackageNameFromUid(uid, attributionSource);
2185 
2186                     if (holdsDataManagementPermission) {
2187                         throw new SecurityException(
2188                                 "Apps with android.permission.MANAGE_HEALTH_DATA permission are"
2189                                         + " not allowed to insert data");
2190                     }
2191                     enforceMemoryRateLimit(List.of(request.getDataSize()), request.getDataSize());
2192                     throwExceptionIfDataSyncInProgress();
2193                     boolean isInForeground = mAppOpsManagerLocal.isUidInForeground(uid);
2194                     tryAcquireApiCallQuota(
2195                             uid,
2196                             QuotaCategory.QUOTA_CATEGORY_WRITE,
2197                             isInForeground,
2198                             logger,
2199                             request.getDataSize());
2200 
2201                     mMedicalDataPermissionEnforcer.enforceWriteMedicalDataPermission(
2202                             attributionSource);
2203 
2204                     MedicalDataSource dataSource =
2205                             mMedicalDataSourceHelper.createMedicalDataSource(request, packageName);
2206 
2207                     tryAndReturnResult(callback, dataSource, logger);
2208                 },
2209                 logger,
2210                 errorCallback,
2211                 uid,
2212                 /* isController= */ holdsDataManagementPermission);
2213     }
2214 
2215     /**
2216      * Service implementation of {@link HealthConnectManager#getMedicalDataSources(List, Executor,
2217      * OutcomeReceiver)}.
2218      */
2219     @Override
getMedicalDataSourcesByIds( AttributionSource attributionSource, List<String> ids, IMedicalDataSourcesResponseCallback callback)2220     public void getMedicalDataSourcesByIds(
2221             AttributionSource attributionSource,
2222             List<String> ids,
2223             IMedicalDataSourcesResponseCallback callback) {
2224         checkParamsNonNull(attributionSource, ids, callback);
2225         final ErrorCallback errorCallback = callback::onError;
2226         final int uid = Binder.getCallingUid();
2227         final int pid = Binder.getCallingPid();
2228         final UserHandle userHandle = Binder.getCallingUserHandle();
2229         final boolean holdsDataManagementPermission = hasDataManagementPermission(uid, pid);
2230         final String callingPackageName =
2231                 Objects.requireNonNull(attributionSource.getPackageName());
2232         final HealthConnectServiceLogger.Builder logger =
2233                 new HealthConnectServiceLogger.Builder(
2234                                 holdsDataManagementPermission, GET_MEDICAL_DATA_SOURCES_BY_IDS)
2235                         .setHealthFitnessStatsLog(mStatsLog)
2236                         .setPackageName(callingPackageName);
2237 
2238         scheduleLoggingHealthDataApiErrors(
2239                 () -> {
2240                     if (!isPersonalHealthRecordEnabled()) {
2241                         HealthConnectException unsupportedException =
2242                                 new HealthConnectException(
2243                                         ERROR_UNSUPPORTED_OPERATION,
2244                                         "Creating MedicalDataSource by ids is not supported.");
2245                         Slog.e(TAG, "HealthConnectException: ", unsupportedException);
2246                         tryAndThrowException(
2247                                 errorCallback,
2248                                 unsupportedException,
2249                                 unsupportedException.getErrorCode());
2250                         return;
2251                     }
2252 
2253                     if (ids.size() > MAXIMUM_PAGE_SIZE) {
2254                         HealthConnectException invalidSizeException =
2255                                 new HealthConnectException(
2256                                         ERROR_INVALID_ARGUMENT,
2257                                         "The number of requested IDs must be <= "
2258                                                 + MAXIMUM_PAGE_SIZE);
2259                         tryAndThrowException(
2260                                 errorCallback,
2261                                 invalidSizeException,
2262                                 invalidSizeException.getErrorCode());
2263                         return;
2264                     }
2265                     List<UUID> dataSourceUuids =
2266                             validateMedicalDataSourceIds(ids.stream().collect(toSet())).stream()
2267                                     .toList();
2268                     enforceIsForegroundUser(userHandle);
2269                     verifyPackageNameFromUid(uid, attributionSource);
2270                     throwExceptionIfDataSyncInProgress();
2271                     List<MedicalDataSource> medicalDataSources;
2272                     if (holdsDataManagementPermission) {
2273                         medicalDataSources =
2274                                 mMedicalDataSourceHelper
2275                                         .getMedicalDataSourcesByIdsWithoutPermissionChecks(
2276                                                 dataSourceUuids);
2277                     } else {
2278                         boolean isInForeground = mAppOpsManagerLocal.isUidInForeground(uid);
2279                         logger.setCallerForegroundState(isInForeground);
2280 
2281                         tryAcquireApiCallQuota(
2282                                 uid, QuotaCategory.QUOTA_CATEGORY_READ, isInForeground, logger);
2283 
2284                         Set<String> grantedMedicalPermissions =
2285                                 mMedicalDataPermissionEnforcer
2286                                         .getGrantedMedicalPermissionsForPreflight(
2287                                                 attributionSource);
2288 
2289                         // Enforce caller has permission granted to at least one PHR permission
2290                         // before reading from DB.
2291                         if (grantedMedicalPermissions.isEmpty()) {
2292                             throw new SecurityException(
2293                                     "Caller doesn't have permission to read or write medical"
2294                                             + " data");
2295                         }
2296 
2297                         // If reading from background while READ_HEALTH_DATA_IN_BACKGROUND
2298                         // permission is not granted, then enforce self read.
2299                         boolean isCalledFromBgWithoutBgRead =
2300                                 !isInForeground
2301                                         && shouldEnforceSelfRead(uid, pid, attributionSource);
2302 
2303                         if (Constants.DEBUG) {
2304                             Slog.d(
2305                                     TAG,
2306                                     "Enforce self read for package "
2307                                             + callingPackageName
2308                                             + ":"
2309                                             + isCalledFromBgWithoutBgRead);
2310                         }
2311 
2312                         // Pass related fields to DB to filter results.
2313                         medicalDataSources =
2314                                 mMedicalDataSourceHelper
2315                                         .getMedicalDataSourcesByIdsWithPermissionChecks(
2316                                                 dataSourceUuids,
2317                                                 getPopulatedMedicalResourceTypesWithReadPermissions(
2318                                                         grantedMedicalPermissions),
2319                                                 callingPackageName,
2320                                                 grantedMedicalPermissions.contains(
2321                                                         WRITE_MEDICAL_DATA),
2322                                                 isCalledFromBgWithoutBgRead,
2323                                                 mAppInfoHelper);
2324                     }
2325                     logger.setNumberOfRecords(medicalDataSources.size());
2326                     tryAndReturnResult(callback, medicalDataSources, logger);
2327                 },
2328                 logger,
2329                 errorCallback,
2330                 uid,
2331                 holdsDataManagementPermission);
2332     }
2333 
2334     /**
2335      * Service implementation of {@link
2336      * HealthConnectManager#getMedicalDataSources(GetMedicalDataSourcesRequest, Executor,
2337      * OutcomeReceiver)}.
2338      */
2339     @Override
getMedicalDataSourcesByRequest( AttributionSource attributionSource, GetMedicalDataSourcesRequest request, IMedicalDataSourcesResponseCallback callback)2340     public void getMedicalDataSourcesByRequest(
2341             AttributionSource attributionSource,
2342             GetMedicalDataSourcesRequest request,
2343             IMedicalDataSourcesResponseCallback callback) {
2344         checkParamsNonNull(attributionSource, request, callback);
2345         ErrorCallback errorCallback = callback::onError;
2346         final int uid = Binder.getCallingUid();
2347         final int pid = Binder.getCallingPid();
2348         final UserHandle userHandle = Binder.getCallingUserHandle();
2349         final boolean holdsDataManagementPermission = hasDataManagementPermission(uid, pid);
2350         final String callingPackageName =
2351                 Objects.requireNonNull(attributionSource.getPackageName());
2352         final HealthConnectServiceLogger.Builder logger =
2353                 new HealthConnectServiceLogger.Builder(
2354                                 holdsDataManagementPermission, GET_MEDICAL_DATA_SOURCES_BY_REQUESTS)
2355                         .setHealthFitnessStatsLog(mStatsLog)
2356                         .setPackageName(callingPackageName);
2357 
2358         scheduleLoggingHealthDataApiErrors(
2359                 () -> {
2360                     if (!isPersonalHealthRecordEnabled()) {
2361                         HealthConnectException unsupportedException =
2362                                 new HealthConnectException(
2363                                         ERROR_UNSUPPORTED_OPERATION,
2364                                         "Getting MedicalDataSources by request is not supported.");
2365                         Slog.e(TAG, "HealthConnectException: ", unsupportedException);
2366                         tryAndThrowException(
2367                                 errorCallback,
2368                                 unsupportedException,
2369                                 unsupportedException.getErrorCode());
2370                         return;
2371                     }
2372                     enforceIsForegroundUser(userHandle);
2373                     verifyPackageNameFromUid(uid, attributionSource);
2374                     throwExceptionIfDataSyncInProgress();
2375                     List<MedicalDataSource> medicalDataSources;
2376                     if (holdsDataManagementPermission) {
2377                         medicalDataSources =
2378                                 mMedicalDataSourceHelper
2379                                         .getMedicalDataSourcesByPackageWithoutPermissionChecks(
2380                                                 request.getPackageNames());
2381                     } else {
2382                         boolean isInForeground = mAppOpsManagerLocal.isUidInForeground(uid);
2383                         logger.setCallerForegroundState(isInForeground);
2384 
2385                         tryAcquireApiCallQuota(
2386                                 uid, QuotaCategory.QUOTA_CATEGORY_READ, isInForeground, logger);
2387 
2388                         Set<String> grantedMedicalPermissions =
2389                                 mMedicalDataPermissionEnforcer
2390                                         .getGrantedMedicalPermissionsForPreflight(
2391                                                 attributionSource);
2392 
2393                         // Enforce caller has permission granted to at least one PHR permission
2394                         // before reading from DB.
2395                         if (grantedMedicalPermissions.isEmpty()) {
2396                             throw new SecurityException(
2397                                     "Caller doesn't have permission to read or write medical"
2398                                             + " data");
2399                         }
2400 
2401                         // If reading from background while READ_HEALTH_DATA_IN_BACKGROUND
2402                         // permission is not granted, then enforce self read.
2403                         boolean isCalledFromBgWithoutBgRead =
2404                                 !isInForeground
2405                                         && shouldEnforceSelfRead(uid, pid, attributionSource);
2406 
2407                         if (Constants.DEBUG) {
2408                             Slog.d(
2409                                     TAG,
2410                                     "Enforce self read for package "
2411                                             + callingPackageName
2412                                             + ":"
2413                                             + isCalledFromBgWithoutBgRead);
2414                         }
2415 
2416                         // Pass related fields to DB to filter results.
2417                         medicalDataSources =
2418                                 mMedicalDataSourceHelper
2419                                         .getMedicalDataSourcesByPackageWithPermissionChecks(
2420                                                 request.getPackageNames(),
2421                                                 getPopulatedMedicalResourceTypesWithReadPermissions(
2422                                                         grantedMedicalPermissions),
2423                                                 callingPackageName,
2424                                                 grantedMedicalPermissions.contains(
2425                                                         WRITE_MEDICAL_DATA),
2426                                                 isCalledFromBgWithoutBgRead);
2427                     }
2428                     logger.setNumberOfRecords(medicalDataSources.size());
2429                     tryAndReturnResult(callback, medicalDataSources, logger);
2430                 },
2431                 logger,
2432                 errorCallback,
2433                 uid,
2434                 holdsDataManagementPermission);
2435     }
2436 
2437     /** Service implementation of {@link HealthConnectManager#deleteMedicalDataSourceWithData} */
2438     @Override
deleteMedicalDataSourceWithData( AttributionSource attributionSource, String id, IEmptyResponseCallback callback)2439     public void deleteMedicalDataSourceWithData(
2440             AttributionSource attributionSource, String id, IEmptyResponseCallback callback) {
2441         checkParamsNonNull(attributionSource, id, callback);
2442         final ErrorCallback errorCallback = callback::onError;
2443         final int uid = Binder.getCallingUid();
2444         final int pid = Binder.getCallingPid();
2445         final UserHandle userHandle = Binder.getCallingUserHandle();
2446         final boolean holdsDataManagementPermission = hasDataManagementPermission(uid, pid);
2447         final String callingPackageName =
2448                 Objects.requireNonNull(attributionSource.getPackageName());
2449         final HealthConnectServiceLogger.Builder logger =
2450                 new HealthConnectServiceLogger.Builder(
2451                                 holdsDataManagementPermission, DELETE_MEDICAL_DATA_SOURCE_WITH_DATA)
2452                         .setHealthFitnessStatsLog(mStatsLog)
2453                         .setPackageName(callingPackageName);
2454 
2455         scheduleLoggingHealthDataApiErrors(
2456                 () -> {
2457                     if (!isPersonalHealthRecordEnabled()) {
2458                         HealthConnectException unsupportedException =
2459                                 new HealthConnectException(
2460                                         ERROR_UNSUPPORTED_OPERATION,
2461                                         "Deleting MedicalDataSource is not supported.");
2462                         Slog.e(TAG, "HealthConnectException: ", unsupportedException);
2463                         tryAndThrowException(
2464                                 errorCallback,
2465                                 unsupportedException,
2466                                 unsupportedException.getErrorCode());
2467                         return;
2468                     }
2469 
2470                     if (id.trim().isEmpty()) {
2471                         tryAndThrowException(
2472                                 errorCallback,
2473                                 new IllegalArgumentException("Empty datasource id"),
2474                                 ERROR_INVALID_ARGUMENT);
2475                         return;
2476                     }
2477                     validateMedicalDataSourceIds(Set.of(id));
2478                     UUID uuid = UUID.fromString(id);
2479                     enforceIsForegroundUser(userHandle);
2480                     verifyPackageNameFromUid(uid, attributionSource);
2481                     throwExceptionIfDataSyncInProgress();
2482                     if (holdsDataManagementPermission) {
2483                         mMedicalDataSourceHelper.deleteMedicalDataSourceWithoutPermissionChecks(
2484                                 uuid);
2485                     } else {
2486                         boolean isInForeground = mAppOpsManagerLocal.isUidInForeground(uid);
2487                         logger.setCallerForegroundState(isInForeground);
2488                         tryAcquireApiCallQuota(
2489                                 uid, QuotaCategory.QUOTA_CATEGORY_WRITE, isInForeground, logger);
2490                         mMedicalDataPermissionEnforcer.enforceWriteMedicalDataPermission(
2491                                 attributionSource);
2492                         // This also deletes the contained data, because they are referenced
2493                         // by foreign key, and so are handled by ON DELETE CASCADE in the db.
2494                         mMedicalDataSourceHelper.deleteMedicalDataSourceWithPermissionChecks(
2495                                 uuid, attributionSource.getPackageName());
2496                     }
2497                     tryAndReturnResult(callback, logger);
2498                 },
2499                 logger,
2500                 errorCallback,
2501                 uid,
2502                 /* isController= */ holdsDataManagementPermission);
2503     }
2504 
2505     /**
2506      * Service implementation of {@link HealthConnectManager#upsertMedicalResources} when the flag
2507      * PHR_UPSERT_FIX_USE_SHARED_MEMORY is enabled.
2508      *
2509      * <p>The {@link UpsertMedicalResourceRequestsParcel} will be written to shared memory if
2510      * required, so more data can be sent.
2511      */
2512     @Override
upsertMedicalResourcesFromRequestsParcel( AttributionSource attributionSource, UpsertMedicalResourceRequestsParcel requestsParcel, IMedicalResourceListParcelResponseCallback callback)2513     public void upsertMedicalResourcesFromRequestsParcel(
2514             AttributionSource attributionSource,
2515             UpsertMedicalResourceRequestsParcel requestsParcel,
2516             IMedicalResourceListParcelResponseCallback callback) {
2517         checkParamsNonNull(attributionSource, requestsParcel, callback);
2518 
2519         final ErrorCallback errorCallback = callback::onError;
2520 
2521         upsertMedicalResources(
2522                 attributionSource, requestsParcel.getUpsertRequests(), callback, errorCallback);
2523     }
2524 
2525     /**
2526      * Service implementation of {@link HealthConnectManager#upsertMedicalResources} when the flag
2527      * PHR_UPSERT_FIX_USE_SHARED_MEMORY is disabled.
2528      */
2529     @Override
upsertMedicalResources( AttributionSource attributionSource, List<UpsertMedicalResourceRequest> requests, IMedicalResourcesResponseCallback callback)2530     public void upsertMedicalResources(
2531             AttributionSource attributionSource,
2532             List<UpsertMedicalResourceRequest> requests,
2533             IMedicalResourcesResponseCallback callback) {
2534         checkParamsNonNull(attributionSource, requests, callback);
2535 
2536         final ErrorCallback errorCallback = callback::onError;
2537 
2538         upsertMedicalResources(attributionSource, requests, callback, errorCallback);
2539     }
2540 
upsertMedicalResources( AttributionSource attributionSource, List<UpsertMedicalResourceRequest> requests, android.os.IInterface callback, ErrorCallback errorCallback)2541     private void upsertMedicalResources(
2542             AttributionSource attributionSource,
2543             List<UpsertMedicalResourceRequest> requests,
2544             android.os.IInterface callback,
2545             ErrorCallback errorCallback) {
2546         final int uid = Binder.getCallingUid();
2547         final int pid = Binder.getCallingPid();
2548         final UserHandle userHandle = Binder.getCallingUserHandle();
2549         final boolean holdsDataManagementPermission = hasDataManagementPermission(uid, pid);
2550         final String callingPackageName =
2551                 Objects.requireNonNull(attributionSource.getPackageName());
2552         final HealthConnectServiceLogger.Builder logger =
2553                 new HealthConnectServiceLogger.Builder(
2554                                 holdsDataManagementPermission, UPSERT_MEDICAL_RESOURCES)
2555                         .setHealthFitnessStatsLog(mStatsLog)
2556                         .setPackageName(callingPackageName);
2557 
2558         scheduleLoggingHealthDataApiErrors(
2559                 () -> {
2560                     if (!isPersonalHealthRecordEnabled()) {
2561                         HealthConnectException unsupportedException =
2562                                 new HealthConnectException(
2563                                         ERROR_UNSUPPORTED_OPERATION,
2564                                         "Upsert MedicalResources is not supported.");
2565                         Slog.e(TAG, "HealthConnectException: ", unsupportedException);
2566                         tryAndThrowException(
2567                                 errorCallback,
2568                                 unsupportedException,
2569                                 unsupportedException.getErrorCode());
2570                         return;
2571                     }
2572 
2573                     if (requests.isEmpty()) {
2574                         tryAndReturnMedicalResourcesResult(callback, List.of(), logger);
2575                     }
2576 
2577                     enforceIsForegroundUser(userHandle);
2578                     verifyPackageNameFromUid(uid, attributionSource);
2579                     if (holdsDataManagementPermission) {
2580                         throw new SecurityException(
2581                                 "Apps with android.permission.MANAGE_HEALTH_DATA permission are"
2582                                         + " not allowed to insert data");
2583                     }
2584                     List<Long> requestsSize =
2585                             requests.stream()
2586                                     .map(UpsertMedicalResourceRequest::getDataSize)
2587                                     .toList();
2588                     long requestsTotalSize = requestsSize.stream().mapToLong(Long::valueOf).sum();
2589                     enforceMemoryRateLimit(requestsSize, requestsTotalSize);
2590                     throwExceptionIfDataSyncInProgress();
2591                     boolean isInForeground = mAppOpsManagerLocal.isUidInForeground(uid);
2592                     tryAcquireApiCallQuota(
2593                             uid,
2594                             QuotaCategory.QUOTA_CATEGORY_WRITE,
2595                             isInForeground,
2596                             logger,
2597                             requestsTotalSize);
2598 
2599                     mMedicalDataPermissionEnforcer.enforceWriteMedicalDataPermission(
2600                             attributionSource);
2601 
2602                     List<UpsertMedicalResourceInternalRequest> validatedMedicalResourcesToUpsert =
2603                             new ArrayList<>();
2604                     FhirResourceValidator fhirResourceValidator =
2605                             Flags.phrFhirStructuralValidation()
2606                                     ? getOrCreateFhirResourceValidator()
2607                                     : null;
2608                     for (UpsertMedicalResourceRequest upsertMedicalResourceRequest : requests) {
2609                         MedicalResourceValidator validator =
2610                                 new MedicalResourceValidator(
2611                                         upsertMedicalResourceRequest, fhirResourceValidator);
2612                         validatedMedicalResourcesToUpsert.add(
2613                                 validator.validateAndCreateInternalRequest());
2614                     }
2615 
2616                     // Check that ids within the list of upsert requests are unique
2617                     Set<MedicalResourceId> idsToUpsert =
2618                             validatedMedicalResourcesToUpsert.stream()
2619                                     .map(UpsertMedicalResourceInternalRequest::getMedicalResourceId)
2620                                     .collect(toSet());
2621                     if (idsToUpsert.size() != validatedMedicalResourcesToUpsert.size()) {
2622                         throw new IllegalArgumentException(
2623                                 "Found multiple upsert requests with the same FHIR resource id,"
2624                                         + " type and data source id.");
2625                     }
2626 
2627                     List<MedicalResource> medicalResources =
2628                             mMedicalResourceHelper.upsertMedicalResources(
2629                                     callingPackageName, validatedMedicalResourcesToUpsert);
2630                     logger.setMedicalResourceTypes(
2631                             medicalResources.stream()
2632                                     .map(MedicalResource::getType)
2633                                     .collect(toSet()));
2634                     logger.setNumberOfRecords(medicalResources.size());
2635 
2636                     tryAndReturnMedicalResourcesResult(callback, medicalResources, logger);
2637                 },
2638                 logger,
2639                 errorCallback,
2640                 uid,
2641                 /* isController= */ holdsDataManagementPermission);
2642     }
2643 
2644     /** Creates a FhirResourceValidator if it does not exist and returns it. */
getOrCreateFhirResourceValidator()2645     private FhirResourceValidator getOrCreateFhirResourceValidator() {
2646         // If the flag FHIR_RESOURCE_VALIDATOR_USE_WEAK_REFERENCE is enabled, we use a
2647         // WeakReference so that the FhirResourceValidator can be shared between API
2648         // calls but garbage collected when not in use, due to its size.
2649         if (Flags.phrFhirResourceValidatorUseWeakReference()) {
2650             FhirResourceValidator fhirResourceValidator = mFhirResourceValidatorWeakReference.get();
2651             if (fhirResourceValidator == null) {
2652                 fhirResourceValidator = new FhirResourceValidator();
2653                 mFhirResourceValidatorWeakReference = new WeakReference<>(fhirResourceValidator);
2654             }
2655             return fhirResourceValidator;
2656         } else {
2657             if (mFhirResourceValidator == null) {
2658                 // The FhirResourceValidator is initialised here if null, to avoid
2659                 // unnecessary initialisation when PHR APIs are not used.
2660                 mFhirResourceValidator = new FhirResourceValidator();
2661             }
2662             return mFhirResourceValidator;
2663         }
2664     }
2665 
2666     @Override
readMedicalResourcesByIds( AttributionSource attributionSource, List<MedicalResourceId> medicalResourceIds, IReadMedicalResourcesResponseCallback callback)2667     public void readMedicalResourcesByIds(
2668             AttributionSource attributionSource,
2669             List<MedicalResourceId> medicalResourceIds,
2670             IReadMedicalResourcesResponseCallback callback) {
2671         checkParamsNonNull(attributionSource, medicalResourceIds, callback);
2672         final ErrorCallback errorCallback = callback::onError;
2673         final int uid = Binder.getCallingUid();
2674         final int pid = Binder.getCallingPid();
2675         final UserHandle userHandle = Binder.getCallingUserHandle();
2676         final boolean holdsDataManagementPermission = hasDataManagementPermission(uid, pid);
2677         final String callingPackageName =
2678                 Objects.requireNonNull(attributionSource.getPackageName());
2679         final HealthConnectServiceLogger.Builder logger =
2680                 new HealthConnectServiceLogger.Builder(
2681                                 holdsDataManagementPermission, READ_MEDICAL_RESOURCES_BY_IDS)
2682                         .setHealthFitnessStatsLog(mStatsLog)
2683                         .setPackageName(callingPackageName);
2684 
2685         scheduleLoggingHealthDataApiErrors(
2686                 () -> {
2687                     if (!isPersonalHealthRecordEnabled()) {
2688                         HealthConnectException unsupportedException =
2689                                 new HealthConnectException(
2690                                         ERROR_UNSUPPORTED_OPERATION,
2691                                         "Reading MedicalResources by ids is not supported.");
2692                         Slog.e(TAG, "HealthConnectException: ", unsupportedException);
2693                         tryAndThrowException(
2694                                 errorCallback,
2695                                 unsupportedException,
2696                                 unsupportedException.getErrorCode());
2697                         return;
2698                     }
2699 
2700                     if (personalHealthRecordTelemetry()) {
2701                         // Stores the timestamp for calls made by ANY client, including the
2702                         // controller
2703                         mPreferencesManager.setLastPhrReadMedicalResourcesApiTimeStamp(
2704                                 mTimeSource.getInstantNow());
2705                     }
2706 
2707                     if (medicalResourceIds.isEmpty()) {
2708                         callback.onResult(new ReadMedicalResourcesResponse(List.of(), null, 0));
2709                         return;
2710                     }
2711 
2712                     if (medicalResourceIds.size() > MAXIMUM_PAGE_SIZE) {
2713                         HealthConnectException invalidSizeException =
2714                                 new HealthConnectException(
2715                                         ERROR_INVALID_ARGUMENT,
2716                                         "The number of requested IDs must be <= "
2717                                                 + MAXIMUM_PAGE_SIZE);
2718                         tryAndThrowException(
2719                                 errorCallback,
2720                                 invalidSizeException,
2721                                 invalidSizeException.getErrorCode());
2722                         return;
2723                     }
2724 
2725                     enforceIsForegroundUser(userHandle);
2726                     verifyPackageNameFromUid(uid, attributionSource);
2727                     throwExceptionIfDataSyncInProgress();
2728 
2729                     List<MedicalResource> medicalResources;
2730 
2731                     if (holdsDataManagementPermission) {
2732                         medicalResources =
2733                                 mMedicalResourceHelper
2734                                         .readMedicalResourcesByIdsWithoutPermissionChecks(
2735                                                 medicalResourceIds);
2736                     } else {
2737                         boolean isInForeground = mAppOpsManagerLocal.isUidInForeground(uid);
2738                         logger.setCallerForegroundState(isInForeground);
2739 
2740                         tryAcquireApiCallQuota(
2741                                 uid, QuotaCategory.QUOTA_CATEGORY_READ, isInForeground, logger);
2742 
2743                         Set<String> grantedMedicalPermissions =
2744                                 mMedicalDataPermissionEnforcer
2745                                         .getGrantedMedicalPermissionsForPreflight(
2746                                                 attributionSource);
2747 
2748                         // Enforce caller has permission granted to at least one PHR permission
2749                         // before reading from DB.
2750                         if (grantedMedicalPermissions.isEmpty()) {
2751                             throw new SecurityException(
2752                                     "Caller doesn't have permission to read or write medical"
2753                                             + " data");
2754                         }
2755 
2756                         // If reading from background while READ_HEALTH_DATA_IN_BACKGROUND
2757                         // permission is not granted, then enforce self read.
2758                         boolean isCalledFromBgWithoutBgRead =
2759                                 !isInForeground
2760                                         && shouldEnforceSelfRead(uid, pid, attributionSource);
2761 
2762                         if (Constants.DEBUG) {
2763                             Slog.d(
2764                                     TAG,
2765                                     "Enforce self read for package "
2766                                             + callingPackageName
2767                                             + ":"
2768                                             + isCalledFromBgWithoutBgRead);
2769                         }
2770 
2771                         // Pass related fields to DB to filter results.
2772                         medicalResources =
2773                                 mMedicalResourceHelper
2774                                         .readMedicalResourcesByIdsWithPermissionChecks(
2775                                                 medicalResourceIds,
2776                                                 getPopulatedMedicalResourceTypesWithReadPermissions(
2777                                                         grantedMedicalPermissions),
2778                                                 callingPackageName,
2779                                                 grantedMedicalPermissions.contains(
2780                                                         WRITE_MEDICAL_DATA),
2781                                                 isCalledFromBgWithoutBgRead);
2782                     }
2783 
2784                     logger.setNumberOfRecords(medicalResources.size());
2785                     callback.onResult(new ReadMedicalResourcesResponse(medicalResources, null, 0));
2786                     logger.setHealthDataServiceApiStatusSuccess();
2787                 },
2788                 logger,
2789                 errorCallback,
2790                 uid,
2791                 /* isController= */ holdsDataManagementPermission);
2792     }
2793 
2794     @Override
readMedicalResourcesByRequest( AttributionSource attributionSource, ReadMedicalResourcesRequestParcel request, IReadMedicalResourcesResponseCallback callback)2795     public void readMedicalResourcesByRequest(
2796             AttributionSource attributionSource,
2797             ReadMedicalResourcesRequestParcel request,
2798             IReadMedicalResourcesResponseCallback callback) {
2799         checkParamsNonNull(attributionSource, request, callback);
2800         final ErrorCallback errorCallback = callback::onError;
2801         final int uid = Binder.getCallingUid();
2802         final int pid = Binder.getCallingPid();
2803         final UserHandle userHandle = Binder.getCallingUserHandle();
2804         final boolean holdsDataManagementPermission = hasDataManagementPermission(uid, pid);
2805         final String callingPackageName =
2806                 Objects.requireNonNull(attributionSource.getPackageName());
2807         final HealthConnectServiceLogger.Builder logger =
2808                 new HealthConnectServiceLogger.Builder(
2809                                 holdsDataManagementPermission, READ_MEDICAL_RESOURCES_BY_REQUESTS)
2810                         .setHealthFitnessStatsLog(mStatsLog)
2811                         .setPackageName(callingPackageName);
2812 
2813         scheduleLoggingHealthDataApiErrors(
2814                 () -> {
2815                     if (!isPersonalHealthRecordEnabled()) {
2816                         HealthConnectException unsupportedException =
2817                                 new HealthConnectException(
2818                                         ERROR_UNSUPPORTED_OPERATION,
2819                                         "Reading MedicalResources by request is not supported.");
2820                         Slog.e(TAG, "HealthConnectException: ", unsupportedException);
2821                         tryAndThrowException(
2822                                 errorCallback,
2823                                 unsupportedException,
2824                                 unsupportedException.getErrorCode());
2825                         return;
2826                     }
2827 
2828                     if (personalHealthRecordTelemetry()) {
2829                         // Stores the timestamp for calls made by ANY client, including the
2830                         // controller
2831                         mPreferencesManager.setLastPhrReadMedicalResourcesApiTimeStamp(
2832                                 mTimeSource.getInstantNow());
2833                     }
2834 
2835                     enforceIsForegroundUser(userHandle);
2836                     verifyPackageNameFromUid(uid, attributionSource);
2837                     throwExceptionIfDataSyncInProgress();
2838 
2839                     PhrPageTokenWrapper pageTokenWrapper = PhrPageTokenWrapper.from(request);
2840                     if (pageTokenWrapper.getRequest() == null) {
2841                         throw new IllegalStateException("The request can not be null.");
2842                     }
2843                     logger.setMedicalResourceTypes(
2844                             Set.of(pageTokenWrapper.getRequest().getMedicalResourceType()));
2845                     ReadMedicalResourcesInternalResponse response;
2846 
2847                     if (holdsDataManagementPermission) {
2848                         response =
2849                                 mMedicalResourceHelper
2850                                         .readMedicalResourcesByRequestWithoutPermissionChecks(
2851                                                 pageTokenWrapper, request.getPageSize());
2852                     } else {
2853                         boolean isInForeground = mAppOpsManagerLocal.isUidInForeground(uid);
2854                         logger.setCallerForegroundState(isInForeground);
2855 
2856                         tryAcquireApiCallQuota(
2857                                 uid, QuotaCategory.QUOTA_CATEGORY_READ, isInForeground, logger);
2858 
2859                         boolean enforceSelfRead = false;
2860                         // If both read and write permissions are missing, inside the if condition
2861                         // the statement throws SecurityException.
2862                         if (mMedicalDataPermissionEnforcer
2863                                 .enforceMedicalReadAccessAndGetEnforceSelfRead(
2864                                         pageTokenWrapper.getRequest().getMedicalResourceType(),
2865                                         attributionSource)) {
2866                             // If read permission is missing but write permission is granted,
2867                             // then enforce self read.
2868                             enforceSelfRead = true;
2869                         } else if (!isInForeground) {
2870                             // This is when read permission is granted but the app is reading from
2871                             // the background. Then we enforce self read if
2872                             // READ_HEALTH_DATA_IN_BACKGROUND permission is not granted.
2873                             enforceSelfRead = shouldEnforceSelfRead(uid, pid, attributionSource);
2874                         }
2875                         if (Constants.DEBUG) {
2876                             Slog.d(
2877                                     TAG,
2878                                     "Enforce self read for package "
2879                                             + callingPackageName
2880                                             + ":"
2881                                             + enforceSelfRead);
2882                         }
2883 
2884                         response =
2885                                 mMedicalResourceHelper
2886                                         .readMedicalResourcesByRequestWithPermissionChecks(
2887                                                 pageTokenWrapper,
2888                                                 request.getPageSize(),
2889                                                 callingPackageName,
2890                                                 enforceSelfRead);
2891                     }
2892 
2893                     List<MedicalResource> medicalResources = response.getMedicalResources();
2894                     logger.setNumberOfRecords(medicalResources.size());
2895 
2896                     callback.onResult(
2897                             new ReadMedicalResourcesResponse(
2898                                     medicalResources,
2899                                     response.getPageToken(),
2900                                     response.getRemainingCount()));
2901                     logger.setHealthDataServiceApiStatusSuccess();
2902                 },
2903                 logger,
2904                 errorCallback,
2905                 uid,
2906                 /* isController= */ holdsDataManagementPermission);
2907     }
2908 
2909     @Override
deleteMedicalResourcesByIds( AttributionSource attributionSource, List<MedicalResourceId> medicalResourceIds, IEmptyResponseCallback callback)2910     public void deleteMedicalResourcesByIds(
2911             AttributionSource attributionSource,
2912             List<MedicalResourceId> medicalResourceIds,
2913             IEmptyResponseCallback callback) {
2914 
2915         // Permissions expectations:
2916         // - Apps with data management permissions can delete anything
2917         // - Other apps can only delete data written by the calling package itself.
2918         // - Background deletes are permitted
2919         // - No deletion can happen while data sync is in progress
2920         // - delete shares quota with write.
2921         // - on multi-user devices, calls will only be allowed from the foreground user.
2922 
2923         checkParamsNonNull(attributionSource, medicalResourceIds, callback);
2924         final ErrorCallback errorCallback = callback::onError;
2925         final int uid = Binder.getCallingUid();
2926         final int pid = Binder.getCallingPid();
2927         final UserHandle userHandle = Binder.getCallingUserHandle();
2928         final boolean holdsDataManagementPermission = hasDataManagementPermission(uid, pid);
2929         final String callingPackageName =
2930                 Objects.requireNonNull(attributionSource.getPackageName());
2931         final HealthConnectServiceLogger.Builder logger =
2932                 new HealthConnectServiceLogger.Builder(
2933                                 holdsDataManagementPermission, DELETE_MEDICAL_RESOURCES_BY_IDS)
2934                         .setHealthFitnessStatsLog(mStatsLog)
2935                         .setPackageName(callingPackageName);
2936 
2937         scheduleLoggingHealthDataApiErrors(
2938                 () -> {
2939                     if (!isPersonalHealthRecordEnabled()) {
2940                         HealthConnectException unsupportedException =
2941                                 new HealthConnectException(
2942                                         ERROR_UNSUPPORTED_OPERATION,
2943                                         "Deleting MedicalResources by ids is not supported.");
2944                         Slog.e(TAG, "HealthConnectException: ", unsupportedException);
2945                         tryAndThrowException(
2946                                 errorCallback,
2947                                 unsupportedException,
2948                                 unsupportedException.getErrorCode());
2949                         return;
2950                     }
2951 
2952                     if (medicalResourceIds.isEmpty()) {
2953                         tryAndReturnResult(callback, logger);
2954                         logger.build().log();
2955                         return;
2956                     }
2957 
2958                     enforceIsForegroundUser(userHandle);
2959                     verifyPackageNameFromUid(uid, attributionSource);
2960                     throwExceptionIfDataSyncInProgress();
2961                     if (holdsDataManagementPermission) {
2962                         mMedicalResourceHelper.deleteMedicalResourcesByIdsWithoutPermissionChecks(
2963                                 medicalResourceIds);
2964                     } else {
2965                         boolean isInForeground = mAppOpsManagerLocal.isUidInForeground(uid);
2966                         tryAcquireApiCallQuota(
2967                                 uid, QuotaCategory.QUOTA_CATEGORY_WRITE, isInForeground, logger);
2968                         mMedicalDataPermissionEnforcer.enforceWriteMedicalDataPermission(
2969                                 attributionSource);
2970                         mMedicalResourceHelper.deleteMedicalResourcesByIdsWithPermissionChecks(
2971                                 medicalResourceIds, callingPackageName);
2972                     }
2973                     tryAndReturnResult(callback, logger);
2974                 },
2975                 logger,
2976                 errorCallback,
2977                 uid,
2978                 /* isController= */ holdsDataManagementPermission);
2979     }
2980 
2981     @Override
deleteMedicalResourcesByRequest( AttributionSource attributionSource, DeleteMedicalResourcesRequest request, IEmptyResponseCallback callback)2982     public void deleteMedicalResourcesByRequest(
2983             AttributionSource attributionSource,
2984             DeleteMedicalResourcesRequest request,
2985             IEmptyResponseCallback callback) {
2986 
2987         // Permissions expectations:
2988         // - Apps with data management permissions can delete anything
2989         // - Other apps can only delete data written by the calling package itself.
2990         // - Background deletes are permitted
2991         // - No deletion can happen while data sync is in progress
2992         // - delete shares quota with write.
2993         // - on multi-user devices, calls will only be allowed from the foreground user.
2994 
2995         checkParamsNonNull(attributionSource, request, callback);
2996         final ErrorCallback errorCallback = callback::onError;
2997         final int uid = Binder.getCallingUid();
2998         final int pid = Binder.getCallingPid();
2999         final UserHandle userHandle = Binder.getCallingUserHandle();
3000         final boolean holdsDataManagementPermission = hasDataManagementPermission(uid, pid);
3001         final String callingPackageName =
3002                 Objects.requireNonNull(attributionSource.getPackageName());
3003         final HealthConnectServiceLogger.Builder logger =
3004                 new HealthConnectServiceLogger.Builder(
3005                                 holdsDataManagementPermission, DELETE_MEDICAL_RESOURCES_BY_REQUESTS)
3006                         .setHealthFitnessStatsLog(mStatsLog)
3007                         .setPackageName(callingPackageName);
3008 
3009         scheduleLoggingHealthDataApiErrors(
3010                 () -> {
3011                     if (!isPersonalHealthRecordEnabled()) {
3012                         HealthConnectException unsupportedException =
3013                                 new HealthConnectException(
3014                                         ERROR_UNSUPPORTED_OPERATION,
3015                                         "Deleting MedicalResources by request is not supported.");
3016                         Slog.e(TAG, "HealthConnectException: ", unsupportedException);
3017                         tryAndThrowException(
3018                                 errorCallback,
3019                                 unsupportedException,
3020                                 unsupportedException.getErrorCode());
3021                         return;
3022                     }
3023 
3024                     Set<Integer> medicalResourceTypes = request.getMedicalResourceTypes();
3025                     logger.setMedicalResourceTypes(medicalResourceTypes);
3026                     if (request.getDataSourceIds().isEmpty() && medicalResourceTypes.isEmpty()) {
3027                         tryAndReturnResult(callback, logger);
3028                         return;
3029                     }
3030                     List<UUID> dataSourceUuids = StorageUtils.toUuids(request.getDataSourceIds());
3031                     if (dataSourceUuids.isEmpty() && !request.getDataSourceIds().isEmpty()) {
3032                         throw new IllegalArgumentException("Invalid data source id used");
3033                     }
3034                     enforceIsForegroundUser(userHandle);
3035                     verifyPackageNameFromUid(uid, attributionSource);
3036                     throwExceptionIfDataSyncInProgress();
3037                     if (holdsDataManagementPermission) {
3038                         mMedicalResourceHelper
3039                                 .deleteMedicalResourcesByRequestWithoutPermissionChecks(request);
3040                     } else {
3041                         boolean isInForeground = mAppOpsManagerLocal.isUidInForeground(uid);
3042                         tryAcquireApiCallQuota(
3043                                 uid, QuotaCategory.QUOTA_CATEGORY_WRITE, isInForeground, logger);
3044                         mMedicalDataPermissionEnforcer.enforceWriteMedicalDataPermission(
3045                                 attributionSource);
3046                         mMedicalResourceHelper.deleteMedicalResourcesByRequestWithPermissionChecks(
3047                                 request, callingPackageName);
3048                     }
3049                     tryAndReturnResult(callback, logger);
3050                 },
3051                 logger,
3052                 errorCallback,
3053                 uid,
3054                 /* isController= */ holdsDataManagementPermission);
3055     }
3056 
scheduleLoggingHealthDataApiErrors( Task task, HealthConnectServiceLogger.Builder logger, ErrorCallback errorCallback, int uid, boolean isController)3057     private void scheduleLoggingHealthDataApiErrors(
3058             Task task,
3059             HealthConnectServiceLogger.Builder logger,
3060             ErrorCallback errorCallback,
3061             int uid,
3062             boolean isController) {
3063         mThreadScheduler.schedule(
3064                 mContext,
3065                 () -> {
3066                     int errorCode = ERROR_UNKNOWN;
3067                     Exception exception = null;
3068                     try {
3069                         task.execute();
3070                     } catch (JSONException | SQLiteException jsonException) {
3071                         errorCode = ERROR_IO;
3072                         exception = jsonException;
3073                     } catch (SecurityException securityException) {
3074                         errorCode = ERROR_SECURITY;
3075                         exception = securityException;
3076                     } catch (IllegalArgumentException illegalArgumentException) {
3077                         errorCode = ERROR_INVALID_ARGUMENT;
3078                         exception = illegalArgumentException;
3079                     } catch (HealthConnectException healthConnectException) {
3080                         errorCode = healthConnectException.getErrorCode();
3081                         exception = healthConnectException;
3082                     } catch (Exception e) { // including IllegalStateException
3083                         errorCode = ERROR_INTERNAL;
3084                         exception = e;
3085                     } finally {
3086                         try {
3087                             if (exception != null) {
3088                                 String msg = exception.getClass().getSimpleName() + ": ";
3089                                 if (exception instanceof IllegalArgumentException
3090                                         && Flags.logcatCensorIae()) {
3091                                     Slog.e(TAG, getStackTraceOnlyString(exception));
3092                                 } else {
3093                                     Slog.e(TAG, msg, exception);
3094                                 }
3095                                 if (errorCode == ERROR_UNKNOWN) {
3096                                     Slog.e(TAG, "errorCode should not be ERROR_UNKNOWN!");
3097                                 }
3098                                 logger.setHealthDataServiceApiStatusError(errorCode);
3099                                 tryAndThrowException(errorCallback, exception, errorCode);
3100                             }
3101                         } finally {
3102                             logger.build().log();
3103                         }
3104                     }
3105                 },
3106                 uid,
3107                 isController);
3108     }
3109 
3110     /**
3111      * Returns a string from an exception that contains the stack trace but not the message.
3112      *
3113      * <p>The message for an exception may reveal privacy sensitive information. So this method
3114      * returns the stack trace as a string including the cause chain for the exception, if it
3115      * exists. The stack trace is not communicated through Binder, so is lost to if it is not
3116      * logged.
3117      */
getStackTraceOnlyString(Throwable ex)3118     private static String getStackTraceOnlyString(Throwable ex) {
3119         StringWriter sw = new StringWriter();
3120         PrintWriter pw = new PrintWriter(sw, false);
3121         pw.println(ex.getClass().getName());
3122         printStackTrace(ex, pw);
3123         Throwable cause = ex.getCause();
3124         while (cause != null) {
3125             pw.println(String.format("Caused by: %s", cause.getClass().getName()));
3126             printStackTrace(cause, pw);
3127             cause = cause.getCause();
3128         }
3129         pw.flush();
3130         return sw.toString();
3131     }
3132 
printStackTrace(Throwable ex, PrintWriter pw)3133     private static void printStackTrace(Throwable ex, PrintWriter pw) {
3134         StackTraceElement[] stackTraceElements = ex.getStackTrace();
3135         for (StackTraceElement element : stackTraceElements) {
3136             pw.println(
3137                     String.format(
3138                             " at %s.%s(%s:%s)",
3139                             element.getClassName(),
3140                             element.getMethodName(),
3141                             element.getFileName(),
3142                             element.getLineNumber()));
3143         }
3144     }
3145 
3146     /**
3147      * Retrieves {@link MedicalResourceTypeInfo} for each {@link
3148      * MedicalResource.MedicalResourceType}.
3149      */
3150     @Override
queryAllMedicalResourceTypeInfos(IMedicalResourceTypeInfosCallback callback)3151     public void queryAllMedicalResourceTypeInfos(IMedicalResourceTypeInfosCallback callback) {
3152         checkParamsNonNull(callback);
3153         final ErrorCallback errorCallback = callback::onError;
3154         final int uid = Binder.getCallingUid();
3155         final int pid = Binder.getCallingPid();
3156         final UserHandle userHandle = Binder.getCallingUserHandle();
3157         mThreadScheduler.scheduleControllerTask(
3158                 () -> {
3159                     if (!isPersonalHealthRecordEnabled()) {
3160                         HealthConnectException unsupportedException =
3161                                 new HealthConnectException(
3162                                         ERROR_UNSUPPORTED_OPERATION,
3163                                         "Querying MedicalResource types info is not supported.");
3164                         Slog.e(TAG, "HealthConnectException: ", unsupportedException);
3165                         tryAndThrowException(
3166                                 errorCallback,
3167                                 unsupportedException,
3168                                 unsupportedException.getErrorCode());
3169                         return;
3170                     }
3171 
3172                     try {
3173                         enforceIsForegroundUser(userHandle);
3174                         mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null);
3175                         throwExceptionIfDataSyncInProgress();
3176                         callback.onResult(getPopulatedMedicalResourceTypeInfos());
3177                     } catch (SQLiteException sqLiteException) {
3178                         tryAndThrowException(errorCallback, sqLiteException, ERROR_IO);
3179                     } catch (SecurityException securityException) {
3180                         Slog.e(TAG, "SecurityException: ", securityException);
3181                         tryAndThrowException(errorCallback, securityException, ERROR_SECURITY);
3182                     } catch (HealthConnectException healthConnectException) {
3183                         Slog.e(TAG, "HealthConnectException: ", healthConnectException);
3184                         tryAndThrowException(
3185                                 errorCallback,
3186                                 healthConnectException,
3187                                 healthConnectException.getErrorCode());
3188                     } catch (Exception exception) {
3189                         tryAndThrowException(errorCallback, exception, ERROR_INTERNAL);
3190                     }
3191                 });
3192     }
3193 
3194     @Override
3195     @RequiresApi(Build.VERSION_CODES.BAKLAVA)
getChangesForBackup( @ullable String changeToken, IGetChangesForBackupResponseCallback callback)3196     public void getChangesForBackup(
3197             @Nullable String changeToken, IGetChangesForBackupResponseCallback callback) {
3198         final int uid = Binder.getCallingUid();
3199         final int pid = Binder.getCallingPid();
3200         final UserHandle userHandle = Binder.getCallingUserHandle();
3201         final ErrorCallback errorCallback = callback::onError;
3202         mThreadScheduler.scheduleControllerTask(
3203                 () -> {
3204                     try {
3205                         // TODO(b/400105647): Remove duplicate flag check once excess code size is
3206                         // resolved.
3207                         if (mCloudBackupManager == null
3208                                 || !Flags.cloudBackupAndRestore()
3209                                 || !isCloudBackupRestoreEnabled()) {
3210                             throw new UnsupportedOperationException(
3211                                     "getChangesForBackup is not supported.");
3212                         }
3213                         enforceIsForegroundUser(userHandle);
3214 
3215                         mContext.enforcePermission(
3216                                 BACKUP_HEALTH_CONNECT_DATA_AND_SETTINGS,
3217                                 pid,
3218                                 uid,
3219                                 "Caller does not have permission to call getChangesForBackup.");
3220                         callback.onResult(mCloudBackupManager.getChangesForBackup(changeToken));
3221                     } catch (UnsupportedOperationException e) {
3222                         tryAndThrowException(errorCallback, e, ERROR_UNSUPPORTED_OPERATION);
3223                     } catch (SecurityException e) {
3224                         tryAndThrowException(errorCallback, e, ERROR_SECURITY);
3225                     } catch (IllegalArgumentException e) {
3226                         tryAndThrowException(errorCallback, e, ERROR_INVALID_ARGUMENT);
3227                     } catch (Exception e) {
3228                         tryAndThrowException(errorCallback, e, ERROR_INTERNAL);
3229                     }
3230                 });
3231     }
3232 
3233     @Override
3234     @RequiresApi(Build.VERSION_CODES.BAKLAVA)
getLatestMetadataForBackup(IGetLatestMetadataForBackupResponseCallback callback)3235     public void getLatestMetadataForBackup(IGetLatestMetadataForBackupResponseCallback callback) {
3236         checkParamsNonNull(callback);
3237         final int uid = Binder.getCallingUid();
3238         final int pid = Binder.getCallingPid();
3239         final UserHandle userHandle = Binder.getCallingUserHandle();
3240         final ErrorCallback errorCallback = callback::onError;
3241         mThreadScheduler.scheduleControllerTask(
3242                 () -> {
3243                     try {
3244                         // TODO(b/400105647): Remove duplicate flag check once excess code size is
3245                         // resolved.
3246                         if (mCloudBackupManager == null
3247                                 || !Flags.cloudBackupAndRestore()
3248                                 || !isCloudBackupRestoreEnabled()) {
3249                             throw new UnsupportedOperationException(
3250                                     "getLatestMetadataForBackup is not supported.");
3251                         }
3252                         enforceIsForegroundUser(userHandle);
3253                         mContext.enforcePermission(
3254                                 BACKUP_HEALTH_CONNECT_DATA_AND_SETTINGS,
3255                                 pid,
3256                                 uid,
3257                                 "Caller does not have permission to call"
3258                                         + " getLatestMetadataForBackup.");
3259                         callback.onResult(mCloudBackupManager.getSettingsForBackup());
3260                     } catch (UnsupportedOperationException e) {
3261                         tryAndThrowException(errorCallback, e, ERROR_UNSUPPORTED_OPERATION);
3262                     } catch (SecurityException e) {
3263                         tryAndThrowException(errorCallback, e, ERROR_SECURITY);
3264                     } catch (Exception e) {
3265                         tryAndThrowException(errorCallback, e, ERROR_INTERNAL);
3266                     }
3267                 });
3268     }
3269 
3270     @Override
3271     @RequiresApi(Build.VERSION_CODES.BAKLAVA)
restoreLatestMetadata( BackupMetadata backupMetadata, IEmptyResponseCallback callback)3272     public void restoreLatestMetadata(
3273             BackupMetadata backupMetadata, IEmptyResponseCallback callback) {
3274         checkParamsNonNull(backupMetadata);
3275         checkParamsNonNull(callback);
3276         final int uid = Binder.getCallingUid();
3277         final int pid = Binder.getCallingPid();
3278         final UserHandle userHandle = Binder.getCallingUserHandle();
3279         final ErrorCallback errorCallback = callback::onError;
3280         mThreadScheduler.scheduleControllerTask(
3281                 () -> {
3282                     try {
3283                         // TODO(b/400105647): Remove duplicate flag check once excess code size is
3284                         // resolved.
3285                         if (mCloudRestoreManager == null
3286                                 || !Flags.cloudBackupAndRestore()
3287                                 || !isCloudBackupRestoreEnabled()) {
3288                             throw new UnsupportedOperationException(
3289                                     "restoreSettings is not supported.");
3290                         }
3291                         enforceIsForegroundUser(userHandle);
3292 
3293                         mContext.enforcePermission(
3294                                 RESTORE_HEALTH_CONNECT_DATA_AND_SETTINGS,
3295                                 pid,
3296                                 uid,
3297                                 "Caller does not have permission to call restoreSettings.");
3298                         mCloudRestoreManager.restoreSettings(backupMetadata);
3299                         callback.onResult();
3300                     } catch (UnsupportedOperationException e) {
3301                         tryAndThrowException(errorCallback, e, ERROR_UNSUPPORTED_OPERATION);
3302                     } catch (SecurityException e) {
3303                         tryAndThrowException(errorCallback, e, ERROR_SECURITY);
3304                     } catch (IllegalArgumentException e) {
3305                         tryAndThrowException(errorCallback, e, ERROR_INVALID_ARGUMENT);
3306                     } catch (Exception e) {
3307                         tryAndThrowException(errorCallback, e, ERROR_INTERNAL);
3308                     }
3309                 });
3310     }
3311 
3312     @Override
3313     @RequiresApi(Build.VERSION_CODES.BAKLAVA)
canRestore(int dataVersion, ICanRestoreResponseCallback callback)3314     public void canRestore(int dataVersion, ICanRestoreResponseCallback callback) {
3315         checkParamsNonNull(dataVersion);
3316         final int uid = Binder.getCallingUid();
3317         final int pid = Binder.getCallingPid();
3318         final UserHandle userHandle = Binder.getCallingUserHandle();
3319         final ErrorCallback errorCallback = callback::onError;
3320         mThreadScheduler.scheduleControllerTask(
3321                 () -> {
3322                     try {
3323                         // TODO(b/400105647): Remove duplicate flag check once excess code size is
3324                         // resolved.
3325                         if (mCloudRestoreManager == null
3326                                 || !Flags.cloudBackupAndRestore()
3327                                 || !isCloudBackupRestoreEnabled()) {
3328                             throw new UnsupportedOperationException("canRestore is not supported.");
3329                         }
3330                         enforceIsForegroundUser(userHandle);
3331                         mContext.enforcePermission(
3332                                 RESTORE_HEALTH_CONNECT_DATA_AND_SETTINGS,
3333                                 pid,
3334                                 uid,
3335                                 "Caller does not have permission to call canRestore.");
3336                         callback.onResult(mCloudRestoreManager.canRestore(dataVersion));
3337                     } catch (UnsupportedOperationException e) {
3338                         tryAndThrowException(errorCallback, e, ERROR_UNSUPPORTED_OPERATION);
3339                     } catch (SecurityException e) {
3340                         tryAndThrowException(errorCallback, e, ERROR_SECURITY);
3341                     } catch (Exception e) {
3342                         tryAndThrowException(errorCallback, e, ERROR_INTERNAL);
3343                     }
3344                 });
3345     }
3346 
3347     @Override
3348     @RequiresApi(Build.VERSION_CODES.BAKLAVA)
restoreChanges(List<RestoreChange> changes, IEmptyResponseCallback callback)3349     public void restoreChanges(List<RestoreChange> changes, IEmptyResponseCallback callback) {
3350         checkParamsNonNull(changes);
3351         final int uid = Binder.getCallingUid();
3352         final int pid = Binder.getCallingPid();
3353         final UserHandle userHandle = Binder.getCallingUserHandle();
3354         final ErrorCallback errorCallback = callback::onError;
3355         mThreadScheduler.scheduleControllerTask(
3356                 () -> {
3357                     try {
3358                         // TODO(b/400105647): Remove duplicate flag check once excess code size is
3359                         // resolved.
3360                         if (mCloudRestoreManager == null
3361                                 || !Flags.cloudBackupAndRestore()
3362                                 || !isCloudBackupRestoreEnabled()) {
3363                             throw new UnsupportedOperationException(
3364                                     "restoreChanges is not supported.");
3365                         }
3366                         enforceIsForegroundUser(userHandle);
3367                         mContext.enforcePermission(
3368                                 RESTORE_HEALTH_CONNECT_DATA_AND_SETTINGS,
3369                                 pid,
3370                                 uid,
3371                                 "Caller does not have permission to call" + " restoreChanges.");
3372                         mCloudRestoreManager.restoreChanges(changes);
3373                         callback.onResult();
3374                     } catch (UnsupportedOperationException e) {
3375                         tryAndThrowException(errorCallback, e, ERROR_UNSUPPORTED_OPERATION);
3376                     } catch (SecurityException e) {
3377                         tryAndThrowException(errorCallback, e, ERROR_SECURITY);
3378                     } catch (IllegalArgumentException e) {
3379                         tryAndThrowException(errorCallback, e, ERROR_INVALID_ARGUMENT);
3380                     } catch (Exception e) {
3381                         tryAndThrowException(errorCallback, e, ERROR_INTERNAL);
3382                     }
3383                 });
3384     }
3385 
3386     // Cancel BR timeouts - this might be needed when a user is going into background.
cancelBackupRestoreTimeouts()3387     void cancelBackupRestoreTimeouts() {
3388         mBackupRestore.cancelAllJobs();
3389     }
3390 
tryAcquireApiCallQuota( int uid, @QuotaCategory.Type int quotaCategory, boolean isInForeground, HealthConnectServiceLogger.Builder logger)3391     private void tryAcquireApiCallQuota(
3392             int uid,
3393             @QuotaCategory.Type int quotaCategory,
3394             boolean isInForeground,
3395             HealthConnectServiceLogger.Builder logger) {
3396         try {
3397             mRateLimiter.tryAcquireApiCallQuota(uid, quotaCategory, isInForeground);
3398         } catch (RateLimiterException rateLimiterException) {
3399             logger.setRateLimit(
3400                     rateLimiterException.getRateLimiterQuotaBucket(),
3401                     rateLimiterException.getRateLimiterQuotaLimit());
3402             throw new HealthConnectException(
3403                     rateLimiterException.getErrorCode(), rateLimiterException.getMessage());
3404         }
3405     }
3406 
tryAcquireApiCallQuota( int uid, @QuotaCategory.Type int quotaCategory, boolean isInForeground, HealthConnectServiceLogger.Builder logger, long memoryCost)3407     private void tryAcquireApiCallQuota(
3408             int uid,
3409             @QuotaCategory.Type int quotaCategory,
3410             boolean isInForeground,
3411             HealthConnectServiceLogger.Builder logger,
3412             long memoryCost) {
3413         try {
3414             mRateLimiter.tryAcquireApiCallQuota(uid, quotaCategory, isInForeground, memoryCost);
3415         } catch (RateLimiterException rateLimiterException) {
3416             logger.setRateLimit(
3417                     rateLimiterException.getRateLimiterQuotaBucket(),
3418                     rateLimiterException.getRateLimiterQuotaLimit());
3419             throw new HealthConnectException(
3420                     rateLimiterException.getErrorCode(), rateLimiterException.getMessage());
3421         }
3422     }
3423 
enforceMemoryRateLimit(List<Long> recordsSize, long recordsChunkSize)3424     private void enforceMemoryRateLimit(List<Long> recordsSize, long recordsChunkSize) {
3425         recordsSize.forEach(mRateLimiter::checkMaxRecordMemoryUsage);
3426         mRateLimiter.checkMaxChunkMemoryUsage(recordsChunkSize);
3427     }
3428 
3429     /**
3430      * On a multi-user device, enforce that the calling user handle (user account) is the same as
3431      * the current foreground user (account).
3432      */
enforceIsForegroundUser(UserHandle callingUserHandle)3433     private void enforceIsForegroundUser(UserHandle callingUserHandle) {
3434         if (!callingUserHandle.equals(mCurrentForegroundUser)) {
3435             throw new IllegalStateException(
3436                     "Calling user: "
3437                             + callingUserHandle.getIdentifier()
3438                             + "is not the current foreground user: "
3439                             + mCurrentForegroundUser.getIdentifier()
3440                             + ". HC request must be called"
3441                             + " from the current foreground user.");
3442         }
3443     }
3444 
isDataSyncInProgress()3445     private boolean isDataSyncInProgress() {
3446         return mMigrationStateManager.isMigrationInProgress()
3447                 || mBackupRestore.isRestoreMergingInProgress();
3448     }
3449 
getDataMigrationManager(UserHandle userHandle)3450     private DataMigrationManager getDataMigrationManager(UserHandle userHandle) {
3451         final Context userContext = mContext.createContextAsUser(userHandle, 0);
3452 
3453         return new DataMigrationManager(
3454                 userContext,
3455                 mTransactionManager,
3456                 mPermissionHelper,
3457                 mFirstGrantTimeManager,
3458                 mDeviceInfoHelper,
3459                 mAppInfoHelper,
3460                 mHealthDataCategoryPriorityHelper,
3461                 mPriorityMigrationHelper,
3462                 mMigrationEntityHelper,
3463                 mPreferencesManager);
3464     }
3465 
enforceCallingPackageBelongsToUid(String packageName, int callingUid)3466     private void enforceCallingPackageBelongsToUid(String packageName, int callingUid) {
3467         int packageUid;
3468         try {
3469             packageUid = mContext.getPackageManager().getPackageUid(packageName, /* flags */ 0);
3470         } catch (PackageManager.NameNotFoundException e) {
3471             throw new IllegalStateException(packageName + " not found");
3472         }
3473         if (UserHandle.getAppId(packageUid) != UserHandle.getAppId(callingUid)) {
3474             throw new SecurityException(packageName + " does not belong to uid " + callingUid);
3475         }
3476     }
3477 
3478     /**
3479      * Verify various aspects of the calling user.
3480      *
3481      * @param callingUid Uid of the caller, usually retrieved from Binder for authenticity.
3482      * @param callerAttributionSource The permission identity of the caller
3483      */
verifyPackageNameFromUid( int callingUid, AttributionSource callerAttributionSource)3484     private void verifyPackageNameFromUid(
3485             int callingUid, AttributionSource callerAttributionSource) {
3486         // Check does the attribution source is one for the calling app.
3487         callerAttributionSource.enforceCallingUid();
3488         // Obtain the user where the client is running in.
3489         UserHandle callingUserHandle = UserHandle.getUserHandleForUid(callingUid);
3490         Context callingUserContext = mContext.createContextAsUser(callingUserHandle, 0);
3491         String callingPackageName =
3492                 Objects.requireNonNull(callerAttributionSource.getPackageName());
3493         verifyCallingPackage(callingUserContext, callingUid, callingPackageName);
3494     }
3495 
3496     /**
3497      * Check that the caller's supposed package name matches the uid making the call.
3498      *
3499      * @throws SecurityException if the package name and uid don't match.
3500      */
verifyCallingPackage( Context actualCallingUserContext, int actualCallingUid, String claimedCallingPackage)3501     private void verifyCallingPackage(
3502             Context actualCallingUserContext, int actualCallingUid, String claimedCallingPackage) {
3503         int claimedCallingUid = getPackageUid(actualCallingUserContext, claimedCallingPackage);
3504         if (claimedCallingUid != actualCallingUid) {
3505             throw new SecurityException(
3506                     claimedCallingPackage
3507                             + ", with uid "
3508                             + claimedCallingUid
3509                             + " does not belong to uid "
3510                             + actualCallingUid);
3511         }
3512     }
3513 
3514     /** Finds the UID of the {@code packageName} in the given {@code context}. */
getPackageUid(Context context, String packageName)3515     private int getPackageUid(Context context, String packageName) {
3516         try {
3517             return context.getPackageManager().getPackageUid(packageName, /* flags= */ 0);
3518         } catch (PackageManager.NameNotFoundException e) {
3519             return Process.INVALID_UID;
3520         }
3521     }
3522 
enforceShowMigrationInfoIntent(String packageName, int callingUid)3523     private void enforceShowMigrationInfoIntent(String packageName, int callingUid) {
3524         enforceCallingPackageBelongsToUid(packageName, callingUid);
3525 
3526         Intent intentToCheck =
3527                 new Intent(HealthConnectManager.ACTION_SHOW_MIGRATION_INFO).setPackage(packageName);
3528 
3529         ResolveInfo resolveResult =
3530                 mContext.getPackageManager()
3531                         .resolveActivity(
3532                                 intentToCheck,
3533                                 PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_ALL));
3534 
3535         if (Objects.isNull(resolveResult)) {
3536             throw new IllegalArgumentException(
3537                     packageName
3538                             + " does not handle intent "
3539                             + HealthConnectManager.ACTION_SHOW_MIGRATION_INFO);
3540         }
3541     }
3542 
getPopulatedRecordTypeInfoResponses()3543     private Map<Integer, List<DataOrigin>> getPopulatedRecordTypeInfoResponses() {
3544         Map<Integer, Class<? extends Record>> recordIdToExternalRecordClassMap =
3545                 mHealthConnectMappings.getRecordIdToExternalRecordClassMap();
3546         Map<Integer, List<DataOrigin>> recordTypeInfoResponses =
3547                 new ArrayMap<>(recordIdToExternalRecordClassMap.size());
3548         Map<Integer, Set<String>> recordTypeToContributingPackagesMap =
3549                 mAppInfoHelper.getRecordTypesToContributingPackagesMap();
3550         recordIdToExternalRecordClassMap
3551                 .keySet()
3552                 .forEach(
3553                         (recordType) -> {
3554                             if (recordTypeToContributingPackagesMap.containsKey(recordType)) {
3555                                 List<DataOrigin> packages =
3556                                         recordTypeToContributingPackagesMap.get(recordType).stream()
3557                                                 .map(
3558                                                         (packageName) ->
3559                                                                 new DataOrigin.Builder()
3560                                                                         .setPackageName(packageName)
3561                                                                         .build())
3562                                                 .toList();
3563                                 recordTypeInfoResponses.put(recordType, packages);
3564                             } else {
3565                                 recordTypeInfoResponses.put(recordType, Collections.emptyList());
3566                             }
3567                         });
3568         return recordTypeInfoResponses;
3569     }
3570 
getPopulatedMedicalResourceTypeInfos()3571     private List<MedicalResourceTypeInfo> getPopulatedMedicalResourceTypeInfos() {
3572         Map<Integer, Set<MedicalDataSource>> resourceTypeToDataSourcesMap =
3573                 mMedicalResourceHelper.getMedicalResourceTypeToContributingDataSourcesMap();
3574         return MedicalResource.VALID_TYPES.stream()
3575                 .map(
3576                         medicalResourceType ->
3577                                 new MedicalResourceTypeInfo(
3578                                         medicalResourceType,
3579                                         resourceTypeToDataSourcesMap.getOrDefault(
3580                                                 medicalResourceType, Set.of())))
3581                 .collect(toList());
3582     }
3583 
getPopulatedMedicalResourceTypesWithReadPermissions( Set<String> grantedMedicalPermissions)3584     private Set<Integer> getPopulatedMedicalResourceTypesWithReadPermissions(
3585             Set<String> grantedMedicalPermissions) {
3586         return grantedMedicalPermissions.stream()
3587                 .filter(permissionString -> !permissionString.equals(WRITE_MEDICAL_DATA))
3588                 .map(MedicalResourceTypePermissionMapper::getMedicalResourceType)
3589                 .collect(toSet());
3590     }
3591 
hasDataManagementPermission(int uid, int pid)3592     private boolean hasDataManagementPermission(int uid, int pid) {
3593         return isPermissionGranted(MANAGE_HEALTH_DATA_PERMISSION, uid, pid);
3594     }
3595 
isPermissionGranted(String permission, int uid, int pid)3596     private boolean isPermissionGranted(String permission, int uid, int pid) {
3597         return mContext.checkPermission(permission, pid, uid) == PERMISSION_GRANTED;
3598     }
3599 
logRecordTypeSpecificUpsertMetrics( List<RecordInternal<?>> recordInternals, String packageName)3600     private void logRecordTypeSpecificUpsertMetrics(
3601             List<RecordInternal<?>> recordInternals, String packageName) {
3602         checkParamsNonNull(recordInternals, packageName);
3603 
3604         Map<Integer, List<RecordInternal<?>>> recordTypeToRecordInternals =
3605                 getRecordTypeToListOfRecords(recordInternals);
3606         for (Entry<Integer, List<RecordInternal<?>>> recordTypeToRecordInternalsEntry :
3607                 recordTypeToRecordInternals.entrySet()) {
3608             RecordHelper<?> recordHelper =
3609                     mInternalHealthConnectMappings.getRecordHelper(
3610                             recordTypeToRecordInternalsEntry.getKey());
3611             recordHelper.logUpsertMetrics(
3612                     mStatsLog, recordTypeToRecordInternalsEntry.getValue(), packageName);
3613         }
3614     }
3615 
logRecordTypeSpecificReadMetrics( List<RecordInternal<?>> recordInternals, String packageName)3616     private void logRecordTypeSpecificReadMetrics(
3617             List<RecordInternal<?>> recordInternals, String packageName) {
3618         checkParamsNonNull(recordInternals, packageName);
3619 
3620         Map<Integer, List<RecordInternal<?>>> recordTypeToRecordInternals =
3621                 getRecordTypeToListOfRecords(recordInternals);
3622         for (Entry<Integer, List<RecordInternal<?>>> recordTypeToRecordInternalsEntry :
3623                 recordTypeToRecordInternals.entrySet()) {
3624             RecordHelper<?> recordHelper =
3625                     mInternalHealthConnectMappings.getRecordHelper(
3626                             recordTypeToRecordInternalsEntry.getKey());
3627             recordHelper.logReadMetrics(
3628                     mStatsLog, recordTypeToRecordInternalsEntry.getValue(), packageName);
3629         }
3630     }
3631 
getRecordTypeToListOfRecords( List<RecordInternal<?>> recordInternals)3632     private Map<Integer, List<RecordInternal<?>>> getRecordTypeToListOfRecords(
3633             List<RecordInternal<?>> recordInternals) {
3634 
3635         return recordInternals.stream()
3636                 .collect(Collectors.groupingBy(RecordInternal::getRecordType));
3637     }
3638 
throwExceptionIfDataSyncInProgress()3639     private void throwExceptionIfDataSyncInProgress() {
3640         if (isDataSyncInProgress()) {
3641             throw new HealthConnectException(
3642                     HealthConnectException.ERROR_DATA_SYNC_IN_PROGRESS,
3643                     "Storage data sync in progress. API calls are blocked");
3644         }
3645     }
3646 
3647     /**
3648      * Throws an IllegalState Exception if data migration or restore is in process. This is only
3649      * used by HealthConnect synchronous APIs as {@link HealthConnectException} is lost between
3650      * processes on synchronous APIs and can only be returned to the caller for the APIs with a
3651      * callback.
3652      */
throwIllegalStateExceptionIfDataSyncInProgress()3653     private void throwIllegalStateExceptionIfDataSyncInProgress() {
3654         if (isDataSyncInProgress()) {
3655             throw new IllegalStateException("Storage data sync in progress. API calls are blocked");
3656         }
3657     }
3658 
postDeleteTasks(List<Integer> recordTypeIdsToDelete)3659     private void postDeleteTasks(List<Integer> recordTypeIdsToDelete) {
3660         if (recordTypeIdsToDelete != null && !recordTypeIdsToDelete.isEmpty()) {
3661             mAppInfoHelper.syncAppInfoRecordTypesUsed(new HashSet<>(recordTypeIdsToDelete));
3662             mActivityDateHelper.reSyncByRecordTypeIds(recordTypeIdsToDelete);
3663         }
3664     }
3665 
3666     /**
3667      * When read permission is granted but the app is reading from the background, enforce self read
3668      * for this client if READ_HEALTH_DATA_IN_BACKGROUND permission is not granted.
3669      */
shouldEnforceSelfRead(int uid, int pid, AttributionSource attributionSource)3670     private boolean shouldEnforceSelfRead(int uid, int pid, AttributionSource attributionSource) {
3671         return !isPermissionGranted(READ_HEALTH_DATA_IN_BACKGROUND, uid, pid)
3672                 || isBackgroundPermissionFromSplit(attributionSource);
3673     }
3674 
3675     /** Returns true if READ_HEALTH_DATA_IN_BACKGROUND is from split permission. */
isBackgroundPermissionFromSplit(AttributionSource attributionSource)3676     private boolean isBackgroundPermissionFromSplit(AttributionSource attributionSource) {
3677         if (!Flags.replaceBodySensorPermissionEnabled()) {
3678             return false;
3679         }
3680 
3681         String packageName = attributionSource.getPackageName();
3682         if (packageName == null) {
3683             // Cannot read permission flag from null package name, return default false.
3684             return false;
3685         }
3686         UserHandle user = UserHandle.getUserHandleForUid(attributionSource.getUid());
3687         int permissionFlags =
3688                 mContext.getPackageManager()
3689                         .getPermissionFlags(READ_HEALTH_DATA_IN_BACKGROUND, packageName, user);
3690 
3691         int targetSdk;
3692         try {
3693             targetSdk =
3694                     PackageInfoUtils.getPackageInfoUnchecked(
3695                                     packageName,
3696                                     user,
3697                                     PackageManager.PackageInfoFlags.of(0),
3698                                     mContext)
3699                             .applicationInfo
3700                             .targetSdkVersion;
3701         } catch (Exception e) {
3702             // Cannot find the package, default false.
3703             return false;
3704         }
3705 
3706         return HealthConnectPermissionHelper.isFromSplitPermission(permissionFlags, targetSdk);
3707     }
3708 
tryAndReturnResult( IEmptyResponseCallback callback, HealthConnectServiceLogger.Builder logger)3709     private static void tryAndReturnResult(
3710             IEmptyResponseCallback callback, HealthConnectServiceLogger.Builder logger) {
3711         try {
3712             callback.onResult();
3713             logger.setHealthDataServiceApiStatusSuccess();
3714         } catch (RemoteException e) {
3715             Slog.e(TAG, "Remote call failed", e);
3716             logger.setHealthDataServiceApiStatusError(ERROR_INTERNAL);
3717         }
3718     }
3719 
tryAndReturnResult( IInsertRecordsResponseCallback callback, List<String> uuids, HealthConnectServiceLogger.Builder logger)3720     private static void tryAndReturnResult(
3721             IInsertRecordsResponseCallback callback,
3722             List<String> uuids,
3723             HealthConnectServiceLogger.Builder logger) {
3724         try {
3725             callback.onResult(new InsertRecordsResponseParcel(uuids));
3726             logger.setHealthDataServiceApiStatusSuccess();
3727         } catch (RemoteException e) {
3728             Slog.e(TAG, "Remote call failed", e);
3729             logger.setHealthDataServiceApiStatusError(ERROR_INTERNAL);
3730         }
3731     }
3732 
tryAndReturnResult( IMedicalDataSourcesResponseCallback callback, List<MedicalDataSource> response, HealthConnectServiceLogger.Builder logger)3733     private static void tryAndReturnResult(
3734             IMedicalDataSourcesResponseCallback callback,
3735             List<MedicalDataSource> response,
3736             HealthConnectServiceLogger.Builder logger) {
3737         try {
3738             callback.onResult(response);
3739             logger.setHealthDataServiceApiStatusSuccess();
3740         } catch (RemoteException e) {
3741             Slog.e(TAG, "Remote call failed when returning GetMedicalDataSources response", e);
3742             logger.setHealthDataServiceApiStatusError(ERROR_INTERNAL);
3743         }
3744     }
3745 
tryAndReturnResult( IMedicalDataSourceResponseCallback callback, MedicalDataSource medicalDataSource, HealthConnectServiceLogger.Builder logger)3746     private static void tryAndReturnResult(
3747             IMedicalDataSourceResponseCallback callback,
3748             MedicalDataSource medicalDataSource,
3749             HealthConnectServiceLogger.Builder logger) {
3750         try {
3751             callback.onResult(medicalDataSource);
3752             logger.setHealthDataServiceApiStatusSuccess();
3753         } catch (RemoteException e) {
3754             Slog.e(TAG, "Remote call failed when returning MedicalDataSource response", e);
3755             logger.setHealthDataServiceApiStatusError(ERROR_INTERNAL);
3756         }
3757     }
3758 
tryAndReturnMedicalResourcesResult( android.os.IInterface callback, List<MedicalResource> medicalResources, HealthConnectServiceLogger.Builder logger)3759     private static void tryAndReturnMedicalResourcesResult(
3760             android.os.IInterface callback,
3761             List<MedicalResource> medicalResources,
3762             HealthConnectServiceLogger.Builder logger) {
3763         if (callback instanceof IMedicalResourcesResponseCallback) {
3764             tryAndReturnResult(
3765                     (IMedicalResourcesResponseCallback) callback, medicalResources, logger);
3766         } else if (callback instanceof IMedicalResourceListParcelResponseCallback) {
3767             tryAndReturnResult(
3768                     (IMedicalResourceListParcelResponseCallback) callback,
3769                     new MedicalResourceListParcel(medicalResources),
3770                     logger);
3771         } else {
3772             throw new IllegalStateException("Unexpected callback type for upsertMedicalResources");
3773         }
3774     }
3775 
tryAndReturnResult( IMedicalResourcesResponseCallback callback, List<MedicalResource> medicalResources, HealthConnectServiceLogger.Builder logger)3776     private static void tryAndReturnResult(
3777             IMedicalResourcesResponseCallback callback,
3778             List<MedicalResource> medicalResources,
3779             HealthConnectServiceLogger.Builder logger) {
3780         try {
3781             callback.onResult(medicalResources);
3782             logger.setHealthDataServiceApiStatusSuccess();
3783         } catch (RemoteException e) {
3784             Slog.e(TAG, "Remote call to return UpsertMedicalResourcesResponse failed", e);
3785             logger.setHealthDataServiceApiStatusError(ERROR_INTERNAL);
3786         }
3787     }
3788 
tryAndReturnResult( IMedicalResourceListParcelResponseCallback callback, MedicalResourceListParcel medicalResourceListParcel, HealthConnectServiceLogger.Builder logger)3789     private static void tryAndReturnResult(
3790             IMedicalResourceListParcelResponseCallback callback,
3791             MedicalResourceListParcel medicalResourceListParcel,
3792             HealthConnectServiceLogger.Builder logger) {
3793         try {
3794             callback.onResult(medicalResourceListParcel);
3795             logger.setHealthDataServiceApiStatusSuccess();
3796         } catch (RemoteException e) {
3797             Slog.e(TAG, "Remote call to return UpsertMedicalResourcesResponse failed", e);
3798             logger.setHealthDataServiceApiStatusError(ERROR_INTERNAL);
3799         }
3800     }
3801 
tryAndThrowException( IMigrationCallback callback, Exception exception, @MigrationException.ErrorCode int errorCode, @Nullable String failedEntityId)3802     private static void tryAndThrowException(
3803             IMigrationCallback callback,
3804             Exception exception,
3805             @MigrationException.ErrorCode int errorCode,
3806             @Nullable String failedEntityId) {
3807         try {
3808             callback.onError(
3809                     new MigrationException(exception.toString(), errorCode, failedEntityId));
3810         } catch (RemoteException e) {
3811             Log.e(TAG, "Unable to send result to the callback", e);
3812         }
3813     }
3814 
tryAndThrowException( ErrorCallback callback, Exception exception, @HealthConnectException.ErrorCode int errorCode)3815     private static void tryAndThrowException(
3816             ErrorCallback callback,
3817             Exception exception,
3818             @HealthConnectException.ErrorCode int errorCode) {
3819         try {
3820             callback.onError(
3821                     new HealthConnectExceptionParcel(
3822                             new HealthConnectException(errorCode, exception.toString())));
3823         } catch (RemoteException e) {
3824             Log.e(TAG, "Unable to send result to the callback", e);
3825         }
3826     }
3827 
checkParamsNonNull(Object... params)3828     private static void checkParamsNonNull(Object... params) {
3829         for (Object param : params) {
3830             Objects.requireNonNull(param);
3831         }
3832     }
3833 
3834     /** A task to run in {@link #scheduleLoggingHealthDataApiErrors}. */
3835     private interface Task {
3836         /**
3837          * The code to run.
3838          *
3839          * <p>As well as the listed exception types which may be thrown, runtime exceptions
3840          * including {@link SQLiteException}, {@link IllegalArgumentException}, {@link
3841          * IllegalStateException}, {@link SecurityException} and {@link HealthConnectException} are
3842          * expected.
3843          */
execute()3844         void execute() throws RemoteException, JSONException;
3845     }
3846 
3847     /**
3848      * A wrapper interface to put around a callback to HealthConnect. It allows very similar code to
3849      * be written for multiple similar AIDL interfaces.
3850      */
3851     private interface ErrorCallback {
3852 
3853         /** Sends an error to the caller. */
onError(HealthConnectExceptionParcel error)3854         void onError(HealthConnectExceptionParcel error) throws RemoteException;
3855     }
3856 }
3857