| /* |
| * Copyright (C) 2023 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.healthconnect.cts.lib; |
| |
| import static androidx.test.InstrumentationRegistry.getContext; |
| |
| import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.health.connect.HealthConnectException; |
| import android.health.connect.HealthConnectManager; |
| import android.health.connect.InsertRecordsResponse; |
| import android.health.connect.ReadRecordsRequest; |
| import android.health.connect.ReadRecordsResponse; |
| import android.health.connect.RecordIdFilter; |
| import android.health.connect.changelog.ChangeLogTokenRequest; |
| import android.health.connect.changelog.ChangeLogTokenResponse; |
| import android.health.connect.changelog.ChangeLogsRequest; |
| import android.health.connect.changelog.ChangeLogsResponse; |
| import android.health.connect.datatypes.BasalMetabolicRateRecord; |
| import android.health.connect.datatypes.DataOrigin; |
| import android.health.connect.datatypes.Device; |
| import android.health.connect.datatypes.ExerciseRoute; |
| import android.health.connect.datatypes.ExerciseSessionRecord; |
| import android.health.connect.datatypes.ExerciseSessionType; |
| import android.health.connect.datatypes.HeartRateRecord; |
| import android.health.connect.datatypes.Metadata; |
| import android.health.connect.datatypes.Record; |
| import android.health.connect.datatypes.StepsRecord; |
| import android.health.connect.datatypes.units.Power; |
| import android.os.Bundle; |
| import android.os.OutcomeReceiver; |
| import android.util.Log; |
| |
| import androidx.test.core.app.ApplicationProvider; |
| |
| import com.android.cts.install.lib.TestApp; |
| |
| import java.io.Serializable; |
| import java.time.Instant; |
| import java.time.ZoneOffset; |
| import java.time.temporal.ChronoUnit; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.TimeoutException; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| public class TestUtils { |
| static final String TAG = "HealthConnectTest"; |
| public static final String READ_EXERCISE_ROUTE_PERMISSION = |
| "android.permission.health.READ_EXERCISE_ROUTE"; |
| public static final String QUERY_TYPE = "android.healthconnect.cts.queryType"; |
| public static final String INTENT_EXTRA_CALLING_PKG = "android.healthconnect.cts.calling_pkg"; |
| public static final String APP_PKG_NAME_USED_IN_DATA_ORIGIN = |
| "android.healthconnect.cts.pkg.usedInDataOrigin"; |
| public static final String INSERT_RECORD_QUERY = "android.healthconnect.cts.insertRecord"; |
| public static final String READ_RECORDS_QUERY = "android.healthconnect.cts.readRecords"; |
| public static final String READ_RECORDS_SIZE = "android.healthconnect.cts.readRecordsNumber"; |
| public static final String READ_USING_DATA_ORIGIN_FILTERS = |
| "android.healthconnect.cts.readUsingDataOriginFilters"; |
| public static final String READ_RECORD_CLASS_NAME = |
| "android.healthconnect.cts.readRecordsClass"; |
| public static final String READ_CHANGE_LOGS_QUERY = "android.healthconnect.cts.readChangeLogs"; |
| public static final String CHANGE_LOGS_RESPONSE = |
| "android.healthconnect.cts.changeLogsResponse"; |
| public static final String CHANGE_LOG_TOKEN = "android.healthconnect.cts.changeLogToken"; |
| public static final String SUCCESS = "android.healthconnect.cts.success"; |
| public static final String CLIENT_ID = "android.healthconnect.cts.clientId"; |
| public static final String RECORD_IDS = "android.healthconnect.cts.records"; |
| public static final String DELETE_RECORDS_QUERY = "android.healthconnect.cts.deleteRecords"; |
| public static final String UPDATE_RECORDS_QUERY = "android.healthconnect.cts.updateRecords"; |
| public static final String UPDATE_EXERCISE_ROUTE = "android.healthconnect.cts.updateRoute"; |
| |
| public static final String UPSERT_EXERCISE_ROUTE = "android.healthconnect.cts.upsertRoute"; |
| public static final String GET_CHANGE_LOG_TOKEN_QUERY = |
| "android.healthconnect.cts.getChangeLogToken"; |
| public static final String INTENT_EXCEPTION = "android.healthconnect.cts.exception"; |
| private static final long POLLING_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(20); |
| |
| public static class RecordTypeAndRecordIds implements Serializable { |
| private String mRecordType; |
| private List<String> mRecordIds; |
| |
| public RecordTypeAndRecordIds(String recordType, List<String> ids) { |
| mRecordType = recordType; |
| mRecordIds = ids; |
| } |
| |
| public String getRecordType() { |
| return mRecordType; |
| } |
| |
| public List<String> getRecordIds() { |
| return mRecordIds; |
| } |
| } |
| |
| public static Bundle insertRecordAs(TestApp testApp) throws Exception { |
| Bundle bundle = new Bundle(); |
| bundle.putString(QUERY_TYPE, INSERT_RECORD_QUERY); |
| |
| return getFromTestApp(testApp, bundle); |
| } |
| |
| public static Bundle deleteRecordsAs( |
| TestApp testApp, List<RecordTypeAndRecordIds> listOfRecordIdsAndClass) |
| throws Exception { |
| Bundle bundle = new Bundle(); |
| bundle.putString(QUERY_TYPE, DELETE_RECORDS_QUERY); |
| bundle.putSerializable(RECORD_IDS, (Serializable) listOfRecordIdsAndClass); |
| |
| return getFromTestApp(testApp, bundle); |
| } |
| |
| public static Bundle updateRecordsAs( |
| TestApp testAppToUpdateData, List<RecordTypeAndRecordIds> listOfRecordIdsAndClass) |
| throws Exception { |
| Bundle bundle = new Bundle(); |
| bundle.putString(QUERY_TYPE, UPDATE_RECORDS_QUERY); |
| bundle.putSerializable(RECORD_IDS, (Serializable) listOfRecordIdsAndClass); |
| |
| return getFromTestApp(testAppToUpdateData, bundle); |
| } |
| |
| public static Bundle updateRouteAs(TestApp testAppToUpdateData) throws Exception { |
| Bundle bundle = new Bundle(); |
| bundle.putString(QUERY_TYPE, UPDATE_EXERCISE_ROUTE); |
| return getFromTestApp(testAppToUpdateData, bundle); |
| } |
| |
| public static Bundle insertSessionNoRouteAs(TestApp testAppToUpdateData) throws Exception { |
| Bundle bundle = new Bundle(); |
| bundle.putString(QUERY_TYPE, UPSERT_EXERCISE_ROUTE); |
| return getFromTestApp(testAppToUpdateData, bundle); |
| } |
| |
| public static Bundle insertRecordWithAnotherAppPackageName( |
| TestApp testAppToInsertData, TestApp testAppPkgNameUsed) throws Exception { |
| Bundle bundle = new Bundle(); |
| bundle.putString(QUERY_TYPE, INSERT_RECORD_QUERY); |
| bundle.putString(APP_PKG_NAME_USED_IN_DATA_ORIGIN, testAppPkgNameUsed.getPackageName()); |
| |
| return getFromTestApp(testAppToInsertData, bundle); |
| } |
| |
| public static Bundle readRecordsAs(TestApp testApp, ArrayList<String> recordClassesToRead) |
| throws Exception { |
| Bundle bundle = new Bundle(); |
| bundle.putString(QUERY_TYPE, READ_RECORDS_QUERY); |
| bundle.putStringArrayList(READ_RECORD_CLASS_NAME, recordClassesToRead); |
| |
| return getFromTestApp(testApp, bundle); |
| } |
| |
| public static Bundle insertRecordWithGivenClientId(TestApp testApp, double clientId) |
| throws Exception { |
| Bundle bundle = new Bundle(); |
| bundle.putString(QUERY_TYPE, INSERT_RECORD_QUERY); |
| bundle.putDouble(CLIENT_ID, clientId); |
| |
| return getFromTestApp(testApp, bundle); |
| } |
| |
| public static Bundle readRecordsUsingDataOriginFiltersAs( |
| TestApp testApp, ArrayList<String> recordClassesToRead) throws Exception { |
| Bundle bundle = new Bundle(); |
| bundle.putString(QUERY_TYPE, READ_RECORDS_QUERY); |
| bundle.putStringArrayList(READ_RECORD_CLASS_NAME, recordClassesToRead); |
| bundle.putBoolean(READ_USING_DATA_ORIGIN_FILTERS, true); |
| |
| return getFromTestApp(testApp, bundle); |
| } |
| |
| public static Bundle readChangeLogsUsingDataOriginFiltersAs( |
| TestApp testApp, String changeLogToken) throws Exception { |
| Bundle bundle = new Bundle(); |
| bundle.putString(QUERY_TYPE, READ_CHANGE_LOGS_QUERY); |
| bundle.putString(CHANGE_LOG_TOKEN, changeLogToken); |
| bundle.putBoolean(READ_USING_DATA_ORIGIN_FILTERS, true); |
| |
| return getFromTestApp(testApp, bundle); |
| } |
| |
| public static Bundle getChangeLogTokenAs(TestApp testApp, String pkgName) throws Exception { |
| Bundle bundle = new Bundle(); |
| bundle.putString(QUERY_TYPE, GET_CHANGE_LOG_TOKEN_QUERY); |
| bundle.putString(APP_PKG_NAME_USED_IN_DATA_ORIGIN, pkgName); |
| |
| return getFromTestApp(testApp, bundle); |
| } |
| |
| private static Bundle getFromTestApp(TestApp testApp, Bundle bundleToCreateIntent) |
| throws Exception { |
| final CountDownLatch latch = new CountDownLatch(1); |
| AtomicReference<Bundle> response = new AtomicReference<>(); |
| AtomicReference<Exception> exceptionAtomicReference = new AtomicReference<>(); |
| BroadcastReceiver broadcastReceiver = |
| new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| if (intent.hasExtra(INTENT_EXCEPTION)) { |
| exceptionAtomicReference.set( |
| (Exception) (intent.getSerializableExtra(INTENT_EXCEPTION))); |
| } else { |
| response.set(intent.getExtras()); |
| } |
| latch.countDown(); |
| } |
| }; |
| |
| launchTestApp(testApp, bundleToCreateIntent, broadcastReceiver, latch); |
| if (exceptionAtomicReference.get() != null) { |
| throw exceptionAtomicReference.get(); |
| } |
| return response.get(); |
| } |
| |
| private static void launchTestApp( |
| TestApp testApp, |
| Bundle bundleToCreateIntent, |
| BroadcastReceiver broadcastReceiver, |
| CountDownLatch latch) |
| throws Exception { |
| |
| // Register broadcast receiver |
| final IntentFilter intentFilter = new IntentFilter(); |
| intentFilter.addAction(bundleToCreateIntent.getString(QUERY_TYPE)); |
| intentFilter.addCategory(Intent.CATEGORY_DEFAULT); |
| getContext().registerReceiver(broadcastReceiver, intentFilter, Context.RECEIVER_EXPORTED); |
| |
| // Launch the test app. |
| final Intent intent = new Intent(Intent.ACTION_MAIN); |
| intent.setPackage(testApp.getPackageName()); |
| intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); |
| intent.putExtra(INTENT_EXTRA_CALLING_PKG, getContext().getPackageName()); |
| intent.putExtras(bundleToCreateIntent); |
| intent.addCategory(Intent.CATEGORY_LAUNCHER); |
| intent.putExtras(bundleToCreateIntent); |
| |
| Thread.sleep(500); |
| getContext().startActivity(intent); |
| if (!latch.await(POLLING_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) { |
| final String errorMessage = |
| "Timed out while waiting to receive " |
| + bundleToCreateIntent.getString(QUERY_TYPE) |
| + " intent from " |
| + testApp.getPackageName(); |
| throw new TimeoutException(errorMessage); |
| } |
| getContext().unregisterReceiver(broadcastReceiver); |
| } |
| |
| public static String insertRecordAndGetId(Record record, Context context) |
| throws InterruptedException { |
| return insertRecords(Collections.singletonList(record), context) |
| .get(0) |
| .getMetadata() |
| .getId(); |
| } |
| |
| public static List<RecordTypeAndRecordIds> insertRecordsAndGetIds( |
| List<Record> records, Context context) throws InterruptedException { |
| List<Record> insertedRecords = insertRecords(records, context); |
| |
| Map<String, List<String>> recordTypeToRecordIdsMap = new HashMap<>(); |
| for (Record record : insertedRecords) { |
| recordTypeToRecordIdsMap.putIfAbsent(record.getClass().getName(), new ArrayList<>()); |
| recordTypeToRecordIdsMap |
| .get(record.getClass().getName()) |
| .add(record.getMetadata().getId()); |
| } |
| |
| List<RecordTypeAndRecordIds> recordTypeAndRecordIdsList = new ArrayList<>(); |
| for (String recordType : recordTypeToRecordIdsMap.keySet()) { |
| recordTypeAndRecordIdsList.add( |
| new RecordTypeAndRecordIds( |
| recordType, recordTypeToRecordIdsMap.get(recordType))); |
| } |
| |
| return recordTypeAndRecordIdsList; |
| } |
| |
| public static List<Record> insertRecords(List<Record> records, Context context) |
| throws InterruptedException { |
| CountDownLatch latch = new CountDownLatch(1); |
| HealthConnectManager service = context.getSystemService(HealthConnectManager.class); |
| assertThat(service).isNotNull(); |
| AtomicReference<List<Record>> response = new AtomicReference<>(); |
| AtomicReference<HealthConnectException> exceptionAtomicReference = new AtomicReference<>(); |
| service.insertRecords( |
| records, |
| Executors.newSingleThreadExecutor(), |
| new OutcomeReceiver<>() { |
| @Override |
| public void onResult(InsertRecordsResponse result) { |
| response.set(result.getRecords()); |
| latch.countDown(); |
| } |
| |
| @Override |
| public void onError(HealthConnectException exception) { |
| Log.e(TAG, exception.getMessage()); |
| exceptionAtomicReference.set(exception); |
| latch.countDown(); |
| } |
| }); |
| assertThat(latch.await(3, TimeUnit.SECONDS)).isTrue(); |
| if (exceptionAtomicReference.get() != null) { |
| throw exceptionAtomicReference.get(); |
| } |
| assertThat(response.get()).hasSize(records.size()); |
| |
| return response.get(); |
| } |
| |
| public static List<Record> getTestRecords(String packageName) { |
| double clientId = Math.random(); |
| return getTestRecords(packageName, clientId); |
| } |
| |
| public static List<Record> getTestRecords(String packageName, Double clientId) { |
| return Arrays.asList( |
| getExerciseSessionRecord(packageName, clientId, /* withRoute= */ true), |
| getStepsRecord(packageName, clientId), |
| getHeartRateRecord(packageName, clientId), |
| getBasalMetabolicRateRecord(packageName, clientId)); |
| } |
| |
| public static ExerciseSessionRecord getExerciseSessionRecord( |
| String packageName, double clientId, boolean withRoute) { |
| Instant startTime = Instant.now().minusSeconds(3000).truncatedTo(ChronoUnit.MILLIS); |
| Instant endTime = Instant.now(); |
| ExerciseSessionRecord.Builder builder = |
| new ExerciseSessionRecord.Builder( |
| buildSessionMetadata(packageName, clientId), |
| startTime, |
| endTime, |
| ExerciseSessionType.EXERCISE_SESSION_TYPE_OTHER_WORKOUT) |
| .setEndZoneOffset(ZoneOffset.MAX) |
| .setStartZoneOffset(ZoneOffset.MIN) |
| .setNotes("notes") |
| .setTitle("title"); |
| |
| if (withRoute) { |
| builder.setRoute( |
| new ExerciseRoute( |
| List.of( |
| new ExerciseRoute.Location.Builder(startTime, 50., 50.).build(), |
| new ExerciseRoute.Location.Builder( |
| startTime.plusSeconds(2), 51., 51.) |
| .build()))); |
| } |
| return builder.build(); |
| } |
| |
| public static StepsRecord getStepsRecord(String packageName, double clientId) { |
| Device device = |
| new Device.Builder().setManufacturer("google").setModel("Pixel").setType(1).build(); |
| DataOrigin dataOrigin = new DataOrigin.Builder().setPackageName(packageName).build(); |
| return new StepsRecord.Builder( |
| new Metadata.Builder() |
| .setDevice(device) |
| .setDataOrigin(dataOrigin) |
| .setClientRecordId("SR" + clientId) |
| .build(), |
| Instant.now(), |
| Instant.now().plusMillis(1000), |
| 10) |
| .build(); |
| } |
| |
| public static HeartRateRecord getHeartRateRecord(String packageName, double clientId) { |
| HeartRateRecord.HeartRateSample heartRateSample = |
| new HeartRateRecord.HeartRateSample(72, Instant.now().plusMillis(100)); |
| ArrayList<HeartRateRecord.HeartRateSample> heartRateSamples = new ArrayList<>(); |
| heartRateSamples.add(heartRateSample); |
| heartRateSamples.add(heartRateSample); |
| Device device = |
| new Device.Builder().setManufacturer("google").setModel("Pixel").setType(1).build(); |
| DataOrigin dataOrigin = new DataOrigin.Builder().setPackageName(packageName).build(); |
| |
| return new HeartRateRecord.Builder( |
| new Metadata.Builder() |
| .setDevice(device) |
| .setDataOrigin(dataOrigin) |
| .setClientRecordId("HR" + clientId) |
| .build(), |
| Instant.now(), |
| Instant.now().plusMillis(500), |
| heartRateSamples) |
| .build(); |
| } |
| |
| public static BasalMetabolicRateRecord getBasalMetabolicRateRecord( |
| String packageName, double clientId) { |
| Device device = |
| new Device.Builder() |
| .setManufacturer("google") |
| .setModel("Pixel4a") |
| .setType(2) |
| .build(); |
| DataOrigin dataOrigin = new DataOrigin.Builder().setPackageName(packageName).build(); |
| return new BasalMetabolicRateRecord.Builder( |
| new Metadata.Builder() |
| .setDevice(device) |
| .setDataOrigin(dataOrigin) |
| .setClientRecordId("BMR" + clientId) |
| .build(), |
| Instant.now(), |
| Power.fromWatts(100.0)) |
| .build(); |
| } |
| |
| public static void verifyDeleteRecords(List<RecordIdFilter> request, Context context) |
| throws InterruptedException { |
| HealthConnectManager service = context.getSystemService(HealthConnectManager.class); |
| CountDownLatch latch = new CountDownLatch(1); |
| AtomicReference<HealthConnectException> exceptionAtomicReference = new AtomicReference<>(); |
| assertThat(service).isNotNull(); |
| service.deleteRecords( |
| request, |
| Executors.newSingleThreadExecutor(), |
| new OutcomeReceiver<>() { |
| @Override |
| public void onResult(Void result) { |
| latch.countDown(); |
| } |
| |
| @Override |
| public void onError(HealthConnectException healthConnectException) { |
| exceptionAtomicReference.set(healthConnectException); |
| latch.countDown(); |
| } |
| }); |
| assertThat(latch.await(3, TimeUnit.SECONDS)).isEqualTo(true); |
| if (exceptionAtomicReference.get() != null) { |
| throw exceptionAtomicReference.get(); |
| } |
| } |
| |
| public static <T extends Record> List<T> readRecords( |
| ReadRecordsRequest<T> request, Context context) throws InterruptedException { |
| HealthConnectManager service = context.getSystemService(HealthConnectManager.class); |
| CountDownLatch latch = new CountDownLatch(1); |
| assertThat(service).isNotNull(); |
| assertThat(request.getRecordType()).isNotNull(); |
| AtomicReference<List<T>> response = new AtomicReference<>(); |
| AtomicReference<HealthConnectException> healthConnectExceptionAtomicReference = |
| new AtomicReference<>(); |
| service.readRecords( |
| request, |
| Executors.newSingleThreadExecutor(), |
| new OutcomeReceiver<>() { |
| @Override |
| public void onResult(ReadRecordsResponse<T> result) { |
| response.set(result.getRecords()); |
| latch.countDown(); |
| } |
| |
| @Override |
| public void onError(HealthConnectException exception) { |
| Log.e(TAG, exception.getMessage()); |
| healthConnectExceptionAtomicReference.set(exception); |
| latch.countDown(); |
| } |
| }); |
| assertThat(latch.await(3, TimeUnit.SECONDS)).isEqualTo(true); |
| if (healthConnectExceptionAtomicReference.get() != null) { |
| throw healthConnectExceptionAtomicReference.get(); |
| } |
| return response.get(); |
| } |
| |
| public static void updateRecords(List<Record> records, Context context) |
| throws InterruptedException { |
| CountDownLatch latch = new CountDownLatch(1); |
| HealthConnectManager service = context.getSystemService(HealthConnectManager.class); |
| assertThat(service).isNotNull(); |
| AtomicReference<HealthConnectException> exceptionAtomicReference = new AtomicReference<>(); |
| |
| service.updateRecords( |
| records, |
| Executors.newSingleThreadExecutor(), |
| new OutcomeReceiver<>() { |
| @Override |
| public void onResult(Void result) { |
| latch.countDown(); |
| } |
| |
| @Override |
| public void onError(HealthConnectException exception) { |
| exceptionAtomicReference.set(exception); |
| latch.countDown(); |
| } |
| }); |
| assertThat(latch.await(3, TimeUnit.SECONDS)).isTrue(); |
| if (exceptionAtomicReference.get() != null) { |
| throw exceptionAtomicReference.get(); |
| } |
| } |
| |
| public static <T extends Record> List<T> readRecords(ReadRecordsRequest<T> request) |
| throws InterruptedException { |
| Context context = ApplicationProvider.getApplicationContext(); |
| HealthConnectManager service = context.getSystemService(HealthConnectManager.class); |
| CountDownLatch latch = new CountDownLatch(1); |
| assertThat(service).isNotNull(); |
| assertThat(request.getRecordType()).isNotNull(); |
| AtomicReference<List<T>> response = new AtomicReference<>(); |
| AtomicReference<HealthConnectException> healthConnectExceptionAtomicReference = |
| new AtomicReference<>(); |
| service.readRecords( |
| request, |
| Executors.newSingleThreadExecutor(), |
| new OutcomeReceiver<>() { |
| @Override |
| public void onResult(ReadRecordsResponse<T> result) { |
| response.set(result.getRecords()); |
| latch.countDown(); |
| } |
| |
| @Override |
| public void onError(HealthConnectException exception) { |
| Log.e(TAG, exception.getMessage()); |
| healthConnectExceptionAtomicReference.set(exception); |
| latch.countDown(); |
| } |
| }); |
| assertThat(latch.await(3, TimeUnit.SECONDS)).isEqualTo(true); |
| if (healthConnectExceptionAtomicReference.get() != null) { |
| throw healthConnectExceptionAtomicReference.get(); |
| } |
| return response.get(); |
| } |
| |
| public static ChangeLogTokenResponse getChangeLogToken( |
| ChangeLogTokenRequest request, Context context) throws InterruptedException { |
| HealthConnectManager service = context.getSystemService(HealthConnectManager.class); |
| assertThat(service).isNotNull(); |
| CountDownLatch latch = new CountDownLatch(1); |
| AtomicReference<ChangeLogTokenResponse> response = new AtomicReference<>(); |
| AtomicReference<HealthConnectException> exceptionAtomicReference = new AtomicReference<>(); |
| service.getChangeLogToken( |
| request, |
| Executors.newSingleThreadExecutor(), |
| new OutcomeReceiver<>() { |
| @Override |
| public void onResult(ChangeLogTokenResponse result) { |
| response.set(result); |
| latch.countDown(); |
| } |
| |
| @Override |
| public void onError(HealthConnectException exception) { |
| Log.e(TAG, exception.getMessage()); |
| exceptionAtomicReference.set(exception); |
| latch.countDown(); |
| } |
| }); |
| assertThat(latch.await(3, TimeUnit.SECONDS)).isTrue(); |
| if (exceptionAtomicReference.get() != null) { |
| throw exceptionAtomicReference.get(); |
| } |
| return response.get(); |
| } |
| |
| public static ChangeLogsResponse getChangeLogs( |
| ChangeLogsRequest changeLogsRequest, Context context) throws InterruptedException { |
| HealthConnectManager service = context.getSystemService(HealthConnectManager.class); |
| assertThat(service).isNotNull(); |
| |
| CountDownLatch latch = new CountDownLatch(1); |
| AtomicReference<ChangeLogsResponse> response = new AtomicReference<>(); |
| AtomicReference<HealthConnectException> healthConnectExceptionAtomicReference = |
| new AtomicReference<>(); |
| service.getChangeLogs( |
| changeLogsRequest, |
| Executors.newSingleThreadExecutor(), |
| new OutcomeReceiver<>() { |
| @Override |
| public void onResult(ChangeLogsResponse result) { |
| response.set(result); |
| latch.countDown(); |
| } |
| |
| @Override |
| public void onError(HealthConnectException exception) { |
| healthConnectExceptionAtomicReference.set(exception); |
| latch.countDown(); |
| } |
| }); |
| assertThat(latch.await(3, TimeUnit.SECONDS)).isEqualTo(true); |
| if (healthConnectExceptionAtomicReference.get() != null) { |
| throw healthConnectExceptionAtomicReference.get(); |
| } |
| |
| return response.get(); |
| } |
| |
| public static void deleteAllStagedRemoteData() { |
| Context context = ApplicationProvider.getApplicationContext(); |
| HealthConnectManager service = context.getSystemService(HealthConnectManager.class); |
| assertThat(service).isNotNull(); |
| runWithShellPermissionIdentity( |
| () -> |
| // TODO(b/241542162): Avoid reflection once TestApi can be called from CTS |
| service.getClass().getMethod("deleteAllStagedRemoteData").invoke(service), |
| "android.permission.DELETE_STAGED_HEALTH_CONNECT_REMOTE_DATA"); |
| } |
| |
| private static Metadata buildSessionMetadata(String packageName, double clientId) { |
| Device device = |
| new Device.Builder().setManufacturer("google").setModel("Pixel").setType(1).build(); |
| DataOrigin dataOrigin = new DataOrigin.Builder().setPackageName(packageName).build(); |
| return new Metadata.Builder() |
| .setDevice(device) |
| .setDataOrigin(dataOrigin) |
| .setClientRecordId(String.valueOf(clientId)) |
| .build(); |
| } |
| } |