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.cts;
18 
19 import com.android.ddmlib.Log.LogLevel;
20 import com.android.tradefed.device.CollectingOutputReceiver;
21 import com.android.tradefed.device.DeviceNotAvailableException;
22 import com.android.tradefed.device.ITestDevice;
23 import com.android.tradefed.log.LogUtil.CLog;
24 import com.android.tradefed.result.InputStreamSource;
25 import com.android.tradefed.testtype.DeviceTestCase;
26 
27 import java.awt.*;
28 import java.awt.image.BufferedImage;
29 import java.lang.Exception;
30 import java.lang.Integer;
31 import java.lang.String;
32 import java.util.HashSet;
33 import java.util.List;
34 import java.util.UUID;
35 import java.util.regex.Matcher;
36 import java.util.regex.Pattern;
37 
38 import static android.server.cts.StateLogger.log;
39 import static android.server.cts.StateLogger.logE;
40 
41 import android.server.cts.ActivityManagerState.ActivityStack;
42 
43 import javax.imageio.ImageIO;
44 
45 public abstract class ActivityManagerTestBase extends DeviceTestCase {
46     private static final boolean PRETEND_DEVICE_SUPPORTS_PIP = false;
47     private static final boolean PRETEND_DEVICE_SUPPORTS_FREEFORM = false;
48     private static final String LOG_SEPARATOR = "LOG_SEPARATOR";
49 
50     // Constants copied from ActivityManager.StackId. If they are changed there, these must be
51     // updated.
52     /** Invalid stack ID. */
53     public static final int INVALID_STACK_ID = -1;
54 
55     /** First static stack ID. */
56     public static final int FIRST_STATIC_STACK_ID = 0;
57 
58     /** Home activity stack ID. */
59     public static final int HOME_STACK_ID = FIRST_STATIC_STACK_ID;
60 
61     /** ID of stack where fullscreen activities are normally launched into. */
62     public static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;
63 
64     /** ID of stack where freeform/resized activities are normally launched into. */
65     public static final int FREEFORM_WORKSPACE_STACK_ID = FULLSCREEN_WORKSPACE_STACK_ID + 1;
66 
67     /** ID of stack that occupies a dedicated region of the screen. */
68     public static final int DOCKED_STACK_ID = FREEFORM_WORKSPACE_STACK_ID + 1;
69 
70     /** ID of stack that always on top (always visible) when it exist. */
71     public static final int PINNED_STACK_ID = DOCKED_STACK_ID + 1;
72 
73     /** Recents activity stack ID. */
74     public static final int RECENTS_STACK_ID = PINNED_STACK_ID + 1;
75 
76     /** Assistant activity stack ID.  This stack is fullscreen and non-resizeable. */
77     public static final int ASSISTANT_STACK_ID = RECENTS_STACK_ID + 1;
78 
79     protected static final int[] ALL_STACK_IDS_BUT_HOME = {
80             FULLSCREEN_WORKSPACE_STACK_ID, FREEFORM_WORKSPACE_STACK_ID, DOCKED_STACK_ID,
81             PINNED_STACK_ID, ASSISTANT_STACK_ID
82     };
83 
84     protected static final int[] ALL_STACK_IDS_BUT_HOME_AND_FULLSCREEN = {
85             FREEFORM_WORKSPACE_STACK_ID, DOCKED_STACK_ID, PINNED_STACK_ID, ASSISTANT_STACK_ID
86     };
87 
88     private static final String TASK_ID_PREFIX = "taskId";
89 
90     private static final String AM_STACK_LIST = "am stack list";
91 
92     private static final String AM_FORCE_STOP_TEST_PACKAGE = "am force-stop android.server.cts";
93     private static final String AM_FORCE_STOP_SECOND_TEST_PACKAGE
94             = "am force-stop android.server.cts.second";
95     private static final String AM_FORCE_STOP_THIRD_TEST_PACKAGE
96             = "am force-stop android.server.cts.third";
97 
98     private static final String AM_REMOVE_STACK = "am stack remove ";
99 
100     protected static final String AM_START_HOME_ACTIVITY_COMMAND =
101             "am start -a android.intent.action.MAIN -c android.intent.category.HOME";
102 
103     protected static final String AM_MOVE_TOP_ACTIVITY_TO_PINNED_STACK_COMMAND =
104             "am stack move-top-activity-to-pinned-stack 1 0 0 500 500";
105 
106     static final String LAUNCHING_ACTIVITY = "LaunchingActivity";
107     static final String ALT_LAUNCHING_ACTIVITY = "AltLaunchingActivity";
108     static final String BROADCAST_RECEIVER_ACTIVITY = "BroadcastReceiverActivity";
109 
110     /** Broadcast shell command for finishing {@link BroadcastReceiverActivity}. */
111     static final String FINISH_ACTIVITY_BROADCAST
112             = "am broadcast -a trigger_broadcast --ez finish true";
113 
114     /** Broadcast shell command for finishing {@link BroadcastReceiverActivity}. */
115     static final String MOVE_TASK_TO_BACK_BROADCAST
116             = "am broadcast -a trigger_broadcast --ez moveToBack true";
117 
118     private static final String AM_RESIZE_DOCKED_STACK = "am stack resize-docked-stack ";
119     private static final String AM_RESIZE_STACK = "am stack resize ";
120 
121     static final String AM_MOVE_TASK = "am stack move-task ";
122 
123     private static final String AM_SUPPORTS_SPLIT_SCREEN_MULTIWINDOW =
124             "am supports-split-screen-multi-window";
125     private static final String AM_NO_HOME_SCREEN = "am no-home-screen";
126 
127     private static final String INPUT_KEYEVENT_HOME = "input keyevent 3";
128     private static final String INPUT_KEYEVENT_BACK = "input keyevent 4";
129     private static final String INPUT_KEYEVENT_APP_SWITCH = "input keyevent 187";
130     public static final String INPUT_KEYEVENT_WINDOW = "input keyevent 171";
131 
132     private static final String LOCK_CREDENTIAL = "1234";
133 
134     private static final int INVALID_DISPLAY_ID = -1;
135 
136     private static final String DEFAULT_COMPONENT_NAME = "android.server.cts";
137 
138     static String componentName = DEFAULT_COMPONENT_NAME;
139 
140     protected static final int INVALID_DEVICE_ROTATION = -1;
141 
142     /** A reference to the device under test. */
143     protected ITestDevice mDevice;
144 
145     private HashSet<String> mAvailableFeatures;
146 
getAmStartCmd(final String activityName)147     protected static String getAmStartCmd(final String activityName) {
148         return "am start -n " + getActivityComponentName(activityName);
149     }
150 
151     /**
152      * @return the am command to start the given activity with the following extra key/value pairs.
153      *         {@param keyValuePairs} must be a list of arguments defining each key/value extra.
154      */
getAmStartCmd(final String activityName, final String... keyValuePairs)155     protected static String getAmStartCmd(final String activityName,
156             final String... keyValuePairs) {
157         String base = getAmStartCmd(activityName);
158         if (keyValuePairs.length % 2 != 0) {
159             throw new RuntimeException("keyValuePairs must be pairs of key/value arguments");
160         }
161         for (int i = 0; i < keyValuePairs.length; i += 2) {
162             base += " --es " + keyValuePairs[i] + " " + keyValuePairs[i + 1];
163         }
164         return base;
165     }
166 
getAmStartCmd(final String activityName, final int displayId)167     protected static String getAmStartCmd(final String activityName, final int displayId) {
168         return "am start -n " + getActivityComponentName(activityName) + " -f 0x18000000"
169                 + " --display " + displayId;
170     }
171 
getAmStartCmdInNewTask(final String activityName)172     protected static String getAmStartCmdInNewTask(final String activityName) {
173         return "am start -n " + getActivityComponentName(activityName) + " -f 0x18000000";
174     }
175 
getAmStartCmdOverHome(final String activityName)176     protected static String getAmStartCmdOverHome(final String activityName) {
177         return "am start --activity-task-on-home -n " + getActivityComponentName(activityName);
178     }
179 
getOrientationBroadcast(int orientation)180     protected static String getOrientationBroadcast(int orientation) {
181         return "am broadcast -a trigger_broadcast --ei orientation " + orientation;
182     }
183 
getActivityComponentName(final String activityName)184     static String getActivityComponentName(final String activityName) {
185         return getActivityComponentName(componentName, activityName);
186     }
187 
isFullyQualifiedActivityName(String name)188     private static boolean isFullyQualifiedActivityName(String name) {
189         return name != null && name.contains(".");
190     }
191 
getActivityComponentName(final String packageName, final String activityName)192     static String getActivityComponentName(final String packageName, final String activityName) {
193         return packageName + "/" + (isFullyQualifiedActivityName(activityName) ? "" : ".") +
194                 activityName;
195     }
196 
197     // A little ugly, but lets avoid having to strip static everywhere for
198     // now.
setComponentName(String name)199     public static void setComponentName(String name) {
200         componentName = name;
201     }
202 
getBaseWindowName()203     static String getBaseWindowName() {
204         return getBaseWindowName(componentName);
205     }
206 
getBaseWindowName(final String packageName)207     static String getBaseWindowName(final String packageName) {
208         return getBaseWindowName(packageName, true /*prependPackageName*/);
209     }
210 
getBaseWindowName(final String packageName, boolean prependPackageName)211     static String getBaseWindowName(final String packageName, boolean prependPackageName) {
212         return packageName + "/" + (prependPackageName ? packageName + "." : "");
213     }
214 
getWindowName(final String activityName)215     static String getWindowName(final String activityName) {
216         return getWindowName(componentName, activityName);
217     }
218 
getWindowName(final String packageName, final String activityName)219     static String getWindowName(final String packageName, final String activityName) {
220         return getBaseWindowName(packageName, !isFullyQualifiedActivityName(activityName))
221                 + activityName;
222     }
223 
224     protected ActivityAndWindowManagersState mAmWmState = new ActivityAndWindowManagersState();
225 
226     private int mInitialAccelerometerRotation;
227     private int mUserRotation;
228     private float mFontScale;
229 
230     private SurfaceTraceReceiver mSurfaceTraceReceiver;
231     private Thread mSurfaceTraceThread;
232 
installSurfaceObserver(SurfaceTraceReceiver.SurfaceObserver observer)233     void installSurfaceObserver(SurfaceTraceReceiver.SurfaceObserver observer) {
234         mSurfaceTraceReceiver = new SurfaceTraceReceiver(observer);
235         mSurfaceTraceThread = new Thread() {
236             @Override
237             public void run() {
238                 try {
239                     mDevice.executeShellCommand("wm surface-trace", mSurfaceTraceReceiver);
240                 } catch (DeviceNotAvailableException e) {
241                     logE("Device not available: " + e.toString());
242                 }
243             }
244         };
245         mSurfaceTraceThread.start();
246     }
247 
removeSurfaceObserver()248     void removeSurfaceObserver() {
249         mSurfaceTraceReceiver.cancel();
250         mSurfaceTraceThread.interrupt();
251     }
252 
253     @Override
setUp()254     protected void setUp() throws Exception {
255         super.setUp();
256         setComponentName(DEFAULT_COMPONENT_NAME);
257 
258         // Get the device, this gives a handle to run commands and install APKs.
259         mDevice = getDevice();
260         wakeUpAndUnlockDevice();
261         pressHomeButton();
262         // Remove special stacks.
263         removeStacks(ALL_STACK_IDS_BUT_HOME_AND_FULLSCREEN);
264         // Store rotation settings.
265         mInitialAccelerometerRotation = getAccelerometerRotation();
266         mUserRotation = getUserRotation();
267         mFontScale = getFontScale();
268     }
269 
270     @Override
tearDown()271     protected void tearDown() throws Exception {
272         super.tearDown();
273         try {
274             executeShellCommand(AM_FORCE_STOP_TEST_PACKAGE);
275             executeShellCommand(AM_FORCE_STOP_SECOND_TEST_PACKAGE);
276             executeShellCommand(AM_FORCE_STOP_THIRD_TEST_PACKAGE);
277             // Restore rotation settings to the state they were before test.
278             setAccelerometerRotation(mInitialAccelerometerRotation);
279             setUserRotation(mUserRotation);
280             setFontScale(mFontScale);
281             // Remove special stacks.
282             removeStacks(ALL_STACK_IDS_BUT_HOME_AND_FULLSCREEN);
283             wakeUpAndUnlockDevice();
284             pressHomeButton();
285         } catch (DeviceNotAvailableException e) {
286         }
287     }
288 
removeStacks(int... stackIds)289     protected void removeStacks(int... stackIds) {
290         try {
291             for (Integer stackId : stackIds) {
292                 executeShellCommand(AM_REMOVE_STACK + stackId);
293             }
294         } catch (DeviceNotAvailableException e) {
295         }
296     }
297 
executeShellCommand(String command)298     protected String executeShellCommand(String command) throws DeviceNotAvailableException {
299         return executeShellCommand(mDevice, command);
300     }
301 
executeShellCommand(ITestDevice device, String command)302     protected static String executeShellCommand(ITestDevice device, String command)
303             throws DeviceNotAvailableException {
304         log("adb shell " + command);
305         return device.executeShellCommand(command);
306     }
307 
executeShellCommand(String command, CollectingOutputReceiver outputReceiver)308     protected void executeShellCommand(String command, CollectingOutputReceiver outputReceiver)
309             throws DeviceNotAvailableException {
310         log("adb shell " + command);
311         mDevice.executeShellCommand(command, outputReceiver);
312     }
313 
takeScreenshot()314     protected BufferedImage takeScreenshot() throws Exception {
315         final InputStreamSource stream = mDevice.getScreenshot("PNG", false /* rescale */);
316         if (stream == null) {
317             fail("Failed to take screenshot of device");
318         }
319         return ImageIO.read(stream.createInputStream());
320     }
321 
launchActivityInComponent(final String componentName, final String targetActivityName, final String... keyValuePairs)322     protected void launchActivityInComponent(final String componentName,
323             final String targetActivityName, final String... keyValuePairs) throws Exception {
324         final String originalComponentName = ActivityManagerTestBase.componentName;
325         setComponentName(componentName);
326         launchActivity(targetActivityName, keyValuePairs);
327         setComponentName(originalComponentName);
328     }
329 
launchActivity(final String targetActivityName, final String... keyValuePairs)330     protected void launchActivity(final String targetActivityName, final String... keyValuePairs)
331             throws Exception {
332         executeShellCommand(getAmStartCmd(targetActivityName, keyValuePairs));
333         mAmWmState.waitForValidState(mDevice, targetActivityName);
334     }
335 
launchActivityNoWait(final String targetActivityName, final String... keyValuePairs)336     protected void launchActivityNoWait(final String targetActivityName,
337             final String... keyValuePairs) throws Exception {
338         executeShellCommand(getAmStartCmd(targetActivityName, keyValuePairs));
339     }
340 
launchActivityInNewTask(final String targetActivityName)341     protected void launchActivityInNewTask(final String targetActivityName) throws Exception {
342         executeShellCommand(getAmStartCmdInNewTask(targetActivityName));
343         mAmWmState.waitForValidState(mDevice, targetActivityName);
344     }
345 
346     /**
347      * Starts an activity in a new stack.
348      * @return the stack id of the newly created stack.
349      */
launchActivityInNewDynamicStack(final String activityName)350     protected int launchActivityInNewDynamicStack(final String activityName) throws Exception {
351         HashSet<Integer> stackIds = getStackIds();
352         executeShellCommand("am stack start " + ActivityAndWindowManagersState.DEFAULT_DISPLAY_ID
353                 + " " + getActivityComponentName(activityName));
354         HashSet<Integer> newStackIds = getStackIds();
355         newStackIds.removeAll(stackIds);
356         if (newStackIds.isEmpty()) {
357             return INVALID_STACK_ID;
358         } else {
359             assertTrue(newStackIds.size() == 1);
360             return newStackIds.iterator().next();
361         }
362     }
363 
364     /**
365      * Returns the set of stack ids.
366      */
getStackIds()367     private HashSet<Integer> getStackIds() throws Exception {
368         mAmWmState.computeState(mDevice, null);
369         final List<ActivityStack> stacks = mAmWmState.getAmState().getStacks();
370         final HashSet<Integer> stackIds = new HashSet<>();
371         for (ActivityStack s : stacks) {
372             stackIds.add(s.mStackId);
373         }
374         return stackIds;
375     }
376 
launchHomeActivity()377     protected void launchHomeActivity()
378             throws Exception {
379         executeShellCommand(AM_START_HOME_ACTIVITY_COMMAND);
380         mAmWmState.waitForHomeActivityVisible(mDevice);
381     }
382 
launchActivityOnDisplay(String targetActivityName, int displayId)383     protected void launchActivityOnDisplay(String targetActivityName, int displayId)
384             throws Exception {
385         executeShellCommand(getAmStartCmd(targetActivityName, displayId));
386 
387         mAmWmState.waitForValidState(mDevice, targetActivityName);
388     }
389 
390     /**
391      * Launch specific target activity. It uses existing instance of {@link #LAUNCHING_ACTIVITY}, so
392      * that one should be started first.
393      * @param toSide Launch to side in split-screen.
394      * @param randomData Make intent URI random by generating random data.
395      * @param multipleTask Allow multiple task launch.
396      * @param targetActivityName Target activity to be launched. Only class name should be provided,
397      *                           package name of {@link #LAUNCHING_ACTIVITY} will be added
398      *                           automatically.
399      * @param displayId Display id where target activity should be launched.
400      * @throws Exception
401      */
launchActivityFromLaunching(boolean toSide, boolean randomData, boolean multipleTask, String targetActivityName, int displayId)402     protected void launchActivityFromLaunching(boolean toSide, boolean randomData,
403             boolean multipleTask, String targetActivityName, int displayId) throws Exception {
404         StringBuilder commandBuilder = new StringBuilder(getAmStartCmd(LAUNCHING_ACTIVITY));
405         commandBuilder.append(" -f 0x20000000");
406         if (toSide) {
407             commandBuilder.append(" --ez launch_to_the_side true");
408         }
409         if (randomData) {
410             commandBuilder.append(" --ez random_data true");
411         }
412         if (multipleTask) {
413             commandBuilder.append(" --ez multiple_task true");
414         }
415         if (targetActivityName != null) {
416             commandBuilder.append(" --es target_activity ").append(targetActivityName);
417         }
418         if (displayId != INVALID_DISPLAY_ID) {
419             commandBuilder.append(" --ei display_id ").append(displayId);
420         }
421         executeShellCommand(commandBuilder.toString());
422 
423         mAmWmState.waitForValidState(mDevice, targetActivityName);
424     }
425 
launchActivityInStack(String activityName, int stackId, final String... keyValuePairs)426     protected void launchActivityInStack(String activityName, int stackId,
427             final String... keyValuePairs) throws Exception {
428         executeShellCommand(getAmStartCmd(activityName, keyValuePairs) + " --stack " + stackId);
429 
430         mAmWmState.waitForValidState(mDevice, activityName, stackId);
431     }
432 
launchActivityInDockStack(String activityName)433     protected void launchActivityInDockStack(String activityName) throws Exception {
434         launchActivity(activityName);
435         // TODO(b/36279415): The way we launch an activity into the docked stack is different from
436         // what the user actually does. Long term we should use
437         // "adb shell input keyevent --longpress _app_swich_key_code_" to trigger a long press on
438         // the recents button which is consistent with what the user does. However, currently sys-ui
439         // does handle FLAG_LONG_PRESS for the app switch key. It just listens for long press on the
440         // view. We need to fix that in sys-ui before we can change this.
441         moveActivityToDockStack(activityName);
442 
443         mAmWmState.waitForValidState(mDevice, activityName, DOCKED_STACK_ID);
444     }
445 
launchActivityToSide(boolean randomData, boolean multipleTaskFlag, String targetActivity)446     protected void launchActivityToSide(boolean randomData, boolean multipleTaskFlag,
447             String targetActivity) throws Exception {
448         final String activityToLaunch = targetActivity != null ? targetActivity : "TestActivity";
449         getLaunchActivityBuilder().setToSide(true).setRandomData(randomData)
450                 .setMultipleTask(multipleTaskFlag).setTargetActivityName(activityToLaunch)
451                 .execute();
452 
453         mAmWmState.waitForValidState(mDevice, activityToLaunch, FULLSCREEN_WORKSPACE_STACK_ID);
454     }
455 
moveActivityToDockStack(String activityName)456     protected void moveActivityToDockStack(String activityName) throws Exception {
457         moveActivityToStack(activityName, DOCKED_STACK_ID);
458     }
459 
moveActivityToStack(String activityName, int stackId)460     protected void moveActivityToStack(String activityName, int stackId) throws Exception {
461         final int taskId = getActivityTaskId(activityName);
462         final String cmd = AM_MOVE_TASK + taskId + " " + stackId + " true";
463         executeShellCommand(cmd);
464 
465         mAmWmState.waitForValidState(mDevice, activityName, stackId);
466     }
467 
resizeActivityTask(String activityName, int left, int top, int right, int bottom)468     protected void resizeActivityTask(String activityName, int left, int top, int right, int bottom)
469             throws Exception {
470         final int taskId = getActivityTaskId(activityName);
471         final String cmd = "am task resize "
472                 + taskId + " " + left + " " + top + " " + right + " " + bottom;
473         executeShellCommand(cmd);
474     }
475 
resizeDockedStack( int stackWidth, int stackHeight, int taskWidth, int taskHeight)476     protected void resizeDockedStack(
477             int stackWidth, int stackHeight, int taskWidth, int taskHeight)
478                     throws DeviceNotAvailableException {
479         executeShellCommand(AM_RESIZE_DOCKED_STACK
480                 + "0 0 " + stackWidth + " " + stackHeight
481                 + " 0 0 " + taskWidth + " " + taskHeight);
482     }
483 
resizeStack(int stackId, int stackLeft, int stackTop, int stackWidth, int stackHeight)484     protected void resizeStack(int stackId, int stackLeft, int stackTop, int stackWidth,
485             int stackHeight) throws DeviceNotAvailableException {
486         executeShellCommand(AM_RESIZE_STACK + String.format("%d %d %d %d %d", stackId, stackLeft,
487                 stackTop, stackWidth, stackHeight));
488     }
489 
pressHomeButton()490     protected void pressHomeButton() throws DeviceNotAvailableException {
491         executeShellCommand(INPUT_KEYEVENT_HOME);
492     }
493 
pressBackButton()494     protected void pressBackButton() throws DeviceNotAvailableException {
495         executeShellCommand(INPUT_KEYEVENT_BACK);
496     }
497 
pressAppSwitchButton()498     protected void pressAppSwitchButton() throws DeviceNotAvailableException {
499         executeShellCommand(INPUT_KEYEVENT_APP_SWITCH);
500     }
501 
502     // Utility method for debugging, not used directly here, but useful, so kept around.
printStacksAndTasks()503     protected void printStacksAndTasks() throws DeviceNotAvailableException {
504         CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
505         executeShellCommand(AM_STACK_LIST, outputReceiver);
506         String output = outputReceiver.getOutput();
507         for (String line : output.split("\\n")) {
508             CLog.logAndDisplay(LogLevel.INFO, line);
509         }
510     }
511 
getActivityTaskId(String name)512     protected int getActivityTaskId(String name) throws DeviceNotAvailableException {
513         CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
514         executeShellCommand(AM_STACK_LIST, outputReceiver);
515         final String output = outputReceiver.getOutput();
516         final Pattern activityPattern = Pattern.compile("(.*) " + getWindowName(name) + " (.*)");
517         for (String line : output.split("\\n")) {
518             Matcher matcher = activityPattern.matcher(line);
519             if (matcher.matches()) {
520                 for (String word : line.split("\\s+")) {
521                     if (word.startsWith(TASK_ID_PREFIX)) {
522                         final String withColon = word.split("=")[1];
523                         return Integer.parseInt(withColon.substring(0, withColon.length() - 1));
524                     }
525                 }
526             }
527         }
528         return -1;
529     }
530 
supportsVrMode()531     protected boolean supportsVrMode() throws DeviceNotAvailableException {
532         return hasDeviceFeature("android.software.vr.mode") &&
533                 hasDeviceFeature("android.hardware.vr.high_performance");
534     }
535 
supportsPip()536     protected boolean supportsPip() throws DeviceNotAvailableException {
537         return hasDeviceFeature("android.software.picture_in_picture")
538                 || PRETEND_DEVICE_SUPPORTS_PIP;
539     }
540 
supportsFreeform()541     protected boolean supportsFreeform() throws DeviceNotAvailableException {
542         return hasDeviceFeature("android.software.freeform_window_management")
543                 || PRETEND_DEVICE_SUPPORTS_FREEFORM;
544     }
545 
isHandheld()546     protected boolean isHandheld() throws DeviceNotAvailableException {
547         return !hasDeviceFeature("android.software.leanback")
548                 && !hasDeviceFeature("android.software.watch");
549     }
550 
supportsSplitScreenMultiWindow()551     protected boolean supportsSplitScreenMultiWindow() throws DeviceNotAvailableException {
552         CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
553         executeShellCommand(AM_SUPPORTS_SPLIT_SCREEN_MULTIWINDOW, outputReceiver);
554         String output = outputReceiver.getOutput();
555         return !output.startsWith("false");
556     }
557 
noHomeScreen()558     protected boolean noHomeScreen() throws DeviceNotAvailableException {
559         CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
560         executeShellCommand(AM_NO_HOME_SCREEN, outputReceiver);
561         String output = outputReceiver.getOutput();
562         return output.startsWith("true");
563     }
564 
hasDeviceFeature(String requiredFeature)565     protected boolean hasDeviceFeature(String requiredFeature) throws DeviceNotAvailableException {
566         if (mAvailableFeatures == null) {
567             // TODO: Move this logic to ITestDevice.
568             final String output = runCommandAndPrintOutput("pm list features");
569 
570             // Extract the id of the new user.
571             mAvailableFeatures = new HashSet<>();
572             for (String feature: output.split("\\s+")) {
573                 // Each line in the output of the command has the format "feature:{FEATURE_VALUE}".
574                 String[] tokens = feature.split(":");
575                 assertTrue("\"" + feature + "\" expected to have format feature:{FEATURE_VALUE}",
576                         tokens.length > 1);
577                 assertEquals(feature, "feature", tokens[0]);
578                 mAvailableFeatures.add(tokens[1]);
579             }
580         }
581         boolean result = mAvailableFeatures.contains(requiredFeature);
582         if (!result) {
583             CLog.logAndDisplay(LogLevel.INFO, "Device doesn't support " + requiredFeature);
584         }
585         return result;
586     }
587 
isDisplayOn()588     private boolean isDisplayOn() throws DeviceNotAvailableException {
589         final CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
590         mDevice.executeShellCommand("dumpsys power", outputReceiver);
591 
592         for (String line : outputReceiver.getOutput().split("\\n")) {
593             line = line.trim();
594 
595             final Matcher matcher = sDisplayStatePattern.matcher(line);
596             if (matcher.matches()) {
597                 final String state = matcher.group(1);
598                 log("power state=" + state);
599                 return "ON".equals(state);
600             }
601         }
602         log("power state :(");
603         return false;
604     }
605 
sleepDevice()606     protected void sleepDevice() throws DeviceNotAvailableException {
607         int retriesLeft = 5;
608         runCommandAndPrintOutput("input keyevent 26");
609         do {
610             if (isDisplayOn()) {
611                 log("***Waiting for display to turn off...");
612                 try {
613                     Thread.sleep(1000);
614                 } catch (InterruptedException e) {
615                     log(e.toString());
616                     // Well I guess we are not waiting...
617                 }
618             } else {
619                 break;
620             }
621         } while (retriesLeft-- > 0);
622     }
623 
wakeUpAndUnlockDevice()624     protected void wakeUpAndUnlockDevice() throws DeviceNotAvailableException {
625         wakeUpDevice();
626         unlockDevice();
627     }
628 
wakeUpDevice()629     protected void wakeUpDevice() throws DeviceNotAvailableException {
630         runCommandAndPrintOutput("input keyevent 224");
631     }
632 
unlockDevice()633     protected void unlockDevice() throws DeviceNotAvailableException {
634         runCommandAndPrintOutput("input keyevent 82");
635     }
636 
unlockDeviceWithCredential()637     protected void unlockDeviceWithCredential() throws Exception {
638         runCommandAndPrintOutput("input keyevent 82");
639         try {
640             Thread.sleep(3000);
641         } catch (InterruptedException e) {
642             //ignored
643         }
644         enterAndConfirmLockCredential();
645     }
646 
enterAndConfirmLockCredential()647     protected void enterAndConfirmLockCredential() throws Exception {
648         // TODO: This should use waitForIdle..but there ain't such a thing on hostside tests, boo :(
649         Thread.sleep(500);
650 
651         runCommandAndPrintOutput("input text " + LOCK_CREDENTIAL);
652         runCommandAndPrintOutput("input keyevent KEYCODE_ENTER");
653     }
654 
gotoKeyguard()655     protected void gotoKeyguard() throws DeviceNotAvailableException {
656         sleepDevice();
657         wakeUpDevice();
658     }
659 
setLockCredential()660     protected void setLockCredential() throws DeviceNotAvailableException {
661         runCommandAndPrintOutput("locksettings set-pin " + LOCK_CREDENTIAL);
662     }
663 
removeLockCredential()664     protected void removeLockCredential() throws DeviceNotAvailableException {
665         runCommandAndPrintOutput("locksettings clear --old " + LOCK_CREDENTIAL);
666     }
667 
668     /**
669      * Sets the device rotation, value corresponds to one of {@link Surface.ROTATION_0},
670      * {@link Surface.ROTATION_90}, {@link Surface.ROTATION_180}, {@link Surface.ROTATION_270}.
671      */
setDeviceRotation(int rotation)672     protected void setDeviceRotation(int rotation) throws Exception {
673         setAccelerometerRotation(0);
674         setUserRotation(rotation);
675         mAmWmState.waitForRotation(mDevice, rotation);
676     }
677 
getDeviceRotation(int displayId)678     protected int getDeviceRotation(int displayId) throws DeviceNotAvailableException {
679         final String displays = runCommandAndPrintOutput("dumpsys display displays").trim();
680         Pattern pattern = Pattern.compile(
681                 "(mDisplayId=" + displayId + ")([\\s\\S]*)(mOverrideDisplayInfo)(.*)"
682                         + "(rotation)(\\s+)(\\d+)");
683         Matcher matcher = pattern.matcher(displays);
684         while (matcher.find()) {
685             final String match = matcher.group(7);
686             return Integer.parseInt(match);
687         }
688 
689         return INVALID_DEVICE_ROTATION;
690     }
691 
getAccelerometerRotation()692     private int getAccelerometerRotation() throws DeviceNotAvailableException {
693         final String rotation =
694                 runCommandAndPrintOutput("settings get system accelerometer_rotation");
695         return Integer.parseInt(rotation.trim());
696     }
697 
setAccelerometerRotation(int rotation)698     private void setAccelerometerRotation(int rotation) throws DeviceNotAvailableException {
699         runCommandAndPrintOutput(
700                 "settings put system accelerometer_rotation " + rotation);
701     }
702 
getUserRotation()703     protected int getUserRotation() throws DeviceNotAvailableException {
704         final String rotation =
705                 runCommandAndPrintOutput("settings get system user_rotation").trim();
706         if ("null".equals(rotation)) {
707             return -1;
708         }
709         return Integer.parseInt(rotation);
710     }
711 
setUserRotation(int rotation)712     private void setUserRotation(int rotation) throws DeviceNotAvailableException {
713         if (rotation == -1) {
714             runCommandAndPrintOutput(
715                     "settings delete system user_rotation");
716         } else {
717             runCommandAndPrintOutput(
718                     "settings put system user_rotation " + rotation);
719         }
720     }
721 
setFontScale(float fontScale)722     protected void setFontScale(float fontScale) throws DeviceNotAvailableException {
723         if (fontScale == 0.0f) {
724             runCommandAndPrintOutput(
725                     "settings delete system font_scale");
726         } else {
727             runCommandAndPrintOutput(
728                     "settings put system font_scale " + fontScale);
729         }
730     }
731 
getFontScale()732     protected float getFontScale() throws DeviceNotAvailableException {
733         try {
734             final String fontScale =
735                     runCommandAndPrintOutput("settings get system font_scale").trim();
736             return Float.parseFloat(fontScale);
737         } catch (NumberFormatException e) {
738             // If we don't have a valid font scale key, return 0.0f now so
739             // that we delete the key in tearDown().
740             return 0.0f;
741         }
742     }
743 
runCommandAndPrintOutput(String command)744     protected String runCommandAndPrintOutput(String command) throws DeviceNotAvailableException {
745         final String output = executeShellCommand(command);
746         log(output);
747         return output;
748     }
749 
750     /**
751      * Tries to clear logcat and inserts log separator in case clearing didn't succeed, so we can
752      * always find the starting point from where to evaluate following logs.
753      * @return Unique log separator.
754      */
clearLogcat()755     protected String clearLogcat() throws DeviceNotAvailableException {
756         mDevice.executeAdbCommand("logcat", "-c");
757         final String uniqueString = UUID.randomUUID().toString();
758         executeShellCommand("log -t " + LOG_SEPARATOR + " " + uniqueString);
759         return uniqueString;
760     }
761 
assertActivityLifecycle(String activityName, boolean relaunched, String logSeparator)762     void assertActivityLifecycle(String activityName, boolean relaunched,
763             String logSeparator) throws DeviceNotAvailableException {
764         int retriesLeft = 5;
765         String resultString;
766         do {
767             resultString = verifyLifecycleCondition(activityName, logSeparator, relaunched);
768             if (resultString != null) {
769                 log("***Waiting for valid lifecycle state: " + resultString);
770                 try {
771                     Thread.sleep(1000);
772                 } catch (InterruptedException e) {
773                     log(e.toString());
774                 }
775             } else {
776                 break;
777             }
778         } while (retriesLeft-- > 0);
779 
780         assertNull(resultString, resultString);
781     }
782 
783     /** @return Error string if lifecycle counts don't match, null if everything is fine. */
verifyLifecycleCondition(String activityName, String logSeparator, boolean relaunched)784     private String verifyLifecycleCondition(String activityName, String logSeparator,
785             boolean relaunched) throws DeviceNotAvailableException {
786         final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName,
787                 logSeparator);
788         if (relaunched) {
789             if (lifecycleCounts.mDestroyCount < 1) {
790                 return activityName + " must have been destroyed. mDestroyCount="
791                         + lifecycleCounts.mDestroyCount;
792             }
793             if (lifecycleCounts.mCreateCount < 1) {
794                 return activityName + " must have been (re)created. mCreateCount="
795                         + lifecycleCounts.mCreateCount;
796             }
797         } else {
798             if (lifecycleCounts.mDestroyCount > 0) {
799                 return activityName + " must *NOT* have been destroyed. mDestroyCount="
800                         + lifecycleCounts.mDestroyCount;
801             }
802             if (lifecycleCounts.mCreateCount > 0) {
803                 return activityName + " must *NOT* have been (re)created. mCreateCount="
804                         + lifecycleCounts.mCreateCount;
805             }
806             if (lifecycleCounts.mConfigurationChangedCount < 1) {
807                 return activityName + " must have received configuration changed. "
808                         + "mConfigurationChangedCount="
809                         + lifecycleCounts.mConfigurationChangedCount;
810             }
811         }
812         return null;
813     }
814 
assertRelaunchOrConfigChanged( String activityName, int numRelaunch, int numConfigChange, String logSeparator)815     protected void assertRelaunchOrConfigChanged(
816             String activityName, int numRelaunch, int numConfigChange, String logSeparator)
817             throws DeviceNotAvailableException {
818         int retriesLeft = 5;
819         String resultString;
820         do {
821             resultString = verifyRelaunchOrConfigChanged(activityName, numRelaunch, numConfigChange,
822                     logSeparator);
823             if (resultString != null) {
824                 log("***Waiting for relaunch or config changed: " + resultString);
825                 try {
826                     Thread.sleep(1000);
827                 } catch (InterruptedException e) {
828                     log(e.toString());
829                 }
830             } else {
831                 break;
832             }
833         } while (retriesLeft-- > 0);
834 
835         assertNull(resultString, resultString);
836     }
837 
838     /** @return Error string if lifecycle counts don't match, null if everything is fine. */
verifyRelaunchOrConfigChanged(String activityName, int numRelaunch, int numConfigChange, String logSeparator)839     private String verifyRelaunchOrConfigChanged(String activityName, int numRelaunch,
840             int numConfigChange, String logSeparator) throws DeviceNotAvailableException {
841         final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName,
842                 logSeparator);
843 
844         if (lifecycleCounts.mDestroyCount != numRelaunch) {
845             return activityName + " has been destroyed " + lifecycleCounts.mDestroyCount
846                     + " time(s), expecting " + numRelaunch;
847         } else if (lifecycleCounts.mCreateCount != numRelaunch) {
848             return activityName + " has been (re)created " + lifecycleCounts.mCreateCount
849                     + " time(s), expecting " + numRelaunch;
850         } else if (lifecycleCounts.mConfigurationChangedCount != numConfigChange) {
851             return activityName + " has received " + lifecycleCounts.mConfigurationChangedCount
852                     + " onConfigurationChanged() calls, expecting " + numConfigChange;
853         }
854         return null;
855     }
856 
assertActivityDestroyed(String activityName, String logSeparator)857     protected void assertActivityDestroyed(String activityName, String logSeparator)
858             throws DeviceNotAvailableException {
859         int retriesLeft = 5;
860         String resultString;
861         do {
862             resultString = verifyActivityDestroyed(activityName, logSeparator);
863             if (resultString != null) {
864                 log("***Waiting for activity destroyed: " + resultString);
865                 try {
866                     Thread.sleep(1000);
867                 } catch (InterruptedException e) {
868                     log(e.toString());
869                 }
870             } else {
871                 break;
872             }
873         } while (retriesLeft-- > 0);
874 
875         assertNull(resultString, resultString);
876     }
877 
878     /** @return Error string if lifecycle counts don't match, null if everything is fine. */
verifyActivityDestroyed(String activityName, String logSeparator)879     private String verifyActivityDestroyed(String activityName, String logSeparator)
880             throws DeviceNotAvailableException {
881         final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName,
882                 logSeparator);
883 
884         if (lifecycleCounts.mDestroyCount != 1) {
885             return activityName + " has been destroyed " + lifecycleCounts.mDestroyCount
886                     + " time(s), expecting single destruction.";
887         } else if (lifecycleCounts.mCreateCount != 0) {
888             return activityName + " has been (re)created " + lifecycleCounts.mCreateCount
889                     + " time(s), not expecting any.";
890         } else if (lifecycleCounts.mConfigurationChangedCount != 0) {
891             return activityName + " has received " + lifecycleCounts.mConfigurationChangedCount
892                     + " onConfigurationChanged() calls, not expecting any.";
893         }
894         return null;
895     }
896 
getDeviceLogsForComponent(String componentName, String logSeparator)897     protected String[] getDeviceLogsForComponent(String componentName, String logSeparator)
898             throws DeviceNotAvailableException {
899         return getDeviceLogsForComponents(new String[]{componentName}, logSeparator);
900     }
901 
getDeviceLogsForComponents(final String[] componentNames, String logSeparator)902     protected String[] getDeviceLogsForComponents(final String[] componentNames,
903             String logSeparator) throws DeviceNotAvailableException {
904         String filters = LOG_SEPARATOR + ":I ";
905         for (String component : componentNames) {
906             filters += component + ":I ";
907         }
908         final String[] result = mDevice.executeAdbCommand(
909                 "logcat", "-v", "brief", "-d", filters, "*:S").split("\\n");
910         if (logSeparator == null) {
911             return result;
912         }
913 
914         // Make sure that we only check logs after the separator.
915         int i = 0;
916         boolean lookingForSeparator = true;
917         while (i < result.length && lookingForSeparator) {
918             if (result[i].contains(logSeparator)) {
919                 lookingForSeparator = false;
920             }
921             i++;
922         }
923         final String[] filteredResult = new String[result.length - i];
924         for (int curPos = 0; i < result.length; curPos++, i++) {
925             filteredResult[curPos] = result[i];
926         }
927         return filteredResult;
928     }
929 
930     private static final Pattern sCreatePattern = Pattern.compile("(.+): onCreate");
931     private static final Pattern sResumePattern = Pattern.compile("(.+): onResume");
932     private static final Pattern sPausePattern = Pattern.compile("(.+): onPause");
933     private static final Pattern sConfigurationChangedPattern =
934             Pattern.compile("(.+): onConfigurationChanged");
935     private static final Pattern sMovedToDisplayPattern =
936             Pattern.compile("(.+): onMovedToDisplay");
937     private static final Pattern sStopPattern = Pattern.compile("(.+): onStop");
938     private static final Pattern sDestroyPattern = Pattern.compile("(.+): onDestroy");
939     private static final Pattern sMultiWindowModeChangedPattern =
940             Pattern.compile("(.+): onMultiWindowModeChanged");
941     private static final Pattern sPictureInPictureModeChangedPattern =
942             Pattern.compile("(.+): onPictureInPictureModeChanged");
943     private static final Pattern sNewConfigPattern = Pattern.compile(
944             "(.+): config size=\\((\\d+),(\\d+)\\) displaySize=\\((\\d+),(\\d+)\\)"
945             + " metricsSize=\\((\\d+),(\\d+)\\) smallestScreenWidth=(\\d+) densityDpi=(\\d+)"
946             + " orientation=(\\d+)");
947     private static final Pattern sDisplayStatePattern =
948             Pattern.compile("Display Power: state=(.+)");
949 
950     class ReportedSizes {
951         int widthDp;
952         int heightDp;
953         int displayWidth;
954         int displayHeight;
955         int metricsWidth;
956         int metricsHeight;
957         int smallestWidthDp;
958         int densityDpi;
959         int orientation;
960 
961         @Override
toString()962         public String toString() {
963             return "ReportedSizes: {widthDp=" + widthDp + " heightDp=" + heightDp
964                     + " displayWidth=" + displayWidth + " displayHeight=" + displayHeight
965                     + " metricsWidth=" + metricsWidth + " metricsHeight=" + metricsHeight
966                     + " smallestWidthDp=" + smallestWidthDp + " densityDpi=" + densityDpi
967                     + " orientation=" + orientation + "}";
968         }
969 
970         @Override
equals(Object obj)971         public boolean equals(Object obj) {
972             if ( this == obj ) return true;
973             if ( !(obj instanceof ReportedSizes) ) return false;
974             ReportedSizes that = (ReportedSizes) obj;
975             return widthDp == that.widthDp
976                     && heightDp == that.heightDp
977                     && displayWidth == that.displayWidth
978                     && displayHeight == that.displayHeight
979                     && metricsWidth == that.metricsWidth
980                     && metricsHeight == that.metricsHeight
981                     && smallestWidthDp == that.smallestWidthDp
982                     && densityDpi == that.densityDpi
983                     && orientation == that.orientation;
984         }
985     }
986 
getLastReportedSizesForActivity(String activityName, String logSeparator)987     ReportedSizes getLastReportedSizesForActivity(String activityName, String logSeparator)
988             throws DeviceNotAvailableException {
989         int retriesLeft = 5;
990         ReportedSizes result;
991         do {
992             result = readLastReportedSizes(activityName, logSeparator);
993             if (result == null) {
994                 log("***Waiting for sizes to be reported...");
995                 try {
996                     Thread.sleep(1000);
997                 } catch (InterruptedException e) {
998                     log(e.toString());
999                     // Well I guess we are not waiting...
1000                 }
1001             } else {
1002                 break;
1003             }
1004         } while (retriesLeft-- > 0);
1005         return result;
1006     }
1007 
readLastReportedSizes(String activityName, String logSeparator)1008     private ReportedSizes readLastReportedSizes(String activityName, String logSeparator)
1009             throws DeviceNotAvailableException {
1010         final String[] lines = getDeviceLogsForComponent(activityName, logSeparator);
1011         for (int i = lines.length - 1; i >= 0; i--) {
1012             final String line = lines[i].trim();
1013             final Matcher matcher = sNewConfigPattern.matcher(line);
1014             if (matcher.matches()) {
1015                 ReportedSizes details = new ReportedSizes();
1016                 details.widthDp = Integer.parseInt(matcher.group(2));
1017                 details.heightDp = Integer.parseInt(matcher.group(3));
1018                 details.displayWidth = Integer.parseInt(matcher.group(4));
1019                 details.displayHeight = Integer.parseInt(matcher.group(5));
1020                 details.metricsWidth = Integer.parseInt(matcher.group(6));
1021                 details.metricsHeight = Integer.parseInt(matcher.group(7));
1022                 details.smallestWidthDp = Integer.parseInt(matcher.group(8));
1023                 details.densityDpi = Integer.parseInt(matcher.group(9));
1024                 details.orientation = Integer.parseInt(matcher.group(10));
1025                 return details;
1026             }
1027         }
1028         return null;
1029     }
1030 
1031     class ActivityLifecycleCounts {
1032         int mCreateCount;
1033         int mResumeCount;
1034         int mConfigurationChangedCount;
1035         int mLastConfigurationChangedLineIndex;
1036         int mMovedToDisplayCount;
1037         int mMultiWindowModeChangedCount;
1038         int mLastMultiWindowModeChangedLineIndex;
1039         int mPictureInPictureModeChangedCount;
1040         int mLastPictureInPictureModeChangedLineIndex;
1041         int mPauseCount;
1042         int mStopCount;
1043         int mLastStopLineIndex;
1044         int mDestroyCount;
1045 
ActivityLifecycleCounts(String activityName, String logSeparator)1046         public ActivityLifecycleCounts(String activityName, String logSeparator)
1047                 throws DeviceNotAvailableException {
1048             int lineIndex = 0;
1049             for (String line : getDeviceLogsForComponent(activityName, logSeparator)) {
1050                 line = line.trim();
1051                 lineIndex++;
1052 
1053                 Matcher matcher = sCreatePattern.matcher(line);
1054                 if (matcher.matches()) {
1055                     mCreateCount++;
1056                     continue;
1057                 }
1058 
1059                 matcher = sResumePattern.matcher(line);
1060                 if (matcher.matches()) {
1061                     mResumeCount++;
1062                     continue;
1063                 }
1064 
1065                 matcher = sConfigurationChangedPattern.matcher(line);
1066                 if (matcher.matches()) {
1067                     mConfigurationChangedCount++;
1068                     mLastConfigurationChangedLineIndex = lineIndex;
1069                     continue;
1070                 }
1071 
1072                 matcher = sMovedToDisplayPattern.matcher(line);
1073                 if (matcher.matches()) {
1074                     mMovedToDisplayCount++;
1075                     continue;
1076                 }
1077 
1078                 matcher = sMultiWindowModeChangedPattern.matcher(line);
1079                 if (matcher.matches()) {
1080                     mMultiWindowModeChangedCount++;
1081                     mLastMultiWindowModeChangedLineIndex = lineIndex;
1082                     continue;
1083                 }
1084 
1085                 matcher = sPictureInPictureModeChangedPattern.matcher(line);
1086                 if (matcher.matches()) {
1087                     mPictureInPictureModeChangedCount++;
1088                     mLastPictureInPictureModeChangedLineIndex = lineIndex;
1089                     continue;
1090                 }
1091 
1092                 matcher = sPausePattern.matcher(line);
1093                 if (matcher.matches()) {
1094                     mPauseCount++;
1095                     continue;
1096                 }
1097 
1098                 matcher = sStopPattern.matcher(line);
1099                 if (matcher.matches()) {
1100                     mStopCount++;
1101                     mLastStopLineIndex = lineIndex;
1102                     continue;
1103                 }
1104 
1105                 matcher = sDestroyPattern.matcher(line);
1106                 if (matcher.matches()) {
1107                     mDestroyCount++;
1108                     continue;
1109                 }
1110             }
1111         }
1112     }
1113 
stopTestCase()1114     protected void stopTestCase() throws Exception {
1115         executeShellCommand("am force-stop " + componentName);
1116     }
1117 
getLaunchActivityBuilder()1118     protected LaunchActivityBuilder getLaunchActivityBuilder() {
1119         return new LaunchActivityBuilder(mAmWmState, mDevice);
1120     }
1121 
1122     protected static class LaunchActivityBuilder {
1123         private final ActivityAndWindowManagersState mAmWmState;
1124         private final ITestDevice mDevice;
1125 
1126         private String mTargetActivityName;
1127         private String mTargetPackage = componentName;
1128         private boolean mToSide;
1129         private boolean mRandomData;
1130         private boolean mNewTask;
1131         private boolean mMultipleTask;
1132         private int mDisplayId = INVALID_DISPLAY_ID;
1133         private String mLaunchingActivityName = LAUNCHING_ACTIVITY;
1134         private boolean mReorderToFront;
1135         private boolean mWaitForLaunched;
1136 
LaunchActivityBuilder(ActivityAndWindowManagersState amWmState, ITestDevice device)1137         public LaunchActivityBuilder(ActivityAndWindowManagersState amWmState,
1138                                      ITestDevice device) {
1139             mAmWmState = amWmState;
1140             mDevice = device;
1141             mWaitForLaunched = true;
1142         }
1143 
setToSide(boolean toSide)1144         public LaunchActivityBuilder setToSide(boolean toSide) {
1145             mToSide = toSide;
1146             return this;
1147         }
1148 
setRandomData(boolean randomData)1149         public LaunchActivityBuilder setRandomData(boolean randomData) {
1150             mRandomData = randomData;
1151             return this;
1152         }
1153 
setNewTask(boolean newTask)1154         public LaunchActivityBuilder setNewTask(boolean newTask) {
1155             mNewTask = newTask;
1156             return this;
1157         }
1158 
setMultipleTask(boolean multipleTask)1159         public LaunchActivityBuilder setMultipleTask(boolean multipleTask) {
1160             mMultipleTask = multipleTask;
1161             return this;
1162         }
1163 
setReorderToFront(boolean reorderToFront)1164         public LaunchActivityBuilder setReorderToFront(boolean reorderToFront) {
1165             mReorderToFront = reorderToFront;
1166             return this;
1167         }
1168 
setTargetActivityName(String name)1169         public LaunchActivityBuilder setTargetActivityName(String name) {
1170             mTargetActivityName = name;
1171             return this;
1172         }
1173 
setTargetPackage(String pkg)1174         public LaunchActivityBuilder setTargetPackage(String pkg) {
1175             mTargetPackage = pkg;
1176             return this;
1177         }
1178 
setDisplayId(int id)1179         public LaunchActivityBuilder setDisplayId(int id) {
1180             mDisplayId = id;
1181             return this;
1182         }
1183 
setLaunchingActivityName(String name)1184         public LaunchActivityBuilder setLaunchingActivityName(String name) {
1185             mLaunchingActivityName = name;
1186             return this;
1187         }
1188 
setWaitForLaunched(boolean shouldWait)1189         public LaunchActivityBuilder setWaitForLaunched(boolean shouldWait) {
1190             mWaitForLaunched = shouldWait;
1191             return this;
1192         }
1193 
execute()1194         public void execute() throws Exception {
1195             StringBuilder commandBuilder = new StringBuilder(getAmStartCmd(mLaunchingActivityName));
1196             commandBuilder.append(" -f 0x20000000");
1197 
1198             // Add a flag to ensure we actually mean to launch an activity.
1199             commandBuilder.append(" --ez launch_activity true");
1200 
1201             if (mToSide) {
1202                 commandBuilder.append(" --ez launch_to_the_side true");
1203             }
1204             if (mRandomData) {
1205                 commandBuilder.append(" --ez random_data true");
1206             }
1207             if (mNewTask) {
1208                 commandBuilder.append(" --ez new_task true");
1209             }
1210             if (mMultipleTask) {
1211                 commandBuilder.append(" --ez multiple_task true");
1212             }
1213             if (mReorderToFront) {
1214                 commandBuilder.append(" --ez reorder_to_front true");
1215             }
1216             if (mTargetActivityName != null) {
1217                 commandBuilder.append(" --es target_activity ").append(mTargetActivityName);
1218                 commandBuilder.append(" --es package_name ").append(mTargetPackage);
1219             }
1220             if (mDisplayId != INVALID_DISPLAY_ID) {
1221                 commandBuilder.append(" --ei display_id ").append(mDisplayId);
1222             }
1223             executeShellCommand(mDevice, commandBuilder.toString());
1224 
1225             if (mWaitForLaunched) {
1226                 mAmWmState.waitForValidState(mDevice, new String[]{mTargetActivityName},
1227                         null /* stackIds */, false /* compareTaskAndStackBounds */, mTargetPackage);
1228             }
1229         }
1230     }
1231 }
1232