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 android.healthconnect.cts.lib; 18 19 import static androidx.test.InstrumentationRegistry.getContext; 20 21 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity; 22 23 import static com.google.common.truth.Truth.assertThat; 24 25 import android.content.BroadcastReceiver; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.IntentFilter; 29 import android.health.connect.HealthConnectException; 30 import android.health.connect.HealthConnectManager; 31 import android.health.connect.InsertRecordsResponse; 32 import android.health.connect.ReadRecordsRequest; 33 import android.health.connect.ReadRecordsResponse; 34 import android.health.connect.RecordIdFilter; 35 import android.health.connect.changelog.ChangeLogTokenRequest; 36 import android.health.connect.changelog.ChangeLogTokenResponse; 37 import android.health.connect.changelog.ChangeLogsRequest; 38 import android.health.connect.changelog.ChangeLogsResponse; 39 import android.health.connect.datatypes.BasalMetabolicRateRecord; 40 import android.health.connect.datatypes.DataOrigin; 41 import android.health.connect.datatypes.Device; 42 import android.health.connect.datatypes.ExerciseRoute; 43 import android.health.connect.datatypes.ExerciseSessionRecord; 44 import android.health.connect.datatypes.ExerciseSessionType; 45 import android.health.connect.datatypes.HeartRateRecord; 46 import android.health.connect.datatypes.Metadata; 47 import android.health.connect.datatypes.Record; 48 import android.health.connect.datatypes.StepsRecord; 49 import android.health.connect.datatypes.units.Power; 50 import android.os.Bundle; 51 import android.os.OutcomeReceiver; 52 import android.util.Log; 53 54 import androidx.test.core.app.ApplicationProvider; 55 56 import com.android.cts.install.lib.TestApp; 57 58 import java.io.Serializable; 59 import java.time.Instant; 60 import java.time.ZoneOffset; 61 import java.util.ArrayList; 62 import java.util.Arrays; 63 import java.util.Collections; 64 import java.util.HashMap; 65 import java.util.List; 66 import java.util.Map; 67 import java.util.concurrent.CountDownLatch; 68 import java.util.concurrent.Executors; 69 import java.util.concurrent.TimeUnit; 70 import java.util.concurrent.TimeoutException; 71 import java.util.concurrent.atomic.AtomicReference; 72 73 public class TestUtils { 74 static final String TAG = "HealthConnectTest"; 75 public static final String QUERY_TYPE = "android.healthconnect.cts.queryType"; 76 public static final String INTENT_EXTRA_CALLING_PKG = "android.healthconnect.cts.calling_pkg"; 77 public static final String APP_PKG_NAME_USED_IN_DATA_ORIGIN = 78 "android.healthconnect.cts.pkg.usedInDataOrigin"; 79 public static final String INSERT_RECORD_QUERY = "android.healthconnect.cts.insertRecord"; 80 public static final String READ_RECORDS_QUERY = "android.healthconnect.cts.readRecords"; 81 public static final String READ_RECORDS_SIZE = "android.healthconnect.cts.readRecordsNumber"; 82 public static final String READ_USING_DATA_ORIGIN_FILTERS = 83 "android.healthconnect.cts.readUsingDataOriginFilters"; 84 public static final String READ_RECORD_CLASS_NAME = 85 "android.healthconnect.cts.readRecordsClass"; 86 public static final String READ_CHANGE_LOGS_QUERY = "android.healthconnect.cts.readChangeLogs"; 87 public static final String CHANGE_LOGS_RESPONSE = 88 "android.healthconnect.cts.changeLogsResponse"; 89 public static final String CHANGE_LOG_TOKEN = "android.healthconnect.cts.changeLogToken"; 90 public static final String SUCCESS = "android.healthconnect.cts.success"; 91 public static final String CLIENT_ID = "android.healthconnect.cts.clientId"; 92 public static final String RECORD_IDS = "android.healthconnect.cts.records"; 93 public static final String DELETE_RECORDS_QUERY = "android.healthconnect.cts.deleteRecords"; 94 public static final String UPDATE_RECORDS_QUERY = "android.healthconnect.cts.updateRecords"; 95 public static final String UPDATE_EXERCISE_ROUTE = "android.healthconnect.cts.updateRoute"; 96 97 public static final String UPSERT_EXERCISE_ROUTE = "android.healthconnect.cts.upsertRoute"; 98 public static final String GET_CHANGE_LOG_TOKEN_QUERY = 99 "android.healthconnect.cts.getChangeLogToken"; 100 public static final String INTENT_EXCEPTION = "android.healthconnect.cts.exception"; 101 private static final long POLLING_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(20); 102 103 public static class RecordTypeAndRecordIds implements Serializable { 104 private String mRecordType; 105 private List<String> mRecordIds; 106 RecordTypeAndRecordIds(String recordType, List<String> ids)107 public RecordTypeAndRecordIds(String recordType, List<String> ids) { 108 mRecordType = recordType; 109 mRecordIds = ids; 110 } 111 getRecordType()112 public String getRecordType() { 113 return mRecordType; 114 } 115 getRecordIds()116 public List<String> getRecordIds() { 117 return mRecordIds; 118 } 119 } 120 insertRecordAs(TestApp testApp)121 public static Bundle insertRecordAs(TestApp testApp) throws Exception { 122 Bundle bundle = new Bundle(); 123 bundle.putString(QUERY_TYPE, INSERT_RECORD_QUERY); 124 125 return getFromTestApp(testApp, bundle); 126 } 127 deleteRecordsAs( TestApp testApp, List<RecordTypeAndRecordIds> listOfRecordIdsAndClass)128 public static Bundle deleteRecordsAs( 129 TestApp testApp, List<RecordTypeAndRecordIds> listOfRecordIdsAndClass) 130 throws Exception { 131 Bundle bundle = new Bundle(); 132 bundle.putString(QUERY_TYPE, DELETE_RECORDS_QUERY); 133 bundle.putSerializable(RECORD_IDS, (Serializable) listOfRecordIdsAndClass); 134 135 return getFromTestApp(testApp, bundle); 136 } 137 updateRecordsAs( TestApp testAppToUpdateData, List<RecordTypeAndRecordIds> listOfRecordIdsAndClass)138 public static Bundle updateRecordsAs( 139 TestApp testAppToUpdateData, List<RecordTypeAndRecordIds> listOfRecordIdsAndClass) 140 throws Exception { 141 Bundle bundle = new Bundle(); 142 bundle.putString(QUERY_TYPE, UPDATE_RECORDS_QUERY); 143 bundle.putSerializable(RECORD_IDS, (Serializable) listOfRecordIdsAndClass); 144 145 return getFromTestApp(testAppToUpdateData, bundle); 146 } 147 updateRouteAs(TestApp testAppToUpdateData)148 public static Bundle updateRouteAs(TestApp testAppToUpdateData) throws Exception { 149 Bundle bundle = new Bundle(); 150 bundle.putString(QUERY_TYPE, UPDATE_EXERCISE_ROUTE); 151 return getFromTestApp(testAppToUpdateData, bundle); 152 } 153 insertSessionNoRouteAs(TestApp testAppToUpdateData)154 public static Bundle insertSessionNoRouteAs(TestApp testAppToUpdateData) throws Exception { 155 Bundle bundle = new Bundle(); 156 bundle.putString(QUERY_TYPE, UPSERT_EXERCISE_ROUTE); 157 return getFromTestApp(testAppToUpdateData, bundle); 158 } 159 insertRecordWithAnotherAppPackageName( TestApp testAppToInsertData, TestApp testAppPkgNameUsed)160 public static Bundle insertRecordWithAnotherAppPackageName( 161 TestApp testAppToInsertData, TestApp testAppPkgNameUsed) throws Exception { 162 Bundle bundle = new Bundle(); 163 bundle.putString(QUERY_TYPE, INSERT_RECORD_QUERY); 164 bundle.putString(APP_PKG_NAME_USED_IN_DATA_ORIGIN, testAppPkgNameUsed.getPackageName()); 165 166 return getFromTestApp(testAppToInsertData, bundle); 167 } 168 readRecordsAs(TestApp testApp, ArrayList<String> recordClassesToRead)169 public static Bundle readRecordsAs(TestApp testApp, ArrayList<String> recordClassesToRead) 170 throws Exception { 171 Bundle bundle = new Bundle(); 172 bundle.putString(QUERY_TYPE, READ_RECORDS_QUERY); 173 bundle.putStringArrayList(READ_RECORD_CLASS_NAME, recordClassesToRead); 174 175 return getFromTestApp(testApp, bundle); 176 } 177 insertRecordWithGivenClientId(TestApp testApp, double clientId)178 public static Bundle insertRecordWithGivenClientId(TestApp testApp, double clientId) 179 throws Exception { 180 Bundle bundle = new Bundle(); 181 bundle.putString(QUERY_TYPE, INSERT_RECORD_QUERY); 182 bundle.putDouble(CLIENT_ID, clientId); 183 184 return getFromTestApp(testApp, bundle); 185 } 186 readRecordsUsingDataOriginFiltersAs( TestApp testApp, ArrayList<String> recordClassesToRead)187 public static Bundle readRecordsUsingDataOriginFiltersAs( 188 TestApp testApp, ArrayList<String> recordClassesToRead) throws Exception { 189 Bundle bundle = new Bundle(); 190 bundle.putString(QUERY_TYPE, READ_RECORDS_QUERY); 191 bundle.putStringArrayList(READ_RECORD_CLASS_NAME, recordClassesToRead); 192 bundle.putBoolean(READ_USING_DATA_ORIGIN_FILTERS, true); 193 194 return getFromTestApp(testApp, bundle); 195 } 196 readChangeLogsUsingDataOriginFiltersAs( TestApp testApp, String changeLogToken)197 public static Bundle readChangeLogsUsingDataOriginFiltersAs( 198 TestApp testApp, String changeLogToken) throws Exception { 199 Bundle bundle = new Bundle(); 200 bundle.putString(QUERY_TYPE, READ_CHANGE_LOGS_QUERY); 201 bundle.putString(CHANGE_LOG_TOKEN, changeLogToken); 202 bundle.putBoolean(READ_USING_DATA_ORIGIN_FILTERS, true); 203 204 return getFromTestApp(testApp, bundle); 205 } 206 getChangeLogTokenAs(TestApp testApp, String pkgName)207 public static Bundle getChangeLogTokenAs(TestApp testApp, String pkgName) throws Exception { 208 Bundle bundle = new Bundle(); 209 bundle.putString(QUERY_TYPE, GET_CHANGE_LOG_TOKEN_QUERY); 210 bundle.putString(APP_PKG_NAME_USED_IN_DATA_ORIGIN, pkgName); 211 212 return getFromTestApp(testApp, bundle); 213 } 214 getFromTestApp(TestApp testApp, Bundle bundleToCreateIntent)215 private static Bundle getFromTestApp(TestApp testApp, Bundle bundleToCreateIntent) 216 throws Exception { 217 final CountDownLatch latch = new CountDownLatch(1); 218 AtomicReference<Bundle> response = new AtomicReference<>(); 219 AtomicReference<Exception> exceptionAtomicReference = new AtomicReference<>(); 220 BroadcastReceiver broadcastReceiver = 221 new BroadcastReceiver() { 222 @Override 223 public void onReceive(Context context, Intent intent) { 224 if (intent.hasExtra(INTENT_EXCEPTION)) { 225 exceptionAtomicReference.set( 226 (Exception) (intent.getSerializableExtra(INTENT_EXCEPTION))); 227 } else { 228 response.set(intent.getExtras()); 229 } 230 latch.countDown(); 231 } 232 }; 233 234 launchTestApp(testApp, bundleToCreateIntent, broadcastReceiver, latch); 235 if (exceptionAtomicReference.get() != null) { 236 throw exceptionAtomicReference.get(); 237 } 238 return response.get(); 239 } 240 launchTestApp( TestApp testApp, Bundle bundleToCreateIntent, BroadcastReceiver broadcastReceiver, CountDownLatch latch)241 private static void launchTestApp( 242 TestApp testApp, 243 Bundle bundleToCreateIntent, 244 BroadcastReceiver broadcastReceiver, 245 CountDownLatch latch) 246 throws Exception { 247 248 // Register broadcast receiver 249 final IntentFilter intentFilter = new IntentFilter(); 250 intentFilter.addAction(bundleToCreateIntent.getString(QUERY_TYPE)); 251 intentFilter.addCategory(Intent.CATEGORY_DEFAULT); 252 getContext().registerReceiver(broadcastReceiver, intentFilter, Context.RECEIVER_EXPORTED); 253 254 // Launch the test app. 255 final Intent intent = new Intent(Intent.ACTION_MAIN); 256 intent.setPackage(testApp.getPackageName()); 257 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 258 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 259 intent.putExtra(INTENT_EXTRA_CALLING_PKG, getContext().getPackageName()); 260 intent.putExtras(bundleToCreateIntent); 261 intent.addCategory(Intent.CATEGORY_LAUNCHER); 262 intent.putExtras(bundleToCreateIntent); 263 264 Thread.sleep(500); 265 getContext().startActivity(intent); 266 if (!latch.await(POLLING_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) { 267 final String errorMessage = 268 "Timed out while waiting to receive " 269 + bundleToCreateIntent.getString(QUERY_TYPE) 270 + " intent from " 271 + testApp.getPackageName(); 272 throw new TimeoutException(errorMessage); 273 } 274 getContext().unregisterReceiver(broadcastReceiver); 275 } 276 insertRecordAndGetId(Record record, Context context)277 public static String insertRecordAndGetId(Record record, Context context) 278 throws InterruptedException { 279 return insertRecords(Collections.singletonList(record), context) 280 .get(0) 281 .getMetadata() 282 .getId(); 283 } 284 insertRecordsAndGetIds( List<Record> records, Context context)285 public static List<RecordTypeAndRecordIds> insertRecordsAndGetIds( 286 List<Record> records, Context context) throws InterruptedException { 287 List<Record> insertedRecords = insertRecords(records, context); 288 289 Map<String, List<String>> recordTypeToRecordIdsMap = new HashMap<>(); 290 for (Record record : insertedRecords) { 291 recordTypeToRecordIdsMap.putIfAbsent(record.getClass().getName(), new ArrayList<>()); 292 recordTypeToRecordIdsMap 293 .get(record.getClass().getName()) 294 .add(record.getMetadata().getId()); 295 } 296 297 List<RecordTypeAndRecordIds> recordTypeAndRecordIdsList = new ArrayList<>(); 298 for (String recordType : recordTypeToRecordIdsMap.keySet()) { 299 recordTypeAndRecordIdsList.add( 300 new RecordTypeAndRecordIds( 301 recordType, recordTypeToRecordIdsMap.get(recordType))); 302 } 303 304 return recordTypeAndRecordIdsList; 305 } 306 insertRecords(List<Record> records, Context context)307 public static List<Record> insertRecords(List<Record> records, Context context) 308 throws InterruptedException { 309 CountDownLatch latch = new CountDownLatch(1); 310 HealthConnectManager service = context.getSystemService(HealthConnectManager.class); 311 assertThat(service).isNotNull(); 312 AtomicReference<List<Record>> response = new AtomicReference<>(); 313 AtomicReference<HealthConnectException> exceptionAtomicReference = new AtomicReference<>(); 314 service.insertRecords( 315 records, 316 Executors.newSingleThreadExecutor(), 317 new OutcomeReceiver<>() { 318 @Override 319 public void onResult(InsertRecordsResponse result) { 320 response.set(result.getRecords()); 321 latch.countDown(); 322 } 323 324 @Override 325 public void onError(HealthConnectException exception) { 326 Log.e(TAG, exception.getMessage()); 327 exceptionAtomicReference.set(exception); 328 latch.countDown(); 329 } 330 }); 331 assertThat(latch.await(3, TimeUnit.SECONDS)).isTrue(); 332 if (exceptionAtomicReference.get() != null) { 333 throw exceptionAtomicReference.get(); 334 } 335 assertThat(response.get()).hasSize(records.size()); 336 337 return response.get(); 338 } 339 getTestRecords(String packageName)340 public static List<Record> getTestRecords(String packageName) { 341 double clientId = Math.random(); 342 return getTestRecords(packageName, clientId); 343 } 344 getTestRecords(String packageName, Double clientId)345 public static List<Record> getTestRecords(String packageName, Double clientId) { 346 return Arrays.asList( 347 getExerciseSessionRecord(packageName, clientId, /* withRoute= */ true), 348 getStepsRecord(packageName, clientId), 349 getHeartRateRecord(packageName, clientId), 350 getBasalMetabolicRateRecord(packageName, clientId)); 351 } 352 getExerciseSessionRecord( String packageName, double clientId, boolean withRoute)353 public static ExerciseSessionRecord getExerciseSessionRecord( 354 String packageName, double clientId, boolean withRoute) { 355 Instant startTime = Instant.now().minusSeconds(3000); 356 Instant endTime = Instant.now(); 357 ExerciseSessionRecord.Builder builder = 358 new ExerciseSessionRecord.Builder( 359 buildSessionMetadata(packageName, clientId), 360 startTime, 361 endTime, 362 ExerciseSessionType.EXERCISE_SESSION_TYPE_OTHER_WORKOUT) 363 .setEndZoneOffset(ZoneOffset.MAX) 364 .setStartZoneOffset(ZoneOffset.MIN) 365 .setNotes("notes") 366 .setTitle("title"); 367 368 if (withRoute) { 369 builder.setRoute( 370 new ExerciseRoute( 371 List.of( 372 new ExerciseRoute.Location.Builder(startTime, 50., 50.).build(), 373 new ExerciseRoute.Location.Builder( 374 startTime.plusSeconds(2), 51., 51.) 375 .build()))); 376 } 377 return builder.build(); 378 } 379 getStepsRecord(String packageName, double clientId)380 public static StepsRecord getStepsRecord(String packageName, double clientId) { 381 Device device = 382 new Device.Builder().setManufacturer("google").setModel("Pixel").setType(1).build(); 383 DataOrigin dataOrigin = new DataOrigin.Builder().setPackageName(packageName).build(); 384 return new StepsRecord.Builder( 385 new Metadata.Builder() 386 .setDevice(device) 387 .setDataOrigin(dataOrigin) 388 .setClientRecordId("SR" + clientId) 389 .build(), 390 Instant.now(), 391 Instant.now().plusMillis(1000), 392 10) 393 .build(); 394 } 395 getHeartRateRecord(String packageName, double clientId)396 public static HeartRateRecord getHeartRateRecord(String packageName, double clientId) { 397 HeartRateRecord.HeartRateSample heartRateSample = 398 new HeartRateRecord.HeartRateSample(72, Instant.now().plusMillis(100)); 399 ArrayList<HeartRateRecord.HeartRateSample> heartRateSamples = new ArrayList<>(); 400 heartRateSamples.add(heartRateSample); 401 heartRateSamples.add(heartRateSample); 402 Device device = 403 new Device.Builder().setManufacturer("google").setModel("Pixel").setType(1).build(); 404 DataOrigin dataOrigin = new DataOrigin.Builder().setPackageName(packageName).build(); 405 406 return new HeartRateRecord.Builder( 407 new Metadata.Builder() 408 .setDevice(device) 409 .setDataOrigin(dataOrigin) 410 .setClientRecordId("HR" + clientId) 411 .build(), 412 Instant.now(), 413 Instant.now().plusMillis(500), 414 heartRateSamples) 415 .build(); 416 } 417 getBasalMetabolicRateRecord( String packageName, double clientId)418 public static BasalMetabolicRateRecord getBasalMetabolicRateRecord( 419 String packageName, double clientId) { 420 Device device = 421 new Device.Builder() 422 .setManufacturer("google") 423 .setModel("Pixel4a") 424 .setType(2) 425 .build(); 426 DataOrigin dataOrigin = new DataOrigin.Builder().setPackageName(packageName).build(); 427 return new BasalMetabolicRateRecord.Builder( 428 new Metadata.Builder() 429 .setDevice(device) 430 .setDataOrigin(dataOrigin) 431 .setClientRecordId("BMR" + clientId) 432 .build(), 433 Instant.now(), 434 Power.fromWatts(100.0)) 435 .build(); 436 } 437 verifyDeleteRecords(List<RecordIdFilter> request, Context context)438 public static void verifyDeleteRecords(List<RecordIdFilter> request, Context context) 439 throws InterruptedException { 440 HealthConnectManager service = context.getSystemService(HealthConnectManager.class); 441 CountDownLatch latch = new CountDownLatch(1); 442 AtomicReference<HealthConnectException> exceptionAtomicReference = new AtomicReference<>(); 443 assertThat(service).isNotNull(); 444 service.deleteRecords( 445 request, 446 Executors.newSingleThreadExecutor(), 447 new OutcomeReceiver<>() { 448 @Override 449 public void onResult(Void result) { 450 latch.countDown(); 451 } 452 453 @Override 454 public void onError(HealthConnectException healthConnectException) { 455 exceptionAtomicReference.set(healthConnectException); 456 latch.countDown(); 457 } 458 }); 459 assertThat(latch.await(3, TimeUnit.SECONDS)).isEqualTo(true); 460 if (exceptionAtomicReference.get() != null) { 461 throw exceptionAtomicReference.get(); 462 } 463 } 464 readRecords( ReadRecordsRequest<T> request, Context context)465 public static <T extends Record> List<T> readRecords( 466 ReadRecordsRequest<T> request, Context context) throws InterruptedException { 467 HealthConnectManager service = context.getSystemService(HealthConnectManager.class); 468 CountDownLatch latch = new CountDownLatch(1); 469 assertThat(service).isNotNull(); 470 assertThat(request.getRecordType()).isNotNull(); 471 AtomicReference<List<T>> response = new AtomicReference<>(); 472 AtomicReference<HealthConnectException> healthConnectExceptionAtomicReference = 473 new AtomicReference<>(); 474 service.readRecords( 475 request, 476 Executors.newSingleThreadExecutor(), 477 new OutcomeReceiver<>() { 478 @Override 479 public void onResult(ReadRecordsResponse<T> result) { 480 response.set(result.getRecords()); 481 latch.countDown(); 482 } 483 484 @Override 485 public void onError(HealthConnectException exception) { 486 Log.e(TAG, exception.getMessage()); 487 healthConnectExceptionAtomicReference.set(exception); 488 latch.countDown(); 489 } 490 }); 491 assertThat(latch.await(3, TimeUnit.SECONDS)).isEqualTo(true); 492 if (healthConnectExceptionAtomicReference.get() != null) { 493 throw healthConnectExceptionAtomicReference.get(); 494 } 495 return response.get(); 496 } 497 updateRecords(List<Record> records, Context context)498 public static void updateRecords(List<Record> records, Context context) 499 throws InterruptedException { 500 CountDownLatch latch = new CountDownLatch(1); 501 HealthConnectManager service = context.getSystemService(HealthConnectManager.class); 502 assertThat(service).isNotNull(); 503 AtomicReference<HealthConnectException> exceptionAtomicReference = new AtomicReference<>(); 504 505 service.updateRecords( 506 records, 507 Executors.newSingleThreadExecutor(), 508 new OutcomeReceiver<>() { 509 @Override 510 public void onResult(Void result) { 511 latch.countDown(); 512 } 513 514 @Override 515 public void onError(HealthConnectException exception) { 516 exceptionAtomicReference.set(exception); 517 latch.countDown(); 518 } 519 }); 520 assertThat(latch.await(3, TimeUnit.SECONDS)).isTrue(); 521 if (exceptionAtomicReference.get() != null) { 522 throw exceptionAtomicReference.get(); 523 } 524 } 525 readRecords(ReadRecordsRequest<T> request)526 public static <T extends Record> List<T> readRecords(ReadRecordsRequest<T> request) 527 throws InterruptedException { 528 Context context = ApplicationProvider.getApplicationContext(); 529 HealthConnectManager service = context.getSystemService(HealthConnectManager.class); 530 CountDownLatch latch = new CountDownLatch(1); 531 assertThat(service).isNotNull(); 532 assertThat(request.getRecordType()).isNotNull(); 533 AtomicReference<List<T>> response = new AtomicReference<>(); 534 AtomicReference<HealthConnectException> healthConnectExceptionAtomicReference = 535 new AtomicReference<>(); 536 service.readRecords( 537 request, 538 Executors.newSingleThreadExecutor(), 539 new OutcomeReceiver<>() { 540 @Override 541 public void onResult(ReadRecordsResponse<T> result) { 542 response.set(result.getRecords()); 543 latch.countDown(); 544 } 545 546 @Override 547 public void onError(HealthConnectException exception) { 548 Log.e(TAG, exception.getMessage()); 549 healthConnectExceptionAtomicReference.set(exception); 550 latch.countDown(); 551 } 552 }); 553 assertThat(latch.await(3, TimeUnit.SECONDS)).isEqualTo(true); 554 if (healthConnectExceptionAtomicReference.get() != null) { 555 throw healthConnectExceptionAtomicReference.get(); 556 } 557 return response.get(); 558 } 559 getChangeLogToken( ChangeLogTokenRequest request, Context context)560 public static ChangeLogTokenResponse getChangeLogToken( 561 ChangeLogTokenRequest request, Context context) throws InterruptedException { 562 HealthConnectManager service = context.getSystemService(HealthConnectManager.class); 563 assertThat(service).isNotNull(); 564 CountDownLatch latch = new CountDownLatch(1); 565 AtomicReference<ChangeLogTokenResponse> response = new AtomicReference<>(); 566 AtomicReference<HealthConnectException> exceptionAtomicReference = new AtomicReference<>(); 567 service.getChangeLogToken( 568 request, 569 Executors.newSingleThreadExecutor(), 570 new OutcomeReceiver<>() { 571 @Override 572 public void onResult(ChangeLogTokenResponse result) { 573 response.set(result); 574 latch.countDown(); 575 } 576 577 @Override 578 public void onError(HealthConnectException exception) { 579 Log.e(TAG, exception.getMessage()); 580 exceptionAtomicReference.set(exception); 581 latch.countDown(); 582 } 583 }); 584 assertThat(latch.await(3, TimeUnit.SECONDS)).isTrue(); 585 if (exceptionAtomicReference.get() != null) { 586 throw exceptionAtomicReference.get(); 587 } 588 return response.get(); 589 } 590 getChangeLogs( ChangeLogsRequest changeLogsRequest, Context context)591 public static ChangeLogsResponse getChangeLogs( 592 ChangeLogsRequest changeLogsRequest, Context context) throws InterruptedException { 593 HealthConnectManager service = context.getSystemService(HealthConnectManager.class); 594 assertThat(service).isNotNull(); 595 596 CountDownLatch latch = new CountDownLatch(1); 597 AtomicReference<ChangeLogsResponse> response = new AtomicReference<>(); 598 AtomicReference<HealthConnectException> healthConnectExceptionAtomicReference = 599 new AtomicReference<>(); 600 service.getChangeLogs( 601 changeLogsRequest, 602 Executors.newSingleThreadExecutor(), 603 new OutcomeReceiver<>() { 604 @Override 605 public void onResult(ChangeLogsResponse result) { 606 response.set(result); 607 latch.countDown(); 608 } 609 610 @Override 611 public void onError(HealthConnectException exception) { 612 healthConnectExceptionAtomicReference.set(exception); 613 latch.countDown(); 614 } 615 }); 616 assertThat(latch.await(3, TimeUnit.SECONDS)).isEqualTo(true); 617 if (healthConnectExceptionAtomicReference.get() != null) { 618 throw healthConnectExceptionAtomicReference.get(); 619 } 620 621 return response.get(); 622 } 623 deleteAllStagedRemoteData()624 public static void deleteAllStagedRemoteData() { 625 Context context = ApplicationProvider.getApplicationContext(); 626 HealthConnectManager service = context.getSystemService(HealthConnectManager.class); 627 assertThat(service).isNotNull(); 628 runWithShellPermissionIdentity( 629 () -> 630 // TODO(b/241542162): Avoid reflection once TestApi can be called from CTS 631 service.getClass().getMethod("deleteAllStagedRemoteData").invoke(service), 632 "android.permission.DELETE_STAGED_HEALTH_CONNECT_REMOTE_DATA"); 633 } 634 buildSessionMetadata(String packageName, double clientId)635 private static Metadata buildSessionMetadata(String packageName, double clientId) { 636 Device device = 637 new Device.Builder().setManufacturer("google").setModel("Pixel").setType(1).build(); 638 DataOrigin dataOrigin = new DataOrigin.Builder().setPackageName(packageName).build(); 639 return new Metadata.Builder() 640 .setDevice(device) 641 .setDataOrigin(dataOrigin) 642 .setClientRecordId(String.valueOf(clientId)) 643 .build(); 644 } 645 } 646