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