1 /* 2 * Copyright (C) 2016 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.server.am; 18 19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; 20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; 21 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; 22 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; 23 import static android.server.am.ComponentNameUtils.getActivityName; 24 import static android.server.am.ComponentNameUtils.getWindowName; 25 import static android.server.am.StateLogger.log; 26 import static android.server.am.StateLogger.logAlways; 27 import static android.server.am.StateLogger.logE; 28 import static android.server.am.UiDeviceUtils.pressBackButton; 29 import static android.server.am.UiDeviceUtils.pressEnterButton; 30 import static android.server.am.UiDeviceUtils.pressHomeButton; 31 import static android.server.am.UiDeviceUtils.pressSleepButton; 32 import static android.server.am.UiDeviceUtils.pressUnlockButton; 33 import static android.server.am.UiDeviceUtils.pressWakeupButton; 34 import static android.server.am.UiDeviceUtils.waitForDeviceIdle; 35 36 import android.app.ActivityManager; 37 import android.content.ComponentName; 38 import android.content.Context; 39 import android.os.SystemClock; 40 import android.support.test.InstrumentationRegistry; 41 42 import com.android.compatibility.common.util.SystemUtil; 43 44 import org.junit.After; 45 import org.junit.Before; 46 47 import java.io.IOException; 48 import java.util.UUID; 49 import java.util.concurrent.TimeUnit; 50 import java.util.regex.Matcher; 51 import java.util.regex.Pattern; 52 53 public abstract class ActivityManagerTestBase { 54 protected static final int[] ALL_ACTIVITY_TYPE_BUT_HOME = { 55 ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_ASSISTANT, ACTIVITY_TYPE_RECENTS, 56 ACTIVITY_TYPE_UNDEFINED 57 }; 58 59 private static final String TASK_ID_PREFIX = "taskId"; 60 61 private static final String AM_STACK_LIST = "am stack list"; 62 63 private static final String AM_FORCE_STOP_TEST_PACKAGE = "am force-stop android.server.am"; 64 private static final String AM_FORCE_STOP_SECOND_TEST_PACKAGE 65 = "am force-stop android.server.am.second"; 66 private static final String AM_FORCE_STOP_THIRD_TEST_PACKAGE 67 = "am force-stop android.server.am.third"; 68 69 protected static final String AM_START_HOME_ACTIVITY_COMMAND = 70 "am start -a android.intent.action.MAIN -c android.intent.category.HOME"; 71 72 private static final String LOCK_CREDENTIAL = "1234"; 73 74 private static final int UI_MODE_TYPE_MASK = 0x0f; 75 private static final int UI_MODE_TYPE_VR_HEADSET = 0x07; 76 77 protected Context mContext; 78 protected ActivityManager mAm; 79 80 /** 81 * @return the am command to start the given activity with the following extra key/value pairs. 82 * {@param keyValuePairs} must be a list of arguments defining each key/value extra. 83 */ 84 // TODO: Make this more generic, for instance accepting flags or extras of other types. getAmStartCmd(final ComponentName activityName, final String... keyValuePairs)85 protected static String getAmStartCmd(final ComponentName activityName, 86 final String... keyValuePairs) { 87 return getAmStartCmdInternal(getActivityName(activityName), keyValuePairs); 88 } 89 getAmStartCmdInternal(final String activityName, final String... keyValuePairs)90 private static String getAmStartCmdInternal(final String activityName, 91 final String... keyValuePairs) { 92 return appendKeyValuePairs( 93 new StringBuilder("am start -n ").append(activityName), 94 keyValuePairs); 95 } 96 appendKeyValuePairs( final StringBuilder cmd, final String... keyValuePairs)97 private static String appendKeyValuePairs( 98 final StringBuilder cmd, final String... keyValuePairs) { 99 if (keyValuePairs.length % 2 != 0) { 100 throw new RuntimeException("keyValuePairs must be pairs of key/value arguments"); 101 } 102 for (int i = 0; i < keyValuePairs.length; i += 2) { 103 final String key = keyValuePairs[i]; 104 final String value = keyValuePairs[i + 1]; 105 cmd.append(" --es ") 106 .append(key) 107 .append(" ") 108 .append(value); 109 } 110 return cmd.toString(); 111 } 112 113 protected ActivityAndWindowManagersState mAmWmState = new ActivityAndWindowManagersState(); 114 115 @Before setUp()116 public void setUp() throws Exception { 117 mContext = InstrumentationRegistry.getContext(); 118 mAm = mContext.getSystemService(ActivityManager.class); 119 120 pressWakeupButton(); 121 pressUnlockButton(); 122 pressHomeButton(); 123 removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME); 124 } 125 126 @After tearDown()127 public void tearDown() throws Exception { 128 // Synchronous execution of removeStacksWithActivityTypes() ensures that all activities but 129 // home are cleaned up from the stack at the end of each test. Am force stop shell commands 130 // might be asynchronous and could interrupt the stack cleanup process if executed first. 131 removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME); 132 executeShellCommand(AM_FORCE_STOP_TEST_PACKAGE); 133 executeShellCommand(AM_FORCE_STOP_SECOND_TEST_PACKAGE); 134 executeShellCommand(AM_FORCE_STOP_THIRD_TEST_PACKAGE); 135 pressHomeButton(); 136 } 137 removeStacksWithActivityTypes(int... activityTypes)138 protected void removeStacksWithActivityTypes(int... activityTypes) { 139 mAm.removeStacksWithActivityTypes(activityTypes); 140 waitForIdle(); 141 } 142 executeShellCommand(String command)143 public static String executeShellCommand(String command) { 144 log("Shell command: " + command); 145 try { 146 return SystemUtil 147 .runShellCommand(InstrumentationRegistry.getInstrumentation(), command); 148 } catch (IOException e) { 149 //bubble it up 150 logE("Error running shell command: " + command); 151 throw new RuntimeException(e); 152 } 153 } 154 launchActivity(final ComponentName activityName, final String... keyValuePairs)155 protected void launchActivity(final ComponentName activityName, final String... keyValuePairs) { 156 executeShellCommand(getAmStartCmd(activityName, keyValuePairs)); 157 mAmWmState.waitForValidState(new WaitForValidActivityState(activityName)); 158 } 159 waitForIdle()160 private static void waitForIdle() { 161 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 162 } 163 launchHomeActivity()164 protected void launchHomeActivity() { 165 executeShellCommand(AM_START_HOME_ACTIVITY_COMMAND); 166 mAmWmState.waitForHomeActivityVisible(); 167 } 168 launchActivity(ComponentName activityName, int windowingMode, final String... keyValuePairs)169 protected void launchActivity(ComponentName activityName, int windowingMode, 170 final String... keyValuePairs) { 171 executeShellCommand(getAmStartCmd(activityName, keyValuePairs) 172 + " --windowingMode " + windowingMode); 173 mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName) 174 .setWindowingMode(windowingMode) 175 .build()); 176 } 177 setActivityTaskWindowingMode(ComponentName activityName, int windowingMode)178 protected void setActivityTaskWindowingMode(ComponentName activityName, int windowingMode) { 179 final int taskId = getActivityTaskId(activityName); 180 mAm.setTaskWindowingMode(taskId, windowingMode, true /* toTop */); 181 mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName) 182 .setActivityType(ACTIVITY_TYPE_STANDARD) 183 .setWindowingMode(windowingMode) 184 .build()); 185 } 186 187 @Deprecated getActivityTaskId(final ComponentName activityName)188 protected int getActivityTaskId(final ComponentName activityName) { 189 final String windowName = getWindowName(activityName); 190 final String output = executeShellCommand(AM_STACK_LIST); 191 final Pattern activityPattern = Pattern.compile("(.*) " + windowName + " (.*)"); 192 for (final String line : output.split("\\n")) { 193 final Matcher matcher = activityPattern.matcher(line); 194 if (matcher.matches()) { 195 for (String word : line.split("\\s+")) { 196 if (word.startsWith(TASK_ID_PREFIX)) { 197 final String withColon = word.split("=")[1]; 198 return Integer.parseInt(withColon.substring(0, withColon.length() - 1)); 199 } 200 } 201 } 202 } 203 return -1; 204 } 205 isTablet()206 protected boolean isTablet() { 207 // Larger than approx 7" tablets 208 return mContext.getResources().getConfiguration().smallestScreenWidthDp >= 600; 209 } 210 211 // TODO: Switch to using a feature flag, when available. isUiModeLockedToVrHeadset()212 protected boolean isUiModeLockedToVrHeadset() { 213 final String output = runCommandAndPrintOutput("dumpsys uimode"); 214 215 Integer curUiMode = null; 216 Boolean uiModeLocked = null; 217 for (String line : output.split("\\n")) { 218 line = line.trim(); 219 Matcher matcher = sCurrentUiModePattern.matcher(line); 220 if (matcher.find()) { 221 curUiMode = Integer.parseInt(matcher.group(1), 16); 222 } 223 matcher = sUiModeLockedPattern.matcher(line); 224 if (matcher.find()) { 225 uiModeLocked = matcher.group(1).equals("true"); 226 } 227 } 228 229 boolean uiModeLockedToVrHeadset = (curUiMode != null) && (uiModeLocked != null) 230 && ((curUiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_VR_HEADSET) && uiModeLocked; 231 232 if (uiModeLockedToVrHeadset) { 233 log("UI mode is locked to VR headset"); 234 } 235 236 return uiModeLockedToVrHeadset; 237 } 238 supportsSplitScreenMultiWindow()239 protected boolean supportsSplitScreenMultiWindow() { 240 return ActivityManager.supportsSplitScreenMultiWindow(mContext); 241 } 242 hasDeviceFeature(final String requiredFeature)243 protected boolean hasDeviceFeature(final String requiredFeature) { 244 return InstrumentationRegistry.getContext() 245 .getPackageManager() 246 .hasSystemFeature(requiredFeature); 247 } 248 isDisplayOn()249 protected boolean isDisplayOn() { 250 final String output = executeShellCommand("dumpsys power"); 251 final Matcher matcher = sDisplayStatePattern.matcher(output); 252 if (matcher.find()) { 253 final String state = matcher.group(1); 254 log("power state=" + state); 255 return "ON".equals(state); 256 } 257 logAlways("power state :("); 258 return false; 259 } 260 261 protected class LockScreenSession implements AutoCloseable { 262 private static final boolean DEBUG = false; 263 264 private final boolean mIsLockDisabled; 265 private boolean mLockCredentialSet; 266 LockScreenSession()267 public LockScreenSession() { 268 mIsLockDisabled = isLockDisabled(); 269 mLockCredentialSet = false; 270 // Enable lock screen (swipe) by default. 271 setLockDisabled(false); 272 } 273 setLockCredential()274 public LockScreenSession setLockCredential() { 275 mLockCredentialSet = true; 276 runCommandAndPrintOutput("locksettings set-pin " + LOCK_CREDENTIAL); 277 return this; 278 } 279 enterAndConfirmLockCredential()280 public LockScreenSession enterAndConfirmLockCredential() { 281 waitForDeviceIdle(3000); 282 283 runCommandAndPrintOutput("input text " + LOCK_CREDENTIAL); 284 pressEnterButton(); 285 return this; 286 } 287 removeLockCredential()288 private void removeLockCredential() { 289 runCommandAndPrintOutput("locksettings clear --old " + LOCK_CREDENTIAL); 290 mLockCredentialSet = false; 291 } 292 disableLockScreen()293 LockScreenSession disableLockScreen() { 294 setLockDisabled(true); 295 return this; 296 } 297 sleepDevice()298 public LockScreenSession sleepDevice() { 299 pressSleepButton(); 300 waitForDisplayStateWithRetry(false /* displayOn */ ); 301 return this; 302 } 303 wakeUpDevice()304 protected LockScreenSession wakeUpDevice() { 305 pressWakeupButton(); 306 waitForDisplayStateWithRetry(true /* displayOn */ ); 307 return this; 308 } 309 310 /** 311 * If the device has a PIN or password set, this doesn't immediately unlock the device. 312 * Instead, it shows the keyguard and brings the entry field into focus, ready for a 313 * call to enterAndConfirmLockCredential(). 314 */ unlockDevice()315 protected LockScreenSession unlockDevice() { 316 pressUnlockButton(); 317 SystemClock.sleep(200); 318 return this; 319 } 320 gotoKeyguard()321 public LockScreenSession gotoKeyguard() { 322 if (DEBUG && isLockDisabled()) { 323 logE("LockScreenSession.gotoKeyguard() is called without lock enabled."); 324 } 325 sleepDevice(); 326 wakeUpDevice(); 327 unlockDevice(); 328 mAmWmState.waitForKeyguardShowingAndNotOccluded(); 329 return this; 330 } 331 332 @Override close()333 public void close() throws Exception { 334 setLockDisabled(mIsLockDisabled); 335 if (mLockCredentialSet) { 336 removeLockCredential(); 337 } 338 339 // Dismiss active keyguard after credential is cleared, so keyguard doesn't ask for 340 // the stale credential. 341 pressBackButton(); 342 sleepDevice(); 343 wakeUpDevice(); 344 unlockDevice(); 345 } 346 347 /** 348 * Returns whether the lock screen is disabled. 349 * 350 * @return true if the lock screen is disabled, false otherwise. 351 */ isLockDisabled()352 private boolean isLockDisabled() { 353 final String isLockDisabled = runCommandAndPrintOutput( 354 "locksettings get-disabled").trim(); 355 return !"null".equals(isLockDisabled) && Boolean.parseBoolean(isLockDisabled); 356 } 357 358 /** 359 * Disable the lock screen. 360 * 361 * @param lockDisabled true if should disable, false otherwise. 362 */ setLockDisabled(boolean lockDisabled)363 protected void setLockDisabled(boolean lockDisabled) { 364 runCommandAndPrintOutput("locksettings set-disabled " + lockDisabled); 365 } 366 waitForDisplayStateWithRetry(boolean displayOn)367 protected void waitForDisplayStateWithRetry(boolean displayOn) { 368 for (int retry = 1; isDisplayOn() != displayOn && retry <= 5; retry++) { 369 logAlways("***Waiting for display state... retry " + retry); 370 SystemClock.sleep(TimeUnit.SECONDS.toMillis(1)); 371 } 372 } 373 } 374 runCommandAndPrintOutput(String command)375 protected static String runCommandAndPrintOutput(String command) { 376 final String output = executeShellCommand(command); 377 log(output); 378 return output; 379 } 380 381 protected static class LogSeparator { 382 private final String mUniqueString; 383 LogSeparator()384 private LogSeparator() { 385 mUniqueString = UUID.randomUUID().toString(); 386 } 387 388 @Override toString()389 public String toString() { 390 return mUniqueString; 391 } 392 } 393 394 // TODO: Now that our test are device side, we can convert these to a more direct communication 395 // channel vs. depending on logs. 396 private static final Pattern sDisplayStatePattern = 397 Pattern.compile("Display Power: state=(.+)"); 398 private static final Pattern sCurrentUiModePattern = Pattern.compile("mCurUiMode=0x(\\d+)"); 399 private static final Pattern sUiModeLockedPattern = 400 Pattern.compile("mUiModeLocked=(true|false)"); 401 stopTestPackage(final ComponentName activityName)402 protected void stopTestPackage(final ComponentName activityName) { 403 executeShellCommand("am force-stop " + activityName.getPackageName()); 404 } 405 } 406