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