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