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; 18 19 import android.cts.statsdatom.lib.AtomTestUtils; 20 import android.cts.statsdatom.lib.DeviceUtils; 21 22 import com.android.tradefed.device.DeviceNotAvailableException; 23 import com.android.tradefed.device.ITestDevice; 24 import com.android.tradefed.util.CommandStatus; 25 import com.android.tradefed.util.RunUtil; 26 27 import java.time.Duration; 28 import java.time.Instant; 29 import java.time.temporal.ChronoUnit; 30 import java.util.Date; 31 32 public class HostSideTestUtil { 33 34 public static final String TEST_APP_PKG_NAME = "android.healthconnect.cts.testhelper"; 35 public static final String DAILY_LOG_TESTS_ACTIVITY = ".DailyLogsTests"; 36 private static final int NUMBER_OF_RETRIES = 10; 37 38 private static final String FEATURE_TV = "android.hardware.type.television"; 39 private static final String FEATURE_EMBEDDED = "android.hardware.type.embedded"; 40 private static final String FEATURE_WATCH = "android.hardware.type.watch"; 41 private static final String FEATURE_LEANBACK = "android.software.leanback"; 42 private static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive"; 43 44 private static final String ENABLE_RATE_LIMITER_FLAG = "enable_rate_limiter"; 45 private static final String NAMESPACE_HEALTH_FITNESS = "health_fitness"; 46 private static String sRateLimiterFlagDefaultValue; 47 48 /** Clears all data on the device, including access logs. */ clearData(ITestDevice device)49 public static void clearData(ITestDevice device) throws Exception { 50 triggerTestInTestApp(device, DAILY_LOG_TESTS_ACTIVITY, "deleteAllRecordsAddedForTest"); 51 // Next two lines will delete newly added Access Logs as all access logs over 7 days are 52 // deleted by the AutoDeleteService which is run by the daily job. 53 increaseDeviceTimeByDays(device, 10); 54 triggerDailyJob(device); 55 } 56 57 /** Triggers a test on the device with the given className and testName. */ triggerTestInTestApp(ITestDevice device, String className, String testName)58 public static void triggerTestInTestApp(ITestDevice device, String className, String testName) 59 throws Exception { 60 61 if (testName != null) { 62 DeviceUtils.runDeviceTests(device, TEST_APP_PKG_NAME, className, testName); 63 } 64 } 65 66 /** Increases the device clock by the given numberOfDays. */ increaseDeviceTimeByDays(ITestDevice device, int numberOfDays)67 public static void increaseDeviceTimeByDays(ITestDevice device, int numberOfDays) 68 throws DeviceNotAvailableException { 69 Instant deviceDate = Instant.ofEpochMilli(device.getDeviceDate()); 70 71 device.setDate(Date.from(deviceDate.plus(numberOfDays, ChronoUnit.DAYS))); 72 device.executeShellCommand( 73 "cmd time_detector set_time_state_for_tests --unix_epoch_time " 74 + deviceDate.plus(numberOfDays, ChronoUnit.DAYS).toEpochMilli() 75 + " --user_should_confirm_time false --elapsed_realtime 0"); 76 77 device.executeShellCommand("am broadcast -a android.intent.action.TIME_SET"); 78 } 79 80 /** Reset device time to revert all changes made during the test. */ resetTime(ITestDevice device, Instant testStartTime, Instant deviceStartTime)81 public static void resetTime(ITestDevice device, Instant testStartTime, Instant deviceStartTime) 82 throws DeviceNotAvailableException { 83 long timeDiff = Duration.between(testStartTime, Instant.now()).toMillis(); 84 85 device.executeShellCommand( 86 "cmd time_detector set_time_state_for_tests --unix_epoch_time " 87 + deviceStartTime.plusMillis(timeDiff).toEpochMilli() 88 + " --user_should_confirm_time false --elapsed_realtime 0"); 89 device.executeShellCommand("am broadcast -a android.intent.action.TIME_SET"); 90 } 91 92 /** Triggers the Health Connect daily job. */ triggerDailyJob(ITestDevice device)93 public static void triggerDailyJob(ITestDevice device) throws Exception { 94 95 // There are multiple instances of HealthConnectDailyService. This command finds the one 96 // that needs to be triggered for this test using the job param 'hc_daily_job'. 97 String output = 98 device.executeShellCommand( 99 "dumpsys jobscheduler | grep -m1 -A0 -B10 \"hc_daily_job\""); 100 int indexOfStart = output.indexOf("/") + 1; 101 String jobId = output.substring(indexOfStart, output.indexOf(":", indexOfStart)); 102 String jobExecutionCommand = 103 "cmd jobscheduler run --namespace HEALTH_CONNECT_DAILY_JOB -f android " + jobId; 104 105 executeJob(device, jobExecutionCommand, NUMBER_OF_RETRIES); 106 RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_LONG); 107 } 108 executeJob(ITestDevice device, String jobExecutionCommand, int retry)109 private static void executeJob(ITestDevice device, String jobExecutionCommand, int retry) 110 throws DeviceNotAvailableException, RuntimeException { 111 if (retry == 0) { 112 throw new RuntimeException("Could not execute job"); 113 } 114 if (device.executeShellV2Command(jobExecutionCommand).getStatus() 115 != CommandStatus.SUCCESS) { 116 executeJob(device, jobExecutionCommand, retry - 1); 117 } 118 } 119 120 /** Checks if the hardware supports Health Connect. */ isHardwareSupported(ITestDevice device)121 public static boolean isHardwareSupported(ITestDevice device) { 122 // These UI tests are not optimised for Watches, TVs, Auto; 123 // IoT devices do not have a UI to run these UI tests 124 try { 125 return !DeviceUtils.hasFeature(device, FEATURE_TV) 126 && !DeviceUtils.hasFeature(device, FEATURE_EMBEDDED) 127 && !DeviceUtils.hasFeature(device, FEATURE_WATCH) 128 && !DeviceUtils.hasFeature(device, FEATURE_LEANBACK) 129 && !DeviceUtils.hasFeature(device, FEATURE_AUTOMOTIVE); 130 } catch (Exception e) { 131 return false; 132 } 133 } 134 135 /** Temporarily disables the rate limiter feature flag. */ setupRateLimitingFeatureFlag(ITestDevice device)136 public static void setupRateLimitingFeatureFlag(ITestDevice device) throws Exception { 137 // Store default value of the flag on device for teardown. 138 sRateLimiterFlagDefaultValue = 139 DeviceUtils.getDeviceConfigFeature( 140 device, NAMESPACE_HEALTH_FITNESS, ENABLE_RATE_LIMITER_FLAG); 141 142 DeviceUtils.putDeviceConfigFeature( 143 device, NAMESPACE_HEALTH_FITNESS, ENABLE_RATE_LIMITER_FLAG, "false"); 144 } 145 146 /** Restores the rate limiter feature flag. */ restoreRateLimitingFeatureFlag(ITestDevice device)147 public static void restoreRateLimitingFeatureFlag(ITestDevice device) throws Exception { 148 if (sRateLimiterFlagDefaultValue == null || sRateLimiterFlagDefaultValue.equals("null")) { 149 DeviceUtils.deleteDeviceConfigFeature( 150 device, NAMESPACE_HEALTH_FITNESS, ENABLE_RATE_LIMITER_FLAG); 151 } else { 152 DeviceUtils.putDeviceConfigFeature( 153 device, 154 NAMESPACE_HEALTH_FITNESS, 155 ENABLE_RATE_LIMITER_FLAG, 156 sRateLimiterFlagDefaultValue); 157 } 158 } 159 } 160