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
17package android.server.cts;
18
19import com.android.tradefed.device.ITestDevice;
20
21import junit.framework.Assert;
22
23import android.server.cts.ActivityManagerState.ActivityStack;
24import android.server.cts.ActivityManagerState.ActivityTask;
25import android.server.cts.WindowManagerState.WindowStack;
26import android.server.cts.WindowManagerState.WindowTask;
27
28import java.awt.Rectangle;
29import java.util.ArrayList;
30import java.util.List;
31import java.util.Objects;
32
33import static android.server.cts.ActivityManagerTestBase.FREEFORM_WORKSPACE_STACK_ID;
34import static android.server.cts.ActivityManagerTestBase.PINNED_STACK_ID;
35import static android.server.cts.StateLogger.log;
36
37/** Combined state of the activity manager and window manager. */
38class 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     */
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     */
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     */
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     */
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     */
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
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
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
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
221    ActivityManagerState getAmState() {
222        return mAmState;
223    }
224
225    WindowManagerState getWmState() {
226        return mWmState;
227    }
228
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
247    void assertContainsStack(String msg, int stackId) throws Exception {
248        assertTrue(msg, mAmState.containsStack(stackId));
249        assertTrue(msg, mWmState.containsStack(stackId));
250    }
251
252    void assertDoesNotContainStack(String msg, int stackId) throws Exception {
253        assertFalse(msg, mAmState.containsStack(stackId));
254        assertFalse(msg, mWmState.containsStack(stackId));
255    }
256
257    void assertFrontStack(String msg, int stackId) throws Exception {
258        assertEquals(msg, stackId, mAmState.getFrontStackId());
259        assertEquals(msg, stackId, mWmState.getFrontStackId());
260    }
261
262    void assertFocusedStack(String msg, int stackId) throws Exception {
263        assertEquals(msg, stackId, mAmState.getFocusedStackId());
264    }
265
266    void assertNotFocusedStack(String msg, int stackId) throws Exception {
267        if (stackId == mAmState.getFocusedStackId()) {
268            failNotEquals(msg, stackId, mAmState.getFocusedStackId());
269        }
270    }
271
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
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
288    void assertResumedActivity(String msg, String activityName) throws Exception {
289        final String componentName = ActivityManagerTestBase.getActivityComponentName(activityName);
290        assertEquals(msg, componentName, mAmState.getResumedActivity());
291    }
292
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
300    void assertFocusedWindow(String msg, String windowName) {
301        assertEquals(msg, windowName, mWmState.getFocusedWindow());
302    }
303
304    void assertNotFocusedWindow(String msg, String windowName) {
305        if (mWmState.getFocusedWindow().equals(windowName)) {
306            failNotEquals(msg, mWmState.getFocusedWindow(), windowName);
307        }
308    }
309
310    void assertFrontWindow(String msg, String windowName) {
311        assertEquals(msg, windowName, mWmState.getFrontWindow());
312    }
313
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
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
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
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. */
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
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
493    static int dpToPx(float dp, int densityDpi){
494        return (int) (dp * densityDpi / DISPLAY_DENSITY_DEFAULT + 0.5f);
495    }
496
497    int defaultMinimalTaskSize(int displayId) {
498        return dpToPx(DEFAULT_RESIZABLE_TASK_SIZE_DP, mWmState.getDisplay(displayId).getDpi());
499    }
500}
501