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.MIGRATE_HEALTH_CONNECT_DATA;
20 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
21 import static android.health.connect.Constants.DEFAULT_LONG;
22 import static android.health.connect.Constants.READ;
23 import static android.health.connect.HealthConnectException.ERROR_INTERNAL;
24 import static android.health.connect.HealthConnectException.ERROR_SECURITY;
25 import static android.health.connect.HealthConnectException.ERROR_UNSUPPORTED_OPERATION;
26 import static android.health.connect.HealthPermissions.MANAGE_HEALTH_DATA_PERMISSION;
27 import static android.health.connect.HealthPermissions.READ_HEALTH_DATA_HISTORY;
28 import static android.health.connect.HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND;
29 
30 import static com.android.healthfitness.flags.Flags.personalHealthRecord;
31 import static com.android.server.healthconnect.logging.HealthConnectServiceLogger.ApiMethods.DELETE_DATA;
32 import static com.android.server.healthconnect.logging.HealthConnectServiceLogger.ApiMethods.GET_CHANGES;
33 import static com.android.server.healthconnect.logging.HealthConnectServiceLogger.ApiMethods.GET_CHANGES_TOKEN;
34 import static com.android.server.healthconnect.logging.HealthConnectServiceLogger.ApiMethods.INSERT_DATA;
35 import static com.android.server.healthconnect.logging.HealthConnectServiceLogger.ApiMethods.READ_AGGREGATED_DATA;
36 import static com.android.server.healthconnect.logging.HealthConnectServiceLogger.ApiMethods.READ_DATA;
37 import static com.android.server.healthconnect.logging.HealthConnectServiceLogger.ApiMethods.UPDATE_DATA;
38 
39 import android.Manifest;
40 import android.annotation.NonNull;
41 import android.annotation.Nullable;
42 import android.content.AttributionSource;
43 import android.content.Context;
44 import android.content.Intent;
45 import android.content.pm.PackageManager;
46 import android.content.pm.ResolveInfo;
47 import android.database.sqlite.SQLiteException;
48 import android.health.connect.Constants;
49 import android.health.connect.FetchDataOriginsPriorityOrderResponse;
50 import android.health.connect.HealthConnectDataState;
51 import android.health.connect.HealthConnectException;
52 import android.health.connect.HealthConnectManager;
53 import android.health.connect.HealthConnectManager.DataDownloadState;
54 import android.health.connect.HealthDataCategory;
55 import android.health.connect.HealthPermissions;
56 import android.health.connect.PageTokenWrapper;
57 import android.health.connect.ReadMedicalResourcesResponse;
58 import android.health.connect.RecordTypeInfoResponse;
59 import android.health.connect.accesslog.AccessLog;
60 import android.health.connect.accesslog.AccessLogsResponseParcel;
61 import android.health.connect.aidl.ActivityDatesRequestParcel;
62 import android.health.connect.aidl.ActivityDatesResponseParcel;
63 import android.health.connect.aidl.AggregateDataRequestParcel;
64 import android.health.connect.aidl.ApplicationInfoResponseParcel;
65 import android.health.connect.aidl.DeleteUsingFiltersRequestParcel;
66 import android.health.connect.aidl.GetPriorityResponseParcel;
67 import android.health.connect.aidl.HealthConnectExceptionParcel;
68 import android.health.connect.aidl.IAccessLogsResponseCallback;
69 import android.health.connect.aidl.IActivityDatesResponseCallback;
70 import android.health.connect.aidl.IAggregateRecordsResponseCallback;
71 import android.health.connect.aidl.IApplicationInfoResponseCallback;
72 import android.health.connect.aidl.IChangeLogsResponseCallback;
73 import android.health.connect.aidl.IDataStagingFinishedCallback;
74 import android.health.connect.aidl.IEmptyResponseCallback;
75 import android.health.connect.aidl.IGetChangeLogTokenCallback;
76 import android.health.connect.aidl.IGetHealthConnectDataStateCallback;
77 import android.health.connect.aidl.IGetHealthConnectMigrationUiStateCallback;
78 import android.health.connect.aidl.IGetPriorityResponseCallback;
79 import android.health.connect.aidl.IHealthConnectService;
80 import android.health.connect.aidl.IInsertRecordsResponseCallback;
81 import android.health.connect.aidl.IMigrationCallback;
82 import android.health.connect.aidl.IReadMedicalResourcesResponseCallback;
83 import android.health.connect.aidl.IReadRecordsResponseCallback;
84 import android.health.connect.aidl.IRecordTypeInfoResponseCallback;
85 import android.health.connect.aidl.InsertRecordsResponseParcel;
86 import android.health.connect.aidl.MedicalIdFiltersParcel;
87 import android.health.connect.aidl.ReadRecordsRequestParcel;
88 import android.health.connect.aidl.ReadRecordsResponseParcel;
89 import android.health.connect.aidl.RecordIdFiltersParcel;
90 import android.health.connect.aidl.RecordTypeInfoResponseParcel;
91 import android.health.connect.aidl.RecordsParcel;
92 import android.health.connect.aidl.UpdatePriorityRequestParcel;
93 import android.health.connect.changelog.ChangeLogTokenRequest;
94 import android.health.connect.changelog.ChangeLogTokenResponse;
95 import android.health.connect.changelog.ChangeLogsRequest;
96 import android.health.connect.changelog.ChangeLogsResponse;
97 import android.health.connect.changelog.ChangeLogsResponse.DeletedLog;
98 import android.health.connect.datatypes.AppInfo;
99 import android.health.connect.datatypes.DataOrigin;
100 import android.health.connect.datatypes.MedicalResource;
101 import android.health.connect.datatypes.Record;
102 import android.health.connect.exportimport.ExportImportDocumentProvider;
103 import android.health.connect.exportimport.IImportStatusCallback;
104 import android.health.connect.exportimport.IQueryDocumentProvidersCallback;
105 import android.health.connect.exportimport.IScheduledExportStatusCallback;
106 import android.health.connect.exportimport.ImportStatus;
107 import android.health.connect.exportimport.ScheduledExportSettings;
108 import android.health.connect.exportimport.ScheduledExportStatus;
109 import android.health.connect.internal.datatypes.RecordInternal;
110 import android.health.connect.internal.datatypes.utils.AggregationTypeIdMapper;
111 import android.health.connect.internal.datatypes.utils.RecordMapper;
112 import android.health.connect.migration.HealthConnectMigrationUiState;
113 import android.health.connect.migration.MigrationEntityParcel;
114 import android.health.connect.migration.MigrationException;
115 import android.health.connect.ratelimiter.RateLimiter;
116 import android.health.connect.ratelimiter.RateLimiter.QuotaCategory;
117 import android.health.connect.ratelimiter.RateLimiterException;
118 import android.health.connect.restore.BackupFileNamesSet;
119 import android.health.connect.restore.StageRemoteDataException;
120 import android.health.connect.restore.StageRemoteDataRequest;
121 import android.net.Uri;
122 import android.os.Binder;
123 import android.os.ParcelFileDescriptor;
124 import android.os.Process;
125 import android.os.RemoteException;
126 import android.os.UserHandle;
127 import android.permission.PermissionManager;
128 import android.util.ArrayMap;
129 import android.util.Log;
130 import android.util.Pair;
131 import android.util.Slog;
132 
133 import com.android.internal.annotations.VisibleForTesting;
134 import com.android.server.LocalManagerRegistry;
135 import com.android.server.appop.AppOpsManagerLocal;
136 import com.android.server.healthconnect.backuprestore.BackupRestore;
137 import com.android.server.healthconnect.exportimport.DocumentProvidersManager;
138 import com.android.server.healthconnect.exportimport.ExportImportJobs;
139 import com.android.server.healthconnect.exportimport.ImportManager;
140 import com.android.server.healthconnect.logging.HealthConnectServiceLogger;
141 import com.android.server.healthconnect.migration.DataMigrationManager;
142 import com.android.server.healthconnect.migration.MigrationCleaner;
143 import com.android.server.healthconnect.migration.MigrationStateManager;
144 import com.android.server.healthconnect.migration.MigrationUiStateManager;
145 import com.android.server.healthconnect.migration.PriorityMigrationHelper;
146 import com.android.server.healthconnect.permission.DataPermissionEnforcer;
147 import com.android.server.healthconnect.permission.FirstGrantTimeManager;
148 import com.android.server.healthconnect.permission.HealthConnectPermissionHelper;
149 import com.android.server.healthconnect.storage.AutoDeleteService;
150 import com.android.server.healthconnect.storage.ExportImportSettingsStorage;
151 import com.android.server.healthconnect.storage.TransactionManager;
152 import com.android.server.healthconnect.storage.datatypehelpers.AccessLogsHelper;
153 import com.android.server.healthconnect.storage.datatypehelpers.ActivityDateHelper;
154 import com.android.server.healthconnect.storage.datatypehelpers.AppInfoHelper;
155 import com.android.server.healthconnect.storage.datatypehelpers.ChangeLogsHelper;
156 import com.android.server.healthconnect.storage.datatypehelpers.ChangeLogsRequestHelper;
157 import com.android.server.healthconnect.storage.datatypehelpers.DatabaseHelper;
158 import com.android.server.healthconnect.storage.datatypehelpers.DeviceInfoHelper;
159 import com.android.server.healthconnect.storage.datatypehelpers.HealthDataCategoryPriorityHelper;
160 import com.android.server.healthconnect.storage.datatypehelpers.RecordHelper;
161 import com.android.server.healthconnect.storage.request.AggregateTransactionRequest;
162 import com.android.server.healthconnect.storage.request.DeleteTransactionRequest;
163 import com.android.server.healthconnect.storage.request.ReadTransactionRequest;
164 import com.android.server.healthconnect.storage.request.UpsertTransactionRequest;
165 import com.android.server.healthconnect.storage.utils.RecordHelperProvider;
166 
167 import java.io.IOException;
168 import java.time.Instant;
169 import java.time.LocalDate;
170 import java.util.ArrayList;
171 import java.util.Arrays;
172 import java.util.Collections;
173 import java.util.HashSet;
174 import java.util.List;
175 import java.util.Map;
176 import java.util.Map.Entry;
177 import java.util.Objects;
178 import java.util.Optional;
179 import java.util.Set;
180 import java.util.UUID;
181 import java.util.stream.Collectors;
182 
183 /**
184  * IHealthConnectService's implementation
185  *
186  * @hide
187  */
188 final class HealthConnectServiceImpl extends IHealthConnectService.Stub {
189     private static final String TAG = "HealthConnectService";
190     // Permission for test api for deleting staged data
191     private static final String DELETE_STAGED_HEALTH_CONNECT_REMOTE_DATA_PERMISSION =
192             "android.permission.DELETE_STAGED_HEALTH_CONNECT_REMOTE_DATA";
193     // Allows an application to act as a backup inter-agent to send and receive HealthConnect data
194     private static final String HEALTH_CONNECT_BACKUP_INTER_AGENT_PERMISSION =
195             "android.permission.HEALTH_CONNECT_BACKUP_INTER_AGENT";
196 
197     private final TransactionManager mTransactionManager;
198     private final HealthConnectDeviceConfigManager mDeviceConfigManager;
199     private final HealthConnectPermissionHelper mPermissionHelper;
200     private final FirstGrantTimeManager mFirstGrantTimeManager;
201     private final Context mContext;
202     private final PermissionManager mPermissionManager;
203 
204     private final BackupRestore mBackupRestore;
205     private final MigrationStateManager mMigrationStateManager;
206 
207     private final DataPermissionEnforcer mDataPermissionEnforcer;
208 
209     private final AppOpsManagerLocal mAppOpsManagerLocal;
210     private final MigrationUiStateManager mMigrationUiStateManager;
211     private final ImportManager mImportManager;
212 
213     private final HealthDataCategoryPriorityHelper mHealthDataCategoryPriorityHelper;
214     private final AppInfoHelper mAppInfoHelper;
215     private final PriorityMigrationHelper mPriorityMigrationHelper;
216     private final RecordMapper mRecordMapper;
217     private final AggregationTypeIdMapper mAggregationTypeIdMapper;
218     private final DeviceInfoHelper mDeviceInfoHelper;
219 
220     private volatile UserHandle mCurrentForegroundUser;
221 
HealthConnectServiceImpl( TransactionManager transactionManager, HealthConnectDeviceConfigManager deviceConfigManager, HealthConnectPermissionHelper permissionHelper, MigrationCleaner migrationCleaner, FirstGrantTimeManager firstGrantTimeManager, MigrationStateManager migrationStateManager, MigrationUiStateManager migrationUiStateManager, Context context)222     HealthConnectServiceImpl(
223             TransactionManager transactionManager,
224             HealthConnectDeviceConfigManager deviceConfigManager,
225             HealthConnectPermissionHelper permissionHelper,
226             MigrationCleaner migrationCleaner,
227             FirstGrantTimeManager firstGrantTimeManager,
228             MigrationStateManager migrationStateManager,
229             MigrationUiStateManager migrationUiStateManager,
230             Context context) {
231         mTransactionManager = transactionManager;
232         mDeviceConfigManager = deviceConfigManager;
233         mPermissionHelper = permissionHelper;
234         mFirstGrantTimeManager = firstGrantTimeManager;
235         mContext = context;
236         mCurrentForegroundUser = context.getUser();
237         mPermissionManager = mContext.getSystemService(PermissionManager.class);
238         mMigrationStateManager = migrationStateManager;
239         mDataPermissionEnforcer =
240                 new DataPermissionEnforcer(mPermissionManager, mContext, deviceConfigManager);
241         mAppOpsManagerLocal = LocalManagerRegistry.getManager(AppOpsManagerLocal.class);
242         mBackupRestore =
243                 new BackupRestore(mFirstGrantTimeManager, mMigrationStateManager, mContext);
244         mMigrationUiStateManager = migrationUiStateManager;
245         mImportManager = new ImportManager(mContext);
246         migrationCleaner.attachTo(migrationStateManager);
247         mMigrationUiStateManager.attachTo(migrationStateManager);
248         mHealthDataCategoryPriorityHelper = HealthDataCategoryPriorityHelper.getInstance();
249         mAppInfoHelper = AppInfoHelper.getInstance();
250         mPriorityMigrationHelper = PriorityMigrationHelper.getInstance();
251         mRecordMapper = RecordMapper.getInstance();
252         mAggregationTypeIdMapper = AggregationTypeIdMapper.getInstance();
253         mDeviceInfoHelper = DeviceInfoHelper.getInstance();
254     }
255 
onUserSwitching(UserHandle currentForegroundUser)256     public void onUserSwitching(UserHandle currentForegroundUser) {
257         mCurrentForegroundUser = currentForegroundUser;
258         mBackupRestore.setupForUser(currentForegroundUser);
259         HealthConnectThreadScheduler.scheduleInternalTask(
260                 () ->
261                         mHealthDataCategoryPriorityHelper.maybeAddContributingAppsToPriorityList(
262                                 mContext));
263     }
264 
265     @Override
grantHealthPermission( @onNull String packageName, @NonNull String permissionName, @NonNull UserHandle user)266     public void grantHealthPermission(
267             @NonNull String packageName, @NonNull String permissionName, @NonNull UserHandle user) {
268         checkParamsNonNull(packageName, permissionName, user);
269 
270         throwIllegalStateExceptionIfDataSyncInProgress();
271         mPermissionHelper.grantHealthPermission(packageName, permissionName, user);
272     }
273 
274     @Override
revokeHealthPermission( @onNull String packageName, @NonNull String permissionName, @Nullable String reason, @NonNull UserHandle user)275     public void revokeHealthPermission(
276             @NonNull String packageName,
277             @NonNull String permissionName,
278             @Nullable String reason,
279             @NonNull UserHandle user) {
280         checkParamsNonNull(packageName, permissionName, user);
281 
282         throwIllegalStateExceptionIfDataSyncInProgress();
283         mPermissionHelper.revokeHealthPermission(packageName, permissionName, reason, user);
284     }
285 
286     @Override
revokeAllHealthPermissions( @onNull String packageName, @Nullable String reason, @NonNull UserHandle user)287     public void revokeAllHealthPermissions(
288             @NonNull String packageName, @Nullable String reason, @NonNull UserHandle user) {
289         checkParamsNonNull(packageName, user);
290 
291         throwIllegalStateExceptionIfDataSyncInProgress();
292         mPermissionHelper.revokeAllHealthPermissions(packageName, reason, user);
293     }
294 
295     @Override
getGrantedHealthPermissions( @onNull String packageName, @NonNull UserHandle user)296     public List<String> getGrantedHealthPermissions(
297             @NonNull String packageName, @NonNull UserHandle user) {
298         checkParamsNonNull(packageName, user);
299 
300         throwIllegalStateExceptionIfDataSyncInProgress();
301         List<String> grantedPermissions =
302                 mPermissionHelper.getGrantedHealthPermissions(packageName, user);
303         return grantedPermissions;
304     }
305 
306     @Override
getHealthPermissionsFlags( @onNull String packageName, @NonNull UserHandle user, List<String> permissions)307     public Map<String, Integer> getHealthPermissionsFlags(
308             @NonNull String packageName, @NonNull UserHandle user, List<String> permissions) {
309         checkParamsNonNull(packageName, user);
310         throwIllegalStateExceptionIfDataSyncInProgress();
311 
312         Map<String, Integer> response =
313                 mPermissionHelper.getHealthPermissionsFlags(packageName, user, permissions);
314         return response;
315     }
316 
317     @Override
setHealthPermissionsUserFixedFlagValue( @onNull String packageName, @NonNull UserHandle user, List<String> permissions, boolean value)318     public void setHealthPermissionsUserFixedFlagValue(
319             @NonNull String packageName,
320             @NonNull UserHandle user,
321             List<String> permissions,
322             boolean value) {
323         checkParamsNonNull(packageName, user);
324         throwIllegalStateExceptionIfDataSyncInProgress();
325 
326         mPermissionHelper.setHealthPermissionsUserFixedFlagValue(
327                 packageName, user, permissions, value);
328     }
329 
330     @Override
getHistoricalAccessStartDateInMilliseconds( @onNull String packageName, @NonNull UserHandle userHandle)331     public long getHistoricalAccessStartDateInMilliseconds(
332             @NonNull String packageName, @NonNull UserHandle userHandle) {
333         checkParamsNonNull(packageName, userHandle);
334 
335         throwIllegalStateExceptionIfDataSyncInProgress();
336         Optional<Instant> date =
337                 mPermissionHelper.getHealthDataStartDateAccess(packageName, userHandle);
338         return date.map(Instant::toEpochMilli).orElse(Constants.DEFAULT_LONG);
339     }
340 
341     /**
342      * Inserts {@code recordsParcel} into the HealthConnect database.
343      *
344      * @param recordsParcel parcel for list of records to be inserted.
345      * @param callback Callback to receive result of performing this operation. The keys returned in
346      *     {@link InsertRecordsResponseParcel} are the unique IDs of the input records. The values
347      *     are in same order as {@code record}. In case of an error or a permission failure the
348      *     HealthConnect service, {@link IInsertRecordsResponseCallback#onError} will be invoked
349      *     with a {@link HealthConnectExceptionParcel}.
350      */
351     @Override
insertRecords( @onNull AttributionSource attributionSource, @NonNull RecordsParcel recordsParcel, @NonNull IInsertRecordsResponseCallback callback)352     public void insertRecords(
353             @NonNull AttributionSource attributionSource,
354             @NonNull RecordsParcel recordsParcel,
355             @NonNull IInsertRecordsResponseCallback callback) {
356         checkParamsNonNull(attributionSource, recordsParcel, callback);
357 
358         final int uid = Binder.getCallingUid();
359         final int pid = Binder.getCallingPid();
360         final UserHandle userHandle = Binder.getCallingUserHandle();
361         final HealthConnectServiceLogger.Builder logger =
362                 new HealthConnectServiceLogger.Builder(false, INSERT_DATA)
363                         .setPackageName(attributionSource.getPackageName());
364 
365         HealthConnectThreadScheduler.schedule(
366                 mContext,
367                 () -> {
368                     try {
369                         enforceIsForegroundUser(userHandle);
370                         verifyPackageNameFromUid(uid, attributionSource);
371                         if (hasDataManagementPermission(uid, pid)) {
372                             throw new SecurityException(
373                                     "Apps with android.permission.MANAGE_HEALTH_DATA permission are"
374                                             + " not allowed to insert records");
375                         }
376                         enforceMemoryRateLimit(
377                                 recordsParcel.getRecordsSize(),
378                                 recordsParcel.getRecordsChunkSize());
379                         final List<RecordInternal<?>> recordInternals = recordsParcel.getRecords();
380                         logger.setNumberOfRecords(recordInternals.size());
381                         throwExceptionIfDataSyncInProgress();
382                         boolean isInForeground = mAppOpsManagerLocal.isUidInForeground(uid);
383                         tryAcquireApiCallQuota(
384                                 uid,
385                                 QuotaCategory.QUOTA_CATEGORY_WRITE,
386                                 isInForeground,
387                                 logger,
388                                 recordsParcel.getRecordsChunkSize());
389                         mDataPermissionEnforcer.enforceRecordsWritePermissions(
390                                 recordInternals, attributionSource);
391                         UpsertTransactionRequest insertRequest =
392                                 new UpsertTransactionRequest(
393                                         attributionSource.getPackageName(),
394                                         recordInternals,
395                                         mContext,
396                                         /* isInsertRequest */ true,
397                                         mDataPermissionEnforcer
398                                                 .collectExtraWritePermissionStateMapping(
399                                                         recordInternals, attributionSource));
400                         List<String> uuids = mTransactionManager.insertAll(insertRequest);
401                         tryAndReturnResult(callback, uuids, logger);
402 
403                         HealthConnectThreadScheduler.scheduleInternalTask(
404                                 () -> postInsertTasks(attributionSource, recordsParcel));
405 
406                         logRecordTypeSpecificUpsertMetrics(
407                                 recordInternals, attributionSource.getPackageName());
408                         logger.setDataTypesFromRecordInternals(recordInternals);
409                     } catch (SQLiteException sqLiteException) {
410                         logger.setHealthDataServiceApiStatusError(HealthConnectException.ERROR_IO);
411                         Slog.e(TAG, "SQLiteException: ", sqLiteException);
412                         tryAndThrowException(
413                                 callback, sqLiteException, HealthConnectException.ERROR_IO);
414                     } catch (SecurityException securityException) {
415                         logger.setHealthDataServiceApiStatusError(ERROR_SECURITY);
416                         Slog.e(TAG, "SecurityException: ", securityException);
417                         tryAndThrowException(callback, securityException, ERROR_SECURITY);
418                     } catch (HealthConnectException healthConnectException) {
419                         logger.setHealthDataServiceApiStatusError(
420                                 healthConnectException.getErrorCode());
421                         Slog.e(TAG, "HealthConnectException: ", healthConnectException);
422                         tryAndThrowException(
423                                 callback,
424                                 healthConnectException,
425                                 healthConnectException.getErrorCode());
426                     } catch (Exception e) {
427                         logger.setHealthDataServiceApiStatusError(ERROR_INTERNAL);
428                         Slog.e(TAG, "Exception: ", e);
429                         tryAndThrowException(callback, e, ERROR_INTERNAL);
430                     } finally {
431                         logger.build().log();
432                     }
433                 },
434                 uid,
435                 false);
436     }
437 
postInsertTasks( @onNull AttributionSource attributionSource, @NonNull RecordsParcel recordsParcel)438     private void postInsertTasks(
439             @NonNull AttributionSource attributionSource, @NonNull RecordsParcel recordsParcel) {
440         ActivityDateHelper.insertRecordDate(recordsParcel.getRecords());
441         Set<Integer> recordsTypesInsertedSet =
442                 recordsParcel.getRecords().stream()
443                         .map(RecordInternal::getRecordType)
444                         .collect(Collectors.toSet());
445         // Update AppInfo table with the record types of records inserted in the request for the
446         // current package.
447         mAppInfoHelper.updateAppInfoRecordTypesUsedOnInsert(
448                 recordsTypesInsertedSet, attributionSource.getPackageName());
449     }
450 
451     /**
452      * Returns aggregation results based on the {@code request} into the HealthConnect database.
453      *
454      * @param request represents the request using which the aggregation is to be performed.
455      * @param callback Callback to receive result of performing this operation.
456      */
aggregateRecords( @onNull AttributionSource attributionSource, @NonNull AggregateDataRequestParcel request, @NonNull IAggregateRecordsResponseCallback callback)457     public void aggregateRecords(
458             @NonNull AttributionSource attributionSource,
459             @NonNull AggregateDataRequestParcel request,
460             @NonNull IAggregateRecordsResponseCallback callback) {
461         checkParamsNonNull(attributionSource, request, callback);
462 
463         final int uid = Binder.getCallingUid();
464         final int pid = Binder.getCallingPid();
465         final UserHandle userHandle = Binder.getCallingUserHandle();
466         final boolean holdsDataManagementPermission = hasDataManagementPermission(uid, pid);
467         final HealthConnectServiceLogger.Builder logger =
468                 new HealthConnectServiceLogger.Builder(
469                                 holdsDataManagementPermission, READ_AGGREGATED_DATA)
470                         .setPackageName(attributionSource.getPackageName());
471 
472         HealthConnectThreadScheduler.schedule(
473                 mContext,
474                 () -> {
475                     try {
476                         enforceIsForegroundUser(userHandle);
477                         verifyPackageNameFromUid(uid, attributionSource);
478                         logger.setNumberOfRecords(request.getAggregateIds().length);
479                         throwExceptionIfDataSyncInProgress();
480                         List<Integer> recordTypesToTest = new ArrayList<>();
481                         for (int aggregateId : request.getAggregateIds()) {
482                             recordTypesToTest.addAll(
483                                     mAggregationTypeIdMapper
484                                             .getAggregationTypeFor(aggregateId)
485                                             .getApplicableRecordTypeIds());
486                         }
487 
488                         long startDateAccess = request.getStartTime();
489                         // TODO(b/309776578): Consider making background reads possible for
490                         // aggregations when only using own data
491                         if (!holdsDataManagementPermission) {
492                             boolean isInForeground = mAppOpsManagerLocal.isUidInForeground(uid);
493                             logger.setCallerForegroundState(isInForeground);
494 
495                             if (!isInForeground) {
496                                 mDataPermissionEnforcer.enforceBackgroundReadRestrictions(
497                                         uid,
498                                         pid,
499                                         /* errorMessage= */ attributionSource.getPackageName()
500                                                 + "must be in foreground to call aggregate method");
501                             }
502                             tryAcquireApiCallQuota(
503                                     uid,
504                                     RateLimiter.QuotaCategory.QUOTA_CATEGORY_READ,
505                                     isInForeground,
506                                     logger);
507                             boolean enforceSelfRead =
508                                     mDataPermissionEnforcer.enforceReadAccessAndGetEnforceSelfRead(
509                                             recordTypesToTest, attributionSource);
510                             if (!hasReadHistoryPermission(uid, pid)) {
511                                 startDateAccess =
512                                         mPermissionHelper
513                                                 .getHealthDataStartDateAccessOrThrow(
514                                                         attributionSource.getPackageName(),
515                                                         userHandle)
516                                                 .toEpochMilli();
517                             }
518                             maybeEnforceOnlyCallingPackageDataRequested(
519                                     request.getPackageFilters(),
520                                     attributionSource.getPackageName(),
521                                     enforceSelfRead,
522                                     "aggregationTypes: "
523                                             + Arrays.stream(request.getAggregateIds())
524                                                     .mapToObj(
525                                                             mAggregationTypeIdMapper
526                                                                     ::getAggregationTypeFor)
527                                                     .collect(Collectors.toList()));
528                         }
529                         callback.onResult(
530                                 new AggregateTransactionRequest(
531                                                 attributionSource.getPackageName(),
532                                                 request,
533                                                 startDateAccess)
534                                         .getAggregateDataResponseParcel());
535                         logger.setDataTypesFromRecordTypes(recordTypesToTest)
536                                 .setHealthDataServiceApiStatusSuccess();
537                     } catch (SQLiteException sqLiteException) {
538                         logger.setHealthDataServiceApiStatusError(HealthConnectException.ERROR_IO);
539                         Slog.e(TAG, "SQLiteException: ", sqLiteException);
540                         tryAndThrowException(
541                                 callback, sqLiteException, HealthConnectException.ERROR_IO);
542                     } catch (SecurityException securityException) {
543                         logger.setHealthDataServiceApiStatusError(ERROR_SECURITY);
544                         Slog.e(TAG, "SecurityException: ", securityException);
545                         tryAndThrowException(callback, securityException, ERROR_SECURITY);
546                     } catch (HealthConnectException healthConnectException) {
547                         logger.setHealthDataServiceApiStatusError(
548                                 healthConnectException.getErrorCode());
549                         Slog.e(TAG, "HealthConnectException: ", healthConnectException);
550                         tryAndThrowException(
551                                 callback,
552                                 healthConnectException,
553                                 healthConnectException.getErrorCode());
554                     } catch (Exception e) {
555                         logger.setHealthDataServiceApiStatusError(ERROR_INTERNAL);
556                         Slog.e(TAG, "Exception: ", e);
557                         tryAndThrowException(callback, e, ERROR_INTERNAL);
558                     } finally {
559                         logger.build().log();
560                     }
561                 },
562                 uid,
563                 holdsDataManagementPermission);
564     }
565 
566     /**
567      * Read records {@code recordsParcel} from HealthConnect database.
568      *
569      * @param request ReadRecordsRequestParcel is parcel for the request object containing {@link
570      *     RecordIdFiltersParcel}.
571      * @param callback Callback to receive result of performing this operation. The records are
572      *     returned in {@link RecordsParcel} . In case of an error or a permission failure the
573      *     HealthConnect service, {@link IReadRecordsResponseCallback#onError} will be invoked with
574      *     a {@link HealthConnectExceptionParcel}.
575      */
576     @Override
readRecords( @onNull AttributionSource attributionSource, @NonNull ReadRecordsRequestParcel request, @NonNull IReadRecordsResponseCallback callback)577     public void readRecords(
578             @NonNull AttributionSource attributionSource,
579             @NonNull ReadRecordsRequestParcel request,
580             @NonNull IReadRecordsResponseCallback callback) {
581         checkParamsNonNull(attributionSource, request, callback);
582 
583         final int uid = Binder.getCallingUid();
584         final int pid = Binder.getCallingPid();
585         final UserHandle userHandle = Binder.getCallingUserHandle();
586         final boolean holdsDataManagementPermission = hasDataManagementPermission(uid, pid);
587         final String callingPackageName =
588                 Objects.requireNonNull(attributionSource.getPackageName());
589         final HealthConnectServiceLogger.Builder logger =
590                 new HealthConnectServiceLogger.Builder(holdsDataManagementPermission, READ_DATA)
591                         .setPackageName(callingPackageName);
592 
593         HealthConnectThreadScheduler.schedule(
594                 mContext,
595                 () -> {
596                     try {
597                         enforceIsForegroundUser(userHandle);
598                         verifyPackageNameFromUid(uid, attributionSource);
599                         throwExceptionIfDataSyncInProgress();
600 
601                         boolean enforceSelfRead = false;
602 
603                         final boolean isInForeground = mAppOpsManagerLocal.isUidInForeground(uid);
604 
605                         if (!holdsDataManagementPermission) {
606                             logger.setCallerForegroundState(isInForeground);
607 
608                             tryAcquireApiCallQuota(
609                                     uid, QuotaCategory.QUOTA_CATEGORY_READ, isInForeground, logger);
610 
611                             if (mDataPermissionEnforcer.enforceReadAccessAndGetEnforceSelfRead(
612                                     request.getRecordType(), attributionSource)) {
613                                 // If read permission is missing but write permission is granted,
614                                 // then enforce self read
615                                 enforceSelfRead = true;
616                             } else if (!isInForeground) {
617                                 // If Background Read feature is disabled
618                                 // or READ_HEALTH_DATA_IN_BACKGROUND permission is not granted,
619                                 // then enforce self read
620                                 enforceSelfRead = isOnlySelfReadInBackgroundAllowed(uid, pid);
621                             }
622                             if (request.getRecordIdFiltersParcel() == null) {
623                                 // Only enforce requested packages if this is a
624                                 // ReadRecordsByRequest using filters. Reading by IDs does not have
625                                 // data origins specified.
626                                 // TODO(b/309778116): Consider throwing an error when reading by Id
627                                 maybeEnforceOnlyCallingPackageDataRequested(
628                                         request.getPackageFilters(),
629                                         callingPackageName,
630                                         enforceSelfRead,
631                                         "recordType: "
632                                                 + mRecordMapper
633                                                         .getRecordIdToExternalRecordClassMap()
634                                                         .get(request.getRecordType()));
635                             }
636 
637                             if (Constants.DEBUG) {
638                                 Slog.d(
639                                         TAG,
640                                         "Enforce self read for package "
641                                                 + callingPackageName
642                                                 + ":"
643                                                 + enforceSelfRead);
644                             }
645                         }
646                         final Set<String> grantedExtraReadPermissions =
647                                 mDataPermissionEnforcer.collectGrantedExtraReadPermissions(
648                                         Set.of(request.getRecordType()), attributionSource);
649 
650                         try {
651                             long startDateAccessEpochMilli = request.getStartTime();
652 
653                             if (!holdsDataManagementPermission
654                                     && !hasReadHistoryPermission(uid, pid)) {
655                                 Instant startDateAccessInstant =
656                                         mPermissionHelper.getHealthDataStartDateAccessOrThrow(
657                                                 callingPackageName, userHandle);
658 
659                                 // Always set the startDateAccess for local time filter, as for
660                                 // local date time we use it in conjunction with the time filter
661                                 // start-time
662                                 if (request.usesLocalTimeFilter()
663                                         || startDateAccessInstant.toEpochMilli()
664                                                 > startDateAccessEpochMilli) {
665                                     startDateAccessEpochMilli =
666                                             startDateAccessInstant.toEpochMilli();
667                                 }
668                             }
669 
670                             ReadTransactionRequest readTransactionRequest =
671                                     new ReadTransactionRequest(
672                                             callingPackageName,
673                                             request,
674                                             startDateAccessEpochMilli,
675                                             enforceSelfRead,
676                                             grantedExtraReadPermissions,
677                                             isInForeground);
678                             // throw an exception if read requested is not for a single record type
679                             // i.e. size of read table request is not equal to 1.
680                             if (readTransactionRequest.getReadRequests().size() != 1) {
681                                 throw new IllegalArgumentException(
682                                         "Read requested is not for a single record type");
683                             }
684 
685                             List<RecordInternal<?>> records;
686                             long pageToken;
687                             if (request.getRecordIdFiltersParcel() != null) {
688                                 records =
689                                         mTransactionManager.readRecordsByIds(
690                                                 readTransactionRequest);
691                                 pageToken = DEFAULT_LONG;
692                             } else {
693                                 Pair<List<RecordInternal<?>>, PageTokenWrapper>
694                                         readRecordsResponse =
695                                                 mTransactionManager.readRecordsAndPageToken(
696                                                         readTransactionRequest);
697                                 records = readRecordsResponse.first;
698                                 pageToken = readRecordsResponse.second.encode();
699                             }
700                             logger.setNumberOfRecords(records.size());
701 
702                             if (Constants.DEBUG) {
703                                 Slog.d(TAG, "pageToken: " + pageToken);
704                             }
705 
706                             final List<Integer> recordTypes =
707                                     Collections.singletonList(request.getRecordType());
708                             // Calls from controller APK should not be recorded in access logs
709                             // If an app is reading only its own data then it is not recorded in
710                             // access logs.
711                             boolean requiresLogging =
712                                     !holdsDataManagementPermission && !enforceSelfRead;
713                             if (requiresLogging) {
714                                 AccessLogsHelper.addAccessLog(
715                                         callingPackageName, recordTypes, READ);
716                             }
717                             callback.onResult(
718                                     new ReadRecordsResponseParcel(
719                                             new RecordsParcel(records), pageToken));
720                             if (requiresLogging) {
721                                 logRecordTypeSpecificReadMetrics(records, callingPackageName);
722                             }
723                             logger.setDataTypesFromRecordInternals(records)
724                                     .setHealthDataServiceApiStatusSuccess();
725                         } catch (TypeNotPresentException exception) {
726                             // All the requested package names are not present, so simply
727                             // return an empty list
728                             if (ReadTransactionRequest.TYPE_NOT_PRESENT_PACKAGE_NAME.equals(
729                                     exception.typeName())) {
730                                 if (Constants.DEBUG) {
731                                     Slog.d(TAG, "No app info recorded for " + callingPackageName);
732                                 }
733                                 callback.onResult(
734                                         new ReadRecordsResponseParcel(
735                                                 new RecordsParcel(new ArrayList<>()),
736                                                 DEFAULT_LONG));
737                                 logger.setHealthDataServiceApiStatusSuccess();
738                             } else {
739                                 logger.setHealthDataServiceApiStatusError(
740                                         HealthConnectException.ERROR_UNKNOWN);
741                                 throw exception;
742                             }
743                         }
744                     } catch (SQLiteException sqLiteException) {
745                         logger.setHealthDataServiceApiStatusError(HealthConnectException.ERROR_IO);
746                         Slog.e(TAG, "SQLiteException: ", sqLiteException);
747                         tryAndThrowException(
748                                 callback, sqLiteException, HealthConnectException.ERROR_IO);
749                     } catch (SecurityException securityException) {
750                         logger.setHealthDataServiceApiStatusError(ERROR_SECURITY);
751                         Slog.e(TAG, "SecurityException: ", securityException);
752                         tryAndThrowException(callback, securityException, ERROR_SECURITY);
753                     } catch (IllegalStateException illegalStateException) {
754                         logger.setHealthDataServiceApiStatusError(ERROR_INTERNAL);
755                         Slog.e(TAG, "IllegalStateException: ", illegalStateException);
756                         tryAndThrowException(callback, illegalStateException, ERROR_INTERNAL);
757                     } catch (HealthConnectException healthConnectException) {
758                         logger.setHealthDataServiceApiStatusError(
759                                 healthConnectException.getErrorCode());
760                         Slog.e(TAG, "HealthConnectException: ", healthConnectException);
761                         tryAndThrowException(
762                                 callback,
763                                 healthConnectException,
764                                 healthConnectException.getErrorCode());
765                     } catch (Exception e) {
766                         logger.setHealthDataServiceApiStatusError(ERROR_INTERNAL);
767                         Slog.e(TAG, "Exception: ", e);
768                         tryAndThrowException(callback, e, ERROR_INTERNAL);
769                     } finally {
770                         logger.build().log();
771                     }
772                 },
773                 uid,
774                 holdsDataManagementPermission);
775     }
776 
maybeEnforceOnlyCallingPackageDataRequested( List<String> packageFilters, String callingPackageName, boolean enforceSelfRead, String entityFailureMessage)777     private void maybeEnforceOnlyCallingPackageDataRequested(
778             List<String> packageFilters,
779             String callingPackageName,
780             boolean enforceSelfRead,
781             String entityFailureMessage) {
782         if (enforceSelfRead
783                 && (packageFilters.size() != 1
784                         || !packageFilters.get(0).equals(callingPackageName))) {
785             throwSecurityException(
786                     "Caller does not have permission to read data for the following ("
787                             + entityFailureMessage
788                             + ") from other applications.");
789         }
790     }
791 
792     /**
793      * Updates {@code recordsParcel} into the HealthConnect database.
794      *
795      * @param recordsParcel parcel for list of records to be updated.
796      * @param callback Callback to receive result of performing this operation. In case of an error
797      *     or a permission failure the HealthConnect service, {@link IEmptyResponseCallback#onError}
798      *     will be invoked with a {@link HealthConnectException}.
799      */
800     @Override
updateRecords( @onNull AttributionSource attributionSource, @NonNull RecordsParcel recordsParcel, @NonNull IEmptyResponseCallback callback)801     public void updateRecords(
802             @NonNull AttributionSource attributionSource,
803             @NonNull RecordsParcel recordsParcel,
804             @NonNull IEmptyResponseCallback callback) {
805         checkParamsNonNull(attributionSource, recordsParcel, callback);
806 
807         final int uid = Binder.getCallingUid();
808         final int pid = Binder.getCallingPid();
809         final UserHandle userHandle = Binder.getCallingUserHandle();
810         final HealthConnectServiceLogger.Builder logger =
811                 new HealthConnectServiceLogger.Builder(false, UPDATE_DATA)
812                         .setPackageName(attributionSource.getPackageName());
813         HealthConnectThreadScheduler.schedule(
814                 mContext,
815                 () -> {
816                     try {
817                         enforceIsForegroundUser(userHandle);
818                         verifyPackageNameFromUid(uid, attributionSource);
819                         if (hasDataManagementPermission(uid, pid)) {
820                             throw new SecurityException(
821                                     "Apps with android.permission.MANAGE_HEALTH_DATA permission are"
822                                             + " not allowed to insert records");
823                         }
824                         enforceMemoryRateLimit(
825                                 recordsParcel.getRecordsSize(),
826                                 recordsParcel.getRecordsChunkSize());
827                         final List<RecordInternal<?>> recordInternals = recordsParcel.getRecords();
828                         logger.setNumberOfRecords(recordInternals.size());
829                         throwExceptionIfDataSyncInProgress();
830                         boolean isInForeground = mAppOpsManagerLocal.isUidInForeground(uid);
831                         tryAcquireApiCallQuota(
832                                 uid,
833                                 QuotaCategory.QUOTA_CATEGORY_WRITE,
834                                 isInForeground,
835                                 logger,
836                                 recordsParcel.getRecordsChunkSize());
837                         mDataPermissionEnforcer.enforceRecordsWritePermissions(
838                                 recordInternals, attributionSource);
839                         UpsertTransactionRequest request =
840                                 new UpsertTransactionRequest(
841                                         attributionSource.getPackageName(),
842                                         recordInternals,
843                                         mContext,
844                                         /* isInsertRequest */ false,
845                                         mDataPermissionEnforcer
846                                                 .collectExtraWritePermissionStateMapping(
847                                                         recordInternals, attributionSource));
848                         mTransactionManager.updateAll(request);
849                         tryAndReturnResult(callback, logger);
850                         logRecordTypeSpecificUpsertMetrics(
851                                 recordInternals, attributionSource.getPackageName());
852                         logger.setDataTypesFromRecordInternals(recordInternals);
853                         // Update activity dates table
854                         HealthConnectThreadScheduler.scheduleInternalTask(
855                                 () ->
856                                         ActivityDateHelper.reSyncByRecordTypeIds(
857                                                 recordInternals.stream()
858                                                         .map(RecordInternal::getRecordType)
859                                                         .toList()));
860                     } catch (SecurityException securityException) {
861                         logger.setHealthDataServiceApiStatusError(ERROR_SECURITY);
862                         tryAndThrowException(callback, securityException, ERROR_SECURITY);
863                     } catch (SQLiteException sqLiteException) {
864                         logger.setHealthDataServiceApiStatusError(HealthConnectException.ERROR_IO);
865                         Slog.e(TAG, "SqlException: ", sqLiteException);
866                         tryAndThrowException(
867                                 callback, sqLiteException, HealthConnectException.ERROR_IO);
868                     } catch (IllegalArgumentException illegalArgumentException) {
869                         logger.setHealthDataServiceApiStatusError(
870                                 HealthConnectException.ERROR_INVALID_ARGUMENT);
871 
872                         Slog.e(TAG, "IllegalArgumentException: ", illegalArgumentException);
873                         tryAndThrowException(
874                                 callback,
875                                 illegalArgumentException,
876                                 HealthConnectException.ERROR_INVALID_ARGUMENT);
877                     } catch (HealthConnectException healthConnectException) {
878                         logger.setHealthDataServiceApiStatusError(
879                                 healthConnectException.getErrorCode());
880                         Slog.e(TAG, "HealthConnectException: ", healthConnectException);
881                         tryAndThrowException(
882                                 callback,
883                                 healthConnectException,
884                                 healthConnectException.getErrorCode());
885                     } catch (Exception e) {
886                         logger.setHealthDataServiceApiStatusError(ERROR_INTERNAL);
887 
888                         Slog.e(TAG, "Exception: ", e);
889                         tryAndThrowException(callback, e, ERROR_INTERNAL);
890                     } finally {
891                         logger.build().log();
892                     }
893                 },
894                 uid,
895                 false);
896     }
897 
898     /**
899      * @see HealthConnectManager#getChangeLogToken
900      */
901     @Override
getChangeLogToken( @onNull AttributionSource attributionSource, @NonNull ChangeLogTokenRequest request, @NonNull IGetChangeLogTokenCallback callback)902     public void getChangeLogToken(
903             @NonNull AttributionSource attributionSource,
904             @NonNull ChangeLogTokenRequest request,
905             @NonNull IGetChangeLogTokenCallback callback) {
906         checkParamsNonNull(attributionSource, request, callback);
907 
908         final int uid = Binder.getCallingUid();
909         final UserHandle userHandle = Binder.getCallingUserHandle();
910         final HealthConnectServiceLogger.Builder logger =
911                 new HealthConnectServiceLogger.Builder(false, GET_CHANGES_TOKEN)
912                         .setPackageName(attributionSource.getPackageName());
913         HealthConnectThreadScheduler.schedule(
914                 mContext,
915                 () -> {
916                     try {
917                         enforceIsForegroundUser(userHandle);
918                         verifyPackageNameFromUid(uid, attributionSource);
919                         tryAcquireApiCallQuota(
920                                 uid,
921                                 QuotaCategory.QUOTA_CATEGORY_READ,
922                                 mAppOpsManagerLocal.isUidInForeground(uid),
923                                 logger);
924                         throwExceptionIfDataSyncInProgress();
925                         if (request.getRecordTypes().isEmpty()) {
926                             throw new IllegalArgumentException(
927                                     "Requested record types must not be empty.");
928                         }
929                         mDataPermissionEnforcer.enforceRecordIdsReadPermissions(
930                                 request.getRecordTypesList(), attributionSource);
931                         callback.onResult(
932                                 new ChangeLogTokenResponse(
933                                         ChangeLogsRequestHelper.getToken(
934                                                 attributionSource.getPackageName(), request)));
935                         logger.setHealthDataServiceApiStatusSuccess();
936                     } catch (SQLiteException sqLiteException) {
937                         logger.setHealthDataServiceApiStatusError(HealthConnectException.ERROR_IO);
938                         Slog.e(TAG, "SQLiteException: ", sqLiteException);
939                         tryAndThrowException(
940                                 callback, sqLiteException, HealthConnectException.ERROR_IO);
941                     } catch (SecurityException securityException) {
942                         logger.setHealthDataServiceApiStatusError(ERROR_SECURITY);
943                         Slog.e(TAG, "SecurityException: ", securityException);
944                         tryAndThrowException(callback, securityException, ERROR_SECURITY);
945                     } catch (IllegalArgumentException illegalArgumentException) {
946                         logger.setHealthDataServiceApiStatusError(
947                                 HealthConnectException.ERROR_INVALID_ARGUMENT);
948                         Slog.e(TAG, "IllegalArgumentException: ", illegalArgumentException);
949                         tryAndThrowException(
950                                 callback,
951                                 illegalArgumentException,
952                                 HealthConnectException.ERROR_INVALID_ARGUMENT);
953                     } catch (HealthConnectException healthConnectException) {
954                         logger.setHealthDataServiceApiStatusError(
955                                 healthConnectException.getErrorCode());
956                         Slog.e(TAG, "HealthConnectException: ", healthConnectException);
957                         tryAndThrowException(
958                                 callback,
959                                 healthConnectException,
960                                 healthConnectException.getErrorCode());
961                     } catch (Exception e) {
962                         logger.setHealthDataServiceApiStatusError(ERROR_INTERNAL);
963                         tryAndThrowException(callback, e, ERROR_INTERNAL);
964                     } finally {
965                         logger.build().log();
966                     }
967                 },
968                 uid,
969                 false);
970     }
971 
972     /**
973      * @hide
974      * @see HealthConnectManager#getChangeLogs
975      */
976     @Override
getChangeLogs( @onNull AttributionSource attributionSource, @NonNull ChangeLogsRequest request, @NonNull IChangeLogsResponseCallback callback)977     public void getChangeLogs(
978             @NonNull AttributionSource attributionSource,
979             @NonNull ChangeLogsRequest request,
980             @NonNull IChangeLogsResponseCallback callback) {
981         checkParamsNonNull(attributionSource, request, callback);
982 
983         final int uid = Binder.getCallingUid();
984         final int pid = Binder.getCallingPid();
985         final UserHandle userHandle = Binder.getCallingUserHandle();
986         final String callerPackageName = Objects.requireNonNull(attributionSource.getPackageName());
987         final HealthConnectServiceLogger.Builder logger =
988                 new HealthConnectServiceLogger.Builder(false, GET_CHANGES)
989                         .setPackageName(callerPackageName);
990 
991         HealthConnectThreadScheduler.schedule(
992                 mContext,
993                 () -> {
994                     try {
995                         enforceIsForegroundUser(userHandle);
996                         verifyPackageNameFromUid(uid, attributionSource);
997                         throwExceptionIfDataSyncInProgress();
998 
999                         boolean isInForeground = mAppOpsManagerLocal.isUidInForeground(uid);
1000                         logger.setCallerForegroundState(isInForeground);
1001 
1002                         if (!isInForeground) {
1003                             mDataPermissionEnforcer.enforceBackgroundReadRestrictions(
1004                                     uid,
1005                                     pid,
1006                                     /* errorMessage= */ callerPackageName
1007                                             + "must be in foreground to call getChangeLogs method");
1008                         }
1009 
1010                         ChangeLogsRequestHelper.TokenRequest changeLogsTokenRequest =
1011                                 ChangeLogsRequestHelper.getRequest(
1012                                         callerPackageName, request.getToken());
1013                         tryAcquireApiCallQuota(
1014                                 uid, QuotaCategory.QUOTA_CATEGORY_READ, isInForeground, logger);
1015                         if (changeLogsTokenRequest.getRecordTypes().isEmpty()) {
1016                             throw new IllegalArgumentException(
1017                                     "Requested record types must not be empty.");
1018                         }
1019                         mDataPermissionEnforcer.enforceRecordIdsReadPermissions(
1020                                 changeLogsTokenRequest.getRecordTypes(), attributionSource);
1021                         long startDateAccessEpochMilli = DEFAULT_LONG;
1022                         if (!hasReadHistoryPermission(uid, pid)) {
1023                             startDateAccessEpochMilli =
1024                                     mPermissionHelper
1025                                             .getHealthDataStartDateAccessOrThrow(
1026                                                     callerPackageName, userHandle)
1027                                             .toEpochMilli();
1028                         }
1029                         final ChangeLogsHelper.ChangeLogsResponse changeLogsResponse =
1030                                 ChangeLogsHelper.getChangeLogs(changeLogsTokenRequest, request);
1031 
1032                         Map<Integer, List<UUID>> recordTypeToInsertedUuids =
1033                                 ChangeLogsHelper.getRecordTypeToInsertedUuids(
1034                                         changeLogsResponse.getChangeLogsMap());
1035 
1036                         Set<String> grantedExtraReadPermissions =
1037                                 mDataPermissionEnforcer.collectGrantedExtraReadPermissions(
1038                                         recordTypeToInsertedUuids.keySet(), attributionSource);
1039 
1040                         List<RecordInternal<?>> recordInternals =
1041                                 mTransactionManager.readRecordsByIds(
1042                                         new ReadTransactionRequest(
1043                                                 callerPackageName,
1044                                                 recordTypeToInsertedUuids,
1045                                                 startDateAccessEpochMilli,
1046                                                 grantedExtraReadPermissions,
1047                                                 isInForeground));
1048 
1049                         List<DeletedLog> deletedLogs =
1050                                 ChangeLogsHelper.getDeletedLogs(
1051                                         changeLogsResponse.getChangeLogsMap());
1052 
1053                         callback.onResult(
1054                                 new ChangeLogsResponse(
1055                                         new RecordsParcel(recordInternals),
1056                                         deletedLogs,
1057                                         changeLogsResponse.getNextPageToken(),
1058                                         changeLogsResponse.hasMorePages()));
1059                         logger.setHealthDataServiceApiStatusSuccess()
1060                                 .setNumberOfRecords(recordInternals.size() + deletedLogs.size())
1061                                 .setDataTypesFromRecordInternals(recordInternals);
1062                     } catch (IllegalArgumentException illegalArgumentException) {
1063                         logger.setHealthDataServiceApiStatusError(
1064                                 HealthConnectException.ERROR_INVALID_ARGUMENT);
1065                         Slog.e(TAG, "IllegalArgumentException: ", illegalArgumentException);
1066                         tryAndThrowException(
1067                                 callback,
1068                                 illegalArgumentException,
1069                                 HealthConnectException.ERROR_INVALID_ARGUMENT);
1070                     } catch (SQLiteException sqLiteException) {
1071                         logger.setHealthDataServiceApiStatusError(HealthConnectException.ERROR_IO);
1072                         Slog.e(TAG, "SQLiteException: ", sqLiteException);
1073                         tryAndThrowException(
1074                                 callback, sqLiteException, HealthConnectException.ERROR_IO);
1075                     } catch (SecurityException securityException) {
1076                         logger.setHealthDataServiceApiStatusError(ERROR_SECURITY);
1077                         Slog.e(TAG, "SecurityException: ", securityException);
1078                         tryAndThrowException(callback, securityException, ERROR_SECURITY);
1079                     } catch (IllegalStateException illegalStateException) {
1080                         logger.setHealthDataServiceApiStatusError(ERROR_INTERNAL);
1081                         Slog.e(TAG, "IllegalStateException: ", illegalStateException);
1082                         tryAndThrowException(callback, illegalStateException, ERROR_INTERNAL);
1083                     } catch (HealthConnectException healthConnectException) {
1084                         logger.setHealthDataServiceApiStatusError(
1085                                 healthConnectException.getErrorCode());
1086                         Slog.e(TAG, "HealthConnectException: ", healthConnectException);
1087                         tryAndThrowException(
1088                                 callback,
1089                                 healthConnectException,
1090                                 healthConnectException.getErrorCode());
1091                     } catch (Exception exception) {
1092                         logger.setHealthDataServiceApiStatusError(ERROR_INTERNAL);
1093                         Slog.e(TAG, "Exception: ", exception);
1094                         tryAndThrowException(callback, exception, ERROR_INTERNAL);
1095                     } finally {
1096                         logger.build().log();
1097                     }
1098                 },
1099                 uid,
1100                 false);
1101     }
1102 
1103     /**
1104      * API to delete records based on {@code request}
1105      *
1106      * <p>NOTE: Though internally we only need a single API to handle deletes as SDK code transform
1107      * all its delete requests to {@link DeleteUsingFiltersRequestParcel}, we have this separation
1108      * to make sure no non-controller APIs can use {@link
1109      * HealthConnectServiceImpl#deleteUsingFilters} API
1110      */
1111     @Override
deleteUsingFiltersForSelf( @onNull AttributionSource attributionSource, @NonNull DeleteUsingFiltersRequestParcel request, @NonNull IEmptyResponseCallback callback)1112     public void deleteUsingFiltersForSelf(
1113             @NonNull AttributionSource attributionSource,
1114             @NonNull DeleteUsingFiltersRequestParcel request,
1115             @NonNull IEmptyResponseCallback callback) {
1116         checkParamsNonNull(attributionSource, request, callback);
1117 
1118         final int uid = Binder.getCallingUid();
1119         final int pid = Binder.getCallingPid();
1120         final UserHandle userHandle = Binder.getCallingUserHandle();
1121         final boolean holdsDataManagementPermission = hasDataManagementPermission(uid, pid);
1122         final HealthConnectServiceLogger.Builder logger =
1123                 new HealthConnectServiceLogger.Builder(holdsDataManagementPermission, DELETE_DATA)
1124                         .setPackageName(attributionSource.getPackageName());
1125 
1126         HealthConnectThreadScheduler.schedule(
1127                 mContext,
1128                 () -> {
1129                     try {
1130                         enforceIsForegroundUser(userHandle);
1131                         verifyPackageNameFromUid(uid, attributionSource);
1132                         throwExceptionIfDataSyncInProgress();
1133                         List<Integer> recordTypeIdsToDelete =
1134                                 (!request.getRecordTypeFilters().isEmpty())
1135                                         ? request.getRecordTypeFilters()
1136                                         : new ArrayList<>(
1137                                                 mRecordMapper
1138                                                         .getRecordIdToExternalRecordClassMap()
1139                                                         .keySet());
1140                         // Requests from non controller apps are not allowed to use non-id
1141                         // filters
1142                         request.setPackageNameFilters(
1143                                 Collections.singletonList(attributionSource.getPackageName()));
1144 
1145                         if (!holdsDataManagementPermission) {
1146                             tryAcquireApiCallQuota(
1147                                     uid,
1148                                     QuotaCategory.QUOTA_CATEGORY_WRITE,
1149                                     mAppOpsManagerLocal.isUidInForeground(uid),
1150                                     logger);
1151                             mDataPermissionEnforcer.enforceRecordIdsWritePermissions(
1152                                     recordTypeIdsToDelete, attributionSource);
1153                         }
1154 
1155                         deleteUsingFiltersInternal(
1156                                 attributionSource,
1157                                 request,
1158                                 callback,
1159                                 logger,
1160                                 recordTypeIdsToDelete,
1161                                 uid,
1162                                 pid);
1163                     } catch (SQLiteException sqLiteException) {
1164                         logger.setHealthDataServiceApiStatusError(HealthConnectException.ERROR_IO);
1165                         tryAndThrowException(
1166                                 callback, sqLiteException, HealthConnectException.ERROR_IO);
1167                     } catch (IllegalArgumentException illegalArgumentException) {
1168                         logger.setHealthDataServiceApiStatusError(
1169                                 HealthConnectException.ERROR_INVALID_ARGUMENT);
1170                         Slog.e(TAG, "IllegalArgumentException: ", illegalArgumentException);
1171                         tryAndThrowException(
1172                                 callback,
1173                                 illegalArgumentException,
1174                                 HealthConnectException.ERROR_INVALID_ARGUMENT);
1175                     } catch (SecurityException securityException) {
1176                         logger.setHealthDataServiceApiStatusError(ERROR_SECURITY);
1177                         Slog.e(TAG, "SecurityException: ", securityException);
1178                         tryAndThrowException(callback, securityException, ERROR_SECURITY);
1179                     } catch (HealthConnectException healthConnectException) {
1180                         logger.setHealthDataServiceApiStatusError(
1181                                 healthConnectException.getErrorCode());
1182                         Slog.e(TAG, "HealthConnectException: ", healthConnectException);
1183                         tryAndThrowException(
1184                                 callback,
1185                                 healthConnectException,
1186                                 healthConnectException.getErrorCode());
1187                     } catch (Exception exception) {
1188                         logger.setHealthDataServiceApiStatusError(ERROR_INTERNAL);
1189                         Slog.e(TAG, "Exception: ", exception);
1190                         tryAndThrowException(callback, exception, ERROR_INTERNAL);
1191                     } finally {
1192                         logger.build().log();
1193                     }
1194                 },
1195                 uid,
1196                 holdsDataManagementPermission);
1197     }
1198 
1199     /**
1200      * API to delete records based on {@code request}
1201      *
1202      * <p>NOTE: Though internally we only need a single API to handle deletes as SDK code transform
1203      * all its delete requests to {@link DeleteUsingFiltersRequestParcel}, we have this separation
1204      * to make sure no non-controller APIs can use this API
1205      */
1206     @Override
deleteUsingFilters( @onNull AttributionSource attributionSource, @NonNull DeleteUsingFiltersRequestParcel request, @NonNull IEmptyResponseCallback callback)1207     public void deleteUsingFilters(
1208             @NonNull AttributionSource attributionSource,
1209             @NonNull DeleteUsingFiltersRequestParcel request,
1210             @NonNull IEmptyResponseCallback callback) {
1211         checkParamsNonNull(attributionSource, request, callback);
1212 
1213         final int uid = Binder.getCallingUid();
1214         final int pid = Binder.getCallingPid();
1215         final UserHandle userHandle = Binder.getCallingUserHandle();
1216         final boolean holdsDataManagementPermission = hasDataManagementPermission(uid, pid);
1217         final HealthConnectServiceLogger.Builder logger =
1218                 new HealthConnectServiceLogger.Builder(holdsDataManagementPermission, DELETE_DATA)
1219                         .setPackageName(attributionSource.getPackageName());
1220 
1221         HealthConnectThreadScheduler.schedule(
1222                 mContext,
1223                 () -> {
1224                     try {
1225                         enforceIsForegroundUser(userHandle);
1226                         verifyPackageNameFromUid(uid, attributionSource);
1227                         throwExceptionIfDataSyncInProgress();
1228                         mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null);
1229                         List<Integer> recordTypeIdsToDelete =
1230                                 (!request.getRecordTypeFilters().isEmpty())
1231                                         ? request.getRecordTypeFilters()
1232                                         : new ArrayList<>(
1233                                                 mRecordMapper
1234                                                         .getRecordIdToExternalRecordClassMap()
1235                                                         .keySet());
1236 
1237                         deleteUsingFiltersInternal(
1238                                 attributionSource,
1239                                 request,
1240                                 callback,
1241                                 logger,
1242                                 recordTypeIdsToDelete,
1243                                 uid,
1244                                 pid);
1245                     } catch (SQLiteException sqLiteException) {
1246                         logger.setHealthDataServiceApiStatusError(HealthConnectException.ERROR_IO);
1247                         tryAndThrowException(
1248                                 callback, sqLiteException, HealthConnectException.ERROR_IO);
1249                     } catch (IllegalArgumentException illegalArgumentException) {
1250                         logger.setHealthDataServiceApiStatusError(
1251                                 HealthConnectException.ERROR_INVALID_ARGUMENT);
1252                         Slog.e(TAG, "IllegalArgumentException: ", illegalArgumentException);
1253                         tryAndThrowException(
1254                                 callback,
1255                                 illegalArgumentException,
1256                                 HealthConnectException.ERROR_INVALID_ARGUMENT);
1257                     } catch (SecurityException securityException) {
1258                         logger.setHealthDataServiceApiStatusError(ERROR_SECURITY);
1259                         Slog.e(TAG, "SecurityException: ", securityException);
1260                         tryAndThrowException(callback, securityException, ERROR_SECURITY);
1261                     } catch (HealthConnectException healthConnectException) {
1262                         logger.setHealthDataServiceApiStatusError(
1263                                 healthConnectException.getErrorCode());
1264                         Slog.e(TAG, "HealthConnectException: ", healthConnectException);
1265                         tryAndThrowException(
1266                                 callback,
1267                                 healthConnectException,
1268                                 healthConnectException.getErrorCode());
1269                     } catch (Exception exception) {
1270                         logger.setHealthDataServiceApiStatusError(ERROR_INTERNAL);
1271                         Slog.e(TAG, "Exception: ", exception);
1272                         tryAndThrowException(callback, exception, ERROR_INTERNAL);
1273                     } finally {
1274                         logger.build().log();
1275                     }
1276                 },
1277                 uid,
1278                 holdsDataManagementPermission);
1279     }
1280 
deleteUsingFiltersInternal( @onNull AttributionSource attributionSource, @NonNull DeleteUsingFiltersRequestParcel request, @NonNull IEmptyResponseCallback callback, @NonNull HealthConnectServiceLogger.Builder logger, List<Integer> recordTypeIdsToDelete, int uid, int pid)1281     private void deleteUsingFiltersInternal(
1282             @NonNull AttributionSource attributionSource,
1283             @NonNull DeleteUsingFiltersRequestParcel request,
1284             @NonNull IEmptyResponseCallback callback,
1285             @NonNull HealthConnectServiceLogger.Builder logger,
1286             List<Integer> recordTypeIdsToDelete,
1287             int uid,
1288             int pid) {
1289         if (request.usesIdFilters() && request.usesNonIdFilters()) {
1290             throw new IllegalArgumentException(
1291                     "Requests with both id and non-id filters are not" + " supported");
1292         }
1293         int numberOfRecordsDeleted =
1294                 mTransactionManager.deleteAll(
1295                         new DeleteTransactionRequest(attributionSource.getPackageName(), request)
1296                                 .setHasManageHealthDataPermission(
1297                                         hasDataManagementPermission(uid, pid)));
1298         tryAndReturnResult(callback, logger);
1299         HealthConnectThreadScheduler.scheduleInternalTask(
1300                 () -> postDeleteTasks(recordTypeIdsToDelete));
1301 
1302         logger.setNumberOfRecords(numberOfRecordsDeleted)
1303                 .setDataTypesFromRecordTypes(recordTypeIdsToDelete);
1304     }
1305 
1306     /** API to get Priority for {@code dataCategory} */
1307     @Override
getCurrentPriority( @onNull String packageName, @HealthDataCategory.Type int dataCategory, @NonNull IGetPriorityResponseCallback callback)1308     public void getCurrentPriority(
1309             @NonNull String packageName,
1310             @HealthDataCategory.Type int dataCategory,
1311             @NonNull IGetPriorityResponseCallback callback) {
1312         checkParamsNonNull(packageName, callback);
1313 
1314         final int uid = Binder.getCallingUid();
1315         final int pid = Binder.getCallingPid();
1316         final UserHandle userHandle = Binder.getCallingUserHandle();
1317         HealthConnectThreadScheduler.scheduleControllerTask(
1318                 () -> {
1319                     try {
1320                         enforceIsForegroundUser(userHandle);
1321                         mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null);
1322                         throwExceptionIfDataSyncInProgress();
1323                         List<DataOrigin> dataOriginInPriorityOrder =
1324                                 mHealthDataCategoryPriorityHelper
1325                                         .getPriorityOrder(dataCategory, mContext)
1326                                         .stream()
1327                                         .map(
1328                                                 (name) ->
1329                                                         new DataOrigin.Builder()
1330                                                                 .setPackageName(name)
1331                                                                 .build())
1332                                         .collect(Collectors.toList());
1333                         callback.onResult(
1334                                 new GetPriorityResponseParcel(
1335                                         new FetchDataOriginsPriorityOrderResponse(
1336                                                 dataOriginInPriorityOrder)));
1337                     } catch (SQLiteException sqLiteException) {
1338                         Slog.e(TAG, "SQLiteException: ", sqLiteException);
1339                         tryAndThrowException(
1340                                 callback, sqLiteException, HealthConnectException.ERROR_IO);
1341                     } catch (SecurityException securityException) {
1342                         Slog.e(TAG, "SecurityException: ", securityException);
1343                         tryAndThrowException(callback, securityException, ERROR_SECURITY);
1344                     } catch (HealthConnectException healthConnectException) {
1345                         Slog.e(TAG, "HealthConnectException: ", healthConnectException);
1346                         tryAndThrowException(
1347                                 callback,
1348                                 healthConnectException,
1349                                 healthConnectException.getErrorCode());
1350                     } catch (Exception exception) {
1351                         Slog.e(TAG, "Exception: ", exception);
1352                         tryAndThrowException(callback, exception, ERROR_INTERNAL);
1353                     }
1354                 });
1355     }
1356 
1357     /** API to update priority for permission category(ies) */
1358     @Override
updatePriority( @onNull String packageName, @NonNull UpdatePriorityRequestParcel updatePriorityRequest, @NonNull IEmptyResponseCallback callback)1359     public void updatePriority(
1360             @NonNull String packageName,
1361             @NonNull UpdatePriorityRequestParcel updatePriorityRequest,
1362             @NonNull IEmptyResponseCallback callback) {
1363         checkParamsNonNull(packageName, updatePriorityRequest, callback);
1364 
1365         final int uid = Binder.getCallingUid();
1366         final int pid = Binder.getCallingPid();
1367         final UserHandle userHandle = Binder.getCallingUserHandle();
1368         HealthConnectThreadScheduler.scheduleControllerTask(
1369                 () -> {
1370                     try {
1371                         enforceIsForegroundUser(userHandle);
1372                         mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null);
1373                         throwExceptionIfDataSyncInProgress();
1374                         mHealthDataCategoryPriorityHelper.setPriorityOrder(
1375                                 updatePriorityRequest.getDataCategory(),
1376                                 updatePriorityRequest.getPackagePriorityOrder());
1377                         callback.onResult();
1378                     } catch (SQLiteException sqLiteException) {
1379                         Slog.e(TAG, "SQLiteException: ", sqLiteException);
1380                         tryAndThrowException(
1381                                 callback, sqLiteException, HealthConnectException.ERROR_IO);
1382                     } catch (SecurityException securityException) {
1383                         Slog.e(TAG, "SecurityException: ", securityException);
1384                         tryAndThrowException(callback, securityException, ERROR_SECURITY);
1385                     } catch (HealthConnectException healthConnectException) {
1386                         Slog.e(TAG, "HealthConnectException: ", healthConnectException);
1387                         tryAndThrowException(
1388                                 callback,
1389                                 healthConnectException,
1390                                 healthConnectException.getErrorCode());
1391                     } catch (Exception exception) {
1392                         Slog.e(TAG, "Exception: ", exception);
1393                         tryAndThrowException(callback, exception, ERROR_INTERNAL);
1394                     }
1395                 });
1396     }
1397 
1398     @Override
setRecordRetentionPeriodInDays( int days, @NonNull UserHandle user, @NonNull IEmptyResponseCallback callback)1399     public void setRecordRetentionPeriodInDays(
1400             int days, @NonNull UserHandle user, @NonNull IEmptyResponseCallback callback) {
1401         checkParamsNonNull(user, callback);
1402 
1403         final int uid = Binder.getCallingUid();
1404         final int pid = Binder.getCallingPid();
1405         final UserHandle userHandle = Binder.getCallingUserHandle();
1406         HealthConnectThreadScheduler.scheduleControllerTask(
1407                 () -> {
1408                     try {
1409                         enforceIsForegroundUser(userHandle);
1410                         mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null);
1411                         throwExceptionIfDataSyncInProgress();
1412                         AutoDeleteService.setRecordRetentionPeriodInDays(days);
1413                         callback.onResult();
1414                     } catch (SQLiteException sqLiteException) {
1415                         Slog.e(TAG, "SQLiteException: ", sqLiteException);
1416                         tryAndThrowException(
1417                                 callback, sqLiteException, HealthConnectException.ERROR_IO);
1418                     } catch (SecurityException securityException) {
1419                         Slog.e(TAG, "SecurityException: ", securityException);
1420                         tryAndThrowException(callback, securityException, ERROR_SECURITY);
1421                     } catch (HealthConnectException healthConnectException) {
1422                         Slog.e(TAG, "HealthConnectException: ", healthConnectException);
1423                         tryAndThrowException(
1424                                 callback,
1425                                 healthConnectException,
1426                                 healthConnectException.getErrorCode());
1427                     } catch (Exception exception) {
1428                         Slog.e(TAG, "Exception: ", exception);
1429                         tryAndThrowException(callback, exception, ERROR_INTERNAL);
1430                     }
1431                 });
1432     }
1433 
1434     @Override
getRecordRetentionPeriodInDays(@onNull UserHandle user)1435     public int getRecordRetentionPeriodInDays(@NonNull UserHandle user) {
1436         checkParamsNonNull(user);
1437 
1438         enforceIsForegroundUser(getCallingUserHandle());
1439         throwExceptionIfDataSyncInProgress();
1440         try {
1441             mContext.enforceCallingPermission(MANAGE_HEALTH_DATA_PERMISSION, null);
1442             return AutoDeleteService.getRecordRetentionPeriodInDays();
1443         } catch (Exception e) {
1444             if (e instanceof SecurityException) {
1445                 throw e;
1446             }
1447             Slog.e(TAG, "Unable to get record retention period for " + user);
1448         }
1449 
1450         throw new RuntimeException();
1451     }
1452 
1453     /**
1454      * Returns information, represented by {@code ApplicationInfoResponse}, for all the packages
1455      * that have contributed to the health connect DB.
1456      *
1457      * @param callback Callback to receive result of performing this operation. In case of an error
1458      *     or a permission failure the HealthConnect service, {@link IEmptyResponseCallback#onError}
1459      *     will be invoked with a {@link HealthConnectException}.
1460      */
1461     @Override
getContributorApplicationsInfo(@onNull IApplicationInfoResponseCallback callback)1462     public void getContributorApplicationsInfo(@NonNull IApplicationInfoResponseCallback callback) {
1463         checkParamsNonNull(callback);
1464 
1465         final int uid = Binder.getCallingUid();
1466         final int pid = Binder.getCallingPid();
1467         final UserHandle userHandle = Binder.getCallingUserHandle();
1468         HealthConnectThreadScheduler.scheduleControllerTask(
1469                 () -> {
1470                     try {
1471                         enforceIsForegroundUser(userHandle);
1472                         mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null);
1473                         throwExceptionIfDataSyncInProgress();
1474                         List<AppInfo> applicationInfos =
1475                                 mAppInfoHelper.getApplicationInfosWithRecordTypes();
1476 
1477                         callback.onResult(new ApplicationInfoResponseParcel(applicationInfos));
1478                     } catch (SQLiteException sqLiteException) {
1479                         Slog.e(TAG, "SqlException: ", sqLiteException);
1480                         tryAndThrowException(
1481                                 callback, sqLiteException, HealthConnectException.ERROR_IO);
1482                     } catch (SecurityException securityException) {
1483                         Slog.e(TAG, "SecurityException: ", securityException);
1484                         tryAndThrowException(callback, securityException, ERROR_SECURITY);
1485                     } catch (HealthConnectException healthConnectException) {
1486                         Slog.e(TAG, "HealthConnectException: ", healthConnectException);
1487                         tryAndThrowException(
1488                                 callback,
1489                                 healthConnectException,
1490                                 healthConnectException.getErrorCode());
1491                     } catch (Exception e) {
1492                         Slog.e(TAG, "Exception: ", e);
1493                         tryAndThrowException(callback, e, ERROR_INTERNAL);
1494                     }
1495                 });
1496     }
1497 
1498     /** Retrieves {@link RecordTypeInfoResponse} for each RecordType. */
1499     @Override
queryAllRecordTypesInfo(@onNull IRecordTypeInfoResponseCallback callback)1500     public void queryAllRecordTypesInfo(@NonNull IRecordTypeInfoResponseCallback callback) {
1501         checkParamsNonNull(callback);
1502 
1503         final int uid = Binder.getCallingUid();
1504         final int pid = Binder.getCallingPid();
1505         final UserHandle userHandle = Binder.getCallingUserHandle();
1506         HealthConnectThreadScheduler.scheduleControllerTask(
1507                 () -> {
1508                     try {
1509                         enforceIsForegroundUser(userHandle);
1510                         mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null);
1511                         throwExceptionIfDataSyncInProgress();
1512                         callback.onResult(
1513                                 new RecordTypeInfoResponseParcel(
1514                                         getPopulatedRecordTypeInfoResponses()));
1515                     } catch (SQLiteException sqLiteException) {
1516                         tryAndThrowException(
1517                                 callback, sqLiteException, HealthConnectException.ERROR_IO);
1518                     } catch (SecurityException securityException) {
1519                         Slog.e(TAG, "SecurityException: ", securityException);
1520                         tryAndThrowException(callback, securityException, ERROR_SECURITY);
1521                     } catch (HealthConnectException healthConnectException) {
1522                         Slog.e(TAG, "HealthConnectException: ", healthConnectException);
1523                         tryAndThrowException(
1524                                 callback,
1525                                 healthConnectException,
1526                                 healthConnectException.getErrorCode());
1527                     } catch (Exception exception) {
1528                         tryAndThrowException(callback, exception, ERROR_INTERNAL);
1529                     }
1530                 });
1531     }
1532 
1533     /**
1534      * @see HealthConnectManager#queryAccessLogs
1535      */
1536     @Override
queryAccessLogs( @onNull String packageName, @NonNull IAccessLogsResponseCallback callback)1537     public void queryAccessLogs(
1538             @NonNull String packageName, @NonNull IAccessLogsResponseCallback callback) {
1539         checkParamsNonNull(packageName, callback);
1540 
1541         final int uid = Binder.getCallingUid();
1542         final int pid = Binder.getCallingPid();
1543         final UserHandle userHandle = Binder.getCallingUserHandle();
1544 
1545         HealthConnectThreadScheduler.scheduleControllerTask(
1546                 () -> {
1547                     try {
1548                         enforceIsForegroundUser(userHandle);
1549                         mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null);
1550                         throwExceptionIfDataSyncInProgress();
1551                         final List<AccessLog> accessLogsList = AccessLogsHelper.queryAccessLogs();
1552                         callback.onResult(new AccessLogsResponseParcel(accessLogsList));
1553                     } catch (SecurityException securityException) {
1554                         Slog.e(TAG, "SecurityException: ", securityException);
1555                         tryAndThrowException(callback, securityException, ERROR_SECURITY);
1556                     } catch (HealthConnectException healthConnectException) {
1557                         Slog.e(TAG, "HealthConnectException: ", healthConnectException);
1558                         tryAndThrowException(
1559                                 callback,
1560                                 healthConnectException,
1561                                 healthConnectException.getErrorCode());
1562                     } catch (Exception exception) {
1563                         Slog.e(TAG, "Exception: ", exception);
1564                         tryAndThrowException(callback, exception, ERROR_INTERNAL);
1565                     }
1566                 });
1567     }
1568 
1569     /**
1570      * Returns a list of unique dates for which the database has at least one entry
1571      *
1572      * @param activityDatesRequestParcel Parcel request containing records classes
1573      * @param callback Callback to receive result of performing this operation. The results are
1574      *     returned in {@link List<LocalDate>} . In case of an error or a permission failure the
1575      *     HealthConnect service, {@link IActivityDatesResponseCallback#onError} will be invoked
1576      *     with a {@link HealthConnectExceptionParcel}.
1577      */
1578     @Override
getActivityDates( @onNull ActivityDatesRequestParcel activityDatesRequestParcel, @NonNull IActivityDatesResponseCallback callback)1579     public void getActivityDates(
1580             @NonNull ActivityDatesRequestParcel activityDatesRequestParcel,
1581             @NonNull IActivityDatesResponseCallback callback) {
1582         checkParamsNonNull(activityDatesRequestParcel, callback);
1583 
1584         final int uid = Binder.getCallingUid();
1585         final int pid = Binder.getCallingPid();
1586         final UserHandle userHandle = Binder.getCallingUserHandle();
1587 
1588         HealthConnectThreadScheduler.scheduleControllerTask(
1589                 () -> {
1590                     try {
1591                         enforceIsForegroundUser(userHandle);
1592                         mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null);
1593                         throwExceptionIfDataSyncInProgress();
1594                         List<LocalDate> localDates =
1595                                 ActivityDateHelper.getActivityDates(
1596                                         activityDatesRequestParcel.getRecordTypes());
1597 
1598                         callback.onResult(new ActivityDatesResponseParcel(localDates));
1599                     } catch (SQLiteException sqLiteException) {
1600                         Slog.e(TAG, "SqlException: ", sqLiteException);
1601                         tryAndThrowException(
1602                                 callback, sqLiteException, HealthConnectException.ERROR_IO);
1603                     } catch (SecurityException securityException) {
1604                         Slog.e(TAG, "SecurityException: ", securityException);
1605                         tryAndThrowException(callback, securityException, ERROR_SECURITY);
1606                     } catch (HealthConnectException healthConnectException) {
1607                         Slog.e(TAG, "HealthConnectException: ", healthConnectException);
1608                         tryAndThrowException(
1609                                 callback,
1610                                 healthConnectException,
1611                                 healthConnectException.getErrorCode());
1612                     } catch (Exception e) {
1613                         Slog.e(TAG, "Exception: ", e);
1614                         tryAndThrowException(callback, e, ERROR_INTERNAL);
1615                     }
1616                 });
1617     }
1618 
1619     /**
1620      * Changes migration state to {@link MIGRATION_STATE_IN_PROGRESS} if the current state allows
1621      * migration to be started.
1622      *
1623      * @param packageName calling package name
1624      * @param callback Callback to receive a result or an error encountered while performing this
1625      *     operation.
1626      */
1627     @Override
startMigration(@onNull String packageName, @NonNull IMigrationCallback callback)1628     public void startMigration(@NonNull String packageName, @NonNull IMigrationCallback callback) {
1629         checkParamsNonNull(packageName, callback);
1630 
1631         int uid = Binder.getCallingUid();
1632         int pid = Binder.getCallingPid();
1633         final UserHandle userHandle = Binder.getCallingUserHandle();
1634 
1635         HealthConnectThreadScheduler.scheduleInternalTask(
1636                 () -> {
1637                     try {
1638                         enforceIsForegroundUser(userHandle);
1639                         mContext.enforcePermission(
1640                                 MIGRATE_HEALTH_CONNECT_DATA,
1641                                 pid,
1642                                 uid,
1643                                 "Caller does not have " + MIGRATE_HEALTH_CONNECT_DATA);
1644                         enforceShowMigrationInfoIntent(packageName, uid);
1645                         mBackupRestore.runWithStatesReadLock(
1646                                 () -> {
1647                                     if (mBackupRestore.isRestoreMergingInProgress()) {
1648                                         throw new MigrationException(
1649                                                 "Cannot start data migration. Backup and restore in"
1650                                                         + " progress.",
1651                                                 MigrationException.ERROR_INTERNAL,
1652                                                 null);
1653                                     }
1654                                     mMigrationStateManager.startMigration(mContext);
1655                                 });
1656                         mPriorityMigrationHelper.populatePreMigrationPriority();
1657                         callback.onSuccess();
1658                     } catch (Exception e) {
1659                         Slog.e(TAG, "Exception: ", e);
1660                         tryAndThrowException(callback, e, MigrationException.ERROR_INTERNAL, null);
1661                     }
1662                 });
1663     }
1664 
1665     /**
1666      * Changes migration state to {@link MIGRATION_STATE_COMPLETE} if migration is not already
1667      * complete.
1668      *
1669      * @param packageName calling package name
1670      * @param callback Callback to receive a result or an error encountered while performing this
1671      *     operation.
1672      */
1673     @Override
finishMigration(@onNull String packageName, @NonNull IMigrationCallback callback)1674     public void finishMigration(@NonNull String packageName, @NonNull IMigrationCallback callback) {
1675         checkParamsNonNull(packageName, callback);
1676 
1677         int uid = Binder.getCallingUid();
1678         int pid = Binder.getCallingPid();
1679         final UserHandle userHandle = Binder.getCallingUserHandle();
1680 
1681         HealthConnectThreadScheduler.scheduleInternalTask(
1682                 () -> {
1683                     try {
1684                         enforceIsForegroundUser(userHandle);
1685                         mContext.enforcePermission(
1686                                 MIGRATE_HEALTH_CONNECT_DATA,
1687                                 pid,
1688                                 uid,
1689                                 "Caller does not have " + MIGRATE_HEALTH_CONNECT_DATA);
1690                         enforceShowMigrationInfoIntent(packageName, uid);
1691                         mMigrationStateManager.finishMigration(mContext);
1692                         mAppInfoHelper.syncAppInfoRecordTypesUsed();
1693                         callback.onSuccess();
1694                     } catch (Exception e) {
1695                         Slog.e(TAG, "Exception: ", e);
1696                         tryAndThrowException(callback, e, MigrationException.ERROR_INTERNAL, null);
1697                     }
1698                 });
1699     }
1700 
1701     /**
1702      * Write data to module storage. The migration state must be {@link MIGRATION_STATE_IN_PROGRESS}
1703      * to be able to write data.
1704      *
1705      * @param packageName calling package name
1706      * @param parcel Migration entity containing the data being migrated.
1707      * @param callback Callback to receive a result or an error encountered while performing this
1708      *     operation.
1709      */
1710     @Override
writeMigrationData( @onNull String packageName, @NonNull MigrationEntityParcel parcel, @NonNull IMigrationCallback callback)1711     public void writeMigrationData(
1712             @NonNull String packageName,
1713             @NonNull MigrationEntityParcel parcel,
1714             @NonNull IMigrationCallback callback) {
1715         checkParamsNonNull(packageName, parcel, callback);
1716 
1717         int uid = Binder.getCallingUid();
1718         int pid = Binder.getCallingPid();
1719         UserHandle callingUserHandle = getCallingUserHandle();
1720 
1721         HealthConnectThreadScheduler.scheduleInternalTask(
1722                 () -> {
1723                     try {
1724                         enforceIsForegroundUser(callingUserHandle);
1725                         mContext.enforcePermission(
1726                                 MIGRATE_HEALTH_CONNECT_DATA,
1727                                 pid,
1728                                 uid,
1729                                 "Caller does not have " + MIGRATE_HEALTH_CONNECT_DATA);
1730                         enforceShowMigrationInfoIntent(packageName, uid);
1731                         mMigrationStateManager.validateWriteMigrationData();
1732                         getDataMigrationManager(callingUserHandle)
1733                                 .apply(parcel.getMigrationEntities());
1734                         callback.onSuccess();
1735                     } catch (DataMigrationManager.EntityWriteException e) {
1736                         Slog.e(TAG, "Exception: ", e);
1737                         tryAndThrowException(
1738                                 callback,
1739                                 e,
1740                                 MigrationException.ERROR_MIGRATE_ENTITY,
1741                                 e.getEntityId());
1742                     } catch (Exception e) {
1743                         Slog.e(TAG, "Exception: ", e);
1744                         tryAndThrowException(callback, e, MigrationException.ERROR_INTERNAL, null);
1745                     }
1746                 });
1747     }
1748 
1749     /**
1750      * @param packageName calling package name
1751      * @param requiredSdkExtension The minimum sdk extension version for module to be ready for data
1752      *     migration from the apk.
1753      * @param callback Callback to receive a result or an error encountered while performing this
1754      *     operation.
1755      */
insertMinDataMigrationSdkExtensionVersion( @onNull String packageName, int requiredSdkExtension, @NonNull IMigrationCallback callback)1756     public void insertMinDataMigrationSdkExtensionVersion(
1757             @NonNull String packageName,
1758             int requiredSdkExtension,
1759             @NonNull IMigrationCallback callback) {
1760         checkParamsNonNull(packageName, callback);
1761 
1762         int uid = Binder.getCallingUid();
1763         int pid = Binder.getCallingPid();
1764         final UserHandle userHandle = Binder.getCallingUserHandle();
1765 
1766         HealthConnectThreadScheduler.scheduleInternalTask(
1767                 () -> {
1768                     try {
1769                         enforceIsForegroundUser(userHandle);
1770                         mContext.enforcePermission(
1771                                 MIGRATE_HEALTH_CONNECT_DATA,
1772                                 pid,
1773                                 uid,
1774                                 "Caller does not have " + MIGRATE_HEALTH_CONNECT_DATA);
1775                         enforceShowMigrationInfoIntent(packageName, uid);
1776                         mMigrationStateManager.validateSetMinSdkVersion();
1777                         mMigrationStateManager.setMinDataMigrationSdkExtensionVersion(
1778                                 mContext, requiredSdkExtension);
1779 
1780                         callback.onSuccess();
1781                     } catch (Exception e) {
1782                         Slog.e(TAG, "Exception: ", e);
1783                         tryAndThrowException(callback, e, MigrationException.ERROR_INTERNAL, null);
1784                     }
1785                 });
1786     }
1787 
1788     /**
1789      * @see HealthConnectManager#stageAllHealthConnectRemoteData
1790      */
1791     @Override
stageAllHealthConnectRemoteData( @onNull StageRemoteDataRequest stageRemoteDataRequest, @NonNull UserHandle userHandle, @NonNull IDataStagingFinishedCallback callback)1792     public void stageAllHealthConnectRemoteData(
1793             @NonNull StageRemoteDataRequest stageRemoteDataRequest,
1794             @NonNull UserHandle userHandle,
1795             @NonNull IDataStagingFinishedCallback callback) {
1796         checkParamsNonNull(stageRemoteDataRequest, userHandle, callback);
1797 
1798         Map<String, ParcelFileDescriptor> origPfdsByFileName =
1799                 stageRemoteDataRequest.getPfdsByFileName();
1800         Map<String, HealthConnectException> exceptionsByFileName =
1801                 new ArrayMap<>(origPfdsByFileName.size());
1802         Map<String, ParcelFileDescriptor> pfdsByFileName =
1803                 new ArrayMap<>(origPfdsByFileName.size());
1804 
1805         try {
1806             mDataPermissionEnforcer.enforceAnyOfPermissions(
1807                     Manifest.permission.STAGE_HEALTH_CONNECT_REMOTE_DATA,
1808                     HEALTH_CONNECT_BACKUP_INTER_AGENT_PERMISSION);
1809 
1810             enforceIsForegroundUser(Binder.getCallingUserHandle());
1811 
1812             for (Entry<String, ParcelFileDescriptor> entry : origPfdsByFileName.entrySet()) {
1813                 try {
1814                     pfdsByFileName.put(entry.getKey(), entry.getValue().dup());
1815                 } catch (IOException e) {
1816                     Slog.e(TAG, "IOException: ", e);
1817                     exceptionsByFileName.put(
1818                             entry.getKey(),
1819                             new HealthConnectException(
1820                                     HealthConnectException.ERROR_IO, e.getMessage()));
1821                 }
1822             }
1823 
1824             HealthConnectThreadScheduler.scheduleInternalTask(
1825                     () -> {
1826                         if (!mBackupRestore.prepForStagingIfNotAlreadyDone()) {
1827                             try {
1828                                 callback.onResult();
1829                             } catch (RemoteException e) {
1830                                 Log.e(TAG, "Restore response could not be sent to the caller.", e);
1831                             }
1832                             return;
1833                         }
1834                         mBackupRestore.stageAllHealthConnectRemoteData(
1835                                 pfdsByFileName, exceptionsByFileName, userHandle, callback);
1836                     });
1837         } catch (SecurityException | IllegalStateException e) {
1838             Log.e(TAG, "Exception encountered while staging", e);
1839             try {
1840                 @HealthConnectException.ErrorCode
1841                 int errorCode = (e instanceof SecurityException) ? ERROR_SECURITY : ERROR_INTERNAL;
1842                 exceptionsByFileName.put("", new HealthConnectException(errorCode, e.getMessage()));
1843 
1844                 callback.onError(new StageRemoteDataException(exceptionsByFileName));
1845             } catch (RemoteException remoteException) {
1846                 Log.e(TAG, "Restore permission response could not be sent to the caller.", e);
1847             }
1848         }
1849     }
1850 
1851     /**
1852      * @see HealthConnectManager#getAllDataForBackup
1853      */
1854     @Override
getAllDataForBackup( @onNull StageRemoteDataRequest stageRemoteDataRequest, @NonNull UserHandle userHandle)1855     public void getAllDataForBackup(
1856             @NonNull StageRemoteDataRequest stageRemoteDataRequest,
1857             @NonNull UserHandle userHandle) {
1858         checkParamsNonNull(stageRemoteDataRequest, userHandle);
1859 
1860         mContext.enforceCallingPermission(HEALTH_CONNECT_BACKUP_INTER_AGENT_PERMISSION, null);
1861         final long token = Binder.clearCallingIdentity();
1862         try {
1863             mBackupRestore.getAllDataForBackup(stageRemoteDataRequest, userHandle);
1864         } finally {
1865             Binder.restoreCallingIdentity(token);
1866         }
1867     }
1868 
1869     /**
1870      * @see HealthConnectManager#getAllBackupFileNames
1871      */
1872     @Override
getAllBackupFileNames(boolean forDeviceToDevice)1873     public BackupFileNamesSet getAllBackupFileNames(boolean forDeviceToDevice) {
1874         mContext.enforceCallingPermission(HEALTH_CONNECT_BACKUP_INTER_AGENT_PERMISSION, null);
1875         return mBackupRestore.getAllBackupFileNames(forDeviceToDevice);
1876     }
1877 
1878     /**
1879      * @see HealthConnectManager#deleteAllStagedRemoteData
1880      */
1881     @Override
deleteAllStagedRemoteData(@onNull UserHandle userHandle)1882     public void deleteAllStagedRemoteData(@NonNull UserHandle userHandle) {
1883         checkParamsNonNull(userHandle);
1884 
1885         mContext.enforceCallingPermission(
1886                 DELETE_STAGED_HEALTH_CONNECT_REMOTE_DATA_PERMISSION, null);
1887         mBackupRestore.deleteAndResetEverything(userHandle);
1888         mMigrationStateManager.clearCaches(mContext);
1889         DatabaseHelper.clearAllData(mTransactionManager);
1890         RateLimiter.clearCache();
1891         String[] packageNames = mContext.getPackageManager().getPackagesForUid(getCallingUid());
1892         for (String packageName : packageNames) {
1893             mFirstGrantTimeManager.setFirstGrantTime(packageName, Instant.now(), userHandle);
1894         }
1895     }
1896 
1897     /**
1898      * @see HealthConnectManager#setLowerRateLimitsForTesting
1899      */
1900     @Override
setLowerRateLimitsForTesting(boolean enabled)1901     public void setLowerRateLimitsForTesting(boolean enabled) {
1902         // Continue using the existing test permission because we can't grant new permissions
1903         // to shell in a mainline update.
1904         mContext.enforceCallingPermission(
1905                 DELETE_STAGED_HEALTH_CONNECT_REMOTE_DATA_PERMISSION, null);
1906         RateLimiter.setLowerRateLimitsForTesting(enabled);
1907     }
1908 
1909     /**
1910      * @see HealthConnectManager#updateDataDownloadState
1911      */
1912     @Override
updateDataDownloadState(@ataDownloadState int downloadState)1913     public void updateDataDownloadState(@DataDownloadState int downloadState) {
1914         mContext.enforceCallingPermission(
1915                 Manifest.permission.STAGE_HEALTH_CONNECT_REMOTE_DATA, null);
1916         enforceIsForegroundUser(getCallingUserHandle());
1917         mBackupRestore.updateDataDownloadState(downloadState);
1918     }
1919 
1920     /**
1921      * @see HealthConnectManager#getHealthConnectDataState
1922      */
1923     @Override
getHealthConnectDataState(@onNull IGetHealthConnectDataStateCallback callback)1924     public void getHealthConnectDataState(@NonNull IGetHealthConnectDataStateCallback callback) {
1925         checkParamsNonNull(callback);
1926 
1927         try {
1928             mDataPermissionEnforcer.enforceAnyOfPermissions(
1929                     MANAGE_HEALTH_DATA_PERMISSION, Manifest.permission.MIGRATE_HEALTH_CONNECT_DATA);
1930             final UserHandle userHandle = Binder.getCallingUserHandle();
1931             enforceIsForegroundUser(userHandle);
1932             HealthConnectThreadScheduler.scheduleInternalTask(
1933                     () -> {
1934                         try {
1935                             @HealthConnectDataState.DataRestoreError
1936                             int dataRestoreError = mBackupRestore.getDataRestoreError();
1937                             @HealthConnectDataState.DataRestoreState
1938                             int dataRestoreState = mBackupRestore.getDataRestoreState();
1939 
1940                             try {
1941                                 callback.onResult(
1942                                         new HealthConnectDataState(
1943                                                 dataRestoreState,
1944                                                 dataRestoreError,
1945                                                 mMigrationStateManager.getMigrationState()));
1946                             } catch (RemoteException remoteException) {
1947                                 Log.e(
1948                                         TAG,
1949                                         "HealthConnectDataState could not be sent to the caller.",
1950                                         remoteException);
1951                             }
1952                         } catch (RuntimeException e) {
1953                             // exception getting the state from the disk
1954                             try {
1955                                 callback.onError(
1956                                         new HealthConnectExceptionParcel(
1957                                                 new HealthConnectException(
1958                                                         HealthConnectException.ERROR_IO,
1959                                                         e.getMessage())));
1960                             } catch (RemoteException remoteException) {
1961                                 Log.e(
1962                                         TAG,
1963                                         "Exception for getHealthConnectDataState could not be sent"
1964                                                 + " to the caller.",
1965                                         remoteException);
1966                             }
1967                         }
1968                     });
1969         } catch (SecurityException | IllegalStateException e) {
1970             Log.e(TAG, "getHealthConnectDataState: Exception encountered", e);
1971             @HealthConnectException.ErrorCode
1972             int errorCode = (e instanceof SecurityException) ? ERROR_SECURITY : ERROR_INTERNAL;
1973             try {
1974                 callback.onError(
1975                         new HealthConnectExceptionParcel(
1976                                 new HealthConnectException(errorCode, e.getMessage())));
1977             } catch (RemoteException remoteException) {
1978                 Log.e(TAG, "getHealthConnectDataState error could not be sent", e);
1979             }
1980         }
1981     }
1982 
1983     /**
1984      * @see HealthConnectManager#getHealthConnectMigrationUiState
1985      */
1986     @Override
getHealthConnectMigrationUiState( @onNull IGetHealthConnectMigrationUiStateCallback callback)1987     public void getHealthConnectMigrationUiState(
1988             @NonNull IGetHealthConnectMigrationUiStateCallback callback) {
1989         checkParamsNonNull(callback);
1990 
1991         final int uid = Binder.getCallingUid();
1992         final int pid = Binder.getCallingPid();
1993         final UserHandle userHandle = Binder.getCallingUserHandle();
1994         HealthConnectThreadScheduler.scheduleInternalTask(
1995                 () -> {
1996                     try {
1997                         enforceIsForegroundUser(userHandle);
1998                         mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null);
1999 
2000                         try {
2001                             callback.onResult(
2002                                     new HealthConnectMigrationUiState(
2003                                             mMigrationUiStateManager
2004                                                     .getHealthConnectMigrationUiState()));
2005                         } catch (RemoteException remoteException) {
2006                             Log.e(
2007                                     TAG,
2008                                     "HealthConnectMigrationUiState could not be sent to the"
2009                                             + " caller.",
2010                                     remoteException);
2011                         }
2012                     } catch (SecurityException securityException) {
2013                         try {
2014                             callback.onError(
2015                                     new HealthConnectExceptionParcel(
2016                                             new HealthConnectException(
2017                                                     ERROR_SECURITY,
2018                                                     securityException.getMessage())));
2019                         } catch (RemoteException remoteException) {
2020                             Log.e(
2021                                     TAG,
2022                                     "Exception for HealthConnectMigrationUiState could not be sent"
2023                                             + " to the caller.",
2024                                     remoteException);
2025                         }
2026                     } catch (RuntimeException e) {
2027                         // exception getting the state from the disk
2028                         try {
2029                             callback.onError(
2030                                     new HealthConnectExceptionParcel(
2031                                             new HealthConnectException(
2032                                                     HealthConnectException.ERROR_IO,
2033                                                     e.getMessage())));
2034                         } catch (RemoteException remoteException) {
2035                             Log.e(
2036                                     TAG,
2037                                     "Exception for HealthConnectMigrationUiState could not be sent"
2038                                             + " to the caller.",
2039                                     remoteException);
2040                         }
2041                     }
2042                 });
2043     }
2044 
2045     @Override
configureScheduledExport( @ullable ScheduledExportSettings settings, @NonNull UserHandle user)2046     public void configureScheduledExport(
2047             @Nullable ScheduledExportSettings settings, @NonNull UserHandle user) {
2048         checkParamsNonNull(user);
2049 
2050         UserHandle userHandle = Binder.getCallingUserHandle();
2051         enforceIsForegroundUser(userHandle);
2052         throwExceptionIfDataSyncInProgress();
2053 
2054         try {
2055             mContext.enforceCallingPermission(MANAGE_HEALTH_DATA_PERMISSION, null);
2056             ExportImportSettingsStorage.configure(settings);
2057 
2058             HealthConnectThreadScheduler.scheduleInternalTask(
2059                     () -> {
2060                         try {
2061                             ExportImportJobs.schedulePeriodicExportJob(
2062                                     mContext, userHandle.getIdentifier());
2063                         } catch (Exception e) {
2064                             Slog.e(TAG, "Failed to schedule periodic export job.", e);
2065                         }
2066                     });
2067         } catch (SQLiteException sqLiteException) {
2068             Slog.e(TAG, "SQLiteException: ", sqLiteException);
2069             throw new HealthConnectException(
2070                     HealthConnectException.ERROR_IO, sqLiteException.toString());
2071         } catch (SecurityException securityException) {
2072             Slog.e(TAG, "SecurityException: ", securityException);
2073             throw new HealthConnectException(
2074                     HealthConnectException.ERROR_SECURITY, securityException.toString());
2075         } catch (HealthConnectException healthConnectException) {
2076             Slog.e(TAG, "HealthConnectException: ", healthConnectException);
2077             throw new HealthConnectException(
2078                     healthConnectException.getErrorCode(), healthConnectException.toString());
2079         } catch (Exception exception) {
2080             Slog.e(TAG, "Exception: ", exception);
2081             throw new HealthConnectException(ERROR_INTERNAL, exception.toString());
2082         }
2083     }
2084 
2085     /** Queries status for a scheduled export */
2086     @Override
getScheduledExportStatus( @onNull UserHandle user, @NonNull IScheduledExportStatusCallback callback)2087     public void getScheduledExportStatus(
2088             @NonNull UserHandle user, @NonNull IScheduledExportStatusCallback callback) {
2089         checkParamsNonNull(user, callback);
2090 
2091         final int uid = Binder.getCallingUid();
2092         final int pid = Binder.getCallingPid();
2093         final UserHandle userHandle = Binder.getCallingUserHandle();
2094         HealthConnectThreadScheduler.scheduleControllerTask(
2095                 () -> {
2096                     try {
2097                         enforceIsForegroundUser(userHandle);
2098                         mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null);
2099                         ScheduledExportStatus status =
2100                                 ExportImportSettingsStorage.getScheduledExportStatus();
2101                         callback.onResult(status);
2102                     } catch (HealthConnectException healthConnectException) {
2103                         Slog.e(TAG, "HealthConnectException: ", healthConnectException);
2104                         tryAndThrowException(
2105                                 callback,
2106                                 healthConnectException,
2107                                 healthConnectException.getErrorCode());
2108                     } catch (Exception exception) {
2109                         Slog.e(TAG, "Exception: ", exception);
2110                         tryAndThrowException(callback, exception, ERROR_INTERNAL);
2111                     }
2112                 });
2113     }
2114 
2115     @Override
getScheduledExportPeriodInDays(@onNull UserHandle user)2116     public int getScheduledExportPeriodInDays(@NonNull UserHandle user) {
2117         checkParamsNonNull(user);
2118 
2119         enforceIsForegroundUser(getCallingUserHandle());
2120         throwExceptionIfDataSyncInProgress();
2121         try {
2122             mContext.enforceCallingPermission(MANAGE_HEALTH_DATA_PERMISSION, null);
2123             return ExportImportSettingsStorage.getScheduledExportPeriodInDays();
2124         } catch (Exception e) {
2125             if (e instanceof SecurityException) {
2126                 throw e;
2127             }
2128             Slog.e(TAG, "Unable to get period between scheduled exports for " + user);
2129         }
2130 
2131         throw new RuntimeException();
2132     }
2133 
2134     /** Queries the status for a data import */
2135     @Override
getImportStatus(@onNull UserHandle user, @NonNull IImportStatusCallback callback)2136     public void getImportStatus(@NonNull UserHandle user, @NonNull IImportStatusCallback callback) {
2137         checkParamsNonNull(user, callback);
2138 
2139         final int uid = Binder.getCallingUid();
2140         final int pid = Binder.getCallingPid();
2141         final UserHandle userHandle = Binder.getCallingUserHandle();
2142         HealthConnectThreadScheduler.scheduleControllerTask(
2143                 () -> {
2144                     try {
2145                         enforceIsForegroundUser(userHandle);
2146                         mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null);
2147                         ImportStatus status = ExportImportSettingsStorage.getImportStatus();
2148                         callback.onResult(status);
2149                     } catch (HealthConnectException healthConnectException) {
2150                         Slog.e(TAG, "HealthConnectException: ", healthConnectException);
2151                         tryAndThrowException(
2152                                 callback,
2153                                 healthConnectException,
2154                                 healthConnectException.getErrorCode());
2155                     } catch (Exception exception) {
2156                         Slog.e(TAG, "Exception: ", exception);
2157                         tryAndThrowException(callback, exception, ERROR_INTERNAL);
2158                     }
2159                 });
2160     }
2161 
2162     @Override
runImport(@onNull UserHandle user, @NonNull Uri file)2163     public void runImport(@NonNull UserHandle user, @NonNull Uri file) {
2164         try {
2165             mImportManager.runImport(user, file);
2166         } catch (Exception e) {
2167             Slog.e(TAG, "Import failed", e);
2168         }
2169     }
2170 
2171     /** Queries the document providers available to be used for export/import. */
2172     @Override
queryDocumentProviders( @onNull UserHandle user, @NonNull IQueryDocumentProvidersCallback callback)2173     public void queryDocumentProviders(
2174             @NonNull UserHandle user, @NonNull IQueryDocumentProvidersCallback callback) {
2175         checkParamsNonNull(user, callback);
2176 
2177         final int uid = Binder.getCallingUid();
2178         final int pid = Binder.getCallingPid();
2179         final UserHandle userHandle = Binder.getCallingUserHandle();
2180         HealthConnectThreadScheduler.scheduleControllerTask(
2181                 () -> {
2182                     try {
2183                         enforceIsForegroundUser(userHandle);
2184                         mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null);
2185                         final Context userContext = mContext.createContextAsUser(userHandle, 0);
2186                         final List<ExportImportDocumentProvider> providers =
2187                                 DocumentProvidersManager.queryDocumentProviders(userContext);
2188                         callback.onResult(providers);
2189                     } catch (SecurityException securityException) {
2190                         Slog.e(TAG, "SecurityException: ", securityException);
2191                         throw new HealthConnectException(
2192                                 HealthConnectException.ERROR_SECURITY,
2193                                 securityException.toString());
2194                     } catch (HealthConnectException healthConnectException) {
2195                         Slog.e(TAG, "HealthConnectException: ", healthConnectException);
2196                         tryAndThrowException(
2197                                 callback,
2198                                 healthConnectException,
2199                                 healthConnectException.getErrorCode());
2200                     } catch (Exception exception) {
2201                         Slog.e(TAG, "Exception: ", exception);
2202                         tryAndThrowException(callback, exception, ERROR_INTERNAL);
2203                     }
2204                 });
2205     }
2206 
2207     @Override
readMedicalResources( @onNull AttributionSource attributionSource, @NonNull MedicalIdFiltersParcel medicalIdFiltersParcel, @NonNull IReadMedicalResourcesResponseCallback callback)2208     public void readMedicalResources(
2209             @NonNull AttributionSource attributionSource,
2210             @NonNull MedicalIdFiltersParcel medicalIdFiltersParcel,
2211             @NonNull IReadMedicalResourcesResponseCallback callback) {
2212         if (!personalHealthRecord()) {
2213             HealthConnectException unsupportedException =
2214                     new HealthConnectException(
2215                             ERROR_UNSUPPORTED_OPERATION,
2216                             "Reading MedicalResources by ids is not supported.");
2217             Slog.e(TAG, "HealthConnectException: ", unsupportedException);
2218             tryAndThrowException(
2219                     callback, unsupportedException, unsupportedException.getErrorCode());
2220             return;
2221         }
2222 
2223         checkParamsNonNull(attributionSource, medicalIdFiltersParcel, callback);
2224 
2225         final int uid = Binder.getCallingUid();
2226         final int pid = Binder.getCallingPid();
2227         final UserHandle userHandle = Binder.getCallingUserHandle();
2228         final boolean holdsDataManagementPermission = hasDataManagementPermission(uid, pid);
2229         final String callingPackageName =
2230                 Objects.requireNonNull(attributionSource.getPackageName());
2231         final HealthConnectServiceLogger.Builder logger =
2232                 new HealthConnectServiceLogger.Builder(holdsDataManagementPermission, READ_DATA)
2233                         .setPackageName(callingPackageName);
2234 
2235         HealthConnectThreadScheduler.schedule(
2236                 mContext,
2237                 () -> {
2238                     try {
2239                         enforceIsForegroundUser(userHandle);
2240                         verifyPackageNameFromUid(uid, attributionSource);
2241                         throwExceptionIfDataSyncInProgress();
2242 
2243                         boolean enforceSelfRead = false;
2244 
2245                         boolean isInForeground = mAppOpsManagerLocal.isUidInForeground(uid);
2246 
2247                         if (!holdsDataManagementPermission) {
2248                             logger.setCallerForegroundState(isInForeground);
2249 
2250                             tryAcquireApiCallQuota(
2251                                     uid, QuotaCategory.QUOTA_CATEGORY_READ, isInForeground, logger);
2252 
2253                             // TODO(b/340204629): Perform basic permission check. And set
2254                             // enforceSelfRead.
2255                             if (!isInForeground) {
2256                                 // If Background Read feature is disabled or
2257                                 // READ_HEALTH_DATA_IN_BACKGROUND permission is not granted, then
2258                                 // enforce self read.
2259                                 enforceSelfRead = isOnlySelfReadInBackgroundAllowed(uid, pid);
2260                             }
2261 
2262                             if (Constants.DEBUG) {
2263                                 Slog.d(
2264                                         TAG,
2265                                         "Enforce self read for package "
2266                                                 + callingPackageName
2267                                                 + ":"
2268                                                 + enforceSelfRead);
2269                             }
2270                         }
2271 
2272                         // TODO(b/340204629): Pass extra fields to DB to perform permission check.
2273                         // TODO(b/343455447): Update the fake empty list here to use the real
2274                         // MedicalResourceId from input when API is updated.
2275                         List<MedicalResource> medicalResources =
2276                                 mTransactionManager.readMedicalResourcesByIds(List.of());
2277                         logger.setNumberOfRecords(medicalResources.size());
2278 
2279                         // TODO(b/343921816): Creates access log.
2280 
2281                         callback.onResult(new ReadMedicalResourcesResponse(medicalResources));
2282                         logger.setHealthDataServiceApiStatusSuccess();
2283                     } catch (SQLiteException sqLiteException) {
2284                         logger.setHealthDataServiceApiStatusError(HealthConnectException.ERROR_IO);
2285                         Slog.e(TAG, "SQLiteException: ", sqLiteException);
2286                         tryAndThrowException(
2287                                 callback, sqLiteException, HealthConnectException.ERROR_IO);
2288                     } catch (SecurityException securityException) {
2289                         logger.setHealthDataServiceApiStatusError(ERROR_SECURITY);
2290                         Slog.e(TAG, "SecurityException: ", securityException);
2291                         tryAndThrowException(callback, securityException, ERROR_SECURITY);
2292                     } catch (IllegalStateException illegalStateException) {
2293                         logger.setHealthDataServiceApiStatusError(ERROR_INTERNAL);
2294                         Slog.e(TAG, "IllegalStateException: ", illegalStateException);
2295                         tryAndThrowException(callback, illegalStateException, ERROR_INTERNAL);
2296                     } catch (HealthConnectException healthConnectException) {
2297                         logger.setHealthDataServiceApiStatusError(
2298                                 healthConnectException.getErrorCode());
2299                         Slog.e(TAG, "HealthConnectException: ", healthConnectException);
2300                         tryAndThrowException(
2301                                 callback,
2302                                 healthConnectException,
2303                                 healthConnectException.getErrorCode());
2304                     } catch (Exception e) {
2305                         logger.setHealthDataServiceApiStatusError(ERROR_INTERNAL);
2306                         Slog.e(TAG, "Exception: ", e);
2307                         tryAndThrowException(callback, e, ERROR_INTERNAL);
2308                     } finally {
2309                         logger.build().log();
2310                     }
2311                 },
2312                 uid,
2313                 holdsDataManagementPermission);
2314     }
2315 
2316     // Cancel BR timeouts - this might be needed when a user is going into background.
cancelBackupRestoreTimeouts()2317     void cancelBackupRestoreTimeouts() {
2318         mBackupRestore.cancelAllJobs();
2319     }
2320 
tryAcquireApiCallQuota( int uid, @QuotaCategory.Type int quotaCategory, boolean isInForeground, HealthConnectServiceLogger.Builder logger)2321     private void tryAcquireApiCallQuota(
2322             int uid,
2323             @QuotaCategory.Type int quotaCategory,
2324             boolean isInForeground,
2325             HealthConnectServiceLogger.Builder logger) {
2326         try {
2327             RateLimiter.tryAcquireApiCallQuota(uid, quotaCategory, isInForeground);
2328         } catch (RateLimiterException rateLimiterException) {
2329             logger.setRateLimit(
2330                     rateLimiterException.getRateLimiterQuotaBucket(),
2331                     rateLimiterException.getRateLimiterQuotaLimit());
2332             throw new HealthConnectException(
2333                     rateLimiterException.getErrorCode(), rateLimiterException.getMessage());
2334         }
2335     }
2336 
tryAcquireApiCallQuota( int uid, @QuotaCategory.Type int quotaCategory, boolean isInForeground, HealthConnectServiceLogger.Builder logger, long memoryCost)2337     private void tryAcquireApiCallQuota(
2338             int uid,
2339             @QuotaCategory.Type int quotaCategory,
2340             boolean isInForeground,
2341             HealthConnectServiceLogger.Builder logger,
2342             long memoryCost) {
2343         try {
2344             RateLimiter.tryAcquireApiCallQuota(uid, quotaCategory, isInForeground, memoryCost);
2345         } catch (RateLimiterException rateLimiterException) {
2346             logger.setRateLimit(
2347                     rateLimiterException.getRateLimiterQuotaBucket(),
2348                     rateLimiterException.getRateLimiterQuotaLimit());
2349             throw new HealthConnectException(
2350                     rateLimiterException.getErrorCode(), rateLimiterException.getMessage());
2351         }
2352     }
2353 
enforceMemoryRateLimit(List<Long> recordsSize, long recordsChunkSize)2354     private void enforceMemoryRateLimit(List<Long> recordsSize, long recordsChunkSize) {
2355         recordsSize.forEach(RateLimiter::checkMaxRecordMemoryUsage);
2356         RateLimiter.checkMaxChunkMemoryUsage(recordsChunkSize);
2357     }
2358 
enforceIsForegroundUser(UserHandle callingUserHandle)2359     private void enforceIsForegroundUser(UserHandle callingUserHandle) {
2360         if (!callingUserHandle.equals(mCurrentForegroundUser)) {
2361             throw new IllegalStateException(
2362                     "Calling user: "
2363                             + callingUserHandle.getIdentifier()
2364                             + "is not the current foreground user: "
2365                             + mCurrentForegroundUser.getIdentifier()
2366                             + ". HC request must be called"
2367                             + " from the current foreground user.");
2368         }
2369     }
2370 
isDataSyncInProgress()2371     private boolean isDataSyncInProgress() {
2372         return mMigrationStateManager.isMigrationInProgress()
2373                 || mBackupRestore.isRestoreMergingInProgress();
2374     }
2375 
2376     @VisibleForTesting
getStagedRemoteFileNames(@onNull UserHandle userHandle)2377     Set<String> getStagedRemoteFileNames(@NonNull UserHandle userHandle) {
2378         return mBackupRestore.getStagedRemoteFileNames(userHandle);
2379     }
2380 
2381     @NonNull
getDataMigrationManager(@onNull UserHandle userHandle)2382     private DataMigrationManager getDataMigrationManager(@NonNull UserHandle userHandle) {
2383         final Context userContext = mContext.createContextAsUser(userHandle, 0);
2384 
2385         return new DataMigrationManager(
2386                 userContext,
2387                 mTransactionManager,
2388                 mPermissionHelper,
2389                 mFirstGrantTimeManager,
2390                 mDeviceInfoHelper,
2391                 mAppInfoHelper,
2392                 mHealthDataCategoryPriorityHelper,
2393                 mPriorityMigrationHelper);
2394     }
2395 
enforceCallingPackageBelongsToUid(String packageName, int callingUid)2396     private void enforceCallingPackageBelongsToUid(String packageName, int callingUid) {
2397         int packageUid;
2398         try {
2399             packageUid =
2400                     mContext.getPackageManager()
2401                             .getPackageUid(
2402                                     packageName, /* flags */ PackageManager.PackageInfoFlags.of(0));
2403         } catch (PackageManager.NameNotFoundException e) {
2404             throw new IllegalStateException(packageName + " not found");
2405         }
2406         if (UserHandle.getAppId(packageUid) != UserHandle.getAppId(callingUid)) {
2407             throwSecurityException(packageName + " does not belong to uid " + callingUid);
2408         }
2409     }
2410 
2411     /**
2412      * Verify various aspects of the calling user.
2413      *
2414      * @param callingUid Uid of the caller, usually retrieved from Binder for authenticity.
2415      * @param callerAttributionSource The permission identity of the caller
2416      */
verifyPackageNameFromUid( int callingUid, @NonNull AttributionSource callerAttributionSource)2417     private void verifyPackageNameFromUid(
2418             int callingUid, @NonNull AttributionSource callerAttributionSource) {
2419         // Check does the attribution source is one for the calling app.
2420         callerAttributionSource.enforceCallingUid();
2421         // Obtain the user where the client is running in.
2422         UserHandle callingUserHandle = UserHandle.getUserHandleForUid(callingUid);
2423         Context callingUserContext = mContext.createContextAsUser(callingUserHandle, 0);
2424         String callingPackageName =
2425                 Objects.requireNonNull(callerAttributionSource.getPackageName());
2426         verifyCallingPackage(callingUserContext, callingUid, callingPackageName);
2427     }
2428 
2429     /**
2430      * Check that the caller's supposed package name matches the uid making the call.
2431      *
2432      * @throws SecurityException if the package name and uid don't match.
2433      */
verifyCallingPackage( @onNull Context actualCallingUserContext, int actualCallingUid, @NonNull String claimedCallingPackage)2434     private void verifyCallingPackage(
2435             @NonNull Context actualCallingUserContext,
2436             int actualCallingUid,
2437             @NonNull String claimedCallingPackage) {
2438         int claimedCallingUid = getPackageUid(actualCallingUserContext, claimedCallingPackage);
2439         if (claimedCallingUid != actualCallingUid) {
2440             throwSecurityException(
2441                     claimedCallingPackage + " does not belong to uid " + actualCallingUid);
2442         }
2443     }
2444 
2445     /** Finds the UID of the {@code packageName} in the given {@code context}. */
getPackageUid(@onNull Context context, @NonNull String packageName)2446     private int getPackageUid(@NonNull Context context, @NonNull String packageName) {
2447         try {
2448             return context.getPackageManager().getPackageUid(packageName, /* flags= */ 0);
2449         } catch (PackageManager.NameNotFoundException e) {
2450             return Process.INVALID_UID;
2451         }
2452     }
2453 
enforceShowMigrationInfoIntent(String packageName, int callingUid)2454     private void enforceShowMigrationInfoIntent(String packageName, int callingUid) {
2455         enforceCallingPackageBelongsToUid(packageName, callingUid);
2456 
2457         Intent intentToCheck =
2458                 new Intent(HealthConnectManager.ACTION_SHOW_MIGRATION_INFO).setPackage(packageName);
2459 
2460         ResolveInfo resolveResult =
2461                 mContext.getPackageManager()
2462                         .resolveActivity(
2463                                 intentToCheck,
2464                                 PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_ALL));
2465 
2466         if (Objects.isNull(resolveResult)) {
2467             throw new IllegalArgumentException(
2468                     packageName
2469                             + " does not handle intent "
2470                             + HealthConnectManager.ACTION_SHOW_MIGRATION_INFO);
2471         }
2472     }
2473 
getPopulatedRecordTypeInfoResponses()2474     private Map<Integer, List<DataOrigin>> getPopulatedRecordTypeInfoResponses() {
2475         Map<Integer, Class<? extends Record>> recordIdToExternalRecordClassMap =
2476                 mRecordMapper.getRecordIdToExternalRecordClassMap();
2477         Map<Integer, List<DataOrigin>> recordTypeInfoResponses =
2478                 new ArrayMap<>(recordIdToExternalRecordClassMap.size());
2479         Map<Integer, Set<String>> recordTypeToContributingPackagesMap =
2480                 mAppInfoHelper.getRecordTypesToContributingPackagesMap();
2481         recordIdToExternalRecordClassMap
2482                 .keySet()
2483                 .forEach(
2484                         (recordType) -> {
2485                             if (recordTypeToContributingPackagesMap.containsKey(recordType)) {
2486                                 List<DataOrigin> packages =
2487                                         recordTypeToContributingPackagesMap.get(recordType).stream()
2488                                                 .map(
2489                                                         (packageName) ->
2490                                                                 new DataOrigin.Builder()
2491                                                                         .setPackageName(packageName)
2492                                                                         .build())
2493                                                 .toList();
2494                                 recordTypeInfoResponses.put(recordType, packages);
2495                             } else {
2496                                 recordTypeInfoResponses.put(recordType, Collections.emptyList());
2497                             }
2498                         });
2499         return recordTypeInfoResponses;
2500     }
2501 
hasDataManagementPermission(int uid, int pid)2502     private boolean hasDataManagementPermission(int uid, int pid) {
2503         return isPermissionGranted(MANAGE_HEALTH_DATA_PERMISSION, uid, pid);
2504     }
2505 
hasReadHistoryPermission(int uid, int pid)2506     private boolean hasReadHistoryPermission(int uid, int pid) {
2507         return mDeviceConfigManager.isHistoryReadFeatureEnabled()
2508                 && isPermissionGranted(READ_HEALTH_DATA_HISTORY, uid, pid);
2509     }
2510 
2511     /**
2512      * Returns true if Background Read feature is disabled or {@link
2513      * HealthPermissions#READ_HEALTH_DATA_IN_BACKGROUND} permission is not granted for the provided
2514      * uid and pid, false otherwise.
2515      */
isOnlySelfReadInBackgroundAllowed(int uid, int pid)2516     private boolean isOnlySelfReadInBackgroundAllowed(int uid, int pid) {
2517         return !mDeviceConfigManager.isBackgroundReadFeatureEnabled()
2518                 || !isPermissionGranted(READ_HEALTH_DATA_IN_BACKGROUND, uid, pid);
2519     }
2520 
isPermissionGranted(String permission, int uid, int pid)2521     private boolean isPermissionGranted(String permission, int uid, int pid) {
2522         return mContext.checkPermission(permission, pid, uid) == PERMISSION_GRANTED;
2523     }
2524 
enforceBinderUidIsSameAsAttributionSourceUid( int binderUid, int attributionSourceUid)2525     private void enforceBinderUidIsSameAsAttributionSourceUid(
2526             int binderUid, int attributionSourceUid) {
2527         if (binderUid != attributionSourceUid) {
2528             throw new SecurityException("Binder uid must be equal to attribution source uid.");
2529         }
2530     }
2531 
logRecordTypeSpecificUpsertMetrics( @onNull List<RecordInternal<?>> recordInternals, @NonNull String packageName)2532     private void logRecordTypeSpecificUpsertMetrics(
2533             @NonNull List<RecordInternal<?>> recordInternals, @NonNull String packageName) {
2534         checkParamsNonNull(recordInternals, packageName);
2535 
2536         Map<Integer, List<RecordInternal<?>>> recordTypeToRecordInternals =
2537                 getRecordTypeToListOfRecords(recordInternals);
2538         for (Entry<Integer, List<RecordInternal<?>>> recordTypeToRecordInternalsEntry :
2539                 recordTypeToRecordInternals.entrySet()) {
2540             RecordHelper<?> recordHelper =
2541                     RecordHelperProvider.getRecordHelper(recordTypeToRecordInternalsEntry.getKey());
2542             recordHelper.logUpsertMetrics(recordTypeToRecordInternalsEntry.getValue(), packageName);
2543         }
2544     }
2545 
logRecordTypeSpecificReadMetrics( @onNull List<RecordInternal<?>> recordInternals, @NonNull String packageName)2546     private void logRecordTypeSpecificReadMetrics(
2547             @NonNull List<RecordInternal<?>> recordInternals, @NonNull String packageName) {
2548         checkParamsNonNull(recordInternals, packageName);
2549 
2550         Map<Integer, List<RecordInternal<?>>> recordTypeToRecordInternals =
2551                 getRecordTypeToListOfRecords(recordInternals);
2552         for (Entry<Integer, List<RecordInternal<?>>> recordTypeToRecordInternalsEntry :
2553                 recordTypeToRecordInternals.entrySet()) {
2554             RecordHelper<?> recordHelper =
2555                     RecordHelperProvider.getRecordHelper(recordTypeToRecordInternalsEntry.getKey());
2556             recordHelper.logReadMetrics(recordTypeToRecordInternalsEntry.getValue(), packageName);
2557         }
2558     }
2559 
getRecordTypeToListOfRecords( List<RecordInternal<?>> recordInternals)2560     private Map<Integer, List<RecordInternal<?>>> getRecordTypeToListOfRecords(
2561             List<RecordInternal<?>> recordInternals) {
2562 
2563         return recordInternals.stream()
2564                 .collect(Collectors.groupingBy(RecordInternal::getRecordType));
2565     }
2566 
throwSecurityException(String message)2567     private void throwSecurityException(String message) {
2568         throw new SecurityException(message);
2569     }
2570 
throwExceptionIfDataSyncInProgress()2571     private void throwExceptionIfDataSyncInProgress() {
2572         if (isDataSyncInProgress()) {
2573             throw new HealthConnectException(
2574                     HealthConnectException.ERROR_DATA_SYNC_IN_PROGRESS,
2575                     "Storage data sync in progress. API calls are blocked");
2576         }
2577     }
2578 
2579     /**
2580      * Throws an IllegalState Exception if data migration or restore is in process. This is only
2581      * used by HealthConnect synchronous APIs as {@link HealthConnectException} is lost between
2582      * processes on synchronous APIs and can only be returned to the caller for the APIs with a
2583      * callback.
2584      */
throwIllegalStateExceptionIfDataSyncInProgress()2585     private void throwIllegalStateExceptionIfDataSyncInProgress() {
2586         if (isDataSyncInProgress()) {
2587             throw new IllegalStateException("Storage data sync in progress. API calls are blocked");
2588         }
2589     }
2590 
postDeleteTasks(List<Integer> recordTypeIdsToDelete)2591     private void postDeleteTasks(List<Integer> recordTypeIdsToDelete) {
2592         if (recordTypeIdsToDelete != null && !recordTypeIdsToDelete.isEmpty()) {
2593             mAppInfoHelper.syncAppInfoRecordTypesUsed(new HashSet<>(recordTypeIdsToDelete));
2594             ActivityDateHelper.reSyncByRecordTypeIds(recordTypeIdsToDelete);
2595         }
2596     }
2597 
tryAndReturnResult( IEmptyResponseCallback callback, HealthConnectServiceLogger.Builder logger)2598     private static void tryAndReturnResult(
2599             IEmptyResponseCallback callback, HealthConnectServiceLogger.Builder logger) {
2600         try {
2601             callback.onResult();
2602             logger.setHealthDataServiceApiStatusSuccess();
2603         } catch (RemoteException e) {
2604             Slog.e(TAG, "Remote call failed", e);
2605             logger.setHealthDataServiceApiStatusError(ERROR_INTERNAL);
2606         }
2607     }
2608 
tryAndReturnResult( IInsertRecordsResponseCallback callback, List<String> uuids, HealthConnectServiceLogger.Builder logger)2609     private static void tryAndReturnResult(
2610             IInsertRecordsResponseCallback callback,
2611             List<String> uuids,
2612             HealthConnectServiceLogger.Builder logger) {
2613         try {
2614             callback.onResult(new InsertRecordsResponseParcel(uuids));
2615             logger.setHealthDataServiceApiStatusSuccess();
2616         } catch (RemoteException e) {
2617             Slog.e(TAG, "Remote call failed", e);
2618             logger.setHealthDataServiceApiStatusError(ERROR_INTERNAL);
2619         }
2620     }
2621 
tryAndThrowException( @onNull IInsertRecordsResponseCallback callback, @NonNull Exception exception, @HealthConnectException.ErrorCode int errorCode)2622     private static void tryAndThrowException(
2623             @NonNull IInsertRecordsResponseCallback callback,
2624             @NonNull Exception exception,
2625             @HealthConnectException.ErrorCode int errorCode) {
2626         try {
2627             callback.onError(
2628                     new HealthConnectExceptionParcel(
2629                             new HealthConnectException(errorCode, exception.toString())));
2630         } catch (RemoteException e) {
2631             Log.e(TAG, "Unable to send result to the callback", e);
2632         }
2633     }
2634 
tryAndThrowException( @onNull IAggregateRecordsResponseCallback callback, @NonNull Exception exception, @HealthConnectException.ErrorCode int errorCode)2635     private static void tryAndThrowException(
2636             @NonNull IAggregateRecordsResponseCallback callback,
2637             @NonNull Exception exception,
2638             @HealthConnectException.ErrorCode int errorCode) {
2639         try {
2640             callback.onError(
2641                     new HealthConnectExceptionParcel(
2642                             new HealthConnectException(errorCode, exception.toString())));
2643         } catch (RemoteException e) {
2644             Log.e(TAG, "Unable to send result to the callback", e);
2645         }
2646     }
2647 
tryAndThrowException( @onNull IReadRecordsResponseCallback callback, @NonNull Exception exception, @HealthConnectException.ErrorCode int errorCode)2648     private static void tryAndThrowException(
2649             @NonNull IReadRecordsResponseCallback callback,
2650             @NonNull Exception exception,
2651             @HealthConnectException.ErrorCode int errorCode) {
2652         try {
2653             callback.onError(
2654                     new HealthConnectExceptionParcel(
2655                             new HealthConnectException(errorCode, exception.toString())));
2656         } catch (RemoteException e) {
2657             Log.e(TAG, "Unable to send result to the callback", e);
2658         }
2659     }
2660 
tryAndThrowException( @onNull IActivityDatesResponseCallback callback, @NonNull Exception exception, @HealthConnectException.ErrorCode int errorCode)2661     private static void tryAndThrowException(
2662             @NonNull IActivityDatesResponseCallback callback,
2663             @NonNull Exception exception,
2664             @HealthConnectException.ErrorCode int errorCode) {
2665         try {
2666             callback.onError(
2667                     new HealthConnectExceptionParcel(
2668                             new HealthConnectException(errorCode, exception.toString())));
2669         } catch (RemoteException e) {
2670             Log.e(TAG, "Unable to send result to the callback", e);
2671         }
2672     }
2673 
tryAndThrowException( @onNull IGetChangeLogTokenCallback callback, @NonNull Exception exception, @HealthConnectException.ErrorCode int errorCode)2674     private static void tryAndThrowException(
2675             @NonNull IGetChangeLogTokenCallback callback,
2676             @NonNull Exception exception,
2677             @HealthConnectException.ErrorCode int errorCode) {
2678         try {
2679             callback.onError(
2680                     new HealthConnectExceptionParcel(
2681                             new HealthConnectException(errorCode, exception.toString())));
2682         } catch (RemoteException e) {
2683             Log.e(TAG, "Unable to send result to the callback", e);
2684         }
2685     }
2686 
tryAndThrowException( @onNull IAccessLogsResponseCallback callback, @NonNull Exception exception, @HealthConnectException.ErrorCode int errorCode)2687     private static void tryAndThrowException(
2688             @NonNull IAccessLogsResponseCallback callback,
2689             @NonNull Exception exception,
2690             @HealthConnectException.ErrorCode int errorCode) {
2691         try {
2692             callback.onError(
2693                     new HealthConnectExceptionParcel(
2694                             new HealthConnectException(errorCode, exception.toString())));
2695         } catch (RemoteException e) {
2696             Log.e(TAG, "Unable to send result to the callback", e);
2697         }
2698     }
2699 
tryAndThrowException( @onNull IEmptyResponseCallback callback, @NonNull Exception exception, @HealthConnectException.ErrorCode int errorCode)2700     private static void tryAndThrowException(
2701             @NonNull IEmptyResponseCallback callback,
2702             @NonNull Exception exception,
2703             @HealthConnectException.ErrorCode int errorCode) {
2704         try {
2705             callback.onError(
2706                     new HealthConnectExceptionParcel(
2707                             new HealthConnectException(errorCode, exception.toString())));
2708         } catch (RemoteException e) {
2709             Log.e(TAG, "Unable to send result to the callback", e);
2710         }
2711     }
2712 
tryAndThrowException( @onNull IApplicationInfoResponseCallback callback, @NonNull Exception exception, @HealthConnectException.ErrorCode int errorCode)2713     private static void tryAndThrowException(
2714             @NonNull IApplicationInfoResponseCallback callback,
2715             @NonNull Exception exception,
2716             @HealthConnectException.ErrorCode int errorCode) {
2717         try {
2718             callback.onError(
2719                     new HealthConnectExceptionParcel(
2720                             new HealthConnectException(errorCode, exception.toString())));
2721         } catch (RemoteException e) {
2722             Log.e(TAG, "Unable to send result to the callback", e);
2723         }
2724     }
2725 
tryAndThrowException( @onNull IChangeLogsResponseCallback callback, @NonNull Exception exception, @HealthConnectException.ErrorCode int errorCode)2726     private static void tryAndThrowException(
2727             @NonNull IChangeLogsResponseCallback callback,
2728             @NonNull Exception exception,
2729             @HealthConnectException.ErrorCode int errorCode) {
2730         try {
2731             callback.onError(
2732                     new HealthConnectExceptionParcel(
2733                             new HealthConnectException(errorCode, exception.toString())));
2734         } catch (RemoteException e) {
2735             Log.e(TAG, "Unable to send result to the callback", e);
2736         }
2737     }
2738 
tryAndThrowException( @onNull IRecordTypeInfoResponseCallback callback, @NonNull Exception exception, @HealthConnectException.ErrorCode int errorCode)2739     private static void tryAndThrowException(
2740             @NonNull IRecordTypeInfoResponseCallback callback,
2741             @NonNull Exception exception,
2742             @HealthConnectException.ErrorCode int errorCode) {
2743         try {
2744             callback.onError(
2745                     new HealthConnectExceptionParcel(
2746                             new HealthConnectException(errorCode, exception.toString())));
2747         } catch (RemoteException e) {
2748             Log.e(TAG, "Unable to send result to the callback", e);
2749         }
2750     }
2751 
tryAndThrowException( @onNull IGetPriorityResponseCallback callback, @NonNull Exception exception, @HealthConnectException.ErrorCode int errorCode)2752     private static void tryAndThrowException(
2753             @NonNull IGetPriorityResponseCallback callback,
2754             @NonNull Exception exception,
2755             @HealthConnectException.ErrorCode int errorCode) {
2756         try {
2757             callback.onError(
2758                     new HealthConnectExceptionParcel(
2759                             new HealthConnectException(errorCode, exception.toString())));
2760         } catch (RemoteException e) {
2761             Log.e(TAG, "Unable to send result to the callback", e);
2762         }
2763     }
2764 
tryAndThrowException( @onNull IMigrationCallback callback, @NonNull Exception exception, @MigrationException.ErrorCode int errorCode, @Nullable String failedEntityId)2765     private static void tryAndThrowException(
2766             @NonNull IMigrationCallback callback,
2767             @NonNull Exception exception,
2768             @MigrationException.ErrorCode int errorCode,
2769             @Nullable String failedEntityId) {
2770         try {
2771             callback.onError(
2772                     new MigrationException(exception.toString(), errorCode, failedEntityId));
2773         } catch (RemoteException e) {
2774             Log.e(TAG, "Unable to send result to the callback", e);
2775         }
2776     }
2777 
tryAndThrowException( @onNull IQueryDocumentProvidersCallback callback, @NonNull Exception exception, @HealthConnectException.ErrorCode int errorCode)2778     private static void tryAndThrowException(
2779             @NonNull IQueryDocumentProvidersCallback callback,
2780             @NonNull Exception exception,
2781             @HealthConnectException.ErrorCode int errorCode) {
2782         try {
2783             callback.onError(
2784                     new HealthConnectExceptionParcel(
2785                             new HealthConnectException(errorCode, exception.toString())));
2786         } catch (RemoteException e) {
2787             Log.e(TAG, "Unable to send result to the callback", e);
2788         }
2789     }
2790 
tryAndThrowException( @onNull IScheduledExportStatusCallback callback, @NonNull Exception exception, @HealthConnectException.ErrorCode int errorCode)2791     private static void tryAndThrowException(
2792             @NonNull IScheduledExportStatusCallback callback,
2793             @NonNull Exception exception,
2794             @HealthConnectException.ErrorCode int errorCode) {
2795         try {
2796             callback.onError(
2797                     new HealthConnectExceptionParcel(
2798                             new HealthConnectException(errorCode, exception.toString())));
2799         } catch (RemoteException e) {
2800             Log.e(TAG, "Unable to send result to the callback", e);
2801         }
2802     }
2803 
tryAndThrowException( @onNull IImportStatusCallback callback, @NonNull Exception exception, @HealthConnectException.ErrorCode int errorCode)2804     private static void tryAndThrowException(
2805             @NonNull IImportStatusCallback callback,
2806             @NonNull Exception exception,
2807             @HealthConnectException.ErrorCode int errorCode) {
2808         try {
2809             callback.onError(
2810                     new HealthConnectExceptionParcel(
2811                             new HealthConnectException(errorCode, exception.toString())));
2812         } catch (RemoteException e) {
2813             Log.e(TAG, "Unable to send result to the callback", e);
2814         }
2815     }
2816 
tryAndThrowException( @onNull IReadMedicalResourcesResponseCallback callback, @NonNull Exception exception, @HealthConnectException.ErrorCode int errorCode)2817     private static void tryAndThrowException(
2818             @NonNull IReadMedicalResourcesResponseCallback callback,
2819             @NonNull Exception exception,
2820             @HealthConnectException.ErrorCode int errorCode) {
2821         try {
2822             callback.onError(
2823                     new HealthConnectExceptionParcel(
2824                             new HealthConnectException(errorCode, exception.toString())));
2825         } catch (RemoteException e) {
2826             Log.e(TAG, "Unable to send ReadMedicalResourcesResponse to the callback", e);
2827         }
2828     }
2829 
checkParamsNonNull(Object... params)2830     private static void checkParamsNonNull(Object... params) {
2831         for (Object param : params) {
2832             Objects.requireNonNull(param);
2833         }
2834     }
2835 }
2836