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.HealthPermissions.MANAGE_HEALTH_DATA_PERMISSION; 26 27 import static com.android.server.healthconnect.logging.HealthConnectServiceLogger.ApiMethods.DELETE_DATA; 28 import static com.android.server.healthconnect.logging.HealthConnectServiceLogger.ApiMethods.GET_CHANGES; 29 import static com.android.server.healthconnect.logging.HealthConnectServiceLogger.ApiMethods.GET_CHANGES_TOKEN; 30 import static com.android.server.healthconnect.logging.HealthConnectServiceLogger.ApiMethods.INSERT_DATA; 31 import static com.android.server.healthconnect.logging.HealthConnectServiceLogger.ApiMethods.READ_AGGREGATED_DATA; 32 import static com.android.server.healthconnect.logging.HealthConnectServiceLogger.ApiMethods.READ_DATA; 33 import static com.android.server.healthconnect.logging.HealthConnectServiceLogger.ApiMethods.UPDATE_DATA; 34 35 import android.Manifest; 36 import android.annotation.NonNull; 37 import android.annotation.Nullable; 38 import android.content.AttributionSource; 39 import android.content.Context; 40 import android.content.Intent; 41 import android.content.pm.PackageManager; 42 import android.content.pm.ResolveInfo; 43 import android.database.sqlite.SQLiteException; 44 import android.health.connect.Constants; 45 import android.health.connect.FetchDataOriginsPriorityOrderResponse; 46 import android.health.connect.HealthConnectDataState; 47 import android.health.connect.HealthConnectException; 48 import android.health.connect.HealthConnectManager; 49 import android.health.connect.HealthConnectManager.DataDownloadState; 50 import android.health.connect.HealthDataCategory; 51 import android.health.connect.HealthPermissions; 52 import android.health.connect.RecordTypeInfoResponse; 53 import android.health.connect.accesslog.AccessLog; 54 import android.health.connect.accesslog.AccessLogsResponseParcel; 55 import android.health.connect.aidl.ActivityDatesRequestParcel; 56 import android.health.connect.aidl.ActivityDatesResponseParcel; 57 import android.health.connect.aidl.AggregateDataRequestParcel; 58 import android.health.connect.aidl.ApplicationInfoResponseParcel; 59 import android.health.connect.aidl.DeleteUsingFiltersRequestParcel; 60 import android.health.connect.aidl.GetPriorityResponseParcel; 61 import android.health.connect.aidl.HealthConnectExceptionParcel; 62 import android.health.connect.aidl.IAccessLogsResponseCallback; 63 import android.health.connect.aidl.IActivityDatesResponseCallback; 64 import android.health.connect.aidl.IAggregateRecordsResponseCallback; 65 import android.health.connect.aidl.IApplicationInfoResponseCallback; 66 import android.health.connect.aidl.IChangeLogsResponseCallback; 67 import android.health.connect.aidl.IDataStagingFinishedCallback; 68 import android.health.connect.aidl.IEmptyResponseCallback; 69 import android.health.connect.aidl.IGetChangeLogTokenCallback; 70 import android.health.connect.aidl.IGetHealthConnectDataStateCallback; 71 import android.health.connect.aidl.IGetHealthConnectMigrationUiStateCallback; 72 import android.health.connect.aidl.IGetPriorityResponseCallback; 73 import android.health.connect.aidl.IHealthConnectService; 74 import android.health.connect.aidl.IInsertRecordsResponseCallback; 75 import android.health.connect.aidl.IMigrationCallback; 76 import android.health.connect.aidl.IReadRecordsResponseCallback; 77 import android.health.connect.aidl.IRecordTypeInfoResponseCallback; 78 import android.health.connect.aidl.InsertRecordsResponseParcel; 79 import android.health.connect.aidl.ReadRecordsRequestParcel; 80 import android.health.connect.aidl.ReadRecordsResponseParcel; 81 import android.health.connect.aidl.RecordIdFiltersParcel; 82 import android.health.connect.aidl.RecordTypeInfoResponseParcel; 83 import android.health.connect.aidl.RecordsParcel; 84 import android.health.connect.aidl.UpdatePriorityRequestParcel; 85 import android.health.connect.changelog.ChangeLogTokenRequest; 86 import android.health.connect.changelog.ChangeLogTokenResponse; 87 import android.health.connect.changelog.ChangeLogsRequest; 88 import android.health.connect.changelog.ChangeLogsResponse; 89 import android.health.connect.changelog.ChangeLogsResponse.DeletedLog; 90 import android.health.connect.datatypes.AppInfo; 91 import android.health.connect.datatypes.DataOrigin; 92 import android.health.connect.datatypes.Record; 93 import android.health.connect.internal.datatypes.RecordInternal; 94 import android.health.connect.internal.datatypes.utils.AggregationTypeIdMapper; 95 import android.health.connect.internal.datatypes.utils.RecordMapper; 96 import android.health.connect.internal.datatypes.utils.RecordTypePermissionCategoryMapper; 97 import android.health.connect.migration.HealthConnectMigrationUiState; 98 import android.health.connect.migration.MigrationEntityParcel; 99 import android.health.connect.migration.MigrationException; 100 import android.health.connect.ratelimiter.RateLimiter; 101 import android.health.connect.ratelimiter.RateLimiter.QuotaCategory; 102 import android.health.connect.ratelimiter.RateLimiterException; 103 import android.health.connect.restore.BackupFileNamesSet; 104 import android.health.connect.restore.StageRemoteDataException; 105 import android.health.connect.restore.StageRemoteDataRequest; 106 import android.os.Binder; 107 import android.os.ParcelFileDescriptor; 108 import android.os.Process; 109 import android.os.RemoteException; 110 import android.os.Trace; 111 import android.os.UserHandle; 112 import android.permission.PermissionManager; 113 import android.util.ArrayMap; 114 import android.util.ArraySet; 115 import android.util.Log; 116 import android.util.Pair; 117 import android.util.Slog; 118 119 import com.android.internal.annotations.VisibleForTesting; 120 import com.android.server.LocalManagerRegistry; 121 import com.android.server.appop.AppOpsManagerLocal; 122 import com.android.server.healthconnect.backuprestore.BackupRestore; 123 import com.android.server.healthconnect.logging.HealthConnectServiceLogger; 124 import com.android.server.healthconnect.migration.DataMigrationManager; 125 import com.android.server.healthconnect.migration.MigrationCleaner; 126 import com.android.server.healthconnect.migration.MigrationStateManager; 127 import com.android.server.healthconnect.migration.MigrationUiStateManager; 128 import com.android.server.healthconnect.migration.PriorityMigrationHelper; 129 import com.android.server.healthconnect.permission.DataPermissionEnforcer; 130 import com.android.server.healthconnect.permission.FirstGrantTimeManager; 131 import com.android.server.healthconnect.permission.HealthConnectPermissionHelper; 132 import com.android.server.healthconnect.storage.AutoDeleteService; 133 import com.android.server.healthconnect.storage.TransactionManager; 134 import com.android.server.healthconnect.storage.datatypehelpers.AccessLogsHelper; 135 import com.android.server.healthconnect.storage.datatypehelpers.ActivityDateHelper; 136 import com.android.server.healthconnect.storage.datatypehelpers.AppInfoHelper; 137 import com.android.server.healthconnect.storage.datatypehelpers.ChangeLogsHelper; 138 import com.android.server.healthconnect.storage.datatypehelpers.ChangeLogsRequestHelper; 139 import com.android.server.healthconnect.storage.datatypehelpers.DeviceInfoHelper; 140 import com.android.server.healthconnect.storage.datatypehelpers.HealthDataCategoryPriorityHelper; 141 import com.android.server.healthconnect.storage.datatypehelpers.MigrationEntityHelper; 142 import com.android.server.healthconnect.storage.datatypehelpers.RecordHelper; 143 import com.android.server.healthconnect.storage.request.AggregateTransactionRequest; 144 import com.android.server.healthconnect.storage.request.DeleteTransactionRequest; 145 import com.android.server.healthconnect.storage.request.ReadTransactionRequest; 146 import com.android.server.healthconnect.storage.request.UpsertTransactionRequest; 147 import com.android.server.healthconnect.storage.utils.RecordHelperProvider; 148 149 import java.io.IOException; 150 import java.time.Instant; 151 import java.time.LocalDate; 152 import java.util.ArrayList; 153 import java.util.Collections; 154 import java.util.HashSet; 155 import java.util.List; 156 import java.util.Map; 157 import java.util.Map.Entry; 158 import java.util.Objects; 159 import java.util.Set; 160 import java.util.concurrent.atomic.AtomicBoolean; 161 import java.util.stream.Collectors; 162 163 /** 164 * IHealthConnectService's implementation 165 * 166 * @hide 167 */ 168 final class HealthConnectServiceImpl extends IHealthConnectService.Stub { 169 private static final String TAG = "HealthConnectService"; 170 // Permission for test api for deleting staged data 171 private static final String DELETE_STAGED_HEALTH_CONNECT_REMOTE_DATA_PERMISSION = 172 "android.permission.DELETE_STAGED_HEALTH_CONNECT_REMOTE_DATA"; 173 // Allows an application to act as a backup inter-agent to send and receive HealthConnect data 174 private static final String HEALTH_CONNECT_BACKUP_INTER_AGENT_PERMISSION = 175 "android.permission.HEALTH_CONNECT_BACKUP_INTER_AGENT"; 176 177 private static final String TAG_INSERT = "HealthConnectInsert"; 178 private static final String TAG_READ = "HealthConnectRead"; 179 private static final String TAG_GRANT_PERMISSION = "HealthConnectGrantReadPermissions"; 180 private static final String TAG_READ_PERMISSION = "HealthConnectReadPermission"; 181 private static final String TAG_INSERT_SUBTASKS = "HealthConnectInsertSubtasks"; 182 183 private static final String TAG_DELETE_SUBTASKS = "HealthConnectDeleteSubtasks"; 184 private static final String TAG_READ_SUBTASKS = "HealthConnectReadSubtasks"; 185 private static final int TRACE_TAG_INSERT = TAG_INSERT.hashCode(); 186 private static final int TRACE_TAG_READ = TAG_READ.hashCode(); 187 private static final int TRACE_TAG_GRANT_PERMISSION = TAG_GRANT_PERMISSION.hashCode(); 188 private static final int TRACE_TAG_READ_PERMISSION = TAG_READ_PERMISSION.hashCode(); 189 private static final int TRACE_TAG_INSERT_SUBTASKS = TAG_INSERT_SUBTASKS.hashCode(); 190 private static final int TRACE_TAG_DELETE_SUBTASKS = TAG_DELETE_SUBTASKS.hashCode(); 191 private static final int TRACE_TAG_READ_SUBTASKS = TAG_READ_SUBTASKS.hashCode(); 192 193 private final TransactionManager mTransactionManager; 194 private final HealthConnectPermissionHelper mPermissionHelper; 195 private final FirstGrantTimeManager mFirstGrantTimeManager; 196 private final Context mContext; 197 private final PermissionManager mPermissionManager; 198 199 private final BackupRestore mBackupRestore; 200 private final MigrationStateManager mMigrationStateManager; 201 202 private final DataPermissionEnforcer mDataPermissionEnforcer; 203 204 private final AppOpsManagerLocal mAppOpsManagerLocal; 205 private final MigrationUiStateManager mMigrationUiStateManager; 206 207 private volatile UserHandle mCurrentForegroundUser; 208 HealthConnectServiceImpl( TransactionManager transactionManager, HealthConnectPermissionHelper permissionHelper, MigrationCleaner migrationCleaner, FirstGrantTimeManager firstGrantTimeManager, MigrationStateManager migrationStateManager, MigrationUiStateManager migrationUiStateManager, Context context)209 HealthConnectServiceImpl( 210 TransactionManager transactionManager, 211 HealthConnectPermissionHelper permissionHelper, 212 MigrationCleaner migrationCleaner, 213 FirstGrantTimeManager firstGrantTimeManager, 214 MigrationStateManager migrationStateManager, 215 MigrationUiStateManager migrationUiStateManager, 216 Context context) { 217 mTransactionManager = transactionManager; 218 mPermissionHelper = permissionHelper; 219 mFirstGrantTimeManager = firstGrantTimeManager; 220 mContext = context; 221 mCurrentForegroundUser = context.getUser(); 222 mPermissionManager = mContext.getSystemService(PermissionManager.class); 223 mMigrationStateManager = migrationStateManager; 224 mDataPermissionEnforcer = new DataPermissionEnforcer(mPermissionManager, mContext); 225 mAppOpsManagerLocal = LocalManagerRegistry.getManager(AppOpsManagerLocal.class); 226 mBackupRestore = 227 new BackupRestore(mFirstGrantTimeManager, mMigrationStateManager, mContext); 228 mMigrationUiStateManager = migrationUiStateManager; 229 migrationCleaner.attachTo(migrationStateManager); 230 mMigrationUiStateManager.attachTo(migrationStateManager); 231 } 232 onUserSwitching(UserHandle currentForegroundUser)233 public void onUserSwitching(UserHandle currentForegroundUser) { 234 mCurrentForegroundUser = currentForegroundUser; 235 mBackupRestore.setupForUser(currentForegroundUser); 236 } 237 238 @Override grantHealthPermission( @onNull String packageName, @NonNull String permissionName, @NonNull UserHandle user)239 public void grantHealthPermission( 240 @NonNull String packageName, @NonNull String permissionName, @NonNull UserHandle user) { 241 throwIllegalStateExceptionIfDataSyncInProgress(); 242 Trace.traceBegin(TRACE_TAG_GRANT_PERMISSION, TAG_GRANT_PERMISSION); 243 mPermissionHelper.grantHealthPermission(packageName, permissionName, user); 244 Trace.traceEnd(TRACE_TAG_GRANT_PERMISSION); 245 } 246 247 @Override revokeHealthPermission( @onNull String packageName, @NonNull String permissionName, @Nullable String reason, @NonNull UserHandle user)248 public void revokeHealthPermission( 249 @NonNull String packageName, 250 @NonNull String permissionName, 251 @Nullable String reason, 252 @NonNull UserHandle user) { 253 throwIllegalStateExceptionIfDataSyncInProgress(); 254 mPermissionHelper.revokeHealthPermission(packageName, permissionName, reason, user); 255 } 256 257 @Override revokeAllHealthPermissions( @onNull String packageName, @Nullable String reason, @NonNull UserHandle user)258 public void revokeAllHealthPermissions( 259 @NonNull String packageName, @Nullable String reason, @NonNull UserHandle user) { 260 throwIllegalStateExceptionIfDataSyncInProgress(); 261 mPermissionHelper.revokeAllHealthPermissions(packageName, reason, user); 262 } 263 264 @Override getGrantedHealthPermissions( @onNull String packageName, @NonNull UserHandle user)265 public List<String> getGrantedHealthPermissions( 266 @NonNull String packageName, @NonNull UserHandle user) { 267 throwIllegalStateExceptionIfDataSyncInProgress(); 268 Trace.traceBegin(TRACE_TAG_READ_PERMISSION, TAG_READ_PERMISSION); 269 List<String> grantedPermissions = 270 mPermissionHelper.getGrantedHealthPermissions(packageName, user); 271 Trace.traceEnd(TRACE_TAG_READ_PERMISSION); 272 return grantedPermissions; 273 } 274 275 @Override getHistoricalAccessStartDateInMilliseconds( @onNull String packageName, @NonNull UserHandle userHandle)276 public long getHistoricalAccessStartDateInMilliseconds( 277 @NonNull String packageName, @NonNull UserHandle userHandle) { 278 throwIllegalStateExceptionIfDataSyncInProgress(); 279 Instant date = mPermissionHelper.getHealthDataStartDateAccess(packageName, userHandle); 280 if (date == null) { 281 return Constants.DEFAULT_LONG; 282 } else { 283 return date.toEpochMilli(); 284 } 285 } 286 287 /** 288 * Inserts {@code recordsParcel} into the HealthConnect database. 289 * 290 * @param recordsParcel parcel for list of records to be inserted. 291 * @param callback Callback to receive result of performing this operation. The keys returned in 292 * {@link InsertRecordsResponseParcel} are the unique IDs of the input records. The values 293 * are in same order as {@code record}. In case of an error or a permission failure the 294 * HealthConnect service, {@link IInsertRecordsResponseCallback#onError} will be invoked 295 * with a {@link HealthConnectExceptionParcel}. 296 */ 297 @Override insertRecords( @onNull AttributionSource attributionSource, @NonNull RecordsParcel recordsParcel, @NonNull IInsertRecordsResponseCallback callback)298 public void insertRecords( 299 @NonNull AttributionSource attributionSource, 300 @NonNull RecordsParcel recordsParcel, 301 @NonNull IInsertRecordsResponseCallback callback) { 302 final int uid = Binder.getCallingUid(); 303 final int pid = Binder.getCallingPid(); 304 final UserHandle userHandle = Binder.getCallingUserHandle(); 305 final HealthConnectServiceLogger.Builder builder = 306 new HealthConnectServiceLogger.Builder(false, INSERT_DATA) 307 .setPackageName(attributionSource.getPackageName()); 308 309 HealthConnectThreadScheduler.schedule( 310 mContext, 311 () -> { 312 try { 313 enforceIsForegroundUser(userHandle); 314 verifyPackageNameFromUid(uid, attributionSource); 315 if (hasDataManagementPermission(uid, pid)) { 316 throw new SecurityException( 317 "Apps with android.permission.MANAGE_HEALTH_DATA permission are" 318 + " not allowed to insert records"); 319 } 320 enforceMemoryRateLimit( 321 recordsParcel.getRecordsSize(), 322 recordsParcel.getRecordsChunkSize()); 323 final List<RecordInternal<?>> recordInternals = recordsParcel.getRecords(); 324 builder.setNumberOfRecords(recordInternals.size()); 325 throwExceptionIfDataSyncInProgress(); 326 mDataPermissionEnforcer.enforceRecordsWritePermissions( 327 recordInternals, attributionSource); 328 boolean isInForeground = mAppOpsManagerLocal.isUidInForeground(uid); 329 tryAcquireApiCallQuota( 330 uid, QuotaCategory.QUOTA_CATEGORY_WRITE, isInForeground, builder); 331 Trace.traceBegin(TRACE_TAG_INSERT, TAG_INSERT); 332 UpsertTransactionRequest insertRequest = 333 new UpsertTransactionRequest( 334 attributionSource.getPackageName(), 335 recordInternals, 336 mContext, 337 /* isInsertRequest */ true, 338 mDataPermissionEnforcer 339 .collectExtraWritePermissionStateMapping( 340 recordInternals, attributionSource)); 341 List<String> uuids = mTransactionManager.insertAll(insertRequest); 342 tryAndReturnResult(callback, uuids, builder); 343 344 HealthConnectThreadScheduler.scheduleInternalTask( 345 () -> postInsertTasks(attributionSource, recordsParcel)); 346 347 finishDataDeliveryWriteRecords(recordInternals, attributionSource); 348 logRecordTypeSpecificUpsertMetrics( 349 recordInternals, attributionSource.getPackageName()); 350 builder.setDataTypesFromRecordInternals(recordInternals); 351 } catch (SQLiteException sqLiteException) { 352 builder.setHealthDataServiceApiStatusError(HealthConnectException.ERROR_IO); 353 Slog.e(TAG, "SQLiteException: ", sqLiteException); 354 tryAndThrowException( 355 callback, sqLiteException, HealthConnectException.ERROR_IO); 356 } catch (SecurityException securityException) { 357 builder.setHealthDataServiceApiStatusError(ERROR_SECURITY); 358 Slog.e(TAG, "SecurityException: ", securityException); 359 tryAndThrowException(callback, securityException, ERROR_SECURITY); 360 } catch (HealthConnectException healthConnectException) { 361 builder.setHealthDataServiceApiStatusError( 362 healthConnectException.getErrorCode()); 363 Slog.e(TAG, "HealthConnectException: ", healthConnectException); 364 tryAndThrowException( 365 callback, 366 healthConnectException, 367 healthConnectException.getErrorCode()); 368 } catch (Exception e) { 369 builder.setHealthDataServiceApiStatusError(ERROR_INTERNAL); 370 Slog.e(TAG, "Exception: ", e); 371 tryAndThrowException(callback, e, ERROR_INTERNAL); 372 } finally { 373 Trace.traceEnd(TRACE_TAG_INSERT); 374 builder.build().log(); 375 } 376 }, 377 uid, 378 false); 379 } 380 postInsertTasks( @onNull AttributionSource attributionSource, @NonNull RecordsParcel recordsParcel)381 private void postInsertTasks( 382 @NonNull AttributionSource attributionSource, @NonNull RecordsParcel recordsParcel) { 383 Trace.traceBegin(TRACE_TAG_INSERT_SUBTASKS, TAG_INSERT.concat("PostInsertTasks")); 384 385 ActivityDateHelper.getInstance().insertRecordDate(recordsParcel.getRecords()); 386 Set<Integer> recordsTypesInsertedSet = 387 recordsParcel.getRecords().stream() 388 .map(RecordInternal::getRecordType) 389 .collect(Collectors.toSet()); 390 // Update AppInfo table with the record types of records inserted in the request for the 391 // current package. 392 AppInfoHelper.getInstance() 393 .updateAppInfoRecordTypesUsedOnInsert( 394 recordsTypesInsertedSet, attributionSource.getPackageName()); 395 396 Trace.traceEnd(TRACE_TAG_INSERT_SUBTASKS); 397 } 398 399 /** 400 * Returns aggregation results based on the {@code request} into the HealthConnect database. 401 * 402 * @param request represents the request using which the aggregation is to be performed. 403 * @param callback Callback to receive result of performing this operation. 404 */ aggregateRecords( @onNull AttributionSource attributionSource, AggregateDataRequestParcel request, IAggregateRecordsResponseCallback callback)405 public void aggregateRecords( 406 @NonNull AttributionSource attributionSource, 407 AggregateDataRequestParcel request, 408 IAggregateRecordsResponseCallback callback) { 409 final int uid = Binder.getCallingUid(); 410 final int pid = Binder.getCallingPid(); 411 final UserHandle userHandle = Binder.getCallingUserHandle(); 412 final boolean holdsDataManagementPermission = hasDataManagementPermission(uid, pid); 413 final HealthConnectServiceLogger.Builder builder = 414 new HealthConnectServiceLogger.Builder( 415 holdsDataManagementPermission, READ_AGGREGATED_DATA) 416 .setPackageName(attributionSource.getPackageName()); 417 418 HealthConnectThreadScheduler.schedule( 419 mContext, 420 () -> { 421 try { 422 enforceIsForegroundUser(userHandle); 423 verifyPackageNameFromUid(uid, attributionSource); 424 builder.setNumberOfRecords(request.getAggregateIds().length); 425 throwExceptionIfDataSyncInProgress(); 426 List<Integer> recordTypesToTest = new ArrayList<>(); 427 for (int aggregateId : request.getAggregateIds()) { 428 recordTypesToTest.addAll( 429 AggregationTypeIdMapper.getInstance() 430 .getAggregationTypeFor(aggregateId) 431 .getApplicableRecordTypeIds()); 432 } 433 434 if (!holdsDataManagementPermission) { 435 boolean isInForeground = mAppOpsManagerLocal.isUidInForeground(uid); 436 if (!isInForeground) { 437 throwSecurityException( 438 attributionSource.getPackageName() 439 + "must be in foreground to call aggregate method"); 440 } 441 mDataPermissionEnforcer.enforceRecordIdsReadPermissions( 442 recordTypesToTest, attributionSource); 443 tryAcquireApiCallQuota( 444 uid, 445 RateLimiter.QuotaCategory.QUOTA_CATEGORY_READ, 446 isInForeground, 447 builder); 448 } 449 callback.onResult( 450 new AggregateTransactionRequest( 451 attributionSource.getPackageName(), request) 452 .getAggregateDataResponseParcel()); 453 finishDataDeliveryRead(recordTypesToTest, attributionSource); 454 builder.setDataTypesFromRecordTypes(recordTypesToTest) 455 .setHealthDataServiceApiStatusSuccess(); 456 } catch (SQLiteException sqLiteException) { 457 builder.setHealthDataServiceApiStatusError(HealthConnectException.ERROR_IO); 458 Slog.e(TAG, "SQLiteException: ", sqLiteException); 459 tryAndThrowException( 460 callback, sqLiteException, HealthConnectException.ERROR_IO); 461 } catch (SecurityException securityException) { 462 builder.setHealthDataServiceApiStatusError(ERROR_SECURITY); 463 Slog.e(TAG, "SecurityException: ", securityException); 464 tryAndThrowException(callback, securityException, ERROR_SECURITY); 465 } catch (HealthConnectException healthConnectException) { 466 builder.setHealthDataServiceApiStatusError( 467 healthConnectException.getErrorCode()); 468 Slog.e(TAG, "HealthConnectException: ", healthConnectException); 469 tryAndThrowException( 470 callback, 471 healthConnectException, 472 healthConnectException.getErrorCode()); 473 } catch (Exception e) { 474 builder.setHealthDataServiceApiStatusError(ERROR_INTERNAL); 475 Slog.e(TAG, "Exception: ", e); 476 tryAndThrowException(callback, e, ERROR_INTERNAL); 477 } finally { 478 builder.build().log(); 479 } 480 }, 481 uid, 482 holdsDataManagementPermission); 483 } 484 485 /** 486 * Read records {@code recordsParcel} from HealthConnect database. 487 * 488 * @param request ReadRecordsRequestParcel is parcel for the request object containing {@link 489 * RecordIdFiltersParcel}. 490 * @param callback Callback to receive result of performing this operation. The records are 491 * returned in {@link RecordsParcel} . In case of an error or a permission failure the 492 * HealthConnect service, {@link IReadRecordsResponseCallback#onError} will be invoked with 493 * a {@link HealthConnectExceptionParcel}. 494 */ 495 @Override readRecords( @onNull AttributionSource attributionSource, @NonNull ReadRecordsRequestParcel request, @NonNull IReadRecordsResponseCallback callback)496 public void readRecords( 497 @NonNull AttributionSource attributionSource, 498 @NonNull ReadRecordsRequestParcel request, 499 @NonNull IReadRecordsResponseCallback callback) { 500 final int uid = Binder.getCallingUid(); 501 final int pid = Binder.getCallingPid(); 502 final UserHandle userHandle = Binder.getCallingUserHandle(); 503 final boolean holdsDataManagementPermission = hasDataManagementPermission(uid, pid); 504 final HealthConnectServiceLogger.Builder builder = 505 new HealthConnectServiceLogger.Builder(holdsDataManagementPermission, READ_DATA) 506 .setPackageName(attributionSource.getPackageName()); 507 508 HealthConnectThreadScheduler.schedule( 509 mContext, 510 () -> { 511 try { 512 enforceIsForegroundUser(userHandle); 513 verifyPackageNameFromUid(uid, attributionSource); 514 throwExceptionIfDataSyncInProgress(); 515 AtomicBoolean enforceSelfRead = new AtomicBoolean(); 516 if (!holdsDataManagementPermission) { 517 boolean isInForeground = mAppOpsManagerLocal.isUidInForeground(uid); 518 // If requesting app has only write permission allowed but no read 519 // permission for the record type or if app is not in foreground then 520 // allow to read its own records. 521 enforceSelfRead.set( 522 mDataPermissionEnforcer.enforceReadAccessAndGetEnforceSelfRead( 523 request.getRecordType(), attributionSource) 524 || !isInForeground); 525 if (Constants.DEBUG) { 526 Slog.d( 527 TAG, 528 "Enforce self read for package " 529 + attributionSource.getPackageName() 530 + ":" 531 + enforceSelfRead.get()); 532 } 533 tryAcquireApiCallQuota( 534 uid, 535 QuotaCategory.QUOTA_CATEGORY_READ, 536 isInForeground, 537 builder); 538 } 539 final Map<String, Boolean> extraReadPermsToGrantState = 540 Collections.unmodifiableMap( 541 mDataPermissionEnforcer 542 .collectExtraReadPermissionToStateMapping( 543 request.getRecordType(), 544 attributionSource)); 545 546 Trace.traceBegin(TRACE_TAG_READ, TAG_READ); 547 try { 548 long startDateAccess = request.getStartTime(); 549 if (!holdsDataManagementPermission) { 550 Instant startInstant = 551 mPermissionHelper.getHealthDataStartDateAccess( 552 attributionSource.getPackageName(), userHandle); 553 if (startInstant == null) { 554 throwExceptionIncorrectPermissionState(); 555 } 556 557 // Always set the startDateAccess for local time filter, as for 558 // local date time we use it in conjunction with the time filter 559 // start-time 560 if (request.usesLocalTimeFilter() 561 || startInstant.toEpochMilli() > startDateAccess) { 562 startDateAccess = startInstant.toEpochMilli(); 563 } 564 } 565 Pair<List<RecordInternal<?>>, Long> readRecordsResponse = 566 mTransactionManager.readRecordsAndGetNextToken( 567 new ReadTransactionRequest( 568 attributionSource.getPackageName(), 569 request, 570 startDateAccess, 571 enforceSelfRead.get(), 572 extraReadPermsToGrantState)); 573 builder.setNumberOfRecords(readRecordsResponse.first.size()); 574 long pageToken = 575 request.getRecordIdFiltersParcel() == null 576 ? readRecordsResponse.second 577 : DEFAULT_LONG; 578 if (pageToken != DEFAULT_LONG) { 579 // pagetoken is used here to store sorting order of the result. 580 // An even pagetoken indicate ascending and Odd page token indicate 581 // descending sort order. This detail from page token will be used 582 // in next read request to have same sort order. 583 pageToken = 584 request.isAscending() ? pageToken * 2 : pageToken * 2 + 1; 585 } 586 587 if (Constants.DEBUG) { 588 Slog.d(TAG, "pageToken: " + pageToken); 589 } 590 591 final String packageName = attributionSource.getPackageName(); 592 final List<Integer> recordTypes = 593 Collections.singletonList(request.getRecordType()); 594 // Calls from controller APK should not be recorded in access logs 595 // If an app is reading only its own data then it is not recorded in 596 // access logs. 597 boolean requiresLogging = 598 !holdsDataManagementPermission && !enforceSelfRead.get(); 599 if (requiresLogging) { 600 Trace.traceBegin( 601 TRACE_TAG_READ_SUBTASKS, TAG_READ.concat("AddAccessLog")); 602 AccessLogsHelper.getInstance() 603 .addAccessLog(packageName, recordTypes, READ); 604 Trace.traceEnd(TRACE_TAG_READ_SUBTASKS); 605 } 606 callback.onResult( 607 new ReadRecordsResponseParcel( 608 new RecordsParcel(readRecordsResponse.first), 609 pageToken)); 610 finishDataDeliveryRead(request.getRecordType(), attributionSource); 611 if (requiresLogging) { 612 logRecordTypeSpecificReadMetrics( 613 readRecordsResponse.first, packageName); 614 } 615 builder.setDataTypesFromRecordInternals(readRecordsResponse.first) 616 .setHealthDataServiceApiStatusSuccess(); 617 } catch (TypeNotPresentException exception) { 618 // All the requested package names are not present, so simply 619 // return an empty list 620 if (ReadTransactionRequest.TYPE_NOT_PRESENT_PACKAGE_NAME.equals( 621 exception.typeName())) { 622 if (Constants.DEBUG) { 623 Slog.d( 624 TAG, 625 "No app info recorded for " 626 + attributionSource.getPackageName()); 627 } 628 callback.onResult( 629 new ReadRecordsResponseParcel( 630 new RecordsParcel(new ArrayList<>()), 631 DEFAULT_LONG)); 632 builder.setHealthDataServiceApiStatusSuccess(); 633 } else { 634 builder.setHealthDataServiceApiStatusError( 635 HealthConnectException.ERROR_UNKNOWN); 636 throw exception; 637 } 638 } 639 } catch (SQLiteException sqLiteException) { 640 builder.setHealthDataServiceApiStatusError(HealthConnectException.ERROR_IO); 641 Slog.e(TAG, "SQLiteException: ", sqLiteException); 642 tryAndThrowException( 643 callback, sqLiteException, HealthConnectException.ERROR_IO); 644 } catch (SecurityException securityException) { 645 builder.setHealthDataServiceApiStatusError(ERROR_SECURITY); 646 Slog.e(TAG, "SecurityException: ", securityException); 647 tryAndThrowException(callback, securityException, ERROR_SECURITY); 648 } catch (IllegalStateException illegalStateException) { 649 builder.setHealthDataServiceApiStatusError(ERROR_INTERNAL); 650 Slog.e(TAG, "IllegalStateException: ", illegalStateException); 651 tryAndThrowException(callback, illegalStateException, ERROR_INTERNAL); 652 } catch (HealthConnectException healthConnectException) { 653 builder.setHealthDataServiceApiStatusError( 654 healthConnectException.getErrorCode()); 655 Slog.e(TAG, "HealthConnectException: ", healthConnectException); 656 tryAndThrowException( 657 callback, 658 healthConnectException, 659 healthConnectException.getErrorCode()); 660 } catch (Exception e) { 661 builder.setHealthDataServiceApiStatusError(ERROR_INTERNAL); 662 Slog.e(TAG, "Exception: ", e); 663 tryAndThrowException(callback, e, ERROR_INTERNAL); 664 } finally { 665 Trace.traceEnd(TRACE_TAG_READ); 666 builder.build().log(); 667 } 668 }, 669 uid, 670 holdsDataManagementPermission); 671 } 672 673 /** 674 * Updates {@code recordsParcel} into the HealthConnect database. 675 * 676 * @param recordsParcel parcel for list of records to be updated. 677 * @param callback Callback to receive result of performing this operation. In case of an error 678 * or a permission failure the HealthConnect service, {@link IEmptyResponseCallback#onError} 679 * will be invoked with a {@link HealthConnectException}. 680 */ 681 @Override updateRecords( @onNull AttributionSource attributionSource, @NonNull RecordsParcel recordsParcel, @NonNull IEmptyResponseCallback callback)682 public void updateRecords( 683 @NonNull AttributionSource attributionSource, 684 @NonNull RecordsParcel recordsParcel, 685 @NonNull IEmptyResponseCallback callback) { 686 final int uid = Binder.getCallingUid(); 687 final int pid = Binder.getCallingPid(); 688 final UserHandle userHandle = Binder.getCallingUserHandle(); 689 final HealthConnectServiceLogger.Builder builder = 690 new HealthConnectServiceLogger.Builder(false, UPDATE_DATA) 691 .setPackageName(attributionSource.getPackageName()); 692 HealthConnectThreadScheduler.schedule( 693 mContext, 694 () -> { 695 try { 696 enforceIsForegroundUser(userHandle); 697 verifyPackageNameFromUid(uid, attributionSource); 698 if (hasDataManagementPermission(uid, pid)) { 699 throw new SecurityException( 700 "Apps with android.permission.MANAGE_HEALTH_DATA permission are" 701 + " not allowed to insert records"); 702 } 703 enforceMemoryRateLimit( 704 recordsParcel.getRecordsSize(), 705 recordsParcel.getRecordsChunkSize()); 706 final List<RecordInternal<?>> recordInternals = recordsParcel.getRecords(); 707 builder.setNumberOfRecords(recordInternals.size()); 708 throwExceptionIfDataSyncInProgress(); 709 mDataPermissionEnforcer.enforceRecordsWritePermissions( 710 recordInternals, attributionSource); 711 boolean isInForeground = mAppOpsManagerLocal.isUidInForeground(uid); 712 tryAcquireApiCallQuota( 713 uid, QuotaCategory.QUOTA_CATEGORY_WRITE, isInForeground, builder); 714 UpsertTransactionRequest request = 715 new UpsertTransactionRequest( 716 attributionSource.getPackageName(), 717 recordInternals, 718 mContext, 719 /* isInsertRequest */ false, 720 mDataPermissionEnforcer 721 .collectExtraWritePermissionStateMapping( 722 recordInternals, attributionSource)); 723 mTransactionManager.updateAll(request); 724 tryAndReturnResult(callback, builder); 725 finishDataDeliveryWriteRecords(recordInternals, attributionSource); 726 logRecordTypeSpecificUpsertMetrics( 727 recordInternals, attributionSource.getPackageName()); 728 builder.setDataTypesFromRecordInternals(recordInternals); 729 // Update activity dates table 730 HealthConnectThreadScheduler.scheduleInternalTask( 731 () -> 732 ActivityDateHelper.getInstance() 733 .reSyncByRecordTypeIds( 734 recordInternals.stream() 735 .map(RecordInternal::getRecordType) 736 .toList())); 737 } catch (SecurityException securityException) { 738 builder.setHealthDataServiceApiStatusError(ERROR_SECURITY); 739 tryAndThrowException(callback, securityException, ERROR_SECURITY); 740 } catch (SQLiteException sqLiteException) { 741 builder.setHealthDataServiceApiStatusError(HealthConnectException.ERROR_IO); 742 Slog.e(TAG, "SqlException: ", sqLiteException); 743 tryAndThrowException( 744 callback, sqLiteException, HealthConnectException.ERROR_IO); 745 } catch (IllegalArgumentException illegalArgumentException) { 746 builder.setHealthDataServiceApiStatusError( 747 HealthConnectException.ERROR_INVALID_ARGUMENT); 748 749 Slog.e(TAG, "IllegalArgumentException: ", illegalArgumentException); 750 tryAndThrowException( 751 callback, 752 illegalArgumentException, 753 HealthConnectException.ERROR_INVALID_ARGUMENT); 754 } catch (HealthConnectException healthConnectException) { 755 builder.setHealthDataServiceApiStatusError( 756 healthConnectException.getErrorCode()); 757 Slog.e(TAG, "HealthConnectException: ", healthConnectException); 758 tryAndThrowException( 759 callback, 760 healthConnectException, 761 healthConnectException.getErrorCode()); 762 } catch (Exception e) { 763 builder.setHealthDataServiceApiStatusError(ERROR_INTERNAL); 764 765 Slog.e(TAG, "Exception: ", e); 766 tryAndThrowException(callback, e, ERROR_INTERNAL); 767 } finally { 768 builder.build().log(); 769 } 770 }, 771 uid, 772 false); 773 } 774 775 /** 776 * @see HealthConnectManager#getChangeLogToken 777 */ 778 @Override getChangeLogToken( @onNull AttributionSource attributionSource, @NonNull ChangeLogTokenRequest request, @NonNull IGetChangeLogTokenCallback callback)779 public void getChangeLogToken( 780 @NonNull AttributionSource attributionSource, 781 @NonNull ChangeLogTokenRequest request, 782 @NonNull IGetChangeLogTokenCallback callback) { 783 final int uid = Binder.getCallingUid(); 784 final UserHandle userHandle = Binder.getCallingUserHandle(); 785 final HealthConnectServiceLogger.Builder builder = 786 new HealthConnectServiceLogger.Builder(false, GET_CHANGES_TOKEN) 787 .setPackageName(attributionSource.getPackageName()); 788 HealthConnectThreadScheduler.schedule( 789 mContext, 790 () -> { 791 try { 792 enforceIsForegroundUser(userHandle); 793 verifyPackageNameFromUid(uid, attributionSource); 794 tryAcquireApiCallQuota( 795 uid, 796 QuotaCategory.QUOTA_CATEGORY_READ, 797 mAppOpsManagerLocal.isUidInForeground(uid), 798 builder); 799 throwExceptionIfDataSyncInProgress(); 800 mDataPermissionEnforcer.enforceRecordIdsReadPermissions( 801 request.getRecordTypesList(), attributionSource); 802 callback.onResult( 803 new ChangeLogTokenResponse( 804 ChangeLogsRequestHelper.getInstance() 805 .getToken( 806 attributionSource.getPackageName(), 807 request))); 808 builder.setHealthDataServiceApiStatusSuccess(); 809 } catch (SQLiteException sqLiteException) { 810 builder.setHealthDataServiceApiStatusError(HealthConnectException.ERROR_IO); 811 Slog.e(TAG, "SQLiteException: ", sqLiteException); 812 tryAndThrowException( 813 callback, sqLiteException, HealthConnectException.ERROR_IO); 814 } catch (SecurityException securityException) { 815 builder.setHealthDataServiceApiStatusError(ERROR_SECURITY); 816 Slog.e(TAG, "SecurityException: ", securityException); 817 tryAndThrowException(callback, securityException, ERROR_SECURITY); 818 } catch (HealthConnectException healthConnectException) { 819 builder.setHealthDataServiceApiStatusError( 820 healthConnectException.getErrorCode()); 821 Slog.e(TAG, "HealthConnectException: ", healthConnectException); 822 tryAndThrowException( 823 callback, 824 healthConnectException, 825 healthConnectException.getErrorCode()); 826 } catch (Exception e) { 827 builder.setHealthDataServiceApiStatusError(ERROR_INTERNAL); 828 tryAndThrowException(callback, e, ERROR_INTERNAL); 829 } 830 { 831 builder.build().log(); 832 } 833 }, 834 uid, 835 false); 836 } 837 838 /** 839 * @hide 840 * @see HealthConnectManager#getChangeLogs 841 */ 842 @Override getChangeLogs( @onNull AttributionSource attributionSource, @NonNull ChangeLogsRequest token, IChangeLogsResponseCallback callback)843 public void getChangeLogs( 844 @NonNull AttributionSource attributionSource, 845 @NonNull ChangeLogsRequest token, 846 IChangeLogsResponseCallback callback) { 847 final int uid = Binder.getCallingUid(); 848 final UserHandle userHandle = Binder.getCallingUserHandle(); 849 final HealthConnectServiceLogger.Builder builder = 850 new HealthConnectServiceLogger.Builder(false, GET_CHANGES) 851 .setPackageName(attributionSource.getPackageName()); 852 853 HealthConnectThreadScheduler.schedule( 854 mContext, 855 () -> { 856 try { 857 enforceIsForegroundUser(userHandle); 858 verifyPackageNameFromUid(uid, attributionSource); 859 throwExceptionIfDataSyncInProgress(); 860 ChangeLogsRequestHelper.TokenRequest changeLogsTokenRequest = 861 ChangeLogsRequestHelper.getRequest( 862 attributionSource.getPackageName(), token.getToken()); 863 mDataPermissionEnforcer.enforceRecordIdsReadPermissions( 864 changeLogsTokenRequest.getRecordTypes(), attributionSource); 865 boolean isInForeground = mAppOpsManagerLocal.isUidInForeground(uid); 866 if (!isInForeground) { 867 throwSecurityException( 868 attributionSource.getPackageName() 869 + " must be in foreground to read the change logs"); 870 } 871 tryAcquireApiCallQuota( 872 uid, QuotaCategory.QUOTA_CATEGORY_READ, isInForeground, builder); 873 Instant startDateInstant = 874 mPermissionHelper.getHealthDataStartDateAccess( 875 attributionSource.getPackageName(), userHandle); 876 if (startDateInstant == null) { 877 throwExceptionIncorrectPermissionState(); 878 } 879 long startDateAccess = startDateInstant.toEpochMilli(); 880 final ChangeLogsHelper.ChangeLogsResponse changeLogsResponse = 881 ChangeLogsHelper.getInstance() 882 .getChangeLogs(changeLogsTokenRequest, token); 883 884 List<RecordInternal<?>> recordInternals = 885 mTransactionManager.readRecords( 886 new ReadTransactionRequest( 887 ChangeLogsHelper.getRecordTypeToInsertedUuids( 888 changeLogsResponse.getChangeLogsMap()), 889 startDateAccess)); 890 List<DeletedLog> deletedLogs = 891 ChangeLogsHelper.getDeletedLogs( 892 changeLogsResponse.getChangeLogsMap()); 893 894 callback.onResult( 895 new ChangeLogsResponse( 896 new RecordsParcel(recordInternals), 897 deletedLogs, 898 changeLogsResponse.getNextPageToken(), 899 changeLogsResponse.hasMorePages())); 900 finishDataDeliveryRead( 901 changeLogsTokenRequest.getRecordTypes(), attributionSource); 902 builder.setHealthDataServiceApiStatusSuccess() 903 .setNumberOfRecords(recordInternals.size() + deletedLogs.size()) 904 .setDataTypesFromRecordInternals(recordInternals); 905 } catch (IllegalArgumentException illegalArgumentException) { 906 builder.setHealthDataServiceApiStatusError( 907 HealthConnectException.ERROR_INVALID_ARGUMENT); 908 Slog.e(TAG, "IllegalArgumentException: ", illegalArgumentException); 909 tryAndThrowException( 910 callback, 911 illegalArgumentException, 912 HealthConnectException.ERROR_INVALID_ARGUMENT); 913 } catch (SQLiteException sqLiteException) { 914 builder.setHealthDataServiceApiStatusError(HealthConnectException.ERROR_IO); 915 Slog.e(TAG, "SQLiteException: ", sqLiteException); 916 tryAndThrowException( 917 callback, sqLiteException, HealthConnectException.ERROR_IO); 918 } catch (SecurityException securityException) { 919 builder.setHealthDataServiceApiStatusError(ERROR_SECURITY); 920 Slog.e(TAG, "SecurityException: ", securityException); 921 tryAndThrowException(callback, securityException, ERROR_SECURITY); 922 } catch (IllegalStateException illegalStateException) { 923 builder.setHealthDataServiceApiStatusError(ERROR_INTERNAL); 924 Slog.e(TAG, "IllegalStateException: ", illegalStateException); 925 tryAndThrowException(callback, illegalStateException, ERROR_INTERNAL); 926 } catch (HealthConnectException healthConnectException) { 927 builder.setHealthDataServiceApiStatusError( 928 healthConnectException.getErrorCode()); 929 Slog.e(TAG, "HealthConnectException: ", healthConnectException); 930 tryAndThrowException( 931 callback, 932 healthConnectException, 933 healthConnectException.getErrorCode()); 934 } catch (Exception exception) { 935 builder.setHealthDataServiceApiStatusError(ERROR_INTERNAL); 936 Slog.e(TAG, "Exception: ", exception); 937 tryAndThrowException(callback, exception, ERROR_INTERNAL); 938 } finally { 939 builder.build().log(); 940 } 941 }, 942 uid, 943 false); 944 } 945 946 /** 947 * API to delete records based on {@code request} 948 * 949 * <p>NOTE: Though internally we only need a single API to handle deletes as SDK code transform 950 * all its delete requests to {@link DeleteUsingFiltersRequestParcel}, we have this separation 951 * to make sure no non-controller APIs can use {@link 952 * HealthConnectServiceImpl#deleteUsingFilters} API 953 */ 954 @Override deleteUsingFiltersForSelf( @onNull AttributionSource attributionSource, @NonNull DeleteUsingFiltersRequestParcel request, @NonNull IEmptyResponseCallback callback)955 public void deleteUsingFiltersForSelf( 956 @NonNull AttributionSource attributionSource, 957 @NonNull DeleteUsingFiltersRequestParcel request, 958 @NonNull IEmptyResponseCallback callback) { 959 final int uid = Binder.getCallingUid(); 960 final int pid = Binder.getCallingPid(); 961 final UserHandle userHandle = Binder.getCallingUserHandle(); 962 final boolean holdsDataManagementPermission = hasDataManagementPermission(uid, pid); 963 final HealthConnectServiceLogger.Builder builder = 964 new HealthConnectServiceLogger.Builder(holdsDataManagementPermission, DELETE_DATA) 965 .setPackageName(attributionSource.getPackageName()); 966 967 HealthConnectThreadScheduler.schedule( 968 mContext, 969 () -> { 970 try { 971 enforceIsForegroundUser(userHandle); 972 verifyPackageNameFromUid(uid, attributionSource); 973 throwExceptionIfDataSyncInProgress(); 974 List<Integer> recordTypeIdsToDelete = 975 (!request.getRecordTypeFilters().isEmpty()) 976 ? request.getRecordTypeFilters() 977 : new ArrayList<>( 978 RecordMapper.getInstance() 979 .getRecordIdToExternalRecordClassMap() 980 .keySet()); 981 // Requests from non controller apps are not allowed to use non-id 982 // filters 983 request.setPackageNameFilters( 984 Collections.singletonList(attributionSource.getPackageName())); 985 986 if (!holdsDataManagementPermission) { 987 mDataPermissionEnforcer.enforceRecordIdsWritePermissions( 988 recordTypeIdsToDelete, attributionSource); 989 tryAcquireApiCallQuota( 990 uid, 991 QuotaCategory.QUOTA_CATEGORY_WRITE, 992 mAppOpsManagerLocal.isUidInForeground(uid), 993 builder); 994 } 995 996 deleteUsingFiltersInternal( 997 attributionSource, 998 request, 999 callback, 1000 builder, 1001 recordTypeIdsToDelete, 1002 uid, 1003 pid); 1004 } catch (SQLiteException sqLiteException) { 1005 builder.setHealthDataServiceApiStatusError(HealthConnectException.ERROR_IO); 1006 tryAndThrowException( 1007 callback, sqLiteException, HealthConnectException.ERROR_IO); 1008 } catch (IllegalArgumentException illegalArgumentException) { 1009 builder.setHealthDataServiceApiStatusError( 1010 HealthConnectException.ERROR_INVALID_ARGUMENT); 1011 Slog.e(TAG, "IllegalArgumentException: ", illegalArgumentException); 1012 tryAndThrowException( 1013 callback, 1014 illegalArgumentException, 1015 HealthConnectException.ERROR_INVALID_ARGUMENT); 1016 } catch (SecurityException securityException) { 1017 builder.setHealthDataServiceApiStatusError(ERROR_SECURITY); 1018 Slog.e(TAG, "SecurityException: ", securityException); 1019 tryAndThrowException(callback, securityException, ERROR_SECURITY); 1020 } catch (HealthConnectException healthConnectException) { 1021 builder.setHealthDataServiceApiStatusError( 1022 healthConnectException.getErrorCode()); 1023 Slog.e(TAG, "HealthConnectException: ", healthConnectException); 1024 tryAndThrowException( 1025 callback, 1026 healthConnectException, 1027 healthConnectException.getErrorCode()); 1028 } catch (Exception exception) { 1029 builder.setHealthDataServiceApiStatusError(ERROR_INTERNAL); 1030 Slog.e(TAG, "Exception: ", exception); 1031 tryAndThrowException(callback, exception, ERROR_INTERNAL); 1032 } finally { 1033 builder.build().log(); 1034 } 1035 }, 1036 uid, 1037 holdsDataManagementPermission); 1038 } 1039 1040 /** 1041 * API to delete records based on {@code request} 1042 * 1043 * <p>NOTE: Though internally we only need a single API to handle deletes as SDK code transform 1044 * all its delete requests to {@link DeleteUsingFiltersRequestParcel}, we have this separation 1045 * to make sure no non-controller APIs can use this API 1046 */ 1047 @Override deleteUsingFilters( @onNull AttributionSource attributionSource, @NonNull DeleteUsingFiltersRequestParcel request, @NonNull IEmptyResponseCallback callback)1048 public void deleteUsingFilters( 1049 @NonNull AttributionSource attributionSource, 1050 @NonNull DeleteUsingFiltersRequestParcel request, 1051 @NonNull IEmptyResponseCallback callback) { 1052 final int uid = Binder.getCallingUid(); 1053 final int pid = Binder.getCallingPid(); 1054 final UserHandle userHandle = Binder.getCallingUserHandle(); 1055 final boolean holdsDataManagementPermission = hasDataManagementPermission(uid, pid); 1056 final HealthConnectServiceLogger.Builder builder = 1057 new HealthConnectServiceLogger.Builder(holdsDataManagementPermission, DELETE_DATA) 1058 .setPackageName(attributionSource.getPackageName()); 1059 1060 HealthConnectThreadScheduler.schedule( 1061 mContext, 1062 () -> { 1063 try { 1064 enforceIsForegroundUser(userHandle); 1065 verifyPackageNameFromUid(uid, attributionSource); 1066 throwExceptionIfDataSyncInProgress(); 1067 mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null); 1068 List<Integer> recordTypeIdsToDelete = 1069 (!request.getRecordTypeFilters().isEmpty()) 1070 ? request.getRecordTypeFilters() 1071 : new ArrayList<>( 1072 RecordMapper.getInstance() 1073 .getRecordIdToExternalRecordClassMap() 1074 .keySet()); 1075 1076 deleteUsingFiltersInternal( 1077 attributionSource, 1078 request, 1079 callback, 1080 builder, 1081 recordTypeIdsToDelete, 1082 uid, 1083 pid); 1084 } catch (SQLiteException sqLiteException) { 1085 builder.setHealthDataServiceApiStatusError(HealthConnectException.ERROR_IO); 1086 tryAndThrowException( 1087 callback, sqLiteException, HealthConnectException.ERROR_IO); 1088 } catch (IllegalArgumentException illegalArgumentException) { 1089 builder.setHealthDataServiceApiStatusError( 1090 HealthConnectException.ERROR_INVALID_ARGUMENT); 1091 Slog.e(TAG, "IllegalArgumentException: ", illegalArgumentException); 1092 tryAndThrowException( 1093 callback, 1094 illegalArgumentException, 1095 HealthConnectException.ERROR_INVALID_ARGUMENT); 1096 } catch (SecurityException securityException) { 1097 builder.setHealthDataServiceApiStatusError(ERROR_SECURITY); 1098 Slog.e(TAG, "SecurityException: ", securityException); 1099 tryAndThrowException(callback, securityException, ERROR_SECURITY); 1100 } catch (HealthConnectException healthConnectException) { 1101 builder.setHealthDataServiceApiStatusError( 1102 healthConnectException.getErrorCode()); 1103 Slog.e(TAG, "HealthConnectException: ", healthConnectException); 1104 tryAndThrowException( 1105 callback, 1106 healthConnectException, 1107 healthConnectException.getErrorCode()); 1108 } catch (Exception exception) { 1109 builder.setHealthDataServiceApiStatusError(ERROR_INTERNAL); 1110 Slog.e(TAG, "Exception: ", exception); 1111 tryAndThrowException(callback, exception, ERROR_INTERNAL); 1112 } finally { 1113 builder.build().log(); 1114 } 1115 }, 1116 uid, 1117 holdsDataManagementPermission); 1118 } 1119 deleteUsingFiltersInternal( @onNull AttributionSource attributionSource, @NonNull DeleteUsingFiltersRequestParcel request, @NonNull IEmptyResponseCallback callback, @NonNull HealthConnectServiceLogger.Builder builder, List<Integer> recordTypeIdsToDelete, int uid, int pid)1120 private void deleteUsingFiltersInternal( 1121 @NonNull AttributionSource attributionSource, 1122 @NonNull DeleteUsingFiltersRequestParcel request, 1123 @NonNull IEmptyResponseCallback callback, 1124 @NonNull HealthConnectServiceLogger.Builder builder, 1125 List<Integer> recordTypeIdsToDelete, 1126 int uid, 1127 int pid) { 1128 if (request.usesIdFilters() && request.usesNonIdFilters()) { 1129 throw new IllegalArgumentException( 1130 "Requests with both id and non-id filters are not" + " supported"); 1131 } 1132 int numberOfRecordsDeleted = 1133 mTransactionManager.deleteAll( 1134 new DeleteTransactionRequest(attributionSource.getPackageName(), request) 1135 .setHasManageHealthDataPermission( 1136 hasDataManagementPermission(uid, pid))); 1137 tryAndReturnResult(callback, builder); 1138 finishDataDeliveryWrite(recordTypeIdsToDelete, attributionSource); 1139 HealthConnectThreadScheduler.scheduleInternalTask( 1140 () -> postDeleteTasks(recordTypeIdsToDelete)); 1141 1142 builder.setNumberOfRecords(numberOfRecordsDeleted) 1143 .setDataTypesFromRecordTypes(recordTypeIdsToDelete); 1144 } 1145 1146 /** API to get Priority for {@code dataCategory} */ 1147 @Override getCurrentPriority( @onNull String packageName, @HealthDataCategory.Type int dataCategory, @NonNull IGetPriorityResponseCallback callback)1148 public void getCurrentPriority( 1149 @NonNull String packageName, 1150 @HealthDataCategory.Type int dataCategory, 1151 @NonNull IGetPriorityResponseCallback callback) { 1152 final int uid = Binder.getCallingUid(); 1153 final int pid = Binder.getCallingPid(); 1154 final UserHandle userHandle = Binder.getCallingUserHandle(); 1155 HealthConnectThreadScheduler.scheduleControllerTask( 1156 () -> { 1157 try { 1158 enforceIsForegroundUser(userHandle); 1159 mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null); 1160 throwExceptionIfDataSyncInProgress(); 1161 List<DataOrigin> dataOriginInPriorityOrder = 1162 HealthDataCategoryPriorityHelper.getInstance() 1163 .getPriorityOrder(dataCategory) 1164 .stream() 1165 .map( 1166 (name) -> 1167 new DataOrigin.Builder() 1168 .setPackageName(name) 1169 .build()) 1170 .collect(Collectors.toList()); 1171 callback.onResult( 1172 new GetPriorityResponseParcel( 1173 new FetchDataOriginsPriorityOrderResponse( 1174 dataOriginInPriorityOrder))); 1175 } catch (SQLiteException sqLiteException) { 1176 Slog.e(TAG, "SQLiteException: ", sqLiteException); 1177 tryAndThrowException( 1178 callback, sqLiteException, HealthConnectException.ERROR_IO); 1179 } catch (SecurityException securityException) { 1180 Slog.e(TAG, "SecurityException: ", securityException); 1181 tryAndThrowException(callback, securityException, ERROR_SECURITY); 1182 } catch (HealthConnectException healthConnectException) { 1183 Slog.e(TAG, "HealthConnectException: ", healthConnectException); 1184 tryAndThrowException( 1185 callback, 1186 healthConnectException, 1187 healthConnectException.getErrorCode()); 1188 } catch (Exception exception) { 1189 Slog.e(TAG, "Exception: ", exception); 1190 tryAndThrowException(callback, exception, ERROR_INTERNAL); 1191 } 1192 }); 1193 } 1194 1195 /** API to update priority for permission category(ies) */ 1196 @Override updatePriority( @onNull String packageName, @NonNull UpdatePriorityRequestParcel updatePriorityRequest, @NonNull IEmptyResponseCallback callback)1197 public void updatePriority( 1198 @NonNull String packageName, 1199 @NonNull UpdatePriorityRequestParcel updatePriorityRequest, 1200 @NonNull IEmptyResponseCallback callback) { 1201 final int uid = Binder.getCallingUid(); 1202 final int pid = Binder.getCallingPid(); 1203 final UserHandle userHandle = Binder.getCallingUserHandle(); 1204 HealthConnectThreadScheduler.scheduleControllerTask( 1205 () -> { 1206 try { 1207 enforceIsForegroundUser(userHandle); 1208 mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null); 1209 throwExceptionIfDataSyncInProgress(); 1210 HealthDataCategoryPriorityHelper.getInstance() 1211 .setPriorityOrder( 1212 updatePriorityRequest.getDataCategory(), 1213 updatePriorityRequest.getPackagePriorityOrder()); 1214 callback.onResult(); 1215 } catch (SQLiteException sqLiteException) { 1216 Slog.e(TAG, "SQLiteException: ", sqLiteException); 1217 tryAndThrowException( 1218 callback, sqLiteException, HealthConnectException.ERROR_IO); 1219 } catch (SecurityException securityException) { 1220 Slog.e(TAG, "SecurityException: ", securityException); 1221 tryAndThrowException(callback, securityException, ERROR_SECURITY); 1222 } catch (HealthConnectException healthConnectException) { 1223 Slog.e(TAG, "HealthConnectException: ", healthConnectException); 1224 tryAndThrowException( 1225 callback, 1226 healthConnectException, 1227 healthConnectException.getErrorCode()); 1228 } catch (Exception exception) { 1229 Slog.e(TAG, "Exception: ", exception); 1230 tryAndThrowException(callback, exception, ERROR_INTERNAL); 1231 } 1232 }); 1233 } 1234 1235 @Override setRecordRetentionPeriodInDays( int days, @NonNull UserHandle user, IEmptyResponseCallback callback)1236 public void setRecordRetentionPeriodInDays( 1237 int days, @NonNull UserHandle user, IEmptyResponseCallback callback) { 1238 final int uid = Binder.getCallingUid(); 1239 final int pid = Binder.getCallingPid(); 1240 final UserHandle userHandle = Binder.getCallingUserHandle(); 1241 HealthConnectThreadScheduler.scheduleControllerTask( 1242 () -> { 1243 try { 1244 enforceIsForegroundUser(userHandle); 1245 mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null); 1246 throwExceptionIfDataSyncInProgress(); 1247 AutoDeleteService.setRecordRetentionPeriodInDays(days); 1248 callback.onResult(); 1249 } catch (SQLiteException sqLiteException) { 1250 Slog.e(TAG, "SQLiteException: ", sqLiteException); 1251 tryAndThrowException( 1252 callback, sqLiteException, HealthConnectException.ERROR_IO); 1253 } catch (SecurityException securityException) { 1254 Slog.e(TAG, "SecurityException: ", securityException); 1255 tryAndThrowException(callback, securityException, ERROR_SECURITY); 1256 } catch (HealthConnectException healthConnectException) { 1257 Slog.e(TAG, "HealthConnectException: ", healthConnectException); 1258 tryAndThrowException( 1259 callback, 1260 healthConnectException, 1261 healthConnectException.getErrorCode()); 1262 } catch (Exception exception) { 1263 Slog.e(TAG, "Exception: ", exception); 1264 tryAndThrowException(callback, exception, ERROR_INTERNAL); 1265 } 1266 }); 1267 } 1268 1269 @Override getRecordRetentionPeriodInDays(@onNull UserHandle user)1270 public int getRecordRetentionPeriodInDays(@NonNull UserHandle user) { 1271 enforceIsForegroundUser(getCallingUserHandle()); 1272 throwExceptionIfDataSyncInProgress(); 1273 try { 1274 mContext.enforceCallingPermission(MANAGE_HEALTH_DATA_PERMISSION, null); 1275 return AutoDeleteService.getRecordRetentionPeriodInDays(); 1276 } catch (Exception e) { 1277 if (e instanceof SecurityException) { 1278 throw e; 1279 } 1280 Slog.e(TAG, "Unable to get record retention period for " + user); 1281 } 1282 1283 throw new RuntimeException(); 1284 } 1285 1286 /** 1287 * Returns information, represented by {@code ApplicationInfoResponse}, for all the packages 1288 * that have contributed to the health connect DB. 1289 * 1290 * @param callback Callback to receive result of performing this operation. In case of an error 1291 * or a permission failure the HealthConnect service, {@link IEmptyResponseCallback#onError} 1292 * will be invoked with a {@link HealthConnectException}. 1293 */ 1294 @Override getContributorApplicationsInfo(@onNull IApplicationInfoResponseCallback callback)1295 public void getContributorApplicationsInfo(@NonNull IApplicationInfoResponseCallback callback) { 1296 final int uid = Binder.getCallingUid(); 1297 final int pid = Binder.getCallingPid(); 1298 final UserHandle userHandle = Binder.getCallingUserHandle(); 1299 HealthConnectThreadScheduler.scheduleControllerTask( 1300 () -> { 1301 try { 1302 enforceIsForegroundUser(userHandle); 1303 mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null); 1304 throwExceptionIfDataSyncInProgress(); 1305 List<AppInfo> applicationInfos = 1306 AppInfoHelper.getInstance().getApplicationInfosWithRecordTypes(); 1307 1308 callback.onResult(new ApplicationInfoResponseParcel(applicationInfos)); 1309 } catch (SQLiteException sqLiteException) { 1310 Slog.e(TAG, "SqlException: ", sqLiteException); 1311 tryAndThrowException( 1312 callback, sqLiteException, HealthConnectException.ERROR_IO); 1313 } catch (SecurityException securityException) { 1314 Slog.e(TAG, "SecurityException: ", securityException); 1315 tryAndThrowException(callback, securityException, ERROR_SECURITY); 1316 } catch (HealthConnectException healthConnectException) { 1317 Slog.e(TAG, "HealthConnectException: ", healthConnectException); 1318 tryAndThrowException( 1319 callback, 1320 healthConnectException, 1321 healthConnectException.getErrorCode()); 1322 } catch (Exception e) { 1323 Slog.e(TAG, "Exception: ", e); 1324 tryAndThrowException(callback, e, ERROR_INTERNAL); 1325 } 1326 }); 1327 } 1328 1329 /** Retrieves {@link RecordTypeInfoResponse} for each RecordType. */ 1330 @Override queryAllRecordTypesInfo(@onNull IRecordTypeInfoResponseCallback callback)1331 public void queryAllRecordTypesInfo(@NonNull IRecordTypeInfoResponseCallback callback) { 1332 final int uid = Binder.getCallingUid(); 1333 final int pid = Binder.getCallingPid(); 1334 final UserHandle userHandle = Binder.getCallingUserHandle(); 1335 HealthConnectThreadScheduler.scheduleControllerTask( 1336 () -> { 1337 try { 1338 enforceIsForegroundUser(userHandle); 1339 mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null); 1340 throwExceptionIfDataSyncInProgress(); 1341 callback.onResult( 1342 new RecordTypeInfoResponseParcel( 1343 getPopulatedRecordTypeInfoResponses())); 1344 } catch (SQLiteException sqLiteException) { 1345 tryAndThrowException( 1346 callback, sqLiteException, HealthConnectException.ERROR_IO); 1347 } catch (SecurityException securityException) { 1348 Slog.e(TAG, "SecurityException: ", securityException); 1349 tryAndThrowException(callback, securityException, ERROR_SECURITY); 1350 } catch (HealthConnectException healthConnectException) { 1351 Slog.e(TAG, "HealthConnectException: ", healthConnectException); 1352 tryAndThrowException( 1353 callback, 1354 healthConnectException, 1355 healthConnectException.getErrorCode()); 1356 } catch (Exception exception) { 1357 tryAndThrowException(callback, exception, ERROR_INTERNAL); 1358 } 1359 }); 1360 } 1361 1362 /** 1363 * @see HealthConnectManager#queryAccessLogs 1364 */ 1365 @Override queryAccessLogs(@onNull String packageName, IAccessLogsResponseCallback callback)1366 public void queryAccessLogs(@NonNull String packageName, IAccessLogsResponseCallback callback) { 1367 final int uid = Binder.getCallingUid(); 1368 final int pid = Binder.getCallingPid(); 1369 final UserHandle userHandle = Binder.getCallingUserHandle(); 1370 1371 HealthConnectThreadScheduler.scheduleControllerTask( 1372 () -> { 1373 try { 1374 enforceIsForegroundUser(userHandle); 1375 mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null); 1376 throwExceptionIfDataSyncInProgress(); 1377 final List<AccessLog> accessLogsList = 1378 AccessLogsHelper.getInstance().queryAccessLogs(); 1379 callback.onResult(new AccessLogsResponseParcel(accessLogsList)); 1380 } catch (SecurityException securityException) { 1381 Slog.e(TAG, "SecurityException: ", securityException); 1382 tryAndThrowException(callback, securityException, ERROR_SECURITY); 1383 } catch (HealthConnectException healthConnectException) { 1384 Slog.e(TAG, "HealthConnectException: ", healthConnectException); 1385 tryAndThrowException( 1386 callback, 1387 healthConnectException, 1388 healthConnectException.getErrorCode()); 1389 } catch (Exception exception) { 1390 Slog.e(TAG, "Exception: ", exception); 1391 tryAndThrowException(callback, exception, ERROR_INTERNAL); 1392 } 1393 }); 1394 } 1395 1396 /** 1397 * Returns a list of unique dates for which the database has at least one entry 1398 * 1399 * @param activityDatesRequestParcel Parcel request containing records classes 1400 * @param callback Callback to receive result of performing this operation. The results are 1401 * returned in {@link List<LocalDate>} . In case of an error or a permission failure the 1402 * HealthConnect service, {@link IActivityDatesResponseCallback#onError} will be invoked 1403 * with a {@link HealthConnectExceptionParcel}. 1404 */ 1405 @Override getActivityDates( @onNull ActivityDatesRequestParcel activityDatesRequestParcel, IActivityDatesResponseCallback callback)1406 public void getActivityDates( 1407 @NonNull ActivityDatesRequestParcel activityDatesRequestParcel, 1408 IActivityDatesResponseCallback callback) { 1409 final int uid = Binder.getCallingUid(); 1410 final int pid = Binder.getCallingPid(); 1411 final UserHandle userHandle = Binder.getCallingUserHandle(); 1412 1413 HealthConnectThreadScheduler.scheduleControllerTask( 1414 () -> { 1415 try { 1416 enforceIsForegroundUser(userHandle); 1417 mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null); 1418 throwExceptionIfDataSyncInProgress(); 1419 List<LocalDate> localDates = 1420 ActivityDateHelper.getInstance() 1421 .getActivityDates( 1422 activityDatesRequestParcel.getRecordTypes()); 1423 1424 callback.onResult(new ActivityDatesResponseParcel(localDates)); 1425 } catch (SQLiteException sqLiteException) { 1426 Slog.e(TAG, "SqlException: ", sqLiteException); 1427 tryAndThrowException( 1428 callback, sqLiteException, HealthConnectException.ERROR_IO); 1429 } catch (SecurityException securityException) { 1430 Slog.e(TAG, "SecurityException: ", securityException); 1431 tryAndThrowException(callback, securityException, ERROR_SECURITY); 1432 } catch (HealthConnectException healthConnectException) { 1433 Slog.e(TAG, "HealthConnectException: ", healthConnectException); 1434 tryAndThrowException( 1435 callback, 1436 healthConnectException, 1437 healthConnectException.getErrorCode()); 1438 } catch (Exception e) { 1439 Slog.e(TAG, "Exception: ", e); 1440 tryAndThrowException(callback, e, ERROR_INTERNAL); 1441 } 1442 }); 1443 } 1444 1445 // TODO(b/265780725): Update javadocs and ensure that the caller handles SHOW_MIGRATION_INFO 1446 // intent. 1447 @Override startMigration(@onNull String packageName, IMigrationCallback callback)1448 public void startMigration(@NonNull String packageName, IMigrationCallback callback) { 1449 int uid = Binder.getCallingUid(); 1450 int pid = Binder.getCallingPid(); 1451 final UserHandle userHandle = Binder.getCallingUserHandle(); 1452 1453 HealthConnectThreadScheduler.scheduleInternalTask( 1454 () -> { 1455 try { 1456 enforceIsForegroundUser(userHandle); 1457 mContext.enforcePermission( 1458 MIGRATE_HEALTH_CONNECT_DATA, 1459 pid, 1460 uid, 1461 "Caller does not have " + MIGRATE_HEALTH_CONNECT_DATA); 1462 enforceShowMigrationInfoIntent(packageName, uid); 1463 mBackupRestore.runWithStatesReadLock( 1464 () -> { 1465 if (mBackupRestore.isRestoreMergingInProgress()) { 1466 throw new MigrationException( 1467 "Cannot start data migration. Backup and restore in" 1468 + " progress.", 1469 MigrationException.ERROR_INTERNAL, 1470 null); 1471 } 1472 mMigrationStateManager.startMigration(mContext); 1473 }); 1474 PriorityMigrationHelper.getInstance().populatePreMigrationPriority(); 1475 callback.onSuccess(); 1476 } catch (Exception e) { 1477 Slog.e(TAG, "Exception: ", e); 1478 tryAndThrowException(callback, e, MigrationException.ERROR_INTERNAL, null); 1479 } 1480 }); 1481 } 1482 1483 // TODO(b/265780725): Update javadocs and ensure that the caller handles SHOW_MIGRATION_INFO 1484 // intent. 1485 @Override finishMigration(@onNull String packageName, IMigrationCallback callback)1486 public void finishMigration(@NonNull String packageName, IMigrationCallback callback) { 1487 int uid = Binder.getCallingUid(); 1488 int pid = Binder.getCallingPid(); 1489 final UserHandle userHandle = Binder.getCallingUserHandle(); 1490 1491 HealthConnectThreadScheduler.scheduleInternalTask( 1492 () -> { 1493 try { 1494 enforceIsForegroundUser(userHandle); 1495 mContext.enforcePermission( 1496 MIGRATE_HEALTH_CONNECT_DATA, 1497 pid, 1498 uid, 1499 "Caller does not have " + MIGRATE_HEALTH_CONNECT_DATA); 1500 enforceShowMigrationInfoIntent(packageName, uid); 1501 mMigrationStateManager.finishMigration(mContext); 1502 AppInfoHelper.getInstance().syncAppInfoRecordTypesUsed(); 1503 callback.onSuccess(); 1504 } catch (Exception e) { 1505 Slog.e(TAG, "Exception: ", e); 1506 // TODO(b/263897830): Verify migration state and send errors properly 1507 tryAndThrowException(callback, e, MigrationException.ERROR_INTERNAL, null); 1508 } 1509 }); 1510 } 1511 1512 // TODO(b/265780725): Update javadocs and ensure that the caller handles SHOW_MIGRATION_INFO 1513 // intent. 1514 @Override writeMigrationData( @onNull String packageName, MigrationEntityParcel parcel, IMigrationCallback callback)1515 public void writeMigrationData( 1516 @NonNull String packageName, 1517 MigrationEntityParcel parcel, 1518 IMigrationCallback callback) { 1519 int uid = Binder.getCallingUid(); 1520 int pid = Binder.getCallingPid(); 1521 UserHandle callingUserHandle = getCallingUserHandle(); 1522 1523 HealthConnectThreadScheduler.scheduleInternalTask( 1524 () -> { 1525 try { 1526 enforceIsForegroundUser(callingUserHandle); 1527 mContext.enforcePermission( 1528 MIGRATE_HEALTH_CONNECT_DATA, 1529 pid, 1530 uid, 1531 "Caller does not have " + MIGRATE_HEALTH_CONNECT_DATA); 1532 enforceShowMigrationInfoIntent(packageName, uid); 1533 mMigrationStateManager.validateWriteMigrationData(); 1534 getDataMigrationManager(callingUserHandle) 1535 .apply(parcel.getMigrationEntities()); 1536 callback.onSuccess(); 1537 } catch (DataMigrationManager.EntityWriteException e) { 1538 Slog.e(TAG, "Exception: ", e); 1539 tryAndThrowException( 1540 callback, 1541 e, 1542 MigrationException.ERROR_MIGRATE_ENTITY, 1543 e.getEntityId()); 1544 } catch (Exception e) { 1545 Slog.e(TAG, "Exception: ", e); 1546 tryAndThrowException(callback, e, MigrationException.ERROR_INTERNAL, null); 1547 } 1548 }); 1549 } 1550 insertMinDataMigrationSdkExtensionVersion( @onNull String packageName, int requiredSdkExtension, IMigrationCallback callback)1551 public void insertMinDataMigrationSdkExtensionVersion( 1552 @NonNull String packageName, int requiredSdkExtension, IMigrationCallback callback) { 1553 int uid = Binder.getCallingUid(); 1554 int pid = Binder.getCallingPid(); 1555 final UserHandle userHandle = Binder.getCallingUserHandle(); 1556 1557 HealthConnectThreadScheduler.scheduleInternalTask( 1558 () -> { 1559 try { 1560 enforceIsForegroundUser(userHandle); 1561 mContext.enforcePermission( 1562 MIGRATE_HEALTH_CONNECT_DATA, 1563 pid, 1564 uid, 1565 "Caller does not have " + MIGRATE_HEALTH_CONNECT_DATA); 1566 enforceShowMigrationInfoIntent(packageName, uid); 1567 mMigrationStateManager.validateSetMinSdkVersion(); 1568 mMigrationStateManager.setMinDataMigrationSdkExtensionVersion( 1569 mContext, requiredSdkExtension); 1570 1571 callback.onSuccess(); 1572 } catch (Exception e) { 1573 Slog.e(TAG, "Exception: ", e); 1574 tryAndThrowException(callback, e, MigrationException.ERROR_INTERNAL, null); 1575 } 1576 }); 1577 } 1578 1579 /** 1580 * @see HealthConnectManager#stageAllHealthConnectRemoteData 1581 */ 1582 @Override stageAllHealthConnectRemoteData( @onNull StageRemoteDataRequest stageRemoteDataRequest, @NonNull UserHandle userHandle, @NonNull IDataStagingFinishedCallback callback)1583 public void stageAllHealthConnectRemoteData( 1584 @NonNull StageRemoteDataRequest stageRemoteDataRequest, 1585 @NonNull UserHandle userHandle, 1586 @NonNull IDataStagingFinishedCallback callback) { 1587 Map<String, ParcelFileDescriptor> origPfdsByFileName = 1588 stageRemoteDataRequest.getPfdsByFileName(); 1589 Map<String, HealthConnectException> exceptionsByFileName = 1590 new ArrayMap<>(origPfdsByFileName.size()); 1591 Map<String, ParcelFileDescriptor> pfdsByFileName = 1592 new ArrayMap<>(origPfdsByFileName.size()); 1593 1594 try { 1595 mDataPermissionEnforcer.enforceAnyOfPermissions( 1596 Manifest.permission.STAGE_HEALTH_CONNECT_REMOTE_DATA, 1597 HEALTH_CONNECT_BACKUP_INTER_AGENT_PERMISSION); 1598 1599 enforceIsForegroundUser(Binder.getCallingUserHandle()); 1600 1601 for (Entry<String, ParcelFileDescriptor> entry : origPfdsByFileName.entrySet()) { 1602 try { 1603 pfdsByFileName.put(entry.getKey(), entry.getValue().dup()); 1604 } catch (IOException e) { 1605 Slog.e(TAG, "IOException: ", e); 1606 exceptionsByFileName.put( 1607 entry.getKey(), 1608 new HealthConnectException( 1609 HealthConnectException.ERROR_IO, e.getMessage())); 1610 } 1611 } 1612 1613 HealthConnectThreadScheduler.scheduleInternalTask( 1614 () -> { 1615 if (!mBackupRestore.prepForStagingIfNotAlreadyDone()) { 1616 try { 1617 callback.onResult(); 1618 } catch (RemoteException e) { 1619 Log.e(TAG, "Restore response could not be sent to the caller.", e); 1620 } 1621 return; 1622 } 1623 mBackupRestore.stageAllHealthConnectRemoteData( 1624 pfdsByFileName, 1625 exceptionsByFileName, 1626 userHandle.getIdentifier(), 1627 callback); 1628 }); 1629 } catch (SecurityException | IllegalStateException e) { 1630 Log.e(TAG, "Exception encountered while staging", e); 1631 try { 1632 @HealthConnectException.ErrorCode int errorCode = 1633 (e instanceof SecurityException) ? ERROR_SECURITY : ERROR_INTERNAL; 1634 exceptionsByFileName.put("", new HealthConnectException( 1635 errorCode, 1636 e.getMessage())); 1637 1638 callback.onError( 1639 new StageRemoteDataException(exceptionsByFileName)); 1640 } catch (RemoteException remoteException) { 1641 Log.e(TAG, "Restore permission response could not be sent to the caller.", e); 1642 } 1643 } 1644 } 1645 1646 /** 1647 * @see HealthConnectManager#getAllDataForBackup 1648 */ 1649 @Override getAllDataForBackup( @onNull StageRemoteDataRequest stageRemoteDataRequest, @NonNull UserHandle userHandle)1650 public void getAllDataForBackup( 1651 @NonNull StageRemoteDataRequest stageRemoteDataRequest, 1652 @NonNull UserHandle userHandle) { 1653 mContext.enforceCallingPermission(HEALTH_CONNECT_BACKUP_INTER_AGENT_PERMISSION, null); 1654 mBackupRestore.getAllDataForBackup(stageRemoteDataRequest, userHandle); 1655 } 1656 1657 /** 1658 * @see HealthConnectManager#getAllBackupFileNames 1659 */ 1660 @Override getAllBackupFileNames(boolean forDeviceToDevice)1661 public BackupFileNamesSet getAllBackupFileNames(boolean forDeviceToDevice) { 1662 mContext.enforceCallingPermission(HEALTH_CONNECT_BACKUP_INTER_AGENT_PERMISSION, null); 1663 return mBackupRestore.getAllBackupFileNames(forDeviceToDevice); 1664 } 1665 1666 /** 1667 * @see HealthConnectManager#deleteAllStagedRemoteData 1668 */ 1669 @Override deleteAllStagedRemoteData(@onNull UserHandle userHandle)1670 public void deleteAllStagedRemoteData(@NonNull UserHandle userHandle) { 1671 mContext.enforceCallingPermission( 1672 DELETE_STAGED_HEALTH_CONNECT_REMOTE_DATA_PERMISSION, null); 1673 mBackupRestore.deleteAndResetEverything(userHandle); 1674 mMigrationStateManager.clearCaches(mContext); 1675 AppInfoHelper.getInstance().clearData(mTransactionManager); 1676 ActivityDateHelper.getInstance().clearData(mTransactionManager); 1677 MigrationEntityHelper.getInstance().clearData(mTransactionManager); 1678 HealthDataCategoryPriorityHelper.getInstance().clearData(mTransactionManager); 1679 PriorityMigrationHelper.getInstance().clearData(mTransactionManager); 1680 RateLimiter.clearCache(); 1681 String[] packageNames = mContext.getPackageManager().getPackagesForUid(getCallingUid()); 1682 for (String packageName : packageNames) { 1683 mFirstGrantTimeManager.setFirstGrantTime(packageName, Instant.now(), userHandle); 1684 } 1685 } 1686 1687 /** 1688 * @see HealthConnectManager#updateDataDownloadState 1689 */ 1690 @Override updateDataDownloadState(@ataDownloadState int downloadState)1691 public void updateDataDownloadState(@DataDownloadState int downloadState) { 1692 mContext.enforceCallingPermission( 1693 Manifest.permission.STAGE_HEALTH_CONNECT_REMOTE_DATA, null); 1694 enforceIsForegroundUser(getCallingUserHandle()); 1695 mBackupRestore.updateDataDownloadState(downloadState); 1696 } 1697 1698 /** 1699 * @see HealthConnectManager#getHealthConnectDataState 1700 */ 1701 @Override getHealthConnectDataState(@onNull IGetHealthConnectDataStateCallback callback)1702 public void getHealthConnectDataState(@NonNull IGetHealthConnectDataStateCallback callback) { 1703 try { 1704 mDataPermissionEnforcer.enforceAnyOfPermissions( 1705 MANAGE_HEALTH_DATA_PERMISSION, Manifest.permission.MIGRATE_HEALTH_CONNECT_DATA); 1706 final UserHandle userHandle = Binder.getCallingUserHandle(); 1707 enforceIsForegroundUser(userHandle); 1708 HealthConnectThreadScheduler.scheduleInternalTask( 1709 () -> { 1710 try { 1711 @HealthConnectDataState.DataRestoreError 1712 int dataRestoreError = mBackupRestore.getDataRestoreError(); 1713 @HealthConnectDataState.DataRestoreState 1714 int dataRestoreState = mBackupRestore.getDataRestoreState(); 1715 1716 try { 1717 callback.onResult( 1718 new HealthConnectDataState( 1719 dataRestoreState, 1720 dataRestoreError, 1721 mMigrationStateManager.getMigrationState())); 1722 } catch (RemoteException remoteException) { 1723 Log.e( 1724 TAG, 1725 "HealthConnectDataState could not be sent to the caller.", 1726 remoteException); 1727 } 1728 } catch (RuntimeException e) { 1729 // exception getting the state from the disk 1730 try { 1731 callback.onError( 1732 new HealthConnectExceptionParcel( 1733 new HealthConnectException( 1734 HealthConnectException.ERROR_IO, 1735 e.getMessage()))); 1736 } catch (RemoteException remoteException) { 1737 Log.e( 1738 TAG, 1739 "Exception for getHealthConnectDataState could not be sent" 1740 + " to the caller.", 1741 remoteException); 1742 } 1743 } 1744 }); 1745 } catch (SecurityException | IllegalStateException e) { 1746 Log.e(TAG, "getHealthConnectDataState: Exception encountered", e); 1747 @HealthConnectException.ErrorCode int errorCode = 1748 (e instanceof SecurityException) ? ERROR_SECURITY 1749 : ERROR_INTERNAL; 1750 try { 1751 callback.onError( 1752 new HealthConnectExceptionParcel( 1753 new HealthConnectException( 1754 errorCode, 1755 e.getMessage()))); 1756 } catch (RemoteException remoteException) { 1757 Log.e(TAG, "getHealthConnectDataState error could not be sent", e); 1758 } 1759 } 1760 } 1761 1762 /** 1763 * @see HealthConnectManager#getHealthConnectMigrationUiState 1764 */ 1765 @Override getHealthConnectMigrationUiState( @onNull IGetHealthConnectMigrationUiStateCallback callback)1766 public void getHealthConnectMigrationUiState( 1767 @NonNull IGetHealthConnectMigrationUiStateCallback callback) { 1768 final int uid = Binder.getCallingUid(); 1769 final int pid = Binder.getCallingPid(); 1770 final UserHandle userHandle = Binder.getCallingUserHandle(); 1771 HealthConnectThreadScheduler.scheduleInternalTask( 1772 () -> { 1773 try { 1774 enforceIsForegroundUser(userHandle); 1775 mContext.enforcePermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid, null); 1776 1777 try { 1778 callback.onResult( 1779 new HealthConnectMigrationUiState( 1780 mMigrationUiStateManager 1781 .getHealthConnectMigrationUiState())); 1782 } catch (RemoteException remoteException) { 1783 Log.e( 1784 TAG, 1785 "HealthConnectMigrationUiState could not be sent to the" 1786 + " caller.", 1787 remoteException); 1788 } 1789 } catch (SecurityException securityException) { 1790 try { 1791 callback.onError( 1792 new HealthConnectExceptionParcel( 1793 new HealthConnectException( 1794 ERROR_SECURITY, 1795 securityException.getMessage()))); 1796 } catch (RemoteException remoteException) { 1797 Log.e( 1798 TAG, 1799 "Exception for HealthConnectMigrationUiState could not be sent" 1800 + " to the caller.", 1801 remoteException); 1802 } 1803 } catch (RuntimeException e) { 1804 // exception getting the state from the disk 1805 try { 1806 callback.onError( 1807 new HealthConnectExceptionParcel( 1808 new HealthConnectException( 1809 HealthConnectException.ERROR_IO, 1810 e.getMessage()))); 1811 } catch (RemoteException remoteException) { 1812 Log.e( 1813 TAG, 1814 "Exception for HealthConnectMigrationUiState could not be sent" 1815 + " to the caller.", 1816 remoteException); 1817 } 1818 } 1819 }); 1820 } 1821 1822 // Cancel BR timeouts - this might be needed when a user is going into background. cancelBackupRestoreTimeouts()1823 void cancelBackupRestoreTimeouts() { 1824 mBackupRestore.cancelAllJobs(); 1825 } 1826 tryAcquireApiCallQuota( int uid, @QuotaCategory.Type int quotaCategory, boolean isInForeground, HealthConnectServiceLogger.Builder builder)1827 private void tryAcquireApiCallQuota( 1828 int uid, 1829 @QuotaCategory.Type int quotaCategory, 1830 boolean isInForeground, 1831 HealthConnectServiceLogger.Builder builder) { 1832 try { 1833 RateLimiter.tryAcquireApiCallQuota(uid, quotaCategory, isInForeground); 1834 } catch (RateLimiterException rateLimiterException) { 1835 builder.setRateLimit( 1836 rateLimiterException.getRateLimiterQuotaBucket(), 1837 rateLimiterException.getRateLimiterQuotaLimit()); 1838 throw new HealthConnectException( 1839 rateLimiterException.getErrorCode(), rateLimiterException.getMessage()); 1840 } 1841 } 1842 enforceMemoryRateLimit(List<Long> recordsSize, long recordsChunkSize)1843 private void enforceMemoryRateLimit(List<Long> recordsSize, long recordsChunkSize) { 1844 recordsSize.forEach(RateLimiter::checkMaxRecordMemoryUsage); 1845 RateLimiter.checkMaxChunkMemoryUsage(recordsChunkSize); 1846 } 1847 enforceIsForegroundUser(UserHandle callingUserHandle)1848 private void enforceIsForegroundUser(UserHandle callingUserHandle) { 1849 if (!callingUserHandle.equals(mCurrentForegroundUser)) { 1850 throw new IllegalStateException( 1851 "Calling user: " 1852 + callingUserHandle.getIdentifier() 1853 + "is not the current foreground user: " 1854 + mCurrentForegroundUser.getIdentifier() 1855 + ". HC request must be called" 1856 + " from the current foreground user."); 1857 } 1858 } 1859 isDataSyncInProgress()1860 private boolean isDataSyncInProgress() { 1861 return mMigrationStateManager.isMigrationInProgress() 1862 || mBackupRestore.isRestoreMergingInProgress(); 1863 } 1864 1865 @VisibleForTesting getStagedRemoteFileNames(int userId)1866 Set<String> getStagedRemoteFileNames(int userId) { 1867 return mBackupRestore.getStagedRemoteFileNames(userId); 1868 } 1869 1870 @NonNull getDataMigrationManager(@onNull UserHandle userHandle)1871 private DataMigrationManager getDataMigrationManager(@NonNull UserHandle userHandle) { 1872 final Context userContext = mContext.createContextAsUser(userHandle, 0); 1873 1874 return new DataMigrationManager( 1875 userContext, 1876 mTransactionManager, 1877 mPermissionHelper, 1878 mFirstGrantTimeManager, 1879 DeviceInfoHelper.getInstance(), 1880 AppInfoHelper.getInstance(), 1881 MigrationEntityHelper.getInstance(), 1882 RecordHelperProvider.getInstance(), 1883 HealthDataCategoryPriorityHelper.getInstance(), 1884 PriorityMigrationHelper.getInstance(), 1885 ActivityDateHelper.getInstance()); 1886 } 1887 enforceCallingPackageBelongsToUid(String packageName, int callingUid)1888 private void enforceCallingPackageBelongsToUid(String packageName, int callingUid) { 1889 int packageUid; 1890 try { 1891 packageUid = 1892 mContext.getPackageManager() 1893 .getPackageUid( 1894 packageName, /* flags */ PackageManager.PackageInfoFlags.of(0)); 1895 } catch (PackageManager.NameNotFoundException e) { 1896 throw new IllegalStateException(packageName + " not found"); 1897 } 1898 if (UserHandle.getAppId(packageUid) != UserHandle.getAppId(callingUid)) { 1899 throwSecurityException(packageName + " does not belong to uid " + callingUid); 1900 } 1901 } 1902 1903 /** 1904 * Verify various aspects of the calling user. 1905 * 1906 * @param callingUid Uid of the caller, usually retrieved from Binder for authenticity. 1907 * @param callerAttributionSource The permission identity of the caller 1908 */ verifyPackageNameFromUid( int callingUid, @NonNull AttributionSource callerAttributionSource)1909 private void verifyPackageNameFromUid( 1910 int callingUid, @NonNull AttributionSource callerAttributionSource) { 1911 // Check does the attribution source is one for the calling app. 1912 callerAttributionSource.enforceCallingUid(); 1913 // Obtain the user where the client is running in. 1914 UserHandle callingUserHandle = UserHandle.getUserHandleForUid(callingUid); 1915 Context callingUserContext = mContext.createContextAsUser(callingUserHandle, 0); 1916 String callingPackageName = 1917 Objects.requireNonNull(callerAttributionSource.getPackageName()); 1918 verifyCallingPackage(callingUserContext, callingUid, callingPackageName); 1919 } 1920 1921 /** 1922 * Check that the caller's supposed package name matches the uid making the call. 1923 * 1924 * @throws SecurityException if the package name and uid don't match. 1925 */ verifyCallingPackage( @onNull Context actualCallingUserContext, int actualCallingUid, @NonNull String claimedCallingPackage)1926 private void verifyCallingPackage( 1927 @NonNull Context actualCallingUserContext, 1928 int actualCallingUid, 1929 @NonNull String claimedCallingPackage) { 1930 int claimedCallingUid = getPackageUid(actualCallingUserContext, claimedCallingPackage); 1931 if (claimedCallingUid != actualCallingUid) { 1932 throwSecurityException( 1933 claimedCallingPackage + " does not belong to uid " + actualCallingUid); 1934 } 1935 } 1936 1937 /** Finds the UID of the {@code packageName} in the given {@code context}. */ getPackageUid(@onNull Context context, @NonNull String packageName)1938 private int getPackageUid(@NonNull Context context, @NonNull String packageName) { 1939 try { 1940 return context.getPackageManager().getPackageUid(packageName, /* flags= */ 0); 1941 } catch (PackageManager.NameNotFoundException e) { 1942 return Process.INVALID_UID; 1943 } 1944 } 1945 enforceShowMigrationInfoIntent(String packageName, int callingUid)1946 private void enforceShowMigrationInfoIntent(String packageName, int callingUid) { 1947 enforceCallingPackageBelongsToUid(packageName, callingUid); 1948 1949 Intent intentToCheck = 1950 new Intent(HealthConnectManager.ACTION_SHOW_MIGRATION_INFO).setPackage(packageName); 1951 1952 ResolveInfo resolveResult = 1953 mContext.getPackageManager() 1954 .resolveActivity( 1955 intentToCheck, 1956 PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_ALL)); 1957 1958 if (Objects.isNull(resolveResult)) { 1959 throw new IllegalArgumentException( 1960 packageName 1961 + " does not handle intent " 1962 + HealthConnectManager.ACTION_SHOW_MIGRATION_INFO); 1963 } 1964 } 1965 getPopulatedRecordTypeInfoResponses()1966 private Map<Integer, List<DataOrigin>> getPopulatedRecordTypeInfoResponses() { 1967 Map<Integer, Class<? extends Record>> recordIdToExternalRecordClassMap = 1968 RecordMapper.getInstance().getRecordIdToExternalRecordClassMap(); 1969 AppInfoHelper appInfoHelper = AppInfoHelper.getInstance(); 1970 Map<Integer, List<DataOrigin>> recordTypeInfoResponses = 1971 new ArrayMap<>(recordIdToExternalRecordClassMap.size()); 1972 Map<Integer, Set<String>> recordTypeToContributingPackagesMap = 1973 appInfoHelper.getRecordTypesToContributingPackagesMap(); 1974 recordIdToExternalRecordClassMap 1975 .keySet() 1976 .forEach( 1977 (recordType) -> { 1978 if (recordTypeToContributingPackagesMap.containsKey(recordType)) { 1979 List<DataOrigin> packages = 1980 recordTypeToContributingPackagesMap.get(recordType).stream() 1981 .map( 1982 (packageName) -> 1983 new DataOrigin.Builder() 1984 .setPackageName(packageName) 1985 .build()) 1986 .toList(); 1987 recordTypeInfoResponses.put(recordType, packages); 1988 } else { 1989 recordTypeInfoResponses.put(recordType, Collections.emptyList()); 1990 } 1991 }); 1992 return recordTypeInfoResponses; 1993 } 1994 hasDataManagementPermission(int uid, int pid)1995 private boolean hasDataManagementPermission(int uid, int pid) { 1996 return mContext.checkPermission(MANAGE_HEALTH_DATA_PERMISSION, pid, uid) 1997 == PERMISSION_GRANTED; 1998 } 1999 finishDataDeliveryRead(int recordTypeId, AttributionSource attributionSource)2000 private void finishDataDeliveryRead(int recordTypeId, AttributionSource attributionSource) { 2001 finishDataDeliveryRead(Collections.singletonList(recordTypeId), attributionSource); 2002 } 2003 finishDataDeliveryRead( List<Integer> recordTypeIds, AttributionSource attributionSource)2004 private void finishDataDeliveryRead( 2005 List<Integer> recordTypeIds, AttributionSource attributionSource) { 2006 Trace.traceBegin(TRACE_TAG_READ_SUBTASKS, TAG_READ.concat("FinishDataDeliveryRead")); 2007 2008 try { 2009 for (Integer recordTypeId : recordTypeIds) { 2010 String permissionName = 2011 HealthPermissions.getHealthReadPermission( 2012 RecordTypePermissionCategoryMapper 2013 .getHealthPermissionCategoryForRecordType(recordTypeId)); 2014 mPermissionManager.finishDataDelivery(permissionName, attributionSource); 2015 } 2016 } catch (Exception exception) { 2017 // Ignore: HC API has already fulfilled the result, ignore any exception we hit here 2018 } 2019 Trace.traceEnd(TRACE_TAG_READ_SUBTASKS); 2020 } 2021 finishDataDeliveryWriteRecords( List<RecordInternal<?>> recordInternals, AttributionSource attributionSource)2022 private void finishDataDeliveryWriteRecords( 2023 List<RecordInternal<?>> recordInternals, AttributionSource attributionSource) { 2024 Trace.traceBegin(TRACE_TAG_READ_SUBTASKS, TAG_READ.concat(".FinishDataDeliveryWrite")); 2025 Set<Integer> recordTypeIdsToEnforce = new ArraySet<>(); 2026 for (RecordInternal<?> recordInternal : recordInternals) { 2027 recordTypeIdsToEnforce.add(recordInternal.getRecordType()); 2028 } 2029 2030 finishDataDeliveryWrite(recordTypeIdsToEnforce.stream().toList(), attributionSource); 2031 Trace.traceEnd(TRACE_TAG_READ_SUBTASKS); 2032 } 2033 finishDataDeliveryWrite( List<Integer> recordTypeIds, AttributionSource attributionSource)2034 private void finishDataDeliveryWrite( 2035 List<Integer> recordTypeIds, AttributionSource attributionSource) { 2036 try { 2037 for (Integer recordTypeId : recordTypeIds) { 2038 String permissionName = 2039 HealthPermissions.getHealthWritePermission( 2040 RecordTypePermissionCategoryMapper 2041 .getHealthPermissionCategoryForRecordType(recordTypeId)); 2042 mPermissionManager.finishDataDelivery(permissionName, attributionSource); 2043 } 2044 } catch (Exception exception) { 2045 // Ignore: HC API has already fulfilled the result, ignore any exception we hit here 2046 } 2047 } 2048 enforceBinderUidIsSameAsAttributionSourceUid( int binderUid, int attributionSourceUid)2049 private void enforceBinderUidIsSameAsAttributionSourceUid( 2050 int binderUid, int attributionSourceUid) { 2051 if (binderUid != attributionSourceUid) { 2052 throw new SecurityException("Binder uid must be equal to attribution source uid."); 2053 } 2054 } 2055 throwExceptionIncorrectPermissionState()2056 private void throwExceptionIncorrectPermissionState() { 2057 throw new IllegalStateException( 2058 "Incorrect health permission state, likely" 2059 + " because the calling application's manifest does not specify handling " 2060 + Intent.ACTION_VIEW_PERMISSION_USAGE 2061 + " with " 2062 + HealthConnectManager.CATEGORY_HEALTH_PERMISSIONS); 2063 } 2064 logRecordTypeSpecificUpsertMetrics( @onNull List<RecordInternal<?>> recordInternals, @NonNull String packageName)2065 private void logRecordTypeSpecificUpsertMetrics( 2066 @NonNull List<RecordInternal<?>> recordInternals, @NonNull String packageName) { 2067 Objects.requireNonNull(recordInternals); 2068 Objects.requireNonNull(packageName); 2069 2070 Map<Integer, List<RecordInternal<?>>> recordTypeToRecordInternals = 2071 getRecordTypeToListOfRecords(recordInternals); 2072 for (Entry<Integer, List<RecordInternal<?>>> recordTypeToRecordInternalsEntry : 2073 recordTypeToRecordInternals.entrySet()) { 2074 RecordHelper<?> recordHelper = 2075 RecordHelperProvider.getInstance() 2076 .getRecordHelper(recordTypeToRecordInternalsEntry.getKey()); 2077 recordHelper.logUpsertMetrics(recordTypeToRecordInternalsEntry.getValue(), packageName); 2078 } 2079 } 2080 logRecordTypeSpecificReadMetrics( @onNull List<RecordInternal<?>> recordInternals, @NonNull String packageName)2081 private void logRecordTypeSpecificReadMetrics( 2082 @NonNull List<RecordInternal<?>> recordInternals, @NonNull String packageName) { 2083 Objects.requireNonNull(recordInternals); 2084 Objects.requireNonNull(packageName); 2085 2086 Map<Integer, List<RecordInternal<?>>> recordTypeToRecordInternals = 2087 getRecordTypeToListOfRecords(recordInternals); 2088 for (Entry<Integer, List<RecordInternal<?>>> recordTypeToRecordInternalsEntry : 2089 recordTypeToRecordInternals.entrySet()) { 2090 RecordHelper<?> recordHelper = 2091 RecordHelperProvider.getInstance() 2092 .getRecordHelper(recordTypeToRecordInternalsEntry.getKey()); 2093 recordHelper.logReadMetrics(recordTypeToRecordInternalsEntry.getValue(), packageName); 2094 } 2095 } 2096 getRecordTypeToListOfRecords( List<RecordInternal<?>> recordInternals)2097 private Map<Integer, List<RecordInternal<?>>> getRecordTypeToListOfRecords( 2098 List<RecordInternal<?>> recordInternals) { 2099 2100 return recordInternals.stream() 2101 .collect(Collectors.groupingBy(RecordInternal::getRecordType)); 2102 } 2103 throwSecurityException(String message)2104 private void throwSecurityException(String message) { 2105 throw new SecurityException(message); 2106 } 2107 throwExceptionIfDataSyncInProgress()2108 private void throwExceptionIfDataSyncInProgress() { 2109 if (isDataSyncInProgress()) { 2110 throw new HealthConnectException( 2111 HealthConnectException.ERROR_DATA_SYNC_IN_PROGRESS, 2112 "Storage data sync in progress. API calls are blocked"); 2113 } 2114 } 2115 2116 /** 2117 * Throws an IllegalState Exception if data migration or restore is in process. This is only 2118 * used by HealthConnect synchronous APIs as {@link HealthConnectException} is lost between 2119 * processes on synchronous APIs and can only be returned to the caller for the APIs with a 2120 * callback. 2121 */ throwIllegalStateExceptionIfDataSyncInProgress()2122 private void throwIllegalStateExceptionIfDataSyncInProgress() { 2123 if (isDataSyncInProgress()) { 2124 throw new IllegalStateException("Storage data sync in progress. API calls are blocked"); 2125 } 2126 } 2127 postDeleteTasks(List<Integer> recordTypeIdsToDelete)2128 private static void postDeleteTasks(List<Integer> recordTypeIdsToDelete) { 2129 Trace.traceBegin(TRACE_TAG_DELETE_SUBTASKS, TAG_INSERT.concat("PostDeleteTasks")); 2130 if (recordTypeIdsToDelete != null && !recordTypeIdsToDelete.isEmpty()) { 2131 AppInfoHelper.getInstance() 2132 .syncAppInfoRecordTypesUsed(new HashSet<>(recordTypeIdsToDelete)); 2133 ActivityDateHelper.getInstance().reSyncByRecordTypeIds(recordTypeIdsToDelete); 2134 } 2135 Trace.traceEnd(TRACE_TAG_DELETE_SUBTASKS); 2136 } 2137 tryAndReturnResult( IEmptyResponseCallback callback, HealthConnectServiceLogger.Builder builder)2138 private static void tryAndReturnResult( 2139 IEmptyResponseCallback callback, HealthConnectServiceLogger.Builder builder) { 2140 try { 2141 callback.onResult(); 2142 builder.setHealthDataServiceApiStatusSuccess(); 2143 } catch (RemoteException e) { 2144 Slog.e(TAG, "Remote call failed", e); 2145 builder.setHealthDataServiceApiStatusError(ERROR_INTERNAL); 2146 } 2147 } 2148 tryAndReturnResult( IInsertRecordsResponseCallback callback, List<String> uuids, HealthConnectServiceLogger.Builder builder)2149 private static void tryAndReturnResult( 2150 IInsertRecordsResponseCallback callback, 2151 List<String> uuids, 2152 HealthConnectServiceLogger.Builder builder) { 2153 try { 2154 callback.onResult(new InsertRecordsResponseParcel(uuids)); 2155 builder.setHealthDataServiceApiStatusSuccess(); 2156 } catch (RemoteException e) { 2157 Slog.e(TAG, "Remote call failed", e); 2158 builder.setHealthDataServiceApiStatusError(ERROR_INTERNAL); 2159 } 2160 } 2161 tryAndThrowException( @onNull IInsertRecordsResponseCallback callback, @NonNull Exception exception, @HealthConnectException.ErrorCode int errorCode)2162 private static void tryAndThrowException( 2163 @NonNull IInsertRecordsResponseCallback callback, 2164 @NonNull Exception exception, 2165 @HealthConnectException.ErrorCode int errorCode) { 2166 try { 2167 callback.onError( 2168 new HealthConnectExceptionParcel( 2169 new HealthConnectException(errorCode, exception.toString()))); 2170 } catch (RemoteException e) { 2171 Log.e(TAG, "Unable to send result to the callback", e); 2172 } 2173 } 2174 tryAndThrowException( @onNull IAggregateRecordsResponseCallback callback, @NonNull Exception exception, @HealthConnectException.ErrorCode int errorCode)2175 private static void tryAndThrowException( 2176 @NonNull IAggregateRecordsResponseCallback callback, 2177 @NonNull Exception exception, 2178 @HealthConnectException.ErrorCode int errorCode) { 2179 try { 2180 callback.onError( 2181 new HealthConnectExceptionParcel( 2182 new HealthConnectException(errorCode, exception.toString()))); 2183 } catch (RemoteException e) { 2184 Log.e(TAG, "Unable to send result to the callback", e); 2185 } 2186 } 2187 tryAndThrowException( @onNull IReadRecordsResponseCallback callback, @NonNull Exception exception, @HealthConnectException.ErrorCode int errorCode)2188 private static void tryAndThrowException( 2189 @NonNull IReadRecordsResponseCallback callback, 2190 @NonNull Exception exception, 2191 @HealthConnectException.ErrorCode int errorCode) { 2192 try { 2193 callback.onError( 2194 new HealthConnectExceptionParcel( 2195 new HealthConnectException(errorCode, exception.toString()))); 2196 } catch (RemoteException e) { 2197 Log.e(TAG, "Unable to send result to the callback", e); 2198 } 2199 } 2200 tryAndThrowException( @onNull IActivityDatesResponseCallback callback, @NonNull Exception exception, @HealthConnectException.ErrorCode int errorCode)2201 private static void tryAndThrowException( 2202 @NonNull IActivityDatesResponseCallback callback, 2203 @NonNull Exception exception, 2204 @HealthConnectException.ErrorCode int errorCode) { 2205 try { 2206 callback.onError( 2207 new HealthConnectExceptionParcel( 2208 new HealthConnectException(errorCode, exception.toString()))); 2209 } catch (RemoteException e) { 2210 Log.e(TAG, "Unable to send result to the callback", e); 2211 } 2212 } 2213 tryAndThrowException( @onNull IGetChangeLogTokenCallback callback, @NonNull Exception exception, @HealthConnectException.ErrorCode int errorCode)2214 private static void tryAndThrowException( 2215 @NonNull IGetChangeLogTokenCallback callback, 2216 @NonNull Exception exception, 2217 @HealthConnectException.ErrorCode int errorCode) { 2218 try { 2219 callback.onError( 2220 new HealthConnectExceptionParcel( 2221 new HealthConnectException(errorCode, exception.toString()))); 2222 } catch (RemoteException e) { 2223 Log.e(TAG, "Unable to send result to the callback", e); 2224 } 2225 } 2226 tryAndThrowException( @onNull IAccessLogsResponseCallback callback, @NonNull Exception exception, @HealthConnectException.ErrorCode int errorCode)2227 private static void tryAndThrowException( 2228 @NonNull IAccessLogsResponseCallback callback, 2229 @NonNull Exception exception, 2230 @HealthConnectException.ErrorCode int errorCode) { 2231 try { 2232 callback.onError( 2233 new HealthConnectExceptionParcel( 2234 new HealthConnectException(errorCode, exception.toString()))); 2235 } catch (RemoteException e) { 2236 Log.e(TAG, "Unable to send result to the callback", e); 2237 } 2238 } 2239 tryAndThrowException( @onNull IEmptyResponseCallback callback, @NonNull Exception exception, @HealthConnectException.ErrorCode int errorCode)2240 private static void tryAndThrowException( 2241 @NonNull IEmptyResponseCallback callback, 2242 @NonNull Exception exception, 2243 @HealthConnectException.ErrorCode int errorCode) { 2244 try { 2245 callback.onError( 2246 new HealthConnectExceptionParcel( 2247 new HealthConnectException(errorCode, exception.toString()))); 2248 } catch (RemoteException e) { 2249 Log.e(TAG, "Unable to send result to the callback", e); 2250 } 2251 } 2252 tryAndThrowException( @onNull IApplicationInfoResponseCallback callback, @NonNull Exception exception, @HealthConnectException.ErrorCode int errorCode)2253 private static void tryAndThrowException( 2254 @NonNull IApplicationInfoResponseCallback callback, 2255 @NonNull Exception exception, 2256 @HealthConnectException.ErrorCode int errorCode) { 2257 try { 2258 callback.onError( 2259 new HealthConnectExceptionParcel( 2260 new HealthConnectException(errorCode, exception.toString()))); 2261 } catch (RemoteException e) { 2262 Log.e(TAG, "Unable to send result to the callback", e); 2263 } 2264 } 2265 tryAndThrowException( @onNull IChangeLogsResponseCallback callback, @NonNull Exception exception, @HealthConnectException.ErrorCode int errorCode)2266 private static void tryAndThrowException( 2267 @NonNull IChangeLogsResponseCallback callback, 2268 @NonNull Exception exception, 2269 @HealthConnectException.ErrorCode int errorCode) { 2270 try { 2271 callback.onError( 2272 new HealthConnectExceptionParcel( 2273 new HealthConnectException(errorCode, exception.toString()))); 2274 } catch (RemoteException e) { 2275 Log.e(TAG, "Unable to send result to the callback", e); 2276 } 2277 } 2278 tryAndThrowException( @onNull IRecordTypeInfoResponseCallback callback, @NonNull Exception exception, @HealthConnectException.ErrorCode int errorCode)2279 private static void tryAndThrowException( 2280 @NonNull IRecordTypeInfoResponseCallback callback, 2281 @NonNull Exception exception, 2282 @HealthConnectException.ErrorCode int errorCode) { 2283 try { 2284 callback.onError( 2285 new HealthConnectExceptionParcel( 2286 new HealthConnectException(errorCode, exception.toString()))); 2287 } catch (RemoteException e) { 2288 Log.e(TAG, "Unable to send result to the callback", e); 2289 } 2290 } 2291 tryAndThrowException( @onNull IGetPriorityResponseCallback callback, @NonNull Exception exception, @HealthConnectException.ErrorCode int errorCode)2292 private static void tryAndThrowException( 2293 @NonNull IGetPriorityResponseCallback callback, 2294 @NonNull Exception exception, 2295 @HealthConnectException.ErrorCode int errorCode) { 2296 try { 2297 callback.onError( 2298 new HealthConnectExceptionParcel( 2299 new HealthConnectException(errorCode, exception.toString()))); 2300 } catch (RemoteException e) { 2301 Log.e(TAG, "Unable to send result to the callback", e); 2302 } 2303 } 2304 tryAndThrowException( @onNull IMigrationCallback callback, @NonNull Exception exception, @MigrationException.ErrorCode int errorCode, @Nullable String failedEntityId)2305 private static void tryAndThrowException( 2306 @NonNull IMigrationCallback callback, 2307 @NonNull Exception exception, 2308 @MigrationException.ErrorCode int errorCode, 2309 @Nullable String failedEntityId) { 2310 try { 2311 callback.onError( 2312 new MigrationException(exception.toString(), errorCode, failedEntityId)); 2313 } catch (RemoteException e) { 2314 Log.e(TAG, "Unable to send result to the callback", e); 2315 } 2316 } 2317 } 2318