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     private static final String AM_RESIZE_DOCKED_STACK = "am stack resize-docked-stack ";
74 
75     private static final String AM_MOVE_TASK = "am stack movetask ";
76 
77     /** A reference to the device under test. */
78     protected ITestDevice mDevice;
79 
80     private HashSet<String> mAvailableFeatures;
81 
getAmStartCmd(final String activityName)82     protected static String getAmStartCmd(final String activityName) {
83         return "am start -n " + getActivityComponentName(activityName);
84     }
85 
getAmStartCmdOverHome(final String activityName)86     protected static String getAmStartCmdOverHome(final String activityName) {
87         return "am start --activity-task-on-home -n " + getActivityComponentName(activityName);
88     }
89 
getActivityComponentName(final String activityName)90     static String getActivityComponentName(final String activityName) {
91         return "android.server.app/." + activityName;
92     }
93 
getWindowName(final String activityName)94     static String getWindowName(final String activityName) {
95         return "android.server.app/android.server.app." + activityName;
96     }
97 
98     protected ActivityAndWindowManagersState mAmWmState = new ActivityAndWindowManagersState();
99 
100     private int mInitialAccelerometerRotation;
101     private int mUserRotation;
102 
103     @Override
setUp()104     protected void setUp() throws Exception {
105         super.setUp();
106 
107         // Get the device, this gives a handle to run commands and install APKs.
108         mDevice = getDevice();
109         unlockDevice();
110         // Remove special stacks.
111         executeShellCommand(AM_REMOVE_STACK + PINNED_STACK_ID);
112         executeShellCommand(AM_REMOVE_STACK + DOCKED_STACK_ID);
113         executeShellCommand(AM_REMOVE_STACK + FREEFORM_WORKSPACE_STACK_ID);
114         // Store rotation settings.
115         mInitialAccelerometerRotation = getAccelerometerRotation();
116         mUserRotation = getUserRotation();
117     }
118 
119     @Override
tearDown()120     protected void tearDown() throws Exception {
121         super.tearDown();
122         try {
123             unlockDevice();
124             executeShellCommand(AM_FORCE_STOP_TEST_PACKAGE);
125             // Restore rotation settings to the state they were before test.
126             setAccelerometerRotation(mInitialAccelerometerRotation);
127             setUserRotation(mUserRotation);
128             // Remove special stacks.
129             executeShellCommand(AM_REMOVE_STACK + PINNED_STACK_ID);
130             executeShellCommand(AM_REMOVE_STACK + DOCKED_STACK_ID);
131             executeShellCommand(AM_REMOVE_STACK + FREEFORM_WORKSPACE_STACK_ID);
132         } catch (DeviceNotAvailableException e) {
133         }
134     }
135 
executeShellCommand(String command)136     protected String executeShellCommand(String command) throws DeviceNotAvailableException {
137         log("adb shell " + command);
138         return mDevice.executeShellCommand(command);
139     }
140 
executeShellCommand(String command, CollectingOutputReceiver outputReceiver)141     protected void executeShellCommand(String command, CollectingOutputReceiver outputReceiver)
142             throws DeviceNotAvailableException {
143         log("adb shell " + command);
144         mDevice.executeShellCommand(command, outputReceiver);
145     }
146 
launchActivityInStack(String activityName, int stackId)147     protected void launchActivityInStack(String activityName, int stackId) throws Exception {
148         executeShellCommand(getAmStartCmd(activityName) + " --stack " + stackId);
149     }
150 
launchActivityInDockStack(String activityName)151     protected void launchActivityInDockStack(String activityName) throws Exception {
152         executeShellCommand(getAmStartCmd(activityName));
153         moveActivityToDockStack(activityName);
154     }
155 
moveActivityToDockStack(String activityName)156     protected void moveActivityToDockStack(String activityName) throws Exception {
157         moveActivityToStack(activityName, DOCKED_STACK_ID);
158     }
159 
moveActivityToStack(String activityName, int stackId)160     protected void moveActivityToStack(String activityName, int stackId) throws Exception {
161         final int taskId = getActivityTaskId(activityName);
162         final String cmd = AM_MOVE_TASK + taskId + " " + stackId + " true";
163         executeShellCommand(cmd);
164     }
165 
resizeActivityTask(String activityName, int left, int top, int right, int bottom)166     protected void resizeActivityTask(String activityName, int left, int top, int right, int bottom)
167             throws Exception {
168         final int taskId = getActivityTaskId(activityName);
169         final String cmd = "am task resize "
170                 + taskId + " " + left + " " + top + " " + right + " " + bottom;
171         executeShellCommand(cmd);
172     }
173 
resizeDockedStack( int stackWidth, int stackHeight, int taskWidth, int taskHeight)174     protected void resizeDockedStack(
175             int stackWidth, int stackHeight, int taskWidth, int taskHeight)
176                     throws DeviceNotAvailableException {
177         executeShellCommand(AM_RESIZE_DOCKED_STACK
178                 + "0 0 " + stackWidth + " " + stackHeight
179                 + " 0 0 " + taskWidth + " " + taskHeight);
180     }
181 
182     // Utility method for debugging, not used directly here, but useful, so kept around.
printStacksAndTasks()183     protected void printStacksAndTasks() throws DeviceNotAvailableException {
184         CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
185         executeShellCommand(AM_STACK_LIST, outputReceiver);
186         String output = outputReceiver.getOutput();
187         for (String line : output.split("\\n")) {
188             CLog.logAndDisplay(LogLevel.INFO, line);
189         }
190     }
191 
getActivityTaskId(String name)192     protected int getActivityTaskId(String name) throws DeviceNotAvailableException {
193         CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
194         executeShellCommand(AM_STACK_LIST, outputReceiver);
195         final String output = outputReceiver.getOutput();
196         final Pattern activityPattern = Pattern.compile("(.*) " + getWindowName(name) + " (.*)");
197         for (String line : output.split("\\n")) {
198             Matcher matcher = activityPattern.matcher(line);
199             if (matcher.matches()) {
200                 for (String word : line.split("\\s+")) {
201                     if (word.startsWith(TASK_ID_PREFIX)) {
202                         final String withColon = word.split("=")[1];
203                         return Integer.parseInt(withColon.substring(0, withColon.length() - 1));
204                     }
205                 }
206             }
207         }
208         return -1;
209     }
210 
supportsPip()211     protected boolean supportsPip() throws DeviceNotAvailableException {
212         return hasDeviceFeature("android.software.picture_in_picture")
213                 || PRETEND_DEVICE_SUPPORTS_PIP;
214     }
215 
supportsFreeform()216     protected boolean supportsFreeform() throws DeviceNotAvailableException {
217         return hasDeviceFeature("android.software.freeform_window_management")
218                 || PRETEND_DEVICE_SUPPORTS_FREEFORM;
219     }
220 
hasDeviceFeature(String requiredFeature)221     protected boolean hasDeviceFeature(String requiredFeature) throws DeviceNotAvailableException {
222         if (mAvailableFeatures == null) {
223             // TODO: Move this logic to ITestDevice.
224             final String output = runCommandAndPrintOutput("pm list features");
225 
226             // Extract the id of the new user.
227             mAvailableFeatures = new HashSet<>();
228             for (String feature: output.split("\\s+")) {
229                 // Each line in the output of the command has the format "feature:{FEATURE_VALUE}".
230                 String[] tokens = feature.split(":");
231                 assertTrue("\"" + feature + "\" expected to have format feature:{FEATURE_VALUE}",
232                         tokens.length > 1);
233                 assertEquals(feature, "feature", tokens[0]);
234                 mAvailableFeatures.add(tokens[1]);
235             }
236         }
237         boolean result = mAvailableFeatures.contains(requiredFeature);
238         if (!result) {
239             CLog.logAndDisplay(LogLevel.INFO, "Device doesn't support " + requiredFeature);
240         }
241         return result;
242     }
243 
isDisplayOn()244     private boolean isDisplayOn() throws DeviceNotAvailableException {
245         final CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
246         mDevice.executeShellCommand("dumpsys power", outputReceiver);
247 
248         for (String line : outputReceiver.getOutput().split("\\n")) {
249             line = line.trim();
250 
251             final Matcher matcher = sDisplayStatePattern.matcher(line);
252             if (matcher.matches()) {
253                 final String state = matcher.group(1);
254                 log("power state=" + state);
255                 return "ON".equals(state);
256             }
257         }
258         log("power state :(");
259         return false;
260     }
261 
lockDevice()262     protected void lockDevice() throws DeviceNotAvailableException {
263         int retriesLeft = 5;
264         runCommandAndPrintOutput("input keyevent 26");
265         do {
266             if (isDisplayOn()) {
267                 log("***Waiting for display to turn off...");
268                 try {
269                     Thread.sleep(1000);
270                 } catch (InterruptedException e) {
271                     log(e.toString());
272                     // Well I guess we are not waiting...
273                 }
274             } else {
275                 break;
276             }
277         } while (retriesLeft-- > 0);
278     }
279 
unlockDevice()280     protected void unlockDevice() throws DeviceNotAvailableException {
281         if (!isDisplayOn()) {
282             runCommandAndPrintOutput("input keyevent 224");
283             runCommandAndPrintOutput("input keyevent 82");
284         }
285     }
286 
setDeviceRotation(int rotation)287     protected void setDeviceRotation(int rotation) throws DeviceNotAvailableException {
288         setAccelerometerRotation(0);
289         setUserRotation(rotation);
290     }
291 
getAccelerometerRotation()292     private int getAccelerometerRotation() throws DeviceNotAvailableException {
293         final String rotation =
294                 runCommandAndPrintOutput("settings get system accelerometer_rotation");
295         return Integer.parseInt(rotation.trim());
296     }
297 
setAccelerometerRotation(int rotation)298     private void setAccelerometerRotation(int rotation) throws DeviceNotAvailableException {
299         runCommandAndPrintOutput(
300                 "settings put system accelerometer_rotation " + rotation);
301     }
302 
getUserRotation()303     private int getUserRotation() throws DeviceNotAvailableException {
304         final String rotation =
305                 runCommandAndPrintOutput("settings get system user_rotation").trim();
306         if ("null".equals(rotation)) {
307             return -1;
308         }
309         return Integer.parseInt(rotation);
310     }
311 
setUserRotation(int rotation)312     private void setUserRotation(int rotation) throws DeviceNotAvailableException {
313         if (rotation == -1) {
314             runCommandAndPrintOutput(
315                     "settings delete system user_rotation");
316         } else {
317             runCommandAndPrintOutput(
318                     "settings put system user_rotation " + rotation);
319         }
320     }
321 
runCommandAndPrintOutput(String command)322     protected String runCommandAndPrintOutput(String command) throws DeviceNotAvailableException {
323         final String output = executeShellCommand(command);
324         log(output);
325         return output;
326     }
327 
clearLogcat()328     protected void clearLogcat() throws DeviceNotAvailableException {
329         mDevice.executeAdbCommand("logcat", "-c");
330     }
331 
assertActivityLifecycle(String activityName, boolean relaunched)332     protected void assertActivityLifecycle(String activityName, boolean relaunched)
333             throws DeviceNotAvailableException {
334         final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName);
335 
336         if (relaunched) {
337             if (lifecycleCounts.mDestroyCount < 1) {
338                 fail(activityName + " must have been destroyed. mDestroyCount="
339                         + lifecycleCounts.mDestroyCount);
340             }
341             if (lifecycleCounts.mCreateCount < 1) {
342                 fail(activityName + " must have been (re)created. mCreateCount="
343                         + lifecycleCounts.mCreateCount);
344             }
345         } else {
346             if (lifecycleCounts.mDestroyCount > 0) {
347                 fail(activityName + " must *NOT* have been destroyed. mDestroyCount="
348                         + lifecycleCounts.mDestroyCount);
349             }
350             if (lifecycleCounts.mCreateCount > 0) {
351                 fail(activityName + " must *NOT* have been (re)created. mCreateCount="
352                         + lifecycleCounts.mCreateCount);
353             }
354             if (lifecycleCounts.mConfigurationChangedCount < 1) {
355                 fail(activityName + " must have received configuration changed. "
356                         + "mConfigurationChangedCount="
357                         + lifecycleCounts.mConfigurationChangedCount);
358             }
359         }
360     }
361 
getDeviceLogsForActivity(String activityName)362     private String[] getDeviceLogsForActivity(String activityName)
363             throws DeviceNotAvailableException {
364         return mDevice.executeAdbCommand("logcat", "-v", "brief", "-d", activityName + ":I", "*:S")
365                 .split("\\n");
366     }
367 
368     private static final Pattern sCreatePattern = Pattern.compile("(.+): onCreate");
369     private static final Pattern sConfigurationChangedPattern =
370             Pattern.compile("(.+): onConfigurationChanged");
371     private static final Pattern sDestroyPattern = Pattern.compile("(.+): onDestroy");
372     private static final Pattern sNewConfigPattern = Pattern.compile(
373             "(.+): config size=\\((\\d+),(\\d+)\\) displaySize=\\((\\d+),(\\d+)\\)" +
374             " metricsSize=\\((\\d+),(\\d+)\\)");
375     private static final Pattern sDisplayStatePattern =
376             Pattern.compile("Display Power: state=(.+)");
377 
378     protected class ReportedSizes {
379         int widthDp;
380         int heightDp;
381         int displayWidth;
382         int displayHeight;
383         int metricsWidth;
384         int metricsHeight;
385     }
386 
getLastReportedSizesForActivity(String activityName)387     protected ReportedSizes getLastReportedSizesForActivity(String activityName)
388             throws DeviceNotAvailableException {
389         final String[] lines = getDeviceLogsForActivity(activityName);
390         for (int i = lines.length - 1; i >= 0; i--) {
391             final String line = lines[i].trim();
392             final Matcher matcher = sNewConfigPattern.matcher(line);
393             if (matcher.matches()) {
394                 ReportedSizes details = new ReportedSizes();
395                 details.widthDp = Integer.parseInt(matcher.group(2));
396                 details.heightDp = Integer.parseInt(matcher.group(3));
397                 details.displayWidth = Integer.parseInt(matcher.group(4));
398                 details.displayHeight = Integer.parseInt(matcher.group(5));
399                 details.metricsWidth = Integer.parseInt(matcher.group(6));
400                 details.metricsHeight = Integer.parseInt(matcher.group(7));
401                 return details;
402             }
403         }
404         return null;
405     }
406 
407     private class ActivityLifecycleCounts {
408         int mCreateCount;
409         int mConfigurationChangedCount;
410         int mDestroyCount;
411 
ActivityLifecycleCounts(String activityName)412         public ActivityLifecycleCounts(String activityName) throws DeviceNotAvailableException {
413             for (String line : getDeviceLogsForActivity(activityName)) {
414                 line = line.trim();
415 
416                 Matcher matcher = sCreatePattern.matcher(line);
417                 if (matcher.matches()) {
418                     mCreateCount++;
419                     continue;
420                 }
421 
422                 matcher = sConfigurationChangedPattern.matcher(line);
423                 if (matcher.matches()) {
424                     mConfigurationChangedCount++;
425                     continue;
426                 }
427 
428                 matcher = sDestroyPattern.matcher(line);
429                 if (matcher.matches()) {
430                     mDestroyCount++;
431                     continue;
432                 }
433             }
434         }
435     }
436 }
437