1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.healthconnect; 18 19 import static android.Manifest.permission.MIGRATE_HEALTH_CONNECT_DATA; 20 import static android.health.connect.HealthConnectException.ERROR_UNSUPPORTED_OPERATION; 21 import static android.health.connect.HealthConnectManager.DATA_DOWNLOAD_STARTED; 22 23 import static com.android.healthfitness.flags.Flags.FLAG_PERSONAL_HEALTH_RECORD; 24 import static com.android.server.healthconnect.backuprestore.BackupRestore.DATA_DOWNLOAD_STATE_KEY; 25 import static com.android.server.healthconnect.backuprestore.BackupRestore.DATA_RESTORE_STATE_KEY; 26 import static com.android.server.healthconnect.backuprestore.BackupRestore.INTERNAL_RESTORE_STATE_STAGING_DONE; 27 import static com.android.server.healthconnect.backuprestore.BackupRestore.INTERNAL_RESTORE_STATE_STAGING_IN_PROGRESS; 28 29 import static com.google.common.truth.Truth.assertThat; 30 import static com.google.common.truth.Truth.assertWithMessage; 31 32 import static org.mockito.ArgumentMatchers.any; 33 import static org.mockito.ArgumentMatchers.anyInt; 34 import static org.mockito.ArgumentMatchers.anyString; 35 import static org.mockito.ArgumentMatchers.eq; 36 import static org.mockito.Mockito.clearInvocations; 37 import static org.mockito.Mockito.doNothing; 38 import static org.mockito.Mockito.mock; 39 import static org.mockito.Mockito.timeout; 40 import static org.mockito.Mockito.times; 41 import static org.mockito.Mockito.verify; 42 import static org.mockito.Mockito.verifyZeroInteractions; 43 import static org.mockito.Mockito.when; 44 45 import android.content.Context; 46 import android.content.pm.PackageManager; 47 import android.content.pm.ResolveInfo; 48 import android.health.connect.MedicalIdFilter; 49 import android.health.connect.aidl.HealthConnectExceptionParcel; 50 import android.health.connect.aidl.IDataStagingFinishedCallback; 51 import android.health.connect.aidl.IHealthConnectService; 52 import android.health.connect.aidl.IMigrationCallback; 53 import android.health.connect.aidl.IReadMedicalResourcesResponseCallback; 54 import android.health.connect.aidl.MedicalIdFiltersParcel; 55 import android.health.connect.exportimport.ScheduledExportSettings; 56 import android.health.connect.migration.MigrationEntityParcel; 57 import android.health.connect.migration.MigrationException; 58 import android.health.connect.restore.StageRemoteDataRequest; 59 import android.healthconnect.cts.utils.AssumptionCheckerRule; 60 import android.net.Uri; 61 import android.os.Environment; 62 import android.os.ParcelFileDescriptor; 63 import android.os.RemoteException; 64 import android.os.UserHandle; 65 import android.platform.test.annotations.DisableFlags; 66 import android.platform.test.flag.junit.SetFlagsRule; 67 import android.util.ArrayMap; 68 69 import androidx.test.platform.app.InstrumentationRegistry; 70 import androidx.test.runner.AndroidJUnit4; 71 72 import com.android.modules.utils.testing.ExtendedMockitoRule; 73 import com.android.server.LocalManagerRegistry; 74 import com.android.server.appop.AppOpsManagerLocal; 75 import com.android.server.healthconnect.migration.MigrationCleaner; 76 import com.android.server.healthconnect.migration.MigrationStateManager; 77 import com.android.server.healthconnect.migration.MigrationTestUtils; 78 import com.android.server.healthconnect.migration.MigrationUiStateManager; 79 import com.android.server.healthconnect.permission.FirstGrantTimeManager; 80 import com.android.server.healthconnect.permission.HealthConnectPermissionHelper; 81 import com.android.server.healthconnect.storage.TransactionManager; 82 import com.android.server.healthconnect.storage.datatypehelpers.PreferenceHelper; 83 84 import org.junit.After; 85 import org.junit.Before; 86 import org.junit.Rule; 87 import org.junit.Test; 88 import org.junit.runner.RunWith; 89 import org.mockito.ArgumentCaptor; 90 import org.mockito.Captor; 91 import org.mockito.Mock; 92 import org.mockito.quality.Strictness; 93 94 import java.io.File; 95 import java.io.FileWriter; 96 import java.io.IOException; 97 import java.lang.reflect.Method; 98 import java.util.Arrays; 99 import java.util.List; 100 import java.util.Map; 101 import java.util.Set; 102 import java.util.concurrent.ThreadPoolExecutor; 103 import java.util.concurrent.TimeoutException; 104 105 /** Unit test class for {@link HealthConnectServiceImpl} */ 106 @RunWith(AndroidJUnit4.class) 107 public class HealthConnectServiceImplTest { 108 /** 109 * Health connect service APIs that blocks calls when data sync (ex: backup and restore, data 110 * migration) is in progress. 111 * 112 * <p><b>Before adding a method name to this list, make sure the method implementation contains 113 * the blocking part (i.e: {@link HealthConnectServiceImpl#throwExceptionIfDataSyncInProgress} 114 * for asynchronous APIs and {@link 115 * HealthConnectServiceImpl#throwIllegalStateExceptionIfDataSyncInProgress} for synchronous 116 * APIs). </b> 117 * 118 * <p>Also, consider adding the method to {@link 119 * android.healthconnect.cts.HealthConnectManagerTest#testDataApis_migrationInProgress_apisBlocked} 120 * cts test. 121 */ 122 public static final Set<String> BLOCK_CALLS_DURING_DATA_SYNC_LIST = 123 Set.of( 124 "grantHealthPermission", 125 "revokeHealthPermission", 126 "revokeAllHealthPermissions", 127 "getGrantedHealthPermissions", 128 "getHealthPermissionsFlags", 129 "setHealthPermissionsUserFixedFlagValue", 130 "getHistoricalAccessStartDateInMilliseconds", 131 "insertRecords", 132 "aggregateRecords", 133 "readRecords", 134 "updateRecords", 135 "getChangeLogToken", 136 "getChangeLogs", 137 "deleteUsingFilters", 138 "deleteUsingFiltersForSelf", 139 "getCurrentPriority", 140 "updatePriority", 141 "setRecordRetentionPeriodInDays", 142 "getRecordRetentionPeriodInDays", 143 "getContributorApplicationsInfo", 144 "queryAllRecordTypesInfo", 145 "queryAccessLogs", 146 "getActivityDates", 147 "configureScheduledExport", 148 "getScheduledExportStatus", 149 "getScheduledExportPeriodInDays", 150 "getImportStatus", 151 "runImport", 152 "readMedicalResources"); 153 154 /** Health connect service APIs that do not block calls when data sync is in progress. */ 155 public static final Set<String> DO_NOT_BLOCK_CALLS_DURING_DATA_SYNC_LIST = 156 Set.of( 157 "startMigration", 158 "finishMigration", 159 "writeMigrationData", 160 "stageAllHealthConnectRemoteData", 161 "getAllDataForBackup", 162 "getAllBackupFileNames", 163 "deleteAllStagedRemoteData", 164 "setLowerRateLimitsForTesting", 165 "updateDataDownloadState", 166 "getHealthConnectDataState", 167 "getHealthConnectMigrationUiState", 168 "insertMinDataMigrationSdkExtensionVersion", 169 "asBinder", 170 "queryDocumentProviders"); 171 172 private static final String TEST_URI = "content://com.android.server.healthconnect/testuri"; 173 174 @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); 175 176 @Rule 177 public final ExtendedMockitoRule mExtendedMockitoRule = 178 new ExtendedMockitoRule.Builder(this) 179 .mockStatic(Environment.class) 180 .mockStatic(PreferenceHelper.class) 181 .mockStatic(LocalManagerRegistry.class) 182 .mockStatic(UserHandle.class) 183 .mockStatic(TransactionManager.class) 184 .setStrictness(Strictness.LENIENT) 185 .build(); 186 187 @Mock private TransactionManager mTransactionManager; 188 @Mock private HealthConnectDeviceConfigManager mDeviceConfigManager; 189 @Mock private HealthConnectPermissionHelper mHealthConnectPermissionHelper; 190 @Mock private MigrationCleaner mMigrationCleaner; 191 @Mock private FirstGrantTimeManager mFirstGrantTimeManager; 192 @Mock private MigrationStateManager mMigrationStateManager; 193 @Mock private MigrationUiStateManager mMigrationUiStateManager; 194 @Mock private Context mServiceContext; 195 @Mock private PreferenceHelper mPreferenceHelper; 196 @Mock private AppOpsManagerLocal mAppOpsManagerLocal; 197 @Mock private PackageManager mPackageManager; 198 @Mock IMigrationCallback mCallback; 199 @Captor ArgumentCaptor<HealthConnectExceptionParcel> mErrorCaptor; 200 private Context mContext; 201 private HealthConnectServiceImpl mHealthConnectService; 202 private UserHandle mUserHandle; 203 private File mMockDataDirectory; 204 private ThreadPoolExecutor mInternalTaskScheduler; 205 206 @Rule 207 public AssumptionCheckerRule mSupportedHardwareRule = 208 new AssumptionCheckerRule( 209 android.healthconnect.cts.utils.TestUtils::isHardwareSupported, 210 "Tests should run on supported hardware only."); 211 212 @Before setUp()213 public void setUp() throws Exception { 214 when(UserHandle.of(anyInt())).thenCallRealMethod(); 215 mUserHandle = UserHandle.of(UserHandle.myUserId()); 216 when(mServiceContext.getPackageManager()).thenReturn(mPackageManager); 217 when(mServiceContext.getUser()).thenReturn(mUserHandle); 218 mInternalTaskScheduler = HealthConnectThreadScheduler.sInternalBackgroundExecutor; 219 220 mContext = 221 new HealthConnectUserContext( 222 InstrumentationRegistry.getInstrumentation().getContext(), mUserHandle); 223 mMockDataDirectory = mContext.getDir("mock_data", Context.MODE_PRIVATE); 224 when(Environment.getDataDirectory()).thenReturn(mMockDataDirectory); 225 when(PreferenceHelper.getInstance()).thenReturn(mPreferenceHelper); 226 when(LocalManagerRegistry.getManager(AppOpsManagerLocal.class)) 227 .thenReturn(mAppOpsManagerLocal); 228 when(TransactionManager.getInitialisedInstance()).thenReturn(mTransactionManager); 229 230 mHealthConnectService = 231 new HealthConnectServiceImpl( 232 mTransactionManager, 233 mDeviceConfigManager, 234 mHealthConnectPermissionHelper, 235 mMigrationCleaner, 236 mFirstGrantTimeManager, 237 mMigrationStateManager, 238 mMigrationUiStateManager, 239 mServiceContext); 240 } 241 242 @After tearDown()243 public void tearDown() throws TimeoutException { 244 TestUtils.waitForAllScheduledTasksToComplete(); 245 deleteDir(mMockDataDirectory); 246 clearInvocations(mPreferenceHelper); 247 } 248 249 @Test testInstantiated_attachesMigrationCleanerToMigrationStateManager()250 public void testInstantiated_attachesMigrationCleanerToMigrationStateManager() { 251 verify(mMigrationCleaner).attachTo(mMigrationStateManager); 252 } 253 254 @Test testStageRemoteData_withValidInput_allFilesStaged()255 public void testStageRemoteData_withValidInput_allFilesStaged() throws Exception { 256 File dataDir = mContext.getDataDir(); 257 File testRestoreFile1 = createAndGetNonEmptyFile(dataDir, "testRestoreFile1"); 258 File testRestoreFile2 = createAndGetNonEmptyFile(dataDir, "testRestoreFile2"); 259 260 assertThat(testRestoreFile1.exists()).isTrue(); 261 assertThat(testRestoreFile2.exists()).isTrue(); 262 263 Map<String, ParcelFileDescriptor> pfdsByFileName = new ArrayMap<>(); 264 pfdsByFileName.put( 265 testRestoreFile1.getName(), 266 ParcelFileDescriptor.open(testRestoreFile1, ParcelFileDescriptor.MODE_READ_ONLY)); 267 pfdsByFileName.put( 268 testRestoreFile2.getName(), 269 ParcelFileDescriptor.open(testRestoreFile2, ParcelFileDescriptor.MODE_READ_ONLY)); 270 271 final IDataStagingFinishedCallback callback = mock(IDataStagingFinishedCallback.class); 272 mHealthConnectService.stageAllHealthConnectRemoteData( 273 new StageRemoteDataRequest(pfdsByFileName), mUserHandle, callback); 274 275 verify(callback, timeout(5000).times(1)).onResult(); 276 var stagedFileNames = mHealthConnectService.getStagedRemoteFileNames(mUserHandle); 277 assertThat(stagedFileNames.size()).isEqualTo(2); 278 assertThat(stagedFileNames.contains(testRestoreFile1.getName())).isTrue(); 279 assertThat(stagedFileNames.contains(testRestoreFile2.getName())).isTrue(); 280 } 281 282 @Test testStageRemoteData_withNotReadMode_onlyValidFilesStaged()283 public void testStageRemoteData_withNotReadMode_onlyValidFilesStaged() throws Exception { 284 File dataDir = mContext.getDataDir(); 285 File testRestoreFile1 = createAndGetNonEmptyFile(dataDir, "testRestoreFile1"); 286 File testRestoreFile2 = createAndGetNonEmptyFile(dataDir, "testRestoreFile2"); 287 288 assertThat(testRestoreFile1.exists()).isTrue(); 289 assertThat(testRestoreFile2.exists()).isTrue(); 290 291 Map<String, ParcelFileDescriptor> pfdsByFileName = new ArrayMap<>(); 292 pfdsByFileName.put( 293 testRestoreFile1.getName(), 294 ParcelFileDescriptor.open(testRestoreFile1, ParcelFileDescriptor.MODE_WRITE_ONLY)); 295 pfdsByFileName.put( 296 testRestoreFile2.getName(), 297 ParcelFileDescriptor.open(testRestoreFile2, ParcelFileDescriptor.MODE_READ_ONLY)); 298 299 final IDataStagingFinishedCallback callback = mock(IDataStagingFinishedCallback.class); 300 mHealthConnectService.stageAllHealthConnectRemoteData( 301 new StageRemoteDataRequest(pfdsByFileName), mUserHandle, callback); 302 303 verify(callback, timeout(5000).times(1)).onError(any()); 304 var stagedFileNames = mHealthConnectService.getStagedRemoteFileNames(mUserHandle); 305 assertThat(stagedFileNames.size()).isEqualTo(1); 306 assertThat(stagedFileNames.contains(testRestoreFile2.getName())).isTrue(); 307 } 308 309 // Imitates the state when we are not actively staging but the disk reflects that. 310 // Which means we were interrupted, and therefore we should stage. 311 @Test testStageRemoteData_whenStagingProgress_doesStage()312 public void testStageRemoteData_whenStagingProgress_doesStage() throws Exception { 313 File dataDir = mContext.getDataDir(); 314 File testRestoreFile1 = createAndGetNonEmptyFile(dataDir, "testRestoreFile1"); 315 File testRestoreFile2 = createAndGetNonEmptyFile(dataDir, "testRestoreFile2"); 316 317 assertThat(testRestoreFile1.exists()).isTrue(); 318 assertThat(testRestoreFile2.exists()).isTrue(); 319 320 Map<String, ParcelFileDescriptor> pfdsByFileName = new ArrayMap<>(); 321 pfdsByFileName.put( 322 testRestoreFile1.getName(), 323 ParcelFileDescriptor.open(testRestoreFile1, ParcelFileDescriptor.MODE_READ_ONLY)); 324 pfdsByFileName.put( 325 testRestoreFile2.getName(), 326 ParcelFileDescriptor.open(testRestoreFile2, ParcelFileDescriptor.MODE_READ_ONLY)); 327 328 when(mPreferenceHelper.getPreference(eq(DATA_RESTORE_STATE_KEY))) 329 .thenReturn(String.valueOf(INTERNAL_RESTORE_STATE_STAGING_IN_PROGRESS)); 330 331 final IDataStagingFinishedCallback callback = mock(IDataStagingFinishedCallback.class); 332 mHealthConnectService.stageAllHealthConnectRemoteData( 333 new StageRemoteDataRequest(pfdsByFileName), mUserHandle, callback); 334 335 verify(callback, timeout(5000)).onResult(); 336 var stagedFileNames = mHealthConnectService.getStagedRemoteFileNames(mUserHandle); 337 assertThat(stagedFileNames.size()).isEqualTo(2); 338 assertThat(stagedFileNames.contains(testRestoreFile1.getName())).isTrue(); 339 assertThat(stagedFileNames.contains(testRestoreFile2.getName())).isTrue(); 340 } 341 342 @Test testStageRemoteData_whenStagingDone_doesNotStage()343 public void testStageRemoteData_whenStagingDone_doesNotStage() throws Exception { 344 File dataDir = mContext.getDataDir(); 345 File testRestoreFile1 = createAndGetNonEmptyFile(dataDir, "testRestoreFile1"); 346 File testRestoreFile2 = createAndGetNonEmptyFile(dataDir, "testRestoreFile2"); 347 348 assertThat(testRestoreFile1.exists()).isTrue(); 349 assertThat(testRestoreFile2.exists()).isTrue(); 350 351 Map<String, ParcelFileDescriptor> pfdsByFileName = new ArrayMap<>(); 352 pfdsByFileName.put( 353 testRestoreFile1.getName(), 354 ParcelFileDescriptor.open(testRestoreFile1, ParcelFileDescriptor.MODE_READ_ONLY)); 355 pfdsByFileName.put( 356 testRestoreFile2.getName(), 357 ParcelFileDescriptor.open(testRestoreFile2, ParcelFileDescriptor.MODE_READ_ONLY)); 358 359 when(mPreferenceHelper.getPreference(eq(DATA_RESTORE_STATE_KEY))) 360 .thenReturn(String.valueOf(INTERNAL_RESTORE_STATE_STAGING_DONE)); 361 362 final IDataStagingFinishedCallback callback = mock(IDataStagingFinishedCallback.class); 363 mHealthConnectService.stageAllHealthConnectRemoteData( 364 new StageRemoteDataRequest(pfdsByFileName), mUserHandle, callback); 365 366 verify(callback, timeout(5000)).onResult(); 367 var stagedFileNames = mHealthConnectService.getStagedRemoteFileNames(mUserHandle); 368 assertThat(stagedFileNames.size()).isEqualTo(0); 369 } 370 371 @Test testUpdateDataDownloadState_settingValidState_setsState()372 public void testUpdateDataDownloadState_settingValidState_setsState() { 373 mHealthConnectService.updateDataDownloadState(DATA_DOWNLOAD_STARTED); 374 verify(mPreferenceHelper, times(1)) 375 .insertOrReplacePreference( 376 eq(DATA_DOWNLOAD_STATE_KEY), eq(String.valueOf(DATA_DOWNLOAD_STARTED))); 377 } 378 379 @Test testStartMigration_noShowMigrationInfoIntentAvailable_returnsError()380 public void testStartMigration_noShowMigrationInfoIntentAvailable_returnsError() 381 throws InterruptedException, RemoteException { 382 setUpPassingPermissionCheckFor(MIGRATE_HEALTH_CONNECT_DATA); 383 mHealthConnectService.startMigration(MigrationTestUtils.MOCK_CONFIGURED_PACKAGE, mCallback); 384 Thread.sleep(500); 385 verifyZeroInteractions(mMigrationStateManager); 386 verify(mCallback).onError(any(MigrationException.class)); 387 } 388 389 @Test testStartMigration_showMigrationInfoIntentAvailable()390 public void testStartMigration_showMigrationInfoIntentAvailable() 391 throws MigrationStateManager.IllegalMigrationStateException, 392 InterruptedException, 393 RemoteException { 394 setUpPassingPermissionCheckFor(MIGRATE_HEALTH_CONNECT_DATA); 395 MigrationTestUtils.setResolveActivityResult(new ResolveInfo(), mPackageManager); 396 mHealthConnectService.startMigration(MigrationTestUtils.MOCK_CONFIGURED_PACKAGE, mCallback); 397 Thread.sleep(500); 398 verify(mMigrationStateManager).startMigration(mServiceContext); 399 } 400 401 @Test testFinishMigration_noShowMigrationInfoIntentAvailable_returnsError()402 public void testFinishMigration_noShowMigrationInfoIntentAvailable_returnsError() 403 throws InterruptedException, RemoteException { 404 setUpPassingPermissionCheckFor(MIGRATE_HEALTH_CONNECT_DATA); 405 mHealthConnectService.finishMigration( 406 MigrationTestUtils.MOCK_CONFIGURED_PACKAGE, mCallback); 407 Thread.sleep(500); 408 verifyZeroInteractions(mMigrationStateManager); 409 verify(mCallback).onError(any(MigrationException.class)); 410 } 411 412 @Test testFinishMigration_showMigrationInfoIntentAvailable()413 public void testFinishMigration_showMigrationInfoIntentAvailable() 414 throws MigrationStateManager.IllegalMigrationStateException, 415 InterruptedException, 416 RemoteException { 417 setUpPassingPermissionCheckFor(MIGRATE_HEALTH_CONNECT_DATA); 418 MigrationTestUtils.setResolveActivityResult(new ResolveInfo(), mPackageManager); 419 mHealthConnectService.finishMigration( 420 MigrationTestUtils.MOCK_CONFIGURED_PACKAGE, mCallback); 421 Thread.sleep(500); 422 verify(mMigrationStateManager).finishMigration(mServiceContext); 423 } 424 425 @Test testWriteMigration_noShowMigrationInfoIntentAvailable_returnsError()426 public void testWriteMigration_noShowMigrationInfoIntentAvailable_returnsError() 427 throws InterruptedException, RemoteException { 428 setUpPassingPermissionCheckFor(MIGRATE_HEALTH_CONNECT_DATA); 429 mHealthConnectService.writeMigrationData( 430 MigrationTestUtils.MOCK_CONFIGURED_PACKAGE, 431 mock(MigrationEntityParcel.class), 432 mCallback); 433 Thread.sleep(500); 434 verifyZeroInteractions(mMigrationStateManager); 435 verify(mCallback).onError(any(MigrationException.class)); 436 } 437 438 @Test testWriteMigration_showMigrationInfoIntentAvailable()439 public void testWriteMigration_showMigrationInfoIntentAvailable() 440 throws MigrationStateManager.IllegalMigrationStateException, 441 InterruptedException, 442 RemoteException { 443 setUpPassingPermissionCheckFor(MIGRATE_HEALTH_CONNECT_DATA); 444 MigrationTestUtils.setResolveActivityResult(new ResolveInfo(), mPackageManager); 445 mHealthConnectService.writeMigrationData( 446 MigrationTestUtils.MOCK_CONFIGURED_PACKAGE, 447 mock(MigrationEntityParcel.class), 448 mCallback); 449 Thread.sleep(500); 450 verify(mMigrationStateManager).validateWriteMigrationData(); 451 verify(mCallback).onSuccess(); 452 } 453 454 @Test testInsertMinSdkExtVersion_noShowMigrationInfoIntentAvailable_returnsError()455 public void testInsertMinSdkExtVersion_noShowMigrationInfoIntentAvailable_returnsError() 456 throws InterruptedException, RemoteException { 457 setUpPassingPermissionCheckFor(MIGRATE_HEALTH_CONNECT_DATA); 458 mHealthConnectService.insertMinDataMigrationSdkExtensionVersion( 459 MigrationTestUtils.MOCK_CONFIGURED_PACKAGE, 0, mCallback); 460 Thread.sleep(500); 461 verifyZeroInteractions(mMigrationStateManager); 462 verify(mCallback).onError(any(MigrationException.class)); 463 } 464 465 @Test testInsertMinSdkExtVersion_showMigrationInfoIntentAvailable()466 public void testInsertMinSdkExtVersion_showMigrationInfoIntentAvailable() 467 throws MigrationStateManager.IllegalMigrationStateException, 468 InterruptedException, 469 RemoteException { 470 setUpPassingPermissionCheckFor(MIGRATE_HEALTH_CONNECT_DATA); 471 MigrationTestUtils.setResolveActivityResult(new ResolveInfo(), mPackageManager); 472 mHealthConnectService.insertMinDataMigrationSdkExtensionVersion( 473 MigrationTestUtils.MOCK_CONFIGURED_PACKAGE, 0, mCallback); 474 Thread.sleep(500); 475 verify(mMigrationStateManager).validateSetMinSdkVersion(); 476 verify(mCallback).onSuccess(); 477 } 478 479 @Test testConfigureScheduledExport_schedulesAnInternalTask()480 public void testConfigureScheduledExport_schedulesAnInternalTask() throws Exception { 481 long taskCount = mInternalTaskScheduler.getCompletedTaskCount(); 482 mHealthConnectService.configureScheduledExport( 483 ScheduledExportSettings.withUri(Uri.parse(TEST_URI)), mUserHandle); 484 Thread.sleep(500); 485 486 assertThat(mInternalTaskScheduler.getCompletedTaskCount()).isEqualTo(taskCount + 1); 487 } 488 489 /** 490 * Tests that new HealthConnect APIs block API calls during data sync using {@link 491 * HealthConnectServiceImpl.BlockCallsDuringDataSync} annotation. 492 * 493 * <p>If the API doesn't need to block API calls during data sync(ex: backup and restore, data 494 * migration), add it to the allowedApisList list yo pass this test. 495 */ 496 @Test testHealthConnectServiceApis_blocksCallsDuringDataSync()497 public void testHealthConnectServiceApis_blocksCallsDuringDataSync() { 498 // These APIs are not expected to block API calls during data sync. 499 500 Method[] allMethods = IHealthConnectService.class.getMethods(); 501 for (Method m : allMethods) { 502 assertWithMessage( 503 "Method '%s' does not belong to either" 504 + " BLOCK_CALLS_DURING_DATA_SYNC_LIST or" 505 + " DO_NOT_BLOCK_CALLS_DURING_DATA_SYNC_LIST. Make sure the method" 506 + " implementation includes a section blocking calls during data" 507 + " sync, then add the method to BLOCK_CALLS_DURING_DATA_SYNC_LIST" 508 + " (check the Javadoc for this constant for more details). If the" 509 + " method must allow calls during data sync, add it to" 510 + " DO_NOT_BLOCK_CALLS_DURING_DATA_SYNC_LIST.", 511 m.getName()) 512 .that( 513 DO_NOT_BLOCK_CALLS_DURING_DATA_SYNC_LIST.contains(m.getName()) 514 || BLOCK_CALLS_DURING_DATA_SYNC_LIST.contains(m.getName())) 515 .isTrue(); 516 517 assertWithMessage( 518 "Method '%s' can not belong to both BLOCK_CALLS_DURING_DATA_SYNC_LIST" 519 + " and DO_NOT_BLOCK_CALLS_DURING_DATA_SYNC_LIST.", 520 m.getName()) 521 .that( 522 DO_NOT_BLOCK_CALLS_DURING_DATA_SYNC_LIST.contains(m.getName()) 523 && BLOCK_CALLS_DURING_DATA_SYNC_LIST.contains(m.getName())) 524 .isFalse(); 525 } 526 } 527 528 @Test 529 @DisableFlags(FLAG_PERSONAL_HEALTH_RECORD) testReadMedicalResources_byIds_flagOff_throws()530 public void testReadMedicalResources_byIds_flagOff_throws() throws Exception { 531 IReadMedicalResourcesResponseCallback callback = 532 mock(IReadMedicalResourcesResponseCallback.class); 533 534 mHealthConnectService.readMedicalResources( 535 mContext.getAttributionSource(), 536 new MedicalIdFiltersParcel(List.of(MedicalIdFilter.fromId("id"))), 537 callback); 538 539 verify(callback, timeout(5000).times(1)).onError(mErrorCaptor.capture()); 540 assertThat(mErrorCaptor.getValue().getHealthConnectException().getErrorCode()) 541 .isEqualTo(ERROR_UNSUPPORTED_OPERATION); 542 } 543 setUpPassingPermissionCheckFor(String permission)544 private void setUpPassingPermissionCheckFor(String permission) { 545 doNothing() 546 .when(mServiceContext) 547 .enforcePermission(eq(permission), anyInt(), anyInt(), anyString()); 548 } 549 createAndGetNonEmptyFile(File dir, String fileName)550 private static File createAndGetNonEmptyFile(File dir, String fileName) throws IOException { 551 File file = new File(dir, fileName); 552 FileWriter fileWriter = new FileWriter(file); 553 fileWriter.write("Contents of file " + fileName); 554 fileWriter.close(); 555 return file; 556 } 557 deleteDir(File dir)558 private static void deleteDir(File dir) { 559 File[] files = dir.listFiles(); 560 if (files != null) { 561 for (var file : files) { 562 if (file.isDirectory()) { 563 deleteDir(file); 564 } else { 565 assertThat(file.delete()).isTrue(); 566 } 567 } 568 } 569 assertWithMessage( 570 "Directory " 571 + dir.getAbsolutePath() 572 + " is not empty, Files present = " 573 + Arrays.toString(dir.list())) 574 .that(dir.delete()) 575 .isTrue(); 576 } 577 } 578