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