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