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