• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.testtype.DeviceTestCase;
25 
26 import java.lang.Exception;
27 import java.lang.Integer;
28 import java.lang.String;
29 import java.util.HashSet;
30 import java.util.regex.Matcher;
31 import java.util.regex.Pattern;
32 
33 import static android.server.cts.StateLogger.log;
34 
35 public abstract class ActivityManagerTestBase extends DeviceTestCase {
36     private static final boolean PRETEND_DEVICE_SUPPORTS_PIP = false;
37     private static final boolean PRETEND_DEVICE_SUPPORTS_FREEFORM = false;
38 
39     // Constants copied from ActivityManager.StackId. If they are changed there, these must be
40     // updated.
41     /** First static stack ID. */
42     public static final int FIRST_STATIC_STACK_ID = 0;
43 
44     /** Home activity stack ID. */
45     public static final int HOME_STACK_ID = FIRST_STATIC_STACK_ID;
46 
47     /** ID of stack where fullscreen activities are normally launched into. */
48     public static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;
49 
50     /** ID of stack where freeform/resized activities are normally launched into. */
51     public static final int FREEFORM_WORKSPACE_STACK_ID = FULLSCREEN_WORKSPACE_STACK_ID + 1;
52 
53     /** ID of stack that occupies a dedicated region of the screen. */
54     public static final int DOCKED_STACK_ID = FREEFORM_WORKSPACE_STACK_ID + 1;
55 
56     /** ID of stack that always on top (always visible) when it exist. */
57     public static final int PINNED_STACK_ID = DOCKED_STACK_ID + 1;
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.app";
64 
65     private static final String AM_REMOVE_STACK = "am stack remove ";
66 
67     protected static final String AM_START_HOME_ACTIVITY_COMMAND =
68             "am start -a android.intent.action.MAIN -c android.intent.category.HOME";
69 
70     protected static final String AM_MOVE_TOP_ACTIVITY_TO_PINNED_STACK_COMMAND =
71             "am stack move-top-activity-to-pinned-stack 1 0 0 500 500";
72 
73     protected static final String LAUNCHING_ACTIVITY = "LaunchingActivity";
74 
75     private static final String AM_RESIZE_DOCKED_STACK = "am stack resize-docked-stack ";
76 
77     private static final String AM_MOVE_TASK = "am stack movetask ";
78 
79     private static final String INPUT_KEYEVENT_HOME = "input keyevent 3";
80 
81     /** A reference to the device under test. */
82     protected ITestDevice mDevice;
83 
84     private HashSet<String> mAvailableFeatures;
85 
getAmStartCmd(final String activityName)86     protected static String getAmStartCmd(final String activityName) {
87         return "am start -n " + getActivityComponentName(activityName);
88     }
89 
getAmStartCmdOverHome(final String activityName)90     protected static String getAmStartCmdOverHome(final String activityName) {
91         return "am start --activity-task-on-home -n " + getActivityComponentName(activityName);
92     }
93 
getActivityComponentName(final String activityName)94     static String getActivityComponentName(final String activityName) {
95         return "android.server.app/." + activityName;
96     }
97 
getWindowName(final String activityName)98     static String getWindowName(final String activityName) {
99         return "android.server.app/android.server.app." + activityName;
100     }
101 
102     protected ActivityAndWindowManagersState mAmWmState = new ActivityAndWindowManagersState();
103 
104     private int mInitialAccelerometerRotation;
105     private int mUserRotation;
106     private float mFontScale;
107 
108     @Override
setUp()109     protected void setUp() throws Exception {
110         super.setUp();
111 
112         // Get the device, this gives a handle to run commands and install APKs.
113         mDevice = getDevice();
114         unlockDevice();
115         // Remove special stacks.
116         executeShellCommand(AM_REMOVE_STACK + PINNED_STACK_ID);
117         executeShellCommand(AM_REMOVE_STACK + DOCKED_STACK_ID);
118         executeShellCommand(AM_REMOVE_STACK + FREEFORM_WORKSPACE_STACK_ID);
119         // Store rotation settings.
120         mInitialAccelerometerRotation = getAccelerometerRotation();
121         mUserRotation = getUserRotation();
122         mFontScale = getFontScale();
123     }
124 
125     @Override
tearDown()126     protected void tearDown() throws Exception {
127         super.tearDown();
128         try {
129             unlockDevice();
130             executeShellCommand(AM_FORCE_STOP_TEST_PACKAGE);
131             // Restore rotation settings to the state they were before test.
132             setAccelerometerRotation(mInitialAccelerometerRotation);
133             setUserRotation(mUserRotation);
134             setFontScale(mFontScale);
135             // Remove special stacks.
136             executeShellCommand(AM_REMOVE_STACK + PINNED_STACK_ID);
137             executeShellCommand(AM_REMOVE_STACK + DOCKED_STACK_ID);
138             executeShellCommand(AM_REMOVE_STACK + FREEFORM_WORKSPACE_STACK_ID);
139         } catch (DeviceNotAvailableException e) {
140         }
141     }
142 
executeShellCommand(String command)143     protected String executeShellCommand(String command) throws DeviceNotAvailableException {
144         log("adb shell " + command);
145         return mDevice.executeShellCommand(command);
146     }
147 
executeShellCommand(String command, CollectingOutputReceiver outputReceiver)148     protected void executeShellCommand(String command, CollectingOutputReceiver outputReceiver)
149             throws DeviceNotAvailableException {
150         log("adb shell " + command);
151         mDevice.executeShellCommand(command, outputReceiver);
152     }
153 
154     /**
155      * Launch specific target activity. It uses existing instance of {@link #LAUNCHING_ACTIVITY}, so
156      * that one should be started first.
157      * @param toSide Launch to side in split-screen.
158      * @param randomData Make intent URI random by generating random data.
159      * @param multipleTask Allow multiple task launch.
160      * @param targetActivityName Target activity to be launched. Only class name should be provided,
161      *                           package name of {@link #LAUNCHING_ACTIVITY} will be added
162      *                           automatically.
163      * @throws Exception
164      */
launchActivity(boolean toSide, boolean randomData, boolean multipleTask, String targetActivityName)165     protected void launchActivity(boolean toSide, boolean randomData, boolean multipleTask,
166             String targetActivityName) throws Exception {
167         StringBuilder commandBuilder = new StringBuilder(getAmStartCmd(LAUNCHING_ACTIVITY));
168         commandBuilder.append(" -f 0x20000000");
169         if (toSide) {
170             commandBuilder.append(" --ez launch_to_the_side true");
171         }
172         if (randomData) {
173             commandBuilder.append(" --ez random_data true");
174         }
175         if (multipleTask) {
176             commandBuilder.append(" --ez multiple_task true");
177         }
178         if (targetActivityName != null) {
179             commandBuilder.append(" --es target_activity ").append(targetActivityName);
180         }
181         executeShellCommand(commandBuilder.toString());
182     }
183 
launchActivityInStack(String activityName, int stackId)184     protected void launchActivityInStack(String activityName, int stackId) throws Exception {
185         executeShellCommand(getAmStartCmd(activityName) + " --stack " + stackId);
186     }
187 
launchActivityInDockStack(String activityName)188     protected void launchActivityInDockStack(String activityName) throws Exception {
189         executeShellCommand(getAmStartCmd(activityName));
190         moveActivityToDockStack(activityName);
191     }
192 
moveActivityToDockStack(String activityName)193     protected void moveActivityToDockStack(String activityName) throws Exception {
194         moveActivityToStack(activityName, DOCKED_STACK_ID);
195     }
196 
moveActivityToStack(String activityName, int stackId)197     protected void moveActivityToStack(String activityName, int stackId) throws Exception {
198         final int taskId = getActivityTaskId(activityName);
199         final String cmd = AM_MOVE_TASK + taskId + " " + stackId + " true";
200         executeShellCommand(cmd);
201     }
202 
resizeActivityTask(String activityName, int left, int top, int right, int bottom)203     protected void resizeActivityTask(String activityName, int left, int top, int right, int bottom)
204             throws Exception {
205         final int taskId = getActivityTaskId(activityName);
206         final String cmd = "am task resize "
207                 + taskId + " " + left + " " + top + " " + right + " " + bottom;
208         executeShellCommand(cmd);
209     }
210 
resizeDockedStack( int stackWidth, int stackHeight, int taskWidth, int taskHeight)211     protected void resizeDockedStack(
212             int stackWidth, int stackHeight, int taskWidth, int taskHeight)
213                     throws DeviceNotAvailableException {
214         executeShellCommand(AM_RESIZE_DOCKED_STACK
215                 + "0 0 " + stackWidth + " " + stackHeight
216                 + " 0 0 " + taskWidth + " " + taskHeight);
217     }
218 
pressHomeButton()219     protected void pressHomeButton() throws DeviceNotAvailableException {
220         executeShellCommand(INPUT_KEYEVENT_HOME);
221     }
222 
223     // Utility method for debugging, not used directly here, but useful, so kept around.
printStacksAndTasks()224     protected void printStacksAndTasks() throws DeviceNotAvailableException {
225         CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
226         executeShellCommand(AM_STACK_LIST, outputReceiver);
227         String output = outputReceiver.getOutput();
228         for (String line : output.split("\\n")) {
229             CLog.logAndDisplay(LogLevel.INFO, line);
230         }
231     }
232 
getActivityTaskId(String name)233     protected int getActivityTaskId(String name) throws DeviceNotAvailableException {
234         CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
235         executeShellCommand(AM_STACK_LIST, outputReceiver);
236         final String output = outputReceiver.getOutput();
237         final Pattern activityPattern = Pattern.compile("(.*) " + getWindowName(name) + " (.*)");
238         for (String line : output.split("\\n")) {
239             Matcher matcher = activityPattern.matcher(line);
240             if (matcher.matches()) {
241                 for (String word : line.split("\\s+")) {
242                     if (word.startsWith(TASK_ID_PREFIX)) {
243                         final String withColon = word.split("=")[1];
244                         return Integer.parseInt(withColon.substring(0, withColon.length() - 1));
245                     }
246                 }
247             }
248         }
249         return -1;
250     }
251 
supportsPip()252     protected boolean supportsPip() throws DeviceNotAvailableException {
253         return hasDeviceFeature("android.software.picture_in_picture")
254                 || PRETEND_DEVICE_SUPPORTS_PIP;
255     }
256 
supportsFreeform()257     protected boolean supportsFreeform() throws DeviceNotAvailableException {
258         return hasDeviceFeature("android.software.freeform_window_management")
259                 || PRETEND_DEVICE_SUPPORTS_FREEFORM;
260     }
261 
hasDeviceFeature(String requiredFeature)262     protected boolean hasDeviceFeature(String requiredFeature) throws DeviceNotAvailableException {
263         if (mAvailableFeatures == null) {
264             // TODO: Move this logic to ITestDevice.
265             final String output = runCommandAndPrintOutput("pm list features");
266 
267             // Extract the id of the new user.
268             mAvailableFeatures = new HashSet<>();
269             for (String feature: output.split("\\s+")) {
270                 // Each line in the output of the command has the format "feature:{FEATURE_VALUE}".
271                 String[] tokens = feature.split(":");
272                 assertTrue("\"" + feature + "\" expected to have format feature:{FEATURE_VALUE}",
273                         tokens.length > 1);
274                 assertEquals(feature, "feature", tokens[0]);
275                 mAvailableFeatures.add(tokens[1]);
276             }
277         }
278         boolean result = mAvailableFeatures.contains(requiredFeature);
279         if (!result) {
280             CLog.logAndDisplay(LogLevel.INFO, "Device doesn't support " + requiredFeature);
281         }
282         return result;
283     }
284 
isDisplayOn()285     private boolean isDisplayOn() throws DeviceNotAvailableException {
286         final CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
287         mDevice.executeShellCommand("dumpsys power", outputReceiver);
288 
289         for (String line : outputReceiver.getOutput().split("\\n")) {
290             line = line.trim();
291 
292             final Matcher matcher = sDisplayStatePattern.matcher(line);
293             if (matcher.matches()) {
294                 final String state = matcher.group(1);
295                 log("power state=" + state);
296                 return "ON".equals(state);
297             }
298         }
299         log("power state :(");
300         return false;
301     }
302 
lockDevice()303     protected void lockDevice() throws DeviceNotAvailableException {
304         int retriesLeft = 5;
305         runCommandAndPrintOutput("input keyevent 26");
306         do {
307             if (isDisplayOn()) {
308                 log("***Waiting for display to turn off...");
309                 try {
310                     Thread.sleep(1000);
311                 } catch (InterruptedException e) {
312                     log(e.toString());
313                     // Well I guess we are not waiting...
314                 }
315             } else {
316                 break;
317             }
318         } while (retriesLeft-- > 0);
319     }
320 
unlockDevice()321     protected void unlockDevice() throws DeviceNotAvailableException {
322         if (!isDisplayOn()) {
323             runCommandAndPrintOutput("input keyevent 224");
324             runCommandAndPrintOutput("input keyevent 82");
325         }
326     }
327 
setDeviceRotation(int rotation)328     protected void setDeviceRotation(int rotation) throws DeviceNotAvailableException {
329         setAccelerometerRotation(0);
330         setUserRotation(rotation);
331     }
332 
getAccelerometerRotation()333     private int getAccelerometerRotation() throws DeviceNotAvailableException {
334         final String rotation =
335                 runCommandAndPrintOutput("settings get system accelerometer_rotation");
336         return Integer.parseInt(rotation.trim());
337     }
338 
setAccelerometerRotation(int rotation)339     private void setAccelerometerRotation(int rotation) throws DeviceNotAvailableException {
340         runCommandAndPrintOutput(
341                 "settings put system accelerometer_rotation " + rotation);
342     }
343 
getUserRotation()344     private int getUserRotation() throws DeviceNotAvailableException {
345         final String rotation =
346                 runCommandAndPrintOutput("settings get system user_rotation").trim();
347         if ("null".equals(rotation)) {
348             return -1;
349         }
350         return Integer.parseInt(rotation);
351     }
352 
setUserRotation(int rotation)353     private void setUserRotation(int rotation) throws DeviceNotAvailableException {
354         if (rotation == -1) {
355             runCommandAndPrintOutput(
356                     "settings delete system user_rotation");
357         } else {
358             runCommandAndPrintOutput(
359                     "settings put system user_rotation " + rotation);
360         }
361     }
362 
setFontScale(float fontScale)363     protected void setFontScale(float fontScale) throws DeviceNotAvailableException {
364         if (fontScale == 0.0f) {
365             runCommandAndPrintOutput(
366                     "settings delete system font_scale");
367         } else {
368             runCommandAndPrintOutput(
369                     "settings put system font_scale " + fontScale);
370         }
371     }
372 
getFontScale()373     protected float getFontScale() throws DeviceNotAvailableException {
374         try {
375             final String fontScale =
376                     runCommandAndPrintOutput("settings get system font_scale").trim();
377             return Float.parseFloat(fontScale);
378         } catch (NumberFormatException e) {
379             // If we don't have a valid font scale key, return 0.0f now so
380             // that we delete the key in tearDown().
381             return 0.0f;
382         }
383     }
384 
runCommandAndPrintOutput(String command)385     protected String runCommandAndPrintOutput(String command) throws DeviceNotAvailableException {
386         final String output = executeShellCommand(command);
387         log(output);
388         return output;
389     }
390 
clearLogcat()391     protected void clearLogcat() throws DeviceNotAvailableException {
392         mDevice.executeAdbCommand("logcat", "-c");
393     }
394 
assertActivityLifecycle(String activityName, boolean relaunched)395     protected void assertActivityLifecycle(String activityName, boolean relaunched)
396             throws DeviceNotAvailableException {
397         final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName);
398 
399         if (relaunched) {
400             if (lifecycleCounts.mDestroyCount < 1) {
401                 fail(activityName + " must have been destroyed. mDestroyCount="
402                         + lifecycleCounts.mDestroyCount);
403             }
404             if (lifecycleCounts.mCreateCount < 1) {
405                 fail(activityName + " must have been (re)created. mCreateCount="
406                         + lifecycleCounts.mCreateCount);
407             }
408         } else {
409             if (lifecycleCounts.mDestroyCount > 0) {
410                 fail(activityName + " must *NOT* have been destroyed. mDestroyCount="
411                         + lifecycleCounts.mDestroyCount);
412             }
413             if (lifecycleCounts.mCreateCount > 0) {
414                 fail(activityName + " must *NOT* have been (re)created. mCreateCount="
415                         + lifecycleCounts.mCreateCount);
416             }
417             if (lifecycleCounts.mConfigurationChangedCount < 1) {
418                 fail(activityName + " must have received configuration changed. "
419                         + "mConfigurationChangedCount="
420                         + lifecycleCounts.mConfigurationChangedCount);
421             }
422         }
423     }
424 
assertRelaunchOrConfigChanged( String activityName, int numRelaunch, int numConfigChange)425     protected void assertRelaunchOrConfigChanged(
426             String activityName, int numRelaunch, int numConfigChange)
427             throws DeviceNotAvailableException {
428         final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName);
429 
430         if (lifecycleCounts.mDestroyCount != numRelaunch) {
431             fail(activityName + " has been destroyed " + lifecycleCounts.mDestroyCount
432                     + " time(s), expecting " + numRelaunch);
433         } else if (lifecycleCounts.mCreateCount != numRelaunch) {
434             fail(activityName + " has been (re)created " + lifecycleCounts.mCreateCount
435                     + " time(s), expecting " + numRelaunch);
436         } else if (lifecycleCounts.mConfigurationChangedCount != numConfigChange) {
437             fail(activityName + " has received " + lifecycleCounts.mConfigurationChangedCount
438                     + " onConfigurationChanged() calls, expecting " + numConfigChange);
439         }
440     }
441 
getDeviceLogsForComponent(String componentName)442     protected String[] getDeviceLogsForComponent(String componentName)
443             throws DeviceNotAvailableException {
444         return mDevice.executeAdbCommand(
445                 "logcat", "-v", "brief", "-d", componentName + ":I", "*:S").split("\\n");
446     }
447 
getDeviceLogsForComponents(final String[] componentNames)448     protected String[] getDeviceLogsForComponents(final String[] componentNames)
449             throws DeviceNotAvailableException {
450         String filters = "";
451         for (int i = 0; i < componentNames.length; i++) {
452             filters += componentNames[i] + ":I ";
453         }
454         return mDevice.executeAdbCommand(
455                 "logcat", "-v", "brief", "-d", filters, "*:S").split("\\n");
456     }
457 
458     private static final Pattern sCreatePattern = Pattern.compile("(.+): onCreate");
459     private static final Pattern sConfigurationChangedPattern =
460             Pattern.compile("(.+): onConfigurationChanged");
461     private static final Pattern sDestroyPattern = Pattern.compile("(.+): onDestroy");
462     private static final Pattern sNewConfigPattern = Pattern.compile(
463             "(.+): config size=\\((\\d+),(\\d+)\\) displaySize=\\((\\d+),(\\d+)\\)" +
464             " metricsSize=\\((\\d+),(\\d+)\\)");
465     private static final Pattern sDisplayStatePattern =
466             Pattern.compile("Display Power: state=(.+)");
467 
468     protected class ReportedSizes {
469         int widthDp;
470         int heightDp;
471         int displayWidth;
472         int displayHeight;
473         int metricsWidth;
474         int metricsHeight;
475     }
476 
getLastReportedSizesForActivity(String activityName)477     protected ReportedSizes getLastReportedSizesForActivity(String activityName)
478             throws DeviceNotAvailableException {
479         final String[] lines = getDeviceLogsForComponent(activityName);
480         for (int i = lines.length - 1; i >= 0; i--) {
481             final String line = lines[i].trim();
482             final Matcher matcher = sNewConfigPattern.matcher(line);
483             if (matcher.matches()) {
484                 ReportedSizes details = new ReportedSizes();
485                 details.widthDp = Integer.parseInt(matcher.group(2));
486                 details.heightDp = Integer.parseInt(matcher.group(3));
487                 details.displayWidth = Integer.parseInt(matcher.group(4));
488                 details.displayHeight = Integer.parseInt(matcher.group(5));
489                 details.metricsWidth = Integer.parseInt(matcher.group(6));
490                 details.metricsHeight = Integer.parseInt(matcher.group(7));
491                 return details;
492             }
493         }
494         return null;
495     }
496 
497     private class ActivityLifecycleCounts {
498         int mCreateCount;
499         int mConfigurationChangedCount;
500         int mDestroyCount;
501 
ActivityLifecycleCounts(String activityName)502         public ActivityLifecycleCounts(String activityName) throws DeviceNotAvailableException {
503             for (String line : getDeviceLogsForComponent(activityName)) {
504                 line = line.trim();
505 
506                 Matcher matcher = sCreatePattern.matcher(line);
507                 if (matcher.matches()) {
508                     mCreateCount++;
509                     continue;
510                 }
511 
512                 matcher = sConfigurationChangedPattern.matcher(line);
513                 if (matcher.matches()) {
514                     mConfigurationChangedCount++;
515                     continue;
516                 }
517 
518                 matcher = sDestroyPattern.matcher(line);
519                 if (matcher.matches()) {
520                     mDestroyCount++;
521                     continue;
522                 }
523             }
524         }
525     }
526 }
527