1 /* 2 * Copyright (C) 2014 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.devicepolicy; 18 19 import com.android.cts.migration.MigrationHelper; 20 import com.android.ddmlib.Log.LogLevel; 21 import com.android.ddmlib.testrunner.InstrumentationResultParser; 22 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner; 23 import com.android.ddmlib.testrunner.TestIdentifier; 24 import com.android.ddmlib.testrunner.TestResult; 25 import com.android.ddmlib.testrunner.TestResult.TestStatus; 26 import com.android.ddmlib.testrunner.TestRunResult; 27 import com.android.tradefed.build.IBuildInfo; 28 import com.android.tradefed.device.DeviceNotAvailableException; 29 import com.android.tradefed.device.ITestDevice; 30 import com.android.tradefed.log.LogUtil.CLog; 31 import com.android.tradefed.result.CollectingTestListener; 32 import com.android.tradefed.testtype.DeviceTestCase; 33 import com.android.tradefed.testtype.IBuildReceiver; 34 35 import java.io.File; 36 import java.io.FileNotFoundException; 37 import java.util.ArrayList; 38 import java.util.Arrays; 39 import java.util.Collections; 40 import java.util.HashSet; 41 import java.util.Map; 42 import java.util.regex.Matcher; 43 import java.util.regex.Pattern; 44 45 import javax.annotation.Nullable; 46 47 /** 48 * Base class for device policy tests. It offers utility methods to run tests, set device or profile 49 * owner, etc. 50 */ 51 public class BaseDevicePolicyTest extends DeviceTestCase implements IBuildReceiver { 52 53 private static final String RUNNER = "android.support.test.runner.AndroidJUnitRunner"; 54 55 protected static final int USER_SYSTEM = 0; // From the UserHandle class. 56 57 protected static final int USER_OWNER = 0; 58 59 // From the UserInfo class 60 protected static final int FLAG_PRIMARY = 0x00000001; 61 protected static final int FLAG_GUEST = 0x00000004; 62 protected static final int FLAG_EPHEMERAL = 0x00000100; 63 64 protected static interface Settings { 65 public static final String GLOBAL_NAMESPACE = "global"; 66 public static interface Global { 67 public static final String DEVICE_PROVISIONED = "device_provisioned"; 68 } 69 } 70 71 protected IBuildInfo mCtsBuild; 72 73 private String mPackageVerifier; 74 private HashSet<String> mAvailableFeatures; 75 76 /** Whether DPM is supported. */ 77 protected boolean mHasFeature; 78 protected int mPrimaryUserId; 79 80 /** Whether multi-user is supported. */ 81 protected boolean mSupportsMultiUser; 82 83 /** Users we shouldn't delete in the tests */ 84 private ArrayList<Integer> mFixedUsers; 85 86 @Override setBuild(IBuildInfo buildInfo)87 public void setBuild(IBuildInfo buildInfo) { 88 mCtsBuild = buildInfo; 89 } 90 91 @Override setUp()92 protected void setUp() throws Exception { 93 super.setUp(); 94 assertNotNull(mCtsBuild); // ensure build has been set before test is run. 95 mHasFeature = getDevice().getApiLevel() >= 21 /* Build.VERSION_CODES.L */ 96 && hasDeviceFeature("android.software.device_admin"); 97 mSupportsMultiUser = getMaxNumberOfUsersSupported() > 1; 98 99 // disable the package verifier to avoid the dialog when installing an app 100 mPackageVerifier = getDevice().executeShellCommand( 101 "settings get global package_verifier_enable"); 102 getDevice().executeShellCommand("settings put global package_verifier_enable 0"); 103 104 mFixedUsers = new ArrayList(); 105 mPrimaryUserId = getPrimaryUser(); 106 mFixedUsers.add(mPrimaryUserId); 107 if (mPrimaryUserId != USER_SYSTEM) { 108 mFixedUsers.add(USER_SYSTEM); 109 } 110 switchUser(mPrimaryUserId); 111 removeOwners(); 112 removeTestUsers(); 113 } 114 115 @Override tearDown()116 protected void tearDown() throws Exception { 117 // reset the package verifier setting to its original value 118 getDevice().executeShellCommand("settings put global package_verifier_enable " 119 + mPackageVerifier); 120 removeOwners(); 121 removeTestUsers(); 122 super.tearDown(); 123 } 124 installAppAsUser(String appFileName, int userId)125 protected void installAppAsUser(String appFileName, int userId) throws FileNotFoundException, 126 DeviceNotAvailableException { 127 installAppAsUser(appFileName, true, userId); 128 } 129 installAppAsUser(String appFileName, boolean grantPermissions, int userId)130 protected void installAppAsUser(String appFileName, boolean grantPermissions, int userId) 131 throws FileNotFoundException, DeviceNotAvailableException { 132 CLog.d("Installing app " + appFileName + " for user " + userId); 133 String result = getDevice().installPackageForUser( 134 MigrationHelper.getTestFile(mCtsBuild, appFileName), true, grantPermissions, 135 userId, "-t"); 136 assertNull("Failed to install " + appFileName + " for user " + userId + ": " + result, 137 result); 138 } 139 140 /** Initializes the user with the given id. This is required so that apps can run on it. */ startUser(int userId)141 protected void startUser(int userId) throws Exception { 142 getDevice().startUser(userId); 143 } 144 switchUser(int userId)145 protected void switchUser(int userId) throws Exception { 146 // TODO Move this logic to ITestDevice 147 String command = "am switch-user " + userId; 148 CLog.d("Starting command " + command); 149 String commandOutput = getDevice().executeShellCommand(command); 150 CLog.d("Output for command " + command + ": " + commandOutput); 151 } 152 getMaxNumberOfUsersSupported()153 protected int getMaxNumberOfUsersSupported() throws DeviceNotAvailableException { 154 return getDevice().getMaxNumberOfUsersSupported(); 155 } 156 getUserFlags(int userId)157 protected int getUserFlags(int userId) throws DeviceNotAvailableException { 158 String command = "pm list users"; 159 String commandOutput = getDevice().executeShellCommand(command); 160 CLog.i("Output for command " + command + ": " + commandOutput); 161 162 String[] lines = commandOutput.split("\\r?\\n"); 163 assertTrue(commandOutput + " should contain at least one line", lines.length >= 1); 164 for (int i = 1; i < lines.length; i++) { 165 // Individual user is printed out like this: 166 // \tUserInfo{$id$:$name$:$Integer.toHexString(flags)$} [running] 167 String[] tokens = lines[i].split("\\{|\\}|:"); 168 assertTrue(lines[i] + " doesn't contain 4 or 5 tokens", 169 tokens.length == 4 || tokens.length == 5); 170 // If the user IDs match, return the flags. 171 if (Integer.parseInt(tokens[1]) == userId) { 172 return Integer.parseInt(tokens[3], 16); 173 } 174 } 175 fail("User not found"); 176 return 0; 177 } 178 listUsers()179 protected ArrayList<Integer> listUsers() throws DeviceNotAvailableException { 180 return getDevice().listUsers(); 181 } 182 stopUser(int userId)183 protected void stopUser(int userId) throws Exception { 184 String stopUserCommand = "am stop-user -w -f " + userId; 185 CLog.d("starting command \"" + stopUserCommand + "\" and waiting."); 186 CLog.d("Output for command " + stopUserCommand + ": " 187 + getDevice().executeShellCommand(stopUserCommand)); 188 } 189 removeUser(int userId)190 protected void removeUser(int userId) throws Exception { 191 if (listUsers().contains(userId) && userId != USER_SYSTEM) { 192 // Don't log output, as tests sometimes set no debug user restriction, which 193 // causes this to fail, we should still continue and remove the user. 194 String stopUserCommand = "am stop-user -w -f " + userId; 195 CLog.d("stopping and removing user " + userId); 196 getDevice().executeShellCommand(stopUserCommand); 197 assertTrue("Couldn't remove user", getDevice().removeUser(userId)); 198 } 199 } 200 removeTestUsers()201 protected void removeTestUsers() throws Exception { 202 for (int userId : listUsers()) { 203 if (!mFixedUsers.contains(userId)) { 204 removeUser(userId); 205 } 206 } 207 } 208 209 /** Returns true if the specified tests passed. Tests are run as given user. */ runDeviceTestsAsUser( String pkgName, @Nullable String testClassName, int userId)210 protected boolean runDeviceTestsAsUser( 211 String pkgName, @Nullable String testClassName, int userId) 212 throws DeviceNotAvailableException { 213 return runDeviceTestsAsUser(pkgName, testClassName, null /*testMethodName*/, userId); 214 } 215 216 /** Returns true if the specified tests passed. Tests are run as given user. */ runDeviceTestsAsUser( String pkgName, @Nullable String testClassName, String testMethodName, int userId)217 protected boolean runDeviceTestsAsUser( 218 String pkgName, @Nullable String testClassName, String testMethodName, int userId) 219 throws DeviceNotAvailableException { 220 Map<String, String> params = Collections.emptyMap(); 221 return runDeviceTestsAsUser(pkgName, testClassName, testMethodName, userId, params); 222 } 223 runDeviceTestsAsUser(String pkgName, @Nullable String testClassName, @Nullable String testMethodName, int userId, Map<String, String> params)224 protected boolean runDeviceTestsAsUser(String pkgName, @Nullable String testClassName, 225 @Nullable String testMethodName, int userId, 226 Map<String, String> params) throws DeviceNotAvailableException { 227 if (testClassName != null && testClassName.startsWith(".")) { 228 testClassName = pkgName + testClassName; 229 } 230 231 RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner( 232 pkgName, RUNNER, getDevice().getIDevice()); 233 if (testClassName != null && testMethodName != null) { 234 testRunner.setMethodName(testClassName, testMethodName); 235 } else if (testClassName != null) { 236 testRunner.setClassName(testClassName); 237 } 238 239 for (Map.Entry<String, String> param : params.entrySet()) { 240 testRunner.addInstrumentationArg(param.getKey(), param.getValue()); 241 } 242 243 CollectingTestListener listener = new CollectingTestListener(); 244 assertTrue(getDevice().runInstrumentationTestsAsUser(testRunner, userId, listener)); 245 246 TestRunResult runResult = listener.getCurrentRunResults(); 247 printTestResult(runResult); 248 return !runResult.hasFailedTests() && runResult.getNumTestsInState(TestStatus.PASSED) > 0; 249 } 250 251 /** Returns true if the system supports the split between system and primary user. */ hasUserSplit()252 protected boolean hasUserSplit() throws DeviceNotAvailableException { 253 return getBooleanSystemProperty("ro.fw.system_user_split", false); 254 } 255 256 /** Returns a boolean value of the system property with the specified key. */ getBooleanSystemProperty(String key, boolean defaultValue)257 protected boolean getBooleanSystemProperty(String key, boolean defaultValue) 258 throws DeviceNotAvailableException { 259 final String[] positiveValues = {"1", "y", "yes", "true", "on"}; 260 final String[] negativeValues = {"0", "n", "no", "false", "off"}; 261 String propertyValue = getDevice().getProperty(key); 262 if (propertyValue == null || propertyValue.isEmpty()) { 263 return defaultValue; 264 } 265 if (Arrays.asList(positiveValues).contains(propertyValue)) { 266 return true; 267 } 268 if (Arrays.asList(negativeValues).contains(propertyValue)) { 269 return false; 270 } 271 fail("Unexpected value of boolean system property '" + key + "': " + propertyValue); 272 return false; 273 } 274 275 /** Checks whether it is possible to create the desired number of users. */ canCreateAdditionalUsers(int numberOfUsers)276 protected boolean canCreateAdditionalUsers(int numberOfUsers) 277 throws DeviceNotAvailableException { 278 return listUsers().size() + numberOfUsers <= getMaxNumberOfUsersSupported(); 279 } 280 printTestResult(TestRunResult runResult)281 private void printTestResult(TestRunResult runResult) { 282 for (Map.Entry<TestIdentifier, TestResult> testEntry : 283 runResult.getTestResults().entrySet()) { 284 TestResult testResult = testEntry.getValue(); 285 CLog.d("Test " + testEntry.getKey() + ": " + testResult.getStatus()); 286 if (testResult.getStatus() != TestStatus.PASSED) { 287 CLog.d(testResult.getStackTrace()); 288 } 289 } 290 } 291 hasDeviceFeature(String requiredFeature)292 protected boolean hasDeviceFeature(String requiredFeature) throws DeviceNotAvailableException { 293 if (mAvailableFeatures == null) { 294 // TODO: Move this logic to ITestDevice. 295 String command = "pm list features"; 296 String commandOutput = getDevice().executeShellCommand(command); 297 CLog.i("Output for command " + command + ": " + commandOutput); 298 299 // Extract the id of the new user. 300 mAvailableFeatures = new HashSet<>(); 301 for (String feature: commandOutput.split("\\s+")) { 302 // Each line in the output of the command has the format "feature:{FEATURE_VALUE}". 303 String[] tokens = feature.split(":"); 304 assertTrue("\"" + feature + "\" expected to have format feature:{FEATURE_VALUE}", 305 tokens.length > 1); 306 assertEquals(feature, "feature", tokens[0]); 307 mAvailableFeatures.add(tokens[1]); 308 } 309 } 310 boolean result = mAvailableFeatures.contains(requiredFeature); 311 if (!result) { 312 CLog.d("Device doesn't have required feature " 313 + requiredFeature + ". Test won't run."); 314 } 315 return result; 316 } 317 createUser()318 protected int createUser() throws Exception { 319 int userId = createUser(0); 320 // TODO remove this and audit tests so they start users as necessary 321 startUser(userId); 322 return userId; 323 } 324 createUser(int flags)325 protected int createUser(int flags) throws Exception { 326 boolean guest = FLAG_GUEST == (flags & FLAG_GUEST); 327 boolean ephemeral = FLAG_EPHEMERAL == (flags & FLAG_EPHEMERAL); 328 // TODO Use ITestDevice.createUser() when guest and ephemeral is available 329 String command ="pm create-user " + (guest ? "--guest " : "") 330 + (ephemeral ? "--ephemeral " : "") + "TestUser_" + System.currentTimeMillis(); 331 CLog.d("Starting command " + command); 332 String commandOutput = getDevice().executeShellCommand(command); 333 CLog.d("Output for command " + command + ": " + commandOutput); 334 335 // Extract the id of the new user. 336 String[] tokens = commandOutput.split("\\s+"); 337 assertTrue(tokens.length > 0); 338 assertEquals("Success:", tokens[0]); 339 return Integer.parseInt(tokens[tokens.length-1]); 340 } 341 createManagedProfile(int parentUserId)342 protected int createManagedProfile(int parentUserId) throws DeviceNotAvailableException { 343 String command = "pm create-user --profileOf " + parentUserId + " --managed " 344 + "TestProfile_" + System.currentTimeMillis(); 345 CLog.d("Starting command " + command); 346 String commandOutput = getDevice().executeShellCommand(command); 347 CLog.d("Output for command " + command + ": " + commandOutput); 348 349 // Extract the id of the new user. 350 String[] tokens = commandOutput.split("\\s+"); 351 assertTrue(commandOutput + " expected to have format \"Success: {USER_ID}\"", 352 tokens.length > 0); 353 assertEquals(commandOutput, "Success:", tokens[0]); 354 return Integer.parseInt(tokens[tokens.length-1]); 355 } 356 getPrimaryUser()357 protected int getPrimaryUser() throws DeviceNotAvailableException { 358 return getDevice().getPrimaryUserId(); 359 } 360 getUserSerialNumber(int userId)361 protected int getUserSerialNumber(int userId) throws DeviceNotAvailableException{ 362 // TODO: Move this logic to ITestDevice. 363 // dumpsys user return lines like "UserInfo{0:Owner:13} serialNo=0" 364 String commandOutput = getDevice().executeShellCommand("dumpsys user"); 365 String[] tokens = commandOutput.split("\\n"); 366 for (String token : tokens) { 367 token = token.trim(); 368 if (token.contains("UserInfo{" + userId + ":")) { 369 String[] split = token.split("serialNo="); 370 assertTrue(split.length == 2); 371 int serialNumber = Integer.parseInt(split[1]); 372 CLog.d("Serial number of user " + userId + ": " 373 + serialNumber); 374 return serialNumber; 375 } 376 } 377 fail("Couldn't find user " + userId); 378 return -1; 379 } 380 setProfileOwner(String componentName, int userId, boolean expectFailure)381 protected boolean setProfileOwner(String componentName, int userId, boolean expectFailure) 382 throws DeviceNotAvailableException { 383 String command = "dpm set-profile-owner --user " + userId + " '" + componentName + "'"; 384 String commandOutput = getDevice().executeShellCommand(command); 385 boolean success = commandOutput.startsWith("Success:"); 386 // If we succeeded always log, if we are expecting failure don't log failures 387 // as call stacks for passing tests confuse the logs. 388 if (success || !expectFailure) { 389 CLog.d("Output for command " + command + ": " + commandOutput); 390 } else { 391 CLog.d("Command Failed " + command); 392 } 393 return success; 394 } 395 setProfileOwnerOrFail(String componentName, int userId)396 protected void setProfileOwnerOrFail(String componentName, int userId) 397 throws Exception { 398 if (!setProfileOwner(componentName, userId, /*expectFailure*/ false)) { 399 removeUser(userId); 400 fail("Failed to set profile owner"); 401 } 402 } 403 setDeviceAdminInner(String componentName, int userId)404 private String setDeviceAdminInner(String componentName, int userId) 405 throws DeviceNotAvailableException { 406 String command = "dpm set-active-admin --user " + userId + " '" + componentName + "'"; 407 String commandOutput = getDevice().executeShellCommand(command); 408 return commandOutput; 409 } 410 setDeviceAdmin(String componentName, int userId)411 protected void setDeviceAdmin(String componentName, int userId) 412 throws DeviceNotAvailableException { 413 String commandOutput = setDeviceAdminInner(componentName, userId); 414 CLog.d("Output for command " + commandOutput 415 + ": " + commandOutput); 416 assertTrue(commandOutput + " expected to start with \"Success:\"", 417 commandOutput.startsWith("Success:")); 418 } 419 setDeviceAdminExpectingFailure(String componentName, int userId, String errorMessage)420 protected void setDeviceAdminExpectingFailure(String componentName, int userId, 421 String errorMessage) throws DeviceNotAvailableException { 422 String commandOutput = setDeviceAdminInner(componentName, userId); 423 if (!commandOutput.contains(errorMessage)) { 424 fail(commandOutput + " expected to contain \"" + errorMessage + "\""); 425 } 426 } 427 setDeviceOwner(String componentName, int userId, boolean expectFailure)428 protected boolean setDeviceOwner(String componentName, int userId, boolean expectFailure) 429 throws DeviceNotAvailableException { 430 String command = "dpm set-device-owner --user " + userId + " '" + componentName + "'"; 431 String commandOutput = getDevice().executeShellCommand(command); 432 boolean success = commandOutput.startsWith("Success:"); 433 // If we succeeded always log, if we are expecting failure don't log failures 434 // as call stacks for passing tests confuse the logs. 435 if (success || !expectFailure) { 436 CLog.d("Output for command " + command + ": " + commandOutput); 437 } else { 438 CLog.d("Command Failed " + command); 439 } 440 return success; 441 } 442 getSettings(String namespace, String name, int userId)443 protected String getSettings(String namespace, String name, int userId) 444 throws DeviceNotAvailableException { 445 String command = "settings --user " + userId + " get " + namespace + " " + name; 446 String commandOutput = getDevice().executeShellCommand(command); 447 CLog.d("Output for command " + command + ": " + commandOutput); 448 return commandOutput.replace("\n", "").replace("\r", ""); 449 } 450 putSettings(String namespace, String name, String value, int userId)451 protected void putSettings(String namespace, String name, String value, int userId) 452 throws DeviceNotAvailableException { 453 String command = "settings --user " + userId + " put " + namespace + " " + name 454 + " " + value; 455 String commandOutput = getDevice().executeShellCommand(command); 456 CLog.d("Output for command " + command + ": " + commandOutput); 457 } 458 removeAdmin(String componentName, int userId)459 protected boolean removeAdmin(String componentName, int userId) 460 throws DeviceNotAvailableException { 461 String command = "dpm remove-active-admin --user " + userId + " '" + componentName + "'"; 462 String commandOutput = getDevice().executeShellCommand(command); 463 CLog.d("Output for command " + command + ": " + commandOutput); 464 return commandOutput.startsWith("Success:"); 465 } 466 467 // Tries to remove and profile or device owners it finds. removeOwners()468 protected void removeOwners() throws DeviceNotAvailableException { 469 String command = "dumpsys device_policy"; 470 String commandOutput = getDevice().executeShellCommand(command); 471 String[] lines = commandOutput.split("\\r?\\n"); 472 for (int i = 0; i < lines.length; ++i) { 473 String line = lines[i].trim(); 474 if (line.contains("Profile Owner")) { 475 // Line is "Profile owner (User <id>): 476 String[] tokens = line.split("\\(|\\)| "); 477 int userId = Integer.parseInt(tokens[4]); 478 i++; 479 line = lines[i].trim(); 480 // Line is admin=ComponentInfo{<component>} 481 tokens = line.split("\\{|\\}"); 482 String componentName = tokens[1]; 483 CLog.w("Cleaning up profile owner " + userId + " " + componentName); 484 removeAdmin(componentName, userId); 485 } else if (line.contains("Device Owner:")) { 486 i++; 487 line = lines[i].trim(); 488 // Line is admin=ComponentInfo{<component>} 489 String[] tokens = line.split("\\{|\\}"); 490 String componentName = tokens[1]; 491 // Skip to user id line. 492 i += 3; 493 line = lines[i].trim(); 494 // Line is User ID: <N> 495 tokens = line.split(":"); 496 int userId = Integer.parseInt(tokens[1].trim()); 497 CLog.w("Cleaning up device owner " + userId + " " + componentName); 498 removeAdmin(componentName, userId); 499 } 500 } 501 } 502 } 503