• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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 com.android.quickstep;
18 
19 import static com.android.launcher3.Flags.enableLauncherOverviewInWindow;
20 import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
21 import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
22 import static com.android.quickstep.TaskbarModeSwitchRule.Mode.TRANSIENT;
23 
24 import static org.junit.Assert.assertEquals;
25 import static org.junit.Assert.assertFalse;
26 import static org.junit.Assert.assertNotEquals;
27 import static org.junit.Assert.assertNotNull;
28 import static org.junit.Assert.assertTrue;
29 import static org.junit.Assume.assumeFalse;
30 import static org.junit.Assume.assumeTrue;
31 
32 import android.content.Intent;
33 import android.content.res.Configuration;
34 
35 import androidx.annotation.NonNull;
36 import androidx.test.filters.LargeTest;
37 import androidx.test.platform.app.InstrumentationRegistry;
38 import androidx.test.runner.AndroidJUnit4;
39 import androidx.test.uiautomator.By;
40 import androidx.test.uiautomator.Until;
41 
42 import com.android.launcher3.LauncherState;
43 import com.android.launcher3.tapl.BaseOverview;
44 import com.android.launcher3.tapl.LaunchedAppState;
45 import com.android.launcher3.tapl.LauncherInstrumentation.NavigationModel;
46 import com.android.launcher3.tapl.Overview;
47 import com.android.launcher3.tapl.OverviewActions;
48 import com.android.launcher3.tapl.OverviewTask;
49 import com.android.launcher3.tapl.SelectModeButtons;
50 import com.android.launcher3.tapl.Workspace;
51 import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
52 import com.android.launcher3.util.TestUtil;
53 import com.android.launcher3.util.Wait;
54 import com.android.launcher3.util.rule.ScreenRecordRule;
55 import com.android.launcher3.util.rule.TestStabilityRule;
56 import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
57 import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
58 import com.android.quickstep.fallback.RecentsState;
59 import com.android.quickstep.views.RecentsView;
60 
61 import org.junit.After;
62 import org.junit.Before;
63 import org.junit.Ignore;
64 import org.junit.Test;
65 import org.junit.runner.RunWith;
66 
67 import java.util.Comparator;
68 import java.util.function.Consumer;
69 import java.util.function.Function;
70 
71 @LargeTest
72 @RunWith(AndroidJUnit4.class)
73 public class TaplTestsQuickstep extends AbstractQuickStepTest {
74 
75     private static final String CALCULATOR_APP_PACKAGE =
76             resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR);
77     private static final String READ_DEVICE_CONFIG_PERMISSION =
78             "android.permission.READ_DEVICE_CONFIG";
79 
80     private enum ExpectedState {
81 
82         HOME(LauncherState.NORMAL, RecentsState.HOME),
83         OVERVIEW(LauncherState.OVERVIEW, RecentsState.DEFAULT);
84 
85         private final LauncherState mLauncherState;
86         private final RecentsState mRecentsState;
87 
ExpectedState(LauncherState launcherState, RecentsState recentsState)88         ExpectedState(LauncherState launcherState, RecentsState recentsState) {
89             this.mLauncherState = launcherState;
90             this.mRecentsState = recentsState;
91         }
92     }
93 
94     @Before
setUp()95     public void setUp() throws Exception {
96         super.setUp();
97         runOnRecentsView(recentsView ->
98                 recentsView.getPagedViewOrientedState().forceAllowRotationForTesting(true));
99     }
100 
101     @After
tearDown()102     public void tearDown() {
103         runOnRecentsView(recentsView ->
104                 recentsView.getPagedViewOrientedState().forceAllowRotationForTesting(false),
105                 /* forTearDown= */ true);
106     }
107 
startTestApps()108     public static void startTestApps() throws Exception {
109         startAppFast(getAppPackageName());
110         startAppFast(CALCULATOR_APP_PACKAGE);
111         startTestActivity(2);
112     }
113 
114     @Test
115     @NavigationModeSwitch
116     @PortraitLandscape
testWorkspaceSwitchToAllApps()117     public void testWorkspaceSwitchToAllApps() {
118         assertNotNull("switchToAllApps() returned null",
119                 mLauncher.getWorkspace().switchToAllApps());
120         assertTrue("Launcher internal state is not All Apps",
121                 isInState(() -> LauncherState.ALL_APPS));
122     }
123 
124     @Test
125     @PortraitLandscape
testOverview()126     public void testOverview() throws Exception {
127         startTestAppsWithCheck();
128         // mLauncher.pressHome() also tests an important case of pressing home while in background.
129         Overview overview = mLauncher.goHome().switchToOverview();
130         assertIsInState(
131                 "Launcher internal state didn't switch to Overview", ExpectedState.OVERVIEW);
132         runOnRecentsView(recentsView -> assertTrue("Don't have at least 3 tasks",
133                 recentsView.getTaskViewCount() >= 3));
134 
135         // Test flinging forward and backward.
136         runOnRecentsView(recentsView -> assertEquals("Current task in Overview is not 0",
137                 0, recentsView.getCurrentPage()));
138 
139         overview.flingForward();
140         assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW);
141         final Integer currentTaskAfterFlingForward =
142                 getFromRecentsView(RecentsView::getCurrentPage);
143         runOnRecentsView(recentsView -> assertTrue("Current task in Overview is still 0",
144                 currentTaskAfterFlingForward > 0));
145 
146         overview.flingBackward();
147         assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW);
148         runOnRecentsView(recentsView -> assertTrue("Flinging back in Overview did nothing",
149                 recentsView.getCurrentPage() < currentTaskAfterFlingForward));
150 
151         // Test opening a task.
152         OverviewTask task = mLauncher.goHome().switchToOverview().getCurrentTask();
153         assertNotNull("overview.getCurrentTask() returned null (1)", task);
154         assertNotNull("OverviewTask.open returned null", task.open());
155         assertTrue("Test activity didn't open from Overview", mDevice.wait(Until.hasObject(
156                         By.pkg(getAppPackageName()).text("TestActivity2")),
157                 TestUtil.DEFAULT_UI_TIMEOUT));
158         expectLaunchedAppState();
159 
160         // Test dismissing a task.
161         overview = mLauncher.goHome().switchToOverview();
162         assertIsInState("Launcher internal state didn't switch to Overview",
163                 ExpectedState.OVERVIEW);
164         final Integer numTasks = getFromRecentsView(RecentsView::getTaskViewCount);
165         task = overview.getCurrentTask();
166         assertNotNull("overview.getCurrentTask() returned null (2)", task);
167         task.dismiss();
168         runOnRecentsView(recentsView -> assertEquals(
169                 "Dismissing a task didn't remove 1 task from Overview",
170                 numTasks - 1, recentsView.getTaskViewCount()));
171 
172         // Test dismissing all tasks.
173         mLauncher.goHome().switchToOverview().dismissAllTasks();
174         assertIsInState("Launcher internal state is not Home", ExpectedState.HOME);
175         runOnRecentsView(recentsView -> assertEquals("Still have tasks after dismissing all",
176                 0, recentsView.getTaskViewCount()));
177     }
178 
179     /**
180      * Smoke test for action buttons: Presses all the buttons and makes sure no crashes occur.
181      */
182     @Test
183     @NavigationModeSwitch
184     @PortraitLandscape
testOverviewActions()185     public void testOverviewActions() throws Exception {
186         assumeFalse("Skipping Overview Actions tests for grid only overview",
187                 mLauncher.isTablet() && mLauncher.isGridOnlyOverviewEnabled());
188         startTestAppsWithCheck();
189         OverviewActions actionsView =
190                 mLauncher.goHome().switchToOverview().getOverviewActions();
191         actionsView.clickAndDismissScreenshot();
192     }
193 
194     @Test
testDismissOverviewWithEscKey()195     public void testDismissOverviewWithEscKey() throws Exception {
196         startTestAppsWithCheck();
197         final Overview overview = mLauncher.goHome().switchToOverview();
198         assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW);
199 
200         overview.dismissByEscKey();
201         assertIsInState("Launcher internal state is not Home", ExpectedState.HOME);
202     }
203 
204     @Test
testDismissModalTaskAndOverviewWithEscKey()205     public void testDismissModalTaskAndOverviewWithEscKey() throws Exception {
206         startTestAppsWithCheck();
207         final Overview overview = mLauncher.goHome().switchToOverview();
208 
209         final SelectModeButtons selectModeButtons;
210 
211         if (mLauncher.isTablet() && mLauncher.isGridOnlyOverviewEnabled()) {
212             selectModeButtons = overview.getCurrentTask().tapMenu().tapSelectMenuItem();
213         } else {
214             selectModeButtons = overview.getOverviewActions().clickSelect();
215         }
216 
217         assertTrue("Launcher internal state is not Overview Modal Task",
218                 isInState(() -> LauncherState.OVERVIEW_MODAL_TASK));
219 
220         selectModeButtons.dismissByEscKey();
221 
222         assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW);
223         overview.dismissByEscKey();
224         assertIsInState("Launcher internal state is not Home", ExpectedState.HOME);
225     }
226 
227     @Test
testOpenOverviewWithActionPlusTabKeys()228     public void testOpenOverviewWithActionPlusTabKeys() throws Exception {
229         startTestAppsWithCheck();
230         startAppFast(CALCULATOR_APP_PACKAGE); // Ensure Calculator is last opened app.
231         Workspace home = mLauncher.goHome();
232         assertIsInState("Launcher state is not Home", ExpectedState.HOME);
233 
234         Overview overview = home.openOverviewFromActionPlusTabKeyboardShortcut();
235 
236         assertIsInState("Launcher state is not Overview", ExpectedState.OVERVIEW);
237         overview.launchFocusedTaskByEnterKey(CALCULATOR_APP_PACKAGE); // Assert app is focused.
238     }
239 
240     @Test
testOpenOverviewWithRecentsKey()241     public void testOpenOverviewWithRecentsKey() throws Exception {
242         startTestAppsWithCheck();
243         startAppFast(CALCULATOR_APP_PACKAGE); // Ensure Calculator is last opened app.
244         Workspace home = mLauncher.goHome();
245         assertIsInState("Launcher state is not Home", ExpectedState.HOME);
246 
247         Overview overview = home.openOverviewFromRecentsKeyboardShortcut();
248 
249         assertIsInState("Launcher state is not Overview", ExpectedState.OVERVIEW);
250         overview.launchFocusedTaskByEnterKey(CALCULATOR_APP_PACKAGE); // Assert app is focused.
251     }
252 
253     @Test
254     @NavigationModeSwitch
255     @PortraitLandscape
testSwitchToOverview()256     public void testSwitchToOverview() throws Exception {
257         startTestAppsWithCheck();
258         assertNotNull("Workspace.switchToOverview() returned null",
259                 mLauncher.goHome().switchToOverview());
260         assertIsInState(
261                 "Launcher internal state didn't switch to Overview", ExpectedState.OVERVIEW);
262     }
263 
264     @Test
265     @TaskbarModeSwitch(mode = TRANSIENT)
testSwitchToOverviewWithStashedTaskbar()266     public void testSwitchToOverviewWithStashedTaskbar() throws Exception {
267         try {
268             startTestAppsWithCheck();
269             // Set ignoreTaskbarVisibility, as transient taskbar will be stashed after app launch.
270             mLauncher.setIgnoreTaskbarVisibility(true);
271             mLauncher.getLaunchedAppState().switchToOverview();
272         } finally {
273             mLauncher.setIgnoreTaskbarVisibility(false);
274         }
275     }
276 
277     @Test
278     @NavigationModeSwitch
279     @PortraitLandscape
testBackground()280     public void testBackground() throws Exception {
281         startAppFast(CALCULATOR_APP_PACKAGE);
282         final LaunchedAppState launchedAppState = getAndAssertLaunchedApp();
283 
284         assertNotNull("Background.switchToOverview() returned null",
285                 launchedAppState.switchToOverview());
286         assertIsInState(
287                 "Launcher internal state didn't switch to Overview", ExpectedState.OVERVIEW);
288     }
289 
290     @Test
291     @NavigationModeSwitch
292     @PortraitLandscape
293     @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/325659406
testQuickSwitchFromApp()294     public void testQuickSwitchFromApp() throws Exception {
295         startTestActivity(2);
296         startTestActivity(3);
297         startTestActivity(4);
298 
299         quickSwitchToPreviousAppAndAssert(true /* toRight */);
300         assertTestActivityIsRunning(3,
301                 "The first app we should have quick switched to is not running");
302 
303         quickSwitchToPreviousAppAndAssert(true /* toRight */);
304         if (mLauncher.getNavigationModel() == NavigationModel.THREE_BUTTON) {
305             // 3-button mode toggles between 2 apps, rather than going back further.
306             assertTestActivityIsRunning(4,
307                     "Second quick switch should have returned to the first app.");
308         } else {
309             assertTestActivityIsRunning(2,
310                     "The second app we should have quick switched to is not running");
311         }
312 
313         quickSwitchToPreviousAppAndAssert(false /* toRight */);
314         assertTestActivityIsRunning(3,
315                 "The 2nd app we should have quick switched to is not running");
316 
317         final LaunchedAppState launchedAppState = getAndAssertLaunchedApp();
318         launchedAppState.switchToOverview();
319     }
320 
321     @Test
322     @TaskbarModeSwitch
testQuickSwitchToPreviousAppForTablet()323     public void testQuickSwitchToPreviousAppForTablet() throws Exception {
324         assumeTrue(mLauncher.isTablet());
325         startTestActivity(2);
326         startImeTestActivity();
327 
328         // Set ignoreTaskbarVisibility to true to verify the task bar visibility explicitly.
329         mLauncher.setIgnoreTaskbarVisibility(true);
330 
331 
332         try {
333             boolean isTransientTaskbar = mLauncher.isTransientTaskbar();
334             // Expect task bar invisible when the launched app was the IME activity.
335             LaunchedAppState launchedAppState = getAndAssertLaunchedApp();
336             if (!isTransientTaskbar && isHardwareKeyboard() && !mLauncher.isImeDocked()) {
337                 launchedAppState.assertTaskbarVisible();
338             } else {
339                 launchedAppState.assertTaskbarHidden();
340             }
341 
342             // Quick-switch to the test app with swiping to right.
343             quickSwitchToPreviousAppAndAssert(true /* toRight */);
344 
345             assertTestActivityIsRunning(2,
346                     "The first app we should have quick switched to is not running");
347             launchedAppState = getAndAssertLaunchedApp();
348             if (isTransientTaskbar) {
349                 launchedAppState.assertTaskbarHidden();
350             } else {
351                 // Expect taskbar visible when the launched app was the test activity.
352                 launchedAppState.assertTaskbarVisible();
353             }
354         } finally {
355             // Reset ignoreTaskbarVisibility to ensure other tests still verify it.
356             mLauncher.setIgnoreTaskbarVisibility(false);
357         }
358     }
359 
360     @Test
361     @NavigationModeSwitch
362     @PortraitLandscape
testQuickSwitchFromHome()363     public void testQuickSwitchFromHome() throws Exception {
364         startTestActivity(2);
365         mLauncher.goHome().quickSwitchToPreviousApp();
366         assertTestActivityIsRunning(2,
367                 "The most recent task is not running after quick switching from home");
368         getAndAssertLaunchedApp();
369     }
370 
371     @Test
372     @PortraitLandscape
373     @NavigationModeSwitch
testPressBack()374     public void testPressBack() throws Exception {
375         InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
376                 READ_DEVICE_CONFIG_PERMISSION);
377         // Debug if we need to goHome to prevent wrong previous state b/315525621
378         mLauncher.goHome();
379         mLauncher.getWorkspace().switchToAllApps().pressBackToWorkspace();
380         waitForState("Launcher internal state didn't switch to Home", ExpectedState.HOME);
381 
382         startAppFast(CALCULATOR_APP_PACKAGE);
383         mLauncher.getLaunchedAppState().pressBackToWorkspace();
384         waitForState("Launcher internal state didn't switch to Home", ExpectedState.HOME);
385     }
386 
387     @Test
388     @PortraitLandscape
389     @TaskbarModeSwitch()
390     @Ignore("b/315376057")
testOverviewForTablet()391     public void testOverviewForTablet() throws Exception {
392         assumeTrue(mLauncher.isTablet());
393 
394         for (int i = 2; i <= 14; i++) {
395             startTestActivity(i);
396         }
397 
398         Overview overview = mLauncher.goHome().switchToOverview();
399         runOnRecentsView(recentsView -> assertTrue("Don't have at least 13 tasks",
400                 recentsView.getTaskViewCount() >= 13));
401 
402         // Test scroll the first task off screen
403         overview.scrollCurrentTaskOffScreen();
404         assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW);
405         runOnRecentsView(recentsView -> assertTrue("Current task in Overview is still 0",
406                 recentsView.getCurrentPage() > 0));
407 
408         // Test opening the task.
409         overview.getCurrentTask().open();
410         assertTrue("Test activity didn't open from Overview",
411                 mDevice.wait(Until.hasObject(By.pkg(getAppPackageName()).text(
412                                 mLauncher.isGridOnlyOverviewEnabled() ? "TestActivity12"
413                                         : "TestActivity13")),
414                         TestUtil.DEFAULT_UI_TIMEOUT));
415 
416         // Scroll the task offscreen as it is now first
417         overview = mLauncher.goHome().switchToOverview();
418         overview.scrollCurrentTaskOffScreen();
419         assertIsInState(
420                 "Launcher internal state is not Overview", ExpectedState.OVERVIEW);
421         runOnRecentsView(recentsView -> assertTrue("Current task in Overview is still 0",
422                 recentsView.getCurrentPage() > 0));
423 
424         // Test dismissing the later task.
425         final Integer numTasks = getFromRecentsView(RecentsView::getTaskViewCount);
426         overview.getCurrentTask().dismiss();
427         runOnRecentsView(recentsView -> assertEquals(
428                 "Dismissing a task didn't remove 1 task from Overview",
429                 numTasks - 1, recentsView.getTaskViewCount()));
430         runOnRecentsView(recentsView -> assertTrue("Grid did not rebalance after dismissal",
431                 (Math.abs(recentsView.getTopRowTaskCountForTablet()
432                         - recentsView.getBottomRowTaskCountForTablet()) <= 1)));
433 
434         // Test dismissing more tasks.
435         assertIsInState(
436                 "Launcher internal state didn't remain in Overview", ExpectedState.OVERVIEW);
437         overview.getCurrentTask().dismiss();
438         assertIsInState(
439                 "Launcher internal state didn't remain in Overview", ExpectedState.OVERVIEW);
440         overview.getCurrentTask().dismiss();
441         runOnRecentsView(recentsView -> assertTrue(
442                 "Grid did not rebalance after multiple dismissals",
443                 (Math.abs(recentsView.getTopRowTaskCountForTablet()
444                         - recentsView.getBottomRowTaskCountForTablet()) <= 1)));
445 
446         // Test dismissing all tasks.
447         mLauncher.goHome().switchToOverview().dismissAllTasks();
448         assertIsInState("Launcher internal state is not Home", ExpectedState.HOME);
449         runOnRecentsView(recentsView -> assertEquals("Still have tasks after dismissing all",
450                 0, recentsView.getTaskViewCount()));
451     }
452 
453     @Test
454     @PortraitLandscape
testOverviewDeadzones()455     public void testOverviewDeadzones() throws Exception {
456         startTestAppsWithCheck();
457 
458         Overview overview = mLauncher.goHome().switchToOverview();
459         assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW);
460         runOnRecentsView(recentsView -> assertTrue("Should have at least 3 tasks",
461                 recentsView.getTaskViewCount() >= 3));
462 
463         // It should not dismiss overview when tapping between tasks
464         overview.touchBetweenTasks();
465         overview = mLauncher.getOverview();
466         assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW);
467 
468         // Dismiss when tapping to the right of the focused task
469         overview.touchOutsideFirstTask();
470         assertIsInState("Launcher internal state should be Home", ExpectedState.HOME);
471     }
472 
473     @Test
474     @PortraitLandscape
475     @TaskbarModeSwitch
testTaskbarDeadzonesForTablet()476     public void testTaskbarDeadzonesForTablet() throws Exception {
477         assumeTrue(mLauncher.isTablet());
478 
479         startTestAppsWithCheck();
480 
481         Overview overview = mLauncher.goHome().switchToOverview();
482         assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW);
483         runOnRecentsView(recentsView -> assertTrue("Should have at least 3 tasks",
484                 recentsView.getTaskViewCount() >= 3));
485 
486         if (mLauncher.isTransientTaskbar()) {
487             // On transient taskbar, it should dismiss when tapping outside taskbar bounds.
488             overview.touchTaskbarBottomCorner(/* tapRight= */ false);
489             assertIsInState("Launcher internal state should be Normal", ExpectedState.HOME);
490 
491             overview = mLauncher.getWorkspace().switchToOverview();
492 
493             // On transient taskbar, it should dismiss when tapping outside taskbar bounds.
494             overview.touchTaskbarBottomCorner(/* tapRight= */ true);
495             assertIsInState("Launcher internal state should be Normal", ExpectedState.HOME);
496         } else {
497             // On persistent taskbar, it should not dismiss when tapping the taskbar
498             overview.touchTaskbarBottomCorner(/* tapRight= */ false);
499             assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW);
500 
501             // On persistent taskbar, it should not dismiss when tapping the taskbar
502             overview.touchTaskbarBottomCorner(/* tapRight= */ true);
503             assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW);
504         }
505     }
506 
507     @Test
testDisableRotationCheckForPhone()508     public void testDisableRotationCheckForPhone() throws Exception {
509         assumeFalse(mLauncher.isTablet());
510         try {
511             mLauncher.setExpectedRotationCheckEnabled(false);
512             mLauncher.setEnableRotation(false);
513             mLauncher.getDevice().setOrientationLeft();
514             startTestActivity(7);
515             Wait.atMost("Device should not be in natural orientation",
516                     () -> !mDevice.isNaturalOrientation(), mLauncher);
517             mLauncher.goHome();
518         } finally {
519             mLauncher.setExpectedRotationCheckEnabled(true);
520             mLauncher.setEnableRotation(true);
521             mLauncher.getDevice().setOrientationNatural();
522         }
523     }
524 
525     @Test
testExcludeFromRecents()526     public void testExcludeFromRecents() throws Exception {
527         startExcludeFromRecentsTestActivity();
528         OverviewTask currentTask = getAndAssertLaunchedApp().switchToOverview().getCurrentTask();
529         assertTrue("Can't find ExcludeFromRecentsTestActivity after entering Overview from it",
530                 currentTask.containsContentDescription("ExcludeFromRecents"));
531         // Going home should clear out the excludeFromRecents task.
532         BaseOverview overview = mLauncher.goHome().switchToOverview();
533         if (overview.hasTasks()) {
534             currentTask = overview.getCurrentTask();
535             assertFalse("Found ExcludeFromRecentsTestActivity after entering Overview from Home",
536                     currentTask.containsContentDescription("ExcludeFromRecents"));
537         } else {
538             // Presumably the test started with 0 tasks and remains that way after going home.
539         }
540     }
541 
542     @Test
543     @PortraitLandscape
544     @ScreenRecordRule.ScreenRecord // TODO(b/396447643): Remove screen record.
testDismissCancel()545     public void testDismissCancel() throws Exception {
546         startTestAppsWithCheck();
547         Overview overview = mLauncher.goHome().switchToOverview();
548         assertIsInState("Launcher internal state didn't switch to Overview",
549                 ExpectedState.OVERVIEW);
550         final Integer numTasks = getFromRecentsView(RecentsView::getTaskViewCount);
551         OverviewTask task = overview.getCurrentTask();
552         assertNotNull("overview.getCurrentTask() returned null (2)", task);
553 
554         task.dismissCancel();
555 
556         runOnRecentsView(recentsView -> assertEquals(
557                 "Canceling dismissing a task removed a task from Overview",
558                 numTasks == null ? 0 : numTasks, recentsView.getTaskViewCount()));
559     }
560 
561     @Test
562     @PortraitLandscape
testDismissBottomRow()563     public void testDismissBottomRow() throws Exception {
564         assumeTrue(mLauncher.isTablet());
565         mLauncher.goHome().switchToOverview().dismissAllTasks();
566         startTestAppsWithCheck();
567         Overview overview = mLauncher.goHome().switchToOverview();
568         assertIsInState("Launcher internal state didn't switch to Overview",
569                 ExpectedState.OVERVIEW);
570         final Integer numTasks = getFromRecentsView(RecentsView::getTaskViewCount);
571         OverviewTask bottomTask = overview.getCurrentTasksForTablet().stream().max(
572                 Comparator.comparingInt(OverviewTask::getTaskCenterY)).get();
573         assertNotNull("bottomTask null", bottomTask);
574 
575         bottomTask.dismiss();
576 
577         runOnRecentsView(recentsView -> assertEquals(
578                 "Dismissing a bottomTask didn't remove 1 bottomTask from Overview",
579                 numTasks - 1, recentsView.getTaskViewCount()));
580     }
581 
582     @Test
583     @PortraitLandscape
testDismissLastGridRow()584     public void testDismissLastGridRow() throws Exception {
585         assumeTrue(mLauncher.isTablet());
586         mLauncher.goHome().switchToOverview().dismissAllTasks();
587         startTestAppsWithCheck();
588         startTestActivity(3);
589         startTestActivity(4);
590         runOnRecentsView(
591                 recentsView -> assertNotEquals("Grid overview should have unequal row counts",
592                         recentsView.getTopRowTaskCountForTablet(),
593                         recentsView.getBottomRowTaskCountForTablet()));
594         Overview overview = mLauncher.goHome().switchToOverview();
595         assertIsInState("Launcher internal state didn't switch to Overview",
596                 ExpectedState.OVERVIEW);
597         overview.flingForwardUntilClearAllVisible();
598         assertTrue("Clear All not visible.", overview.isClearAllVisible());
599         final Integer numTasks = getFromRecentsView(RecentsView::getTaskViewCount);
600         OverviewTask lastGridTask = overview.getCurrentTasksForTablet().stream().min(
601                 Comparator.comparingInt(OverviewTask::getTaskCenterX)).get();
602         assertNotNull("lastGridTask null.", lastGridTask);
603 
604         lastGridTask.dismiss();
605 
606         runOnRecentsView(recentsView -> assertEquals(
607                 "Dismissing a lastGridTask didn't remove 1 lastGridTask from Overview",
608                 numTasks - 1, recentsView.getTaskViewCount()));
609         runOnRecentsView(recentsView -> assertEquals("Grid overview should have equal row counts.",
610                 recentsView.getTopRowTaskCountForTablet(),
611                 recentsView.getBottomRowTaskCountForTablet()));
612         assertTrue("Clear All not visible.", overview.isClearAllVisible());
613     }
614 
startTestAppsWithCheck()615     private void startTestAppsWithCheck() throws Exception {
616         startTestApps();
617         expectLaunchedAppState();
618     }
619 
quickSwitchToPreviousAppAndAssert(boolean toRight)620     private void quickSwitchToPreviousAppAndAssert(boolean toRight) {
621         final LaunchedAppState launchedAppState = getAndAssertLaunchedApp();
622         if (toRight) {
623             launchedAppState.quickSwitchToPreviousApp();
624         } else {
625             launchedAppState.quickSwitchToPreviousAppSwipeLeft();
626         }
627 
628         // While enable shell transition, Launcher can be resumed due to transient launch.
629         waitForLauncherCondition("Launcher shouldn't stay in resume forever",
630                 this::isInLaunchedApp, 3000 /* timeout */);
631     }
632 
isHardwareKeyboard()633     private boolean isHardwareKeyboard() {
634         return Configuration.KEYBOARD_QWERTY
635                 == mTargetContext.getResources().getConfiguration().keyboard;
636     }
637 
assertIsInState( @onNull String failureMessage, @NonNull ExpectedState expectedState)638     private void assertIsInState(
639             @NonNull String failureMessage, @NonNull ExpectedState expectedState) {
640         assertTrue(failureMessage, enableLauncherOverviewInWindow()
641                 ? isInRecentsWindowState(() -> expectedState.mRecentsState)
642                 : isInState(() -> expectedState.mLauncherState));
643     }
644 
waitForState( @onNull String failureMessage, @NonNull ExpectedState expectedState)645     private void waitForState(
646             @NonNull String failureMessage, @NonNull ExpectedState expectedState) {
647         if (enableLauncherOverviewInWindow()) {
648             waitForRecentsWindowState(failureMessage, () -> expectedState.mRecentsState);
649         } else {
650             waitForState(failureMessage, () -> expectedState.mLauncherState);
651         }
652     }
653 
expectLaunchedAppState()654     private void expectLaunchedAppState() {
655         executeOnLauncher(launcher -> assertTrue(
656                 "Launcher activity is the top activity; expecting another activity to be the top "
657                         + "one",
658                 isInLaunchedApp(launcher)));
659     }
660 
getFromRecentsView(Function<RecentsView, T> f)661     private <T> T getFromRecentsView(Function<RecentsView, T> f) {
662         return getFromRecentsView(f, false);
663     }
664 
getFromRecentsView(Function<RecentsView, T> f, boolean forTearDown)665     private <T> T getFromRecentsView(Function<RecentsView, T> f, boolean forTearDown) {
666         if (enableLauncherOverviewInWindow()) {
667             return getFromRecentsWindow(recentsWindowManager ->
668                     (forTearDown && recentsWindowManager == null)
669                             ? null :  f.apply(recentsWindowManager.getOverviewPanel()));
670         } else {
671             return getFromLauncher(launcher -> (forTearDown && launcher == null)
672                     ? null : f.apply(launcher.getOverviewPanel()));
673         }
674     }
675 
runOnRecentsView(Consumer<RecentsView> f)676     private void runOnRecentsView(Consumer<RecentsView> f) {
677         runOnRecentsView(f, false);
678     }
679 
runOnRecentsView(Consumer<RecentsView> f, boolean forTearDown)680     private void runOnRecentsView(Consumer<RecentsView> f, boolean forTearDown) {
681         getFromRecentsView(recentsView -> {
682             if (forTearDown && recentsView == null) {
683                 return null;
684             }
685             f.accept(recentsView);
686             return null;
687         }, forTearDown);
688     }
689 }
690