1 /* 2 * Copyright (C) 2017 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.media.cts; 18 19 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; 20 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner; 21 import com.android.tradefed.device.DeviceNotAvailableException; 22 import com.android.tradefed.log.LogUtil.CLog; 23 import com.android.tradefed.result.CollectingTestListener; 24 import com.android.tradefed.result.TestRunResult; 25 26 import java.io.FileNotFoundException; 27 import java.util.HashSet; 28 import java.util.List; 29 import java.util.Set; 30 31 import javax.annotation.Nonnull; 32 import javax.annotation.Nullable; 33 34 /** 35 * Base class for host-side tests for multi-user aware media APIs. 36 */ 37 public class BaseMultiUserTest extends BaseMediaHostSideTest { 38 private static final String SETTINGS_PACKAGE_VERIFIER_NAMESPACE = "global"; 39 private static final String SETTINGS_PACKAGE_VERIFIER_NAME = "package_verifier_enable"; 40 41 /** 42 * User ID for all users. 43 * The value is from the UserHandle class. 44 */ 45 protected static final int USER_ALL = -1; 46 47 /** 48 * User ID for the system user. 49 * The value is from the UserHandle class. 50 */ 51 protected static final int USER_SYSTEM = 0; 52 53 private String mPackageVerifier; 54 55 private int mInitialUserId; 56 private Set<String> mExistingPackages; 57 private List<Integer> mExistingUsers; 58 private HashSet<String> mAvailableFeatures; 59 60 @Override setUp()61 protected void setUp() throws Exception { 62 super.setUp(); 63 // Ensure that build has been set before test is run. 64 assertNotNull(mCtsBuild); 65 mExistingPackages = getDevice().getInstalledPackageNames(); 66 67 // Disable the package verifier to avoid the dialog when installing an app 68 mPackageVerifier = 69 getSettings( 70 SETTINGS_PACKAGE_VERIFIER_NAMESPACE, 71 SETTINGS_PACKAGE_VERIFIER_NAME, 72 USER_ALL); 73 putSettings( 74 SETTINGS_PACKAGE_VERIFIER_NAMESPACE, 75 SETTINGS_PACKAGE_VERIFIER_NAME, 76 "0", 77 USER_ALL); 78 79 mInitialUserId = getDevice().getCurrentUser(); 80 mExistingUsers = getDevice().listUsers(); 81 Integer mainUserId = getDevice().getMainUserId(); 82 Integer primaryUserId = getDevice().getPrimaryUserId(); 83 if (primaryUserId != null) { 84 getDevice().switchUser(primaryUserId); 85 } else if (mainUserId != null) { 86 getDevice().switchUser(mainUserId); 87 } else { 88 // Neither a primary nor a main user exists. Just use the current one. 89 } 90 executeShellCommand("wm dismiss-keyguard"); 91 } 92 93 @Override tearDown()94 protected void tearDown() throws Exception { 95 // Reset the package verifier setting to its original value. 96 putSettings( 97 SETTINGS_PACKAGE_VERIFIER_NAMESPACE, 98 SETTINGS_PACKAGE_VERIFIER_NAME, 99 mPackageVerifier, 100 USER_ALL); 101 102 // We want to fail if something goes wrong in tearDown, but we want to complete as many 103 // cleanup steps as we can to reduce the chances of leaving the device in an inconsistent 104 // state. 105 Throwable lastTearDownError = null; 106 107 // Remove users created during the test. 108 for (int userId : getDevice().listUsers()) { 109 if (!mExistingUsers.contains(userId)) { 110 try { 111 removeUser(userId); 112 } catch (Throwable t) { 113 lastTearDownError = t; 114 } 115 } 116 } 117 // Remove packages installed during the test. 118 for (String packageName : getDevice().getUninstallablePackageNames()) { 119 if (mExistingPackages.contains(packageName)) { 120 continue; 121 } 122 CLog.d("Removing leftover package: " + packageName); 123 try { 124 getDevice().uninstallPackage(packageName); 125 } catch (Throwable t) { 126 lastTearDownError = t; 127 } 128 } 129 if (getDevice().getCurrentUser() != mInitialUserId) { 130 getDevice().switchUser(mInitialUserId); 131 } 132 super.tearDown(); 133 if (lastTearDownError != null) { 134 throw new AssertionError("Something went wrong while cleaning up.", lastTearDownError); 135 } 136 } 137 138 /** 139 * Installs the app as if the user of the ID {@param userId} has installed the app. 140 * 141 * @param appFileName file name of the app. 142 * @param packageName the app's package name. 143 * @param userId user ID to install the app against. 144 * @param asInstantApp whether to install the app as an instant app. 145 */ installAppAsUser( String appFileName, String packageName, int userId, boolean asInstantApp)146 protected void installAppAsUser( 147 String appFileName, String packageName, int userId, boolean asInstantApp) 148 throws FileNotFoundException, DeviceNotAvailableException { 149 // Installation may fail if the package already exists. 150 getDevice().uninstallPackage(packageName); 151 152 CLog.d("Installing app " + appFileName + " for user " + userId); 153 CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild); 154 String result = getDevice().installPackageForUser( 155 buildHelper.getTestFile(appFileName), 156 true, 157 true, 158 userId, 159 "-t", 160 asInstantApp ? "--instant" : ""); 161 assertNull("Failed to install " + appFileName + " for user " + userId + ": " + result, 162 result); 163 } 164 createAndStartUser(String extraParam)165 private int createAndStartUser(String extraParam) throws Exception { 166 String command = "pm create-user" + extraParam + " TestUser_" + System.currentTimeMillis(); 167 String commandOutput = executeShellCommand(command); 168 169 String[] tokens = commandOutput.split("\\s+"); 170 assertTrue(tokens.length > 0); 171 assertEquals("Success:", tokens[0]); 172 int userId = Integer.parseInt(tokens[tokens.length-1]); 173 174 // Start user for MediaSessionService to notice the created user. 175 getDevice().startUser(userId); 176 return userId; 177 } 178 179 /** 180 * Creates and starts a new user. 181 */ createAndStartUser()182 protected int createAndStartUser() throws Exception { 183 return createAndStartUser(""); 184 } 185 186 /** 187 * Creates and starts a restricted profile for the {@param parentUserId}. 188 * 189 * @param parentUserId parent user id. 190 */ createAndStartRestrictedProfile(int parentUserId)191 protected int createAndStartRestrictedProfile(int parentUserId) throws Exception { 192 return createAndStartUser(" --profileOf " + parentUserId + " --restricted"); 193 } 194 195 /** 196 * Creates and starts a managed profile for the {@param parentUserId}. 197 * 198 * @param parentUserId parent user id. 199 */ createAndStartManagedProfile(int parentUserId)200 protected int createAndStartManagedProfile(int parentUserId) throws Exception { 201 return createAndStartUser(" --profileOf " + parentUserId + " --managed"); 202 } 203 204 /** 205 * Removes the user that is created during the test. 206 * <p>It will be no-op if the user cannot be removed or doesn't exist. 207 * 208 * @param userId user ID to remove. 209 */ removeUser(int userId)210 protected void removeUser(int userId) throws Exception { 211 if (getDevice().listUsers().contains(userId) && userId != USER_SYSTEM 212 && !mExistingUsers.contains(userId)) { 213 getDevice().executeShellCommand("am wait-for-broadcast-idle"); 214 // Don't log output, as tests sometimes set no debug user restriction, which 215 // causes this to fail, we should still continue and remove the user. 216 String stopUserCommand = "am stop-user -w -f " + userId; 217 CLog.d("Stopping and removing user " + userId); 218 getDevice().executeShellCommand(stopUserCommand); 219 assertTrue("Couldn't remove user", getDevice().removeUser(userId)); 220 } 221 } 222 223 /** 224 * Runs tests on the device as if it's {@param userId}. 225 * 226 * @param pkgName test package file name that contains the {@link AndroidTestCase} 227 * @param testClassName Class name to test within the test package. Can be {@code null} if you 228 * want to run all test classes in the package. 229 * @param testMethodName Method name to test within the test class. Can be {@code null} if you 230 * want to run all test methods in the class. Will be ignored if {@param testClassName} is 231 * {@code null}. 232 * @param userId user ID to run the tests as. 233 */ runDeviceTests( String pkgName, @Nullable String testClassName, @Nullable String testMethodName, int userId)234 protected void runDeviceTests( 235 String pkgName, @Nullable String testClassName, 236 @Nullable String testMethodName, int userId) throws DeviceNotAvailableException { 237 RemoteAndroidTestRunner testRunner = getTestRunner(pkgName, testClassName, testMethodName); 238 CollectingTestListener listener = new CollectingTestListener(); 239 assertTrue(getDevice().runInstrumentationTestsAsUser(testRunner, userId, listener)); 240 241 final TestRunResult result = listener.getCurrentRunResults(); 242 assertTestsPassed(result); 243 } 244 245 /** 246 * Checks whether it is possible to create the desired number of users. 247 */ canCreateAdditionalUsers(int numberOfUsers)248 protected boolean canCreateAdditionalUsers(int numberOfUsers) 249 throws DeviceNotAvailableException { 250 return getDevice().listUsers().size() + numberOfUsers <= 251 getDevice().getMaxNumberOfUsersSupported(); 252 } 253 254 /** 255 * Gets the system setting as a string from the system settings provider for the user. 256 * 257 * @param namespace namespace of the setting. 258 * @param name name of the setting. 259 * @param userId user ID to query the setting. Can be {@link #USER_ALL}. 260 * @return value of the system setting provider with the given namespace and name. 261 * {@code null}, empty string, or "null" will be returned to the empty string ("") instead. 262 */ getSettings(@onnull String namespace, @Nonnull String name, int userId)263 protected @Nonnull String getSettings(@Nonnull String namespace, @Nonnull String name, 264 int userId) throws Exception { 265 String userFlag = (userId == USER_ALL) ? "" : " --user " + userId; 266 String commandOutput = executeShellCommand( 267 "settings" + userFlag + " get " + namespace + " " + name); 268 if (commandOutput == null || commandOutput.isEmpty() || commandOutput.equals("null")) { 269 commandOutput = ""; 270 } 271 return commandOutput; 272 } 273 274 /** 275 * Puts the string to the system settings provider for the user. 276 * <p>This deletes the setting for an empty {@param value} as 'settings put' doesn't allow 277 * putting empty value. 278 * 279 * @param namespace namespace of the setting. 280 * @param name name of the setting. 281 * @param value value of the system setting provider with the given namespace and name. 282 * @param userId user ID to set the setting. Can be {@link #USER_ALL}. 283 */ putSettings(@onnull String namespace, @Nonnull String name, @Nullable String value, int userId)284 protected void putSettings(@Nonnull String namespace, @Nonnull String name, 285 @Nullable String value, int userId) throws Exception { 286 if (value == null || value.isEmpty()) { 287 // Delete the setting if the value is null or empty as 'settings put' doesn't accept 288 // them. 289 // Ignore userId here because 'settings delete' doesn't support it. 290 executeShellCommand("settings delete " + namespace + " " + name); 291 } else { 292 String userFlag = (userId == USER_ALL) ? "" : " --user " + userId; 293 executeShellCommand("settings" + userFlag + " put " + namespace + " " + name 294 + " " + value); 295 } 296 } 297 hasDeviceFeature(String requiredFeature)298 protected boolean hasDeviceFeature(String requiredFeature) throws DeviceNotAvailableException { 299 if (mAvailableFeatures == null) { 300 // TODO: Move this logic to ITestDevice. 301 String command = "pm list features"; 302 String commandOutput = getDevice().executeShellCommand(command); 303 CLog.i("Output for command " + command + ": " + commandOutput); 304 305 // Extract the id of the new user. 306 mAvailableFeatures = new HashSet<>(); 307 for (String feature : commandOutput.split("\\s+")) { 308 // Each line in the output of the command has the format "feature:{FEATURE_VALUE}". 309 String[] tokens = feature.split(":"); 310 assertTrue( 311 "\"" + feature + "\" expected to have format feature:{FEATURE_VALUE}", 312 tokens.length > 1); 313 assertEquals(feature, "feature", tokens[0]); 314 mAvailableFeatures.add(tokens[1]); 315 } 316 } 317 boolean result = mAvailableFeatures.contains(requiredFeature); 318 return result; 319 } 320 } 321