1 /*
2  * Copyright (C) 2022 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 com.android.cts.verifier;
18 
19 import static org.junit.Assume.assumeTrue;
20 
21 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
22 import com.android.ddmlib.Log;
23 import com.android.tradefed.config.Option;
24 import com.android.tradefed.device.DeviceNotAvailableException;
25 import com.android.tradefed.invoker.TestInformation;
26 import com.android.tradefed.log.LogUtil;
27 import com.android.tradefed.result.InputStreamSource;
28 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
29 import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
30 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
31 import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
32 import com.android.tradefed.util.StreamUtil;
33 
34 import com.google.common.truth.Truth;
35 
36 import junit.framework.AssertionFailedError;
37 
38 import org.junit.After;
39 import org.junit.AssumptionViolatedException;
40 import org.junit.runner.RunWith;
41 
42 import java.time.Duration;
43 import java.time.Instant;
44 import java.util.Arrays;
45 
46 @RunWith(DeviceJUnit4ClassRunner.class)
47 public abstract class CtsVerifierTest extends BaseHostJUnit4Test {
48 
49     private static final String CTS_VERIFIER_PACKAGE_NAME = "com.android.cts.verifier";
50 
51     private static final String[] PERMISSIONS =
52             new String[] {
53                 "android.car.permission.CAR_POWERTRAIN",
54                 "android.car.permission.READ_CAR_POWER_POLICY",
55                 "android.permission.ACCESS_BACKGROUND_LOCATION",
56                 "android.permission.ACCESS_FINE_LOCATION",
57                 "android.permission.ACCESS_LOCATION_EXTRA_COMMAND",
58                 "android.permission.ACCESS_NETWORK_STATE",
59                 "android.permission.ACCESS_WIFI_STATE",
60                 "android.permission.ACTIVITY_RECOGNITION",
61                 "android.permission.BLUETOOTH",
62                 "android.permission.BLUETOOTH_ADMIN",
63                 "android.permission.BLUETOOTH_ADVERTIS",
64                 "android.permission.BLUETOOTH_CONNEC",
65                 "android.permission.BLUETOOTH_SCA",
66                 "android.permission.BODY_SENSOR",
67                 "android.permission.CAMERA",
68                 "android.permission.CHANGE_NETWORK_STATE",
69                 "android.permission.CHANGE_WIFI_STATE",
70                 "android.permission.FOREGROUND_SERVIC",
71                 "android.permission.FULLSCREEN",
72                 "android.permission.HIGH_SAMPLING_RATE_SENSORS",
73                 "android.permission.INTERNET",
74                 "android.permission.NFC",
75                 "android.permission.NFC_TRANSACTION_EVENT",
76                 "android.permission.VIBRATE",
77                 "android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS",
78                 "android.permission.REQUEST_INSTALL_PACKAGES",
79                 "android.permission.REQUEST_DELETE_PACKAGES",
80                 "android.permission.REQUEST_PASSWORD_COMPLEXITY",
81                 "android.permission.SYSTEM_ALERT_WINDO",
82                 "android.permission.POST_NOTIFICATION",
83                 "android.permission.READ_EXTERNAL_STORAGE",
84                 "android.permission.MODIFY_AUDIO_SETTINGS",
85                 "android.permission.RECORD_AUDIO",
86                 "android.permission.WAKE_LOCK",
87                 "com.android.alarm.permission.SET_ALARM",
88                 "android.permission.CALL_PHONE",
89                 "android.permission.READ_PHONE_STATE",
90                 "android.permission.READ_CONTACT",
91                 "android.permission.WRITE_CONTACT",
92                 "com.android.providers.tv.permission.WRITE_EPG_DATA",
93                 "android.permission.USE_FINGERPRIN",
94                 "android.permission.USE_BIOMETRI",
95                 "android.permission.ACCESS_NOTIFICATION_POLICY",
96                 "android.permission.ACCESS_COARSE_LOCATION",
97                 "android.permission.POST_NOTIFICATIONS",
98                 "android.permission.READ_SM",
99                 "android.permission.READ_PHONE_NUMBER",
100                 "android.permission.RECEIVE_SMS",
101                 "android.permission.SEND_SMS",
102                 "android.permission.MANAGE_OWN_CALLS",
103                 "android.permission.QUERY_ALL_PACKAGES",
104                 "android.permission.WRITE_EXTERNAL_STORAGE",
105                 "android.permission.MANAGE_EXTERNAL_STORAGE",
106                 "android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE",
107                 "android.permission.INTERACT_ACROSS_USERS",
108                 "android.permission.SCHEDULE_EXACT_ALARM",
109                 "android.permission.USE_EXACT_ALARM",
110                 "android.permission.NEARBY_WIFI_DEVICES",
111                 "android.permission.READ_LOGS"
112             };
113 
114     @Option(name = "clear-results")
115     public boolean mClearResults;
116 
117     /** Indicates if the test should be run in a device of a fold mode. */
118     @Option(name = "fold-mode")
119     private boolean mFoldMode;
120 
121     private static boolean sShouldUninstallCtsVerifier = false;
122 
123     enum TestResult {
124         NOT_RUN,
125         PASSED,
126         FAILED,
127         ASSUMPTION_FAILED
128     }
129 
130     @BeforeClassWithInfo
ensureCtsVerifierInstalled(TestInformation information)131     public static void ensureCtsVerifierInstalled(TestInformation information) throws Exception {
132         CompatibilityBuildHelper buildHelper =
133                 new CompatibilityBuildHelper(information.getBuildInfo());
134         if (!information
135                 .getDevice()
136                 .getInstalledPackageNames()
137                 .contains(CTS_VERIFIER_PACKAGE_NAME)) {
138             sShouldUninstallCtsVerifier = true;
139             information
140                     .getDevice()
141                     .installPackage(
142                             buildHelper.getTestFile("CtsVerifier.apk"),
143                             /* reinstall= */ true,
144                             "--install-reason 4");
145         }
146 
147         information
148                 .getDevice()
149                 .executeShellCommand(
150                         "appops set "
151                                 + CTS_VERIFIER_PACKAGE_NAME
152                                 + " android:read_device_identifiers allow");
153         information
154                 .getDevice()
155                 .executeShellCommand(
156                         "appops set " + CTS_VERIFIER_PACKAGE_NAME + " MANAGE_EXTERNAL_STORAGE 0");
157 
158         for (String permission : PERMISSIONS) {
159             information
160                     .getDevice()
161                     .executeShellCommand(
162                             "pm grant " + CTS_VERIFIER_PACKAGE_NAME + " " + permission);
163         }
164     }
165 
166     @AfterClassWithInfo
uninstallCtsVerifier(TestInformation information)167     public static void uninstallCtsVerifier(TestInformation information) throws Exception {
168         if (sShouldUninstallCtsVerifier) {
169             information.getDevice().uninstallPackage(CTS_VERIFIER_PACKAGE_NAME);
170         }
171     }
172 
173     @After
closeCtsVerifier()174     public void closeCtsVerifier() throws Exception {
175         getDevice().executeShellCommand("am force-stop " + CTS_VERIFIER_PACKAGE_NAME);
176     }
177 
runTest(String testName, String... configRequirements)178     void runTest(String testName, String... configRequirements) throws Exception {
179         runTest(testName, mFoldMode, configRequirements);
180     }
181 
runTest(String testName, boolean foldedMode, String... configRequirements)182     void runTest(String testName, boolean foldedMode, String... configRequirements)
183             throws Exception {
184         String testNameToRun = foldedMode ? setFoldedTestNameSuffix(testName) : testName;
185         LogUtil.CLog.logAndDisplay(Log.LogLevel.INFO, "Running test " + testNameToRun);
186 
187         Truth.assertWithMessage(
188                         "A device must be connected to the workstation when starting the test")
189                 .that(getDevice())
190                 .isNotNull();
191 
192         if (mClearResults) removeTestResult(testNameToRun);
193 
194         // Check for existing result
195         TestResult currentTestResult = getTestResult(testNameToRun);
196 
197         if (currentTestResult == TestResult.PASSED) {
198             return; // We passed
199         } else if (currentTestResult == TestResult.FAILED) {
200             throw new AssertionFailedError("Failed in CtsVerifier");
201         }
202 
203         getDevice().clearLogcat();
204 
205         // Start the activity
206         String command =
207                 "am start -n "
208                         + CTS_VERIFIER_PACKAGE_NAME
209                         + "/com.android.cts.verifier.CtsInteractiveActivity --es ACTIVITY_NAME "
210                         + testName;
211         if (foldedMode) {
212             command += " --es DISPLAY_MODE folded";
213         }
214         if (configRequirements.length > 0) {
215             command += " --es REQUIREMENTS " + String.join(",", configRequirements);
216         }
217         getDevice().executeShellCommand(command);
218 
219         Instant limit = Instant.now().plus(Duration.ofMinutes(10));
220 
221         while (limit.isAfter(Instant.now())) {
222             currentTestResult = getTestResult(testNameToRun);
223             if (currentTestResult == TestResult.PASSED) {
224                 return; // We passed
225             } else if (currentTestResult == TestResult.FAILED) {
226                 throw new AssertionFailedError("Failed in CtsVerifier");
227             } else if (currentTestResult == TestResult.ASSUMPTION_FAILED) {
228                 throw new AssumptionViolatedException("Test not valid for device");
229             }
230 
231             try (InputStreamSource logcatOutput = getDevice().getLogcat()) {
232                 String logcat = StreamUtil.getStringFromSource(logcatOutput);
233                 if (logcat.contains("AndroidRuntime: Process: com.android.cts.verifier, PID: ")
234                         || logcat.contains("ANR in com.android.cts.verifier")) {
235                     // TODO: Collect reason?
236                     throw new AssertionFailedError("CTSVerifier crashed. Check logcat for reason");
237                 }
238             }
239 
240             Thread.sleep(1000);
241         }
242 
243         throw new AssertionFailedError("Timed out waiting for result from CtsVerifier");
244     }
245 
removeTestResult(String testName)246     private void removeTestResult(String testName) throws Exception {
247         getDevice()
248                 .executeShellCommand(
249                         "content delete --uri content://"
250                                 + CTS_VERIFIER_PACKAGE_NAME
251                                 + ".testresultsprovider/results --where 'testname=\""
252                                 + CTS_VERIFIER_PACKAGE_NAME
253                                 + testName
254                                 + "\"'");
255     }
256 
257     private boolean hasWarnedDisconnected = false;
258 
getTestResult(String testName)259     private TestResult getTestResult(String testName) {
260         try {
261             String resultString =
262                     getDevice()
263                             .executeShellCommand(
264                                     "content query --uri content://"
265                                             + CTS_VERIFIER_PACKAGE_NAME
266                                             + ".testresultsprovider/results --where 'testname=\""
267                                             + CTS_VERIFIER_PACKAGE_NAME
268                                             + testName
269                                             + "\"' --projection testresult");
270             hasWarnedDisconnected = false;
271             if (resultString.contains("testresult=1")) {
272                 return TestResult.PASSED;
273             } else if (resultString.contains("testresult=2")) {
274                 return TestResult.FAILED;
275             } else if (resultString.contains("testresult=3")) {
276                 return TestResult.ASSUMPTION_FAILED;
277             } else if (resultString.contains("testresult=NULL")) {
278                 return TestResult.NOT_RUN;
279             } else if (resultString.contains("No result found")) {
280                 return TestResult.NOT_RUN;
281             } else {
282                 LogUtil.CLog.logAndDisplay(
283                         Log.LogLevel.DEBUG, "Unknown test result " + resultString);
284                 return TestResult.NOT_RUN;
285             }
286         } catch (DeviceNotAvailableException e) {
287             // Happens when the device is disconnected
288             LogUtil.CLog.logAndDisplay(Log.LogLevel.DEBUG, "Error getting test result", e);
289             if (!hasWarnedDisconnected) {
290                 hasWarnedDisconnected = true;
291                 LogUtil.CLog.logAndDisplay(
292                         Log.LogLevel.INFO,
293                         "Device is not connected. Connect the device once the test "
294                                 + "has complete to record the result.");
295             }
296             return TestResult.NOT_RUN;
297         }
298     }
299 
requireActions(String... actions)300     void requireActions(String... actions) throws Exception {
301         for (String action : actions) {
302             String output =
303                     getDevice()
304                             .executeShellCommand(
305                                     "cmd package query-activities --brief -a " + action);
306             if (output.contains("No activities found")) {
307                 assumeTrue("Test requires handler for action " + action, false);
308             }
309         }
310     }
311 
requireFeatures(String... features)312     void requireFeatures(String... features) throws Exception {
313         for (String feature : features) {
314             assumeTrue("Test requires feature " + feature, getDevice().hasFeature(feature));
315         }
316     }
317 
applicableFeatures(String... features)318     void applicableFeatures(String... features) throws Exception {
319         for (String feature : features) {
320             if (getDevice().hasFeature(feature)) {
321                 return;
322             }
323         }
324         assumeTrue("Test only applicable to features " + Arrays.toString(features), false);
325     }
326 
excludeFeatures(String... features)327     void excludeFeatures(String... features) throws Exception {
328         for (String feature : features) {
329             assumeTrue("Test excludes feature " + feature, !getDevice().hasFeature(feature));
330         }
331     }
332 
333     /** Sets test name suffix for the folded mode, which is [folded]. */
setFoldedTestNameSuffix(String testName)334     private static String setFoldedTestNameSuffix(String testName) {
335         return !testName.endsWith("[folded]") ? testName + "[folded]" : testName;
336     }
337 }
338