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