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.tradefed.device.ITestDevice;
20 
21 import junit.framework.Assert;
22 
23 import android.server.cts.ActivityManagerState.ActivityStack;
24 import android.server.cts.ActivityManagerState.ActivityTask;
25 import android.server.cts.WindowManagerState.WindowStack;
26 import android.server.cts.WindowManagerState.WindowTask;
27 
28 import java.awt.Rectangle;
29 import java.util.ArrayList;
30 import java.util.List;
31 import java.util.Objects;
32 
33 import static android.server.cts.ActivityManagerTestBase.FREEFORM_WORKSPACE_STACK_ID;
34 import static android.server.cts.ActivityManagerTestBase.PINNED_STACK_ID;
35 import static android.server.cts.StateLogger.log;
36 
37 /** Combined state of the activity manager and window manager. */
38 class ActivityAndWindowManagersState extends Assert {
39 
40     // Clone of android DisplayMetrics.DENSITY_DEFAULT (DENSITY_MEDIUM)
41     // (Needed in host-side tests to convert dp to px.)
42     private static final int DISPLAY_DENSITY_DEFAULT = 160;
43 
44     // Default minimal size of resizable task, used if none is set explicitly.
45     // Must be kept in sync with 'default_minimal_size_resizable_task' dimen from frameworks/base.
46     private static final int DEFAULT_RESIZABLE_TASK_SIZE_DP = 220;
47 
48     private ActivityManagerState mAmState = new ActivityManagerState();
49     private WindowManagerState mWmState = new WindowManagerState();
50 
51     private final List<WindowManagerState.WindowState> mTempWindowList = new ArrayList<>();
52 
53     /**
54      * Compute AM and WM state of device, check sanity and bounds.
55      * WM state will include only visible windows, stack and task bounds will be compared.
56      *
57      * @param device test device.
58      * @param waitForActivitiesVisible array of activity names to wait for.
59      */
computeState(ITestDevice device, String[] waitForActivitiesVisible)60     void computeState(ITestDevice device, String[] waitForActivitiesVisible) throws Exception {
61         computeState(device, waitForActivitiesVisible, true);
62     }
63 
64     /**
65      * Compute AM and WM state of device, check sanity and bounds.
66      * WM state will include only visible windows.
67      *
68      * @param device test device.
69      * @param waitForActivitiesVisible array of activity names to wait for.
70      * @param compareTaskAndStackBounds pass 'true' if stack and task bounds should be compared,
71      *                                  'false' otherwise.
72      */
computeState(ITestDevice device, String[] waitForActivitiesVisible, boolean compareTaskAndStackBounds)73     void computeState(ITestDevice device, String[] waitForActivitiesVisible,
74                       boolean compareTaskAndStackBounds) throws Exception {
75         computeState(device, true, waitForActivitiesVisible, compareTaskAndStackBounds);
76     }
77 
78     /**
79      * Compute AM and WM state of device, check sanity and bounds.
80      * Stack and task bounds will be compared.
81      *
82      * @param device test device.
83      * @param visibleOnly pass 'true' to include only visible windows in WM state.
84      * @param waitForActivitiesVisible array of activity names to wait for.
85      */
computeState(ITestDevice device, boolean visibleOnly, String[] waitForActivitiesVisible)86     void computeState(ITestDevice device, boolean visibleOnly, String[] waitForActivitiesVisible)
87             throws Exception {
88         computeState(device, visibleOnly, waitForActivitiesVisible, true);
89     }
90 
91     /**
92      * Compute AM and WM state of device, check sanity and bounds.
93      *
94      * @param device test device.
95      * @param visibleOnly pass 'true' if WM state should include only visible windows.
96      * @param waitForActivitiesVisible array of activity names to wait for.
97      * @param compareTaskAndStackBounds pass 'true' if stack and task bounds should be compared,
98      *                                  'false' otherwise.
99      */
computeState(ITestDevice device, boolean visibleOnly, String[] waitForActivitiesVisible, boolean compareTaskAndStackBounds)100     void computeState(ITestDevice device, boolean visibleOnly, String[] waitForActivitiesVisible,
101                       boolean compareTaskAndStackBounds) throws Exception {
102         waitForValidState(device, visibleOnly, waitForActivitiesVisible, null,
103                 compareTaskAndStackBounds);
104 
105         assertSanity();
106         assertValidBounds(compareTaskAndStackBounds);
107     }
108 
109     /**
110      * Wait for consistent state in AM and WM.
111      *
112      * @param device test device.
113      * @param visibleOnly pass 'true' if WM state should include only visible windows.
114      * @param waitForActivitiesVisible array of activity names to wait for.
115      * @param stackIds ids of stack where provided activities should be found.
116      *                 Pass null to skip this check.
117      */
waitForValidState(ITestDevice device, boolean visibleOnly, String[] waitForActivitiesVisible, int[] stackIds, boolean compareTaskAndStackBounds)118     void waitForValidState(ITestDevice device, boolean visibleOnly,
119                            String[] waitForActivitiesVisible, int[] stackIds,
120                            boolean compareTaskAndStackBounds) throws Exception {
121         int retriesLeft = 5;
122         do {
123             // TODO: Get state of AM and WM at the same time to avoid mismatches caused by
124             // requesting dump in some intermediate state.
125             mAmState.computeState(device);
126             mWmState.computeState(device, visibleOnly);
127             if (shouldWaitForValidStacks(compareTaskAndStackBounds)
128                     || shouldWaitForActivities(waitForActivitiesVisible, stackIds)) {
129                 log("***Waiting for valid stacks and activities states...");
130                 try {
131                     Thread.sleep(1000);
132                 } catch (InterruptedException e) {
133                     log(e.toString());
134                     // Well I guess we are not waiting...
135                 }
136             } else {
137                 break;
138             }
139         } while (retriesLeft-- > 0);
140     }
141 
waitForHomeActivityVisible(ITestDevice device)142     void waitForHomeActivityVisible(ITestDevice device) throws Exception {
143         int retriesLeft = 5;
144         do {
145             mAmState.computeState(device);
146             if (!mAmState.isHomeActivityVisible()) {
147                 log("***Waiting for home activity to be visible...");
148                 try {
149                     Thread.sleep(1000);
150                 } catch (InterruptedException e) {
151                     log(e.toString());
152                     // Well I guess we are not waiting...
153                 }
154             } else {
155                 break;
156             }
157         } while (retriesLeft-- > 0);
158     }
159 
shouldWaitForValidStacks(boolean compareTaskAndStackBounds)160     private boolean shouldWaitForValidStacks(boolean compareTaskAndStackBounds) {
161         if (!taskListsInAmAndWmAreEqual()) {
162             // We want to wait for equal task lists in AM and WM in case we caught them in the
163             // middle of some state change operations.
164             log("***taskListsInAmAndWmAreEqual=false");
165             return true;
166         }
167         if (!stackBoundsInAMAndWMAreEqual()) {
168             // We want to wait a little for the stacks in AM and WM to have equal bounds as there
169             // might be a transition animation ongoing when we got the states from WM AM separately.
170             log("***stackBoundsInAMAndWMAreEqual=false");
171             return true;
172         }
173         try {
174             // Temporary fix to avoid catching intermediate state with different task bounds in AM
175             // and WM.
176             assertValidBounds(compareTaskAndStackBounds);
177         } catch (AssertionError e) {
178             log("***taskBoundsInAMAndWMAreEqual=false : " + e.getMessage());
179             return true;
180         }
181         return false;
182     }
183 
shouldWaitForActivities(String[] waitForActivitiesVisible, int[] stackIds)184     private boolean shouldWaitForActivities(String[] waitForActivitiesVisible, int[] stackIds) {
185         if (waitForActivitiesVisible == null || waitForActivitiesVisible.length == 0) {
186             return false;
187         }
188         // If the caller is interested in us waiting for some particular activity windows to be
189         // visible before compute the state. Check for the visibility of those activity windows
190         // and for placing them in correct stacks (if requested).
191         boolean allActivityWindowsVisible = true;
192         boolean tasksInCorrectStacks = true;
193         List<WindowManagerState.WindowState> matchingWindowStates = new ArrayList<>();
194         for (int i = 0; i < waitForActivitiesVisible.length; i++) {
195             // Check if window is visible - it should be represented as one of the window states.
196             final String windowName =
197                     ActivityManagerTestBase.getWindowName(waitForActivitiesVisible[i]);
198             mWmState.getMatchingWindowState(windowName, matchingWindowStates);
199             boolean activityWindowVisible = !matchingWindowStates.isEmpty();
200             if (!activityWindowVisible) {
201                 log("Activity window not visible: " + waitForActivitiesVisible[i]);
202                 allActivityWindowsVisible = false;
203             } else if (stackIds != null) {
204                 // Check if window is already in stack requested by test.
205                 boolean windowInCorrectStack = false;
206                 for (WindowManagerState.WindowState ws : matchingWindowStates) {
207                     if (ws.getStackId() == stackIds[i]) {
208                         windowInCorrectStack = true;
209                         break;
210                     }
211                 }
212                 if (!windowInCorrectStack) {
213                     log("Window in incorrect stack: " + waitForActivitiesVisible[i]);
214                     tasksInCorrectStacks = false;
215                 }
216             }
217         }
218         return !allActivityWindowsVisible || !tasksInCorrectStacks;
219     }
220 
getAmState()221     ActivityManagerState getAmState() {
222         return mAmState;
223     }
224 
getWmState()225     WindowManagerState getWmState() {
226         return mWmState;
227     }
228 
assertSanity()229     void assertSanity() throws Exception {
230         assertTrue("Must have stacks", mAmState.getStackCount() > 0);
231         assertEquals("There should be one and only one resumed activity in the system.",
232                 1, mAmState.getResumedActivitiesCount());
233         assertNotNull("Must have focus activity.", mAmState.getFocusedActivity());
234 
235         for (ActivityStack aStack : mAmState.getStacks()) {
236             final int stackId = aStack.mStackId;
237             for (ActivityTask aTask : aStack.getTasks()) {
238                 assertEquals("Stack can only contain its own tasks", stackId, aTask.mStackId);
239             }
240         }
241 
242         assertNotNull("Must have front window.", mWmState.getFrontWindow());
243         assertNotNull("Must have focused window.", mWmState.getFocusedWindow());
244         assertNotNull("Must have app.", mWmState.getFocusedApp());
245     }
246 
assertContainsStack(String msg, int stackId)247     void assertContainsStack(String msg, int stackId) throws Exception {
248         assertTrue(msg, mAmState.containsStack(stackId));
249         assertTrue(msg, mWmState.containsStack(stackId));
250     }
251 
assertDoesNotContainStack(String msg, int stackId)252     void assertDoesNotContainStack(String msg, int stackId) throws Exception {
253         assertFalse(msg, mAmState.containsStack(stackId));
254         assertFalse(msg, mWmState.containsStack(stackId));
255     }
256 
assertFrontStack(String msg, int stackId)257     void assertFrontStack(String msg, int stackId) throws Exception {
258         assertEquals(msg, stackId, mAmState.getFrontStackId());
259         assertEquals(msg, stackId, mWmState.getFrontStackId());
260     }
261 
assertFocusedStack(String msg, int stackId)262     void assertFocusedStack(String msg, int stackId) throws Exception {
263         assertEquals(msg, stackId, mAmState.getFocusedStackId());
264     }
265 
assertNotFocusedStack(String msg, int stackId)266     void assertNotFocusedStack(String msg, int stackId) throws Exception {
267         if (stackId == mAmState.getFocusedStackId()) {
268             failNotEquals(msg, stackId, mAmState.getFocusedStackId());
269         }
270     }
271 
assertFocusedActivity(String msg, String activityName)272     void assertFocusedActivity(String msg, String activityName) throws Exception {
273         final String componentName = ActivityManagerTestBase.getActivityComponentName(activityName);
274         assertEquals(msg, componentName, mAmState.getFocusedActivity());
275         assertEquals(msg, componentName, mWmState.getFocusedApp());
276     }
277 
assertNotFocusedActivity(String msg, String activityName)278     void assertNotFocusedActivity(String msg, String activityName) throws Exception {
279         final String componentName = ActivityManagerTestBase.getActivityComponentName(activityName);
280         if (mAmState.getFocusedActivity().equals(componentName)) {
281             failNotEquals(msg, mAmState.getFocusedActivity(), componentName);
282         }
283         if (mWmState.getFocusedApp().equals(componentName)) {
284             failNotEquals(msg, mWmState.getFocusedApp(), componentName);
285         }
286     }
287 
assertResumedActivity(String msg, String activityName)288     void assertResumedActivity(String msg, String activityName) throws Exception {
289         final String componentName = ActivityManagerTestBase.getActivityComponentName(activityName);
290         assertEquals(msg, componentName, mAmState.getResumedActivity());
291     }
292 
assertNotResumedActivity(String msg, String activityName)293     void assertNotResumedActivity(String msg, String activityName) throws Exception {
294         final String componentName = ActivityManagerTestBase.getActivityComponentName(activityName);
295         if (mAmState.getResumedActivity().equals(componentName)) {
296             failNotEquals(msg, mAmState.getResumedActivity(), componentName);
297         }
298     }
299 
assertFocusedWindow(String msg, String windowName)300     void assertFocusedWindow(String msg, String windowName) {
301         assertEquals(msg, windowName, mWmState.getFocusedWindow());
302     }
303 
assertNotFocusedWindow(String msg, String windowName)304     void assertNotFocusedWindow(String msg, String windowName) {
305         if (mWmState.getFocusedWindow().equals(windowName)) {
306             failNotEquals(msg, mWmState.getFocusedWindow(), windowName);
307         }
308     }
309 
assertFrontWindow(String msg, String windowName)310     void assertFrontWindow(String msg, String windowName) {
311         assertEquals(msg, windowName, mWmState.getFrontWindow());
312     }
313 
assertVisibility(String activityName, boolean visible)314     void assertVisibility(String activityName, boolean visible) {
315         final String activityComponentName =
316                 ActivityManagerTestBase.getActivityComponentName(activityName);
317         final String windowName =
318                 ActivityManagerTestBase.getWindowName(activityName);
319 
320         final boolean activityVisible = mAmState.isActivityVisible(activityComponentName);
321         final boolean windowVisible = mWmState.isWindowVisible(windowName);
322 
323         if (visible) {
324             assertTrue("Activity=" + activityComponentName + " must be visible.", activityVisible);
325             assertTrue("Window=" + windowName + " must be visible.", windowVisible);
326         } else {
327             assertFalse("Activity=" + activityComponentName + " must NOT be visible.",
328                     activityVisible);
329             assertFalse("Window=" + windowName + " must NOT be visible.", windowVisible);
330         }
331     }
332 
assertHomeActivityVisible(boolean visible)333     void assertHomeActivityVisible(boolean visible) {
334         final boolean activityVisible = mAmState.isHomeActivityVisible();
335 
336         if (visible) {
337             assertTrue("Home activity must be visible.", activityVisible);
338         } else {
339             assertFalse("Home activity must NOT be visible.", activityVisible);
340         }
341     }
342 
taskListsInAmAndWmAreEqual()343     boolean taskListsInAmAndWmAreEqual() {
344         for (ActivityStack aStack : mAmState.getStacks()) {
345             final int stackId = aStack.mStackId;
346             final WindowStack wStack = mWmState.getStack(stackId);
347             if (wStack == null) {
348                 log("Waiting for stack setup in WM, stackId=" + stackId);
349                 return false;
350             }
351 
352             for (ActivityTask aTask : aStack.getTasks()) {
353                 if (wStack.getTask(aTask.mTaskId) == null) {
354                     log("Task is in AM but not in WM, waiting for it to settle, taskId="
355                             + aTask.mTaskId);
356                     return false;
357                 }
358             }
359 
360             for (WindowTask wTask : wStack.mTasks) {
361                 if (aStack.getTask(wTask.mTaskId) == null) {
362                     log("Task is in WM but not in AM, waiting for it to settle, taskId="
363                             + wTask.mTaskId);
364                     return false;
365                 }
366             }
367         }
368         return true;
369     }
370 
stackBoundsInAMAndWMAreEqual()371     boolean stackBoundsInAMAndWMAreEqual() {
372         for (ActivityStack aStack : mAmState.getStacks()) {
373             final int stackId = aStack.mStackId;
374             final WindowStack wStack = mWmState.getStack(stackId);
375             if (aStack.isFullscreen() != wStack.isFullscreen()) {
376                 log("Waiting for correct fullscreen state, stackId=" + stackId);
377                 return false;
378             }
379 
380             final Rectangle aStackBounds = aStack.getBounds();
381             final Rectangle wStackBounds = wStack.getBounds();
382 
383             if (aStack.isFullscreen()) {
384                 if (aStackBounds != null) {
385                     log("Waiting for correct stack state in AM, stackId=" + stackId);
386                     return false;
387                 }
388             } else if (!Objects.equals(aStackBounds, wStackBounds)) {
389                 // If stack is not fullscreen - comparing bounds. Not doing it always because
390                 // for fullscreen stack bounds in WM can be either null or equal to display size.
391                 log("Waiting for stack bound equality in AM and WM, stackId=" + stackId);
392                 return false;
393             }
394         }
395 
396         return true;
397     }
398 
399     /** Check task bounds when docked to top/left. */
assertDockedTaskBounds(int taskSize, String activityName)400     void assertDockedTaskBounds(int taskSize, String activityName) {
401         // Task size can be affected by default minimal size.
402         int defaultMinimalTaskSize = defaultMinimalTaskSize(
403                 mAmState.getStackById(ActivityManagerTestBase.DOCKED_STACK_ID).mDisplayId);
404         int targetSize = Math.max(taskSize, defaultMinimalTaskSize);
405 
406         assertEquals(new Rectangle(0, 0, targetSize, targetSize),
407                 mAmState.getTaskByActivityName(activityName).getBounds());
408     }
409 
assertValidBounds(boolean compareTaskAndStackBounds)410     void assertValidBounds(boolean compareTaskAndStackBounds) {
411         for (ActivityStack aStack : mAmState.getStacks()) {
412             final int stackId = aStack.mStackId;
413             final WindowStack wStack = mWmState.getStack(stackId);
414             assertNotNull("stackId=" + stackId + " in AM but not in WM?", wStack);
415 
416             assertEquals("Stack fullscreen state in AM and WM must be equal stackId=" + stackId,
417                     aStack.isFullscreen(), wStack.isFullscreen());
418 
419             final Rectangle aStackBounds = aStack.getBounds();
420             final Rectangle wStackBounds = wStack.getBounds();
421 
422             if (aStack.isFullscreen()) {
423                 assertNull("Stack bounds in AM must be null stackId=" + stackId, aStackBounds);
424             } else {
425                 assertEquals("Stack bounds in AM and WM must be equal stackId=" + stackId,
426                         aStackBounds, wStackBounds);
427             }
428 
429             for (ActivityTask aTask : aStack.getTasks()) {
430                 final int taskId = aTask.mTaskId;
431                 final WindowTask wTask = wStack.getTask(taskId);
432                 assertNotNull(
433                         "taskId=" + taskId + " in AM but not in WM? stackId=" + stackId, wTask);
434 
435                 final boolean aTaskIsFullscreen = aTask.isFullscreen();
436                 final boolean wTaskIsFullscreen = wTask.isFullscreen();
437                 assertEquals("Task fullscreen state in AM and WM must be equal taskId=" + taskId
438                         + ", stackId=" + stackId, aTaskIsFullscreen, wTaskIsFullscreen);
439 
440                 final Rectangle aTaskBounds = aTask.getBounds();
441                 final Rectangle wTaskBounds = wTask.getBounds();
442 
443                 if (aTaskIsFullscreen) {
444                     assertNull("Task bounds in AM must be null for fullscreen taskId=" + taskId,
445                             aTaskBounds);
446                 } else {
447                     assertEquals("Task bounds in AM and WM must be equal taskId=" + taskId
448                             + ", stackId=" + stackId, aTaskBounds, wTaskBounds);
449 
450                     if (compareTaskAndStackBounds && stackId != FREEFORM_WORKSPACE_STACK_ID) {
451                         int aTaskMinWidth = aTask.getMinWidth();
452                         int aTaskMinHeight = aTask.getMinHeight();
453 
454                         if (aTaskMinWidth == -1 || aTaskMinHeight == -1) {
455                             // Minimal dimension(s) not set for task - it should be using defaults.
456                             int defaultMinimalSize = defaultMinimalTaskSize(aStack.mDisplayId);
457 
458                             if (aTaskMinWidth == -1) {
459                                 aTaskMinWidth = defaultMinimalSize;
460                             }
461                             if (aTaskMinHeight == -1) {
462                                 aTaskMinHeight = defaultMinimalSize;
463                             }
464                         }
465 
466                         if (aStackBounds.getWidth() >= aTaskMinWidth
467                                 && aStackBounds.getHeight() >= aTaskMinHeight
468                                 || stackId == PINNED_STACK_ID) {
469                             // Bounds are not smaller then minimal possible, so stack and task
470                             // bounds must be equal.
471                             assertEquals("Task bounds must be equal to stack bounds taskId="
472                                     + taskId + ", stackId=" + stackId, aStackBounds, wTaskBounds);
473                         } else {
474                             // Minimal dimensions affect task size, so bounds of task and stack must
475                             // be different - will compare dimensions instead.
476                             int targetWidth = (int) Math.max(aTaskMinWidth,
477                                     aStackBounds.getWidth());
478                             assertEquals("Task width must be set according to minimal width"
479                                             + " taskId=" + taskId + ", stackId=" + stackId,
480                                     targetWidth, (int) wTaskBounds.getWidth());
481                             int targetHeight = (int) Math.max(aTaskMinHeight,
482                                     aStackBounds.getHeight());
483                             assertEquals("Task height must be set according to minimal height"
484                                             + " taskId=" + taskId + ", stackId=" + stackId,
485                                     targetHeight, (int) wTaskBounds.getHeight());
486                         }
487                     }
488                 }
489             }
490         }
491     }
492 
dpToPx(float dp, int densityDpi)493     static int dpToPx(float dp, int densityDpi){
494         return (int) (dp * densityDpi / DISPLAY_DENSITY_DEFAULT + 0.5f);
495     }
496 
defaultMinimalTaskSize(int displayId)497     int defaultMinimalTaskSize(int displayId) {
498         return dpToPx(DEFAULT_RESIZABLE_TASK_SIZE_DP, mWmState.getDisplay(displayId).getDpi());
499     }
500 }
501