1 /*
2  * Copyright (C) 2019 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.launcher3.ui;
18 
19 import static androidx.test.InstrumentationRegistry.getInstrumentation;
20 
21 import static org.junit.Assert.assertEquals;
22 import static org.junit.Assert.assertFalse;
23 import static org.junit.Assert.assertNotNull;
24 import static org.junit.Assert.assertNull;
25 import static org.junit.Assert.assertTrue;
26 
27 import androidx.test.filters.LargeTest;
28 import androidx.test.runner.AndroidJUnit4;
29 
30 import com.android.launcher3.Launcher;
31 import com.android.launcher3.LauncherState;
32 import com.android.launcher3.popup.ArrowPopup;
33 import com.android.launcher3.tapl.AllApps;
34 import com.android.launcher3.tapl.AppIcon;
35 import com.android.launcher3.tapl.AppIconMenu;
36 import com.android.launcher3.tapl.AppIconMenuItem;
37 import com.android.launcher3.tapl.Folder;
38 import com.android.launcher3.tapl.FolderIcon;
39 import com.android.launcher3.tapl.Widgets;
40 import com.android.launcher3.tapl.Workspace;
41 import com.android.launcher3.widget.picker.WidgetsFullSheet;
42 import com.android.launcher3.widget.picker.WidgetsRecyclerView;
43 
44 import org.junit.Before;
45 import org.junit.Ignore;
46 import org.junit.Test;
47 import org.junit.runner.RunWith;
48 
49 @LargeTest
50 @RunWith(AndroidJUnit4.class)
51 public class TaplTestsLauncher3 extends AbstractLauncherUiTest {
52     private static final String APP_NAME = "LauncherTestApp";
53 
54     @Before
setUp()55     public void setUp() throws Exception {
56         super.setUp();
57         initialize(this);
58     }
59 
initialize(AbstractLauncherUiTest test)60     public static void initialize(AbstractLauncherUiTest test) throws Exception {
61         test.clearLauncherData();
62         test.mDevice.pressHome();
63         test.waitForLauncherCondition("Launcher didn't start", launcher -> launcher != null);
64         test.waitForState("Launcher internal state didn't switch to Home",
65                 () -> LauncherState.NORMAL);
66         test.waitForResumed("Launcher internal state is still Background");
67         // Check that we switched to home.
68         test.mLauncher.getWorkspace();
69         AbstractLauncherUiTest.checkDetectedLeaks(test.mLauncher);
70     }
71 
72     // Please don't add negative test cases for methods that fail only after a long wait.
expectFail(String message, Runnable action)73     public static void expectFail(String message, Runnable action) {
74         boolean failed = false;
75         try {
76             action.run();
77         } catch (AssertionError e) {
78             failed = true;
79         }
80         assertTrue(message, failed);
81     }
82 
isWorkspaceScrollable(Launcher launcher)83     public static boolean isWorkspaceScrollable(Launcher launcher) {
84         return launcher.getWorkspace().getPageCount() > launcher.getWorkspace().getPanelCount();
85     }
86 
getCurrentWorkspacePage(Launcher launcher)87     private int getCurrentWorkspacePage(Launcher launcher) {
88         return launcher.getWorkspace().getCurrentPage();
89     }
90 
getWidgetsView(Launcher launcher)91     private WidgetsRecyclerView getWidgetsView(Launcher launcher) {
92         return WidgetsFullSheet.getWidgetsView(launcher);
93     }
94 
95     @Test
testDevicePressMenu()96     public void testDevicePressMenu() throws Exception {
97         mDevice.pressMenu();
98         mDevice.waitForIdle();
99         executeOnLauncher(
100                 launcher -> assertNotNull("Launcher internal state didn't switch to Showing Menu",
101                         launcher.getOptionsPopup()));
102         // Check that pressHome works when the menu is shown.
103         mLauncher.pressHome();
104     }
105 
106     @Test
testPressHomeOnAllAppsContextMenu()107     public void testPressHomeOnAllAppsContextMenu() throws Exception {
108         final AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
109         allApps.freeze();
110         try {
111             allApps.getAppIcon("TestActivity7").openMenu();
112         } finally {
113             allApps.unfreeze();
114         }
115         mLauncher.pressHome();
116     }
117 
runAllAppsTest(AbstractLauncherUiTest test, AllApps allApps)118     public static void runAllAppsTest(AbstractLauncherUiTest test, AllApps allApps) {
119         allApps.freeze();
120         try {
121             assertNotNull("allApps parameter is null", allApps);
122 
123             assertTrue(
124                     "Launcher internal state is not All Apps",
125                     test.isInState(() -> LauncherState.ALL_APPS));
126 
127             // Test flinging forward and backward.
128             test.executeOnLauncher(launcher -> assertEquals(
129                     "All Apps started in already scrolled state", 0,
130                     test.getAllAppsScroll(launcher)));
131 
132             allApps.flingForward();
133             assertTrue("Launcher internal state is not All Apps",
134                     test.isInState(() -> LauncherState.ALL_APPS));
135             final Integer flingForwardY = test.getFromLauncher(
136                     launcher -> test.getAllAppsScroll(launcher));
137             test.executeOnLauncher(
138                     launcher -> assertTrue("flingForward() didn't scroll App Apps",
139                             flingForwardY > 0));
140 
141             allApps.flingBackward();
142             assertTrue(
143                     "Launcher internal state is not All Apps",
144                     test.isInState(() -> LauncherState.ALL_APPS));
145             final Integer flingBackwardY = test.getFromLauncher(
146                     launcher -> test.getAllAppsScroll(launcher));
147             test.executeOnLauncher(launcher -> assertTrue("flingBackward() didn't scroll App Apps",
148                     flingBackwardY < flingForwardY));
149 
150             // Test scrolling down to YouTube.
151             assertNotNull("All apps: can't fine YouTube", allApps.getAppIcon("YouTube"));
152             // Test scrolling up to Camera.
153             assertNotNull("All apps: can't fine Camera", allApps.getAppIcon("Camera"));
154             // Test failing to find a non-existing app.
155             final AllApps allAppsFinal = allApps;
156             expectFail("All apps: could find a non-existing app",
157                     () -> allAppsFinal.getAppIcon("NO APP"));
158 
159             assertTrue(
160                     "Launcher internal state is not All Apps",
161                     test.isInState(() -> LauncherState.ALL_APPS));
162         } finally {
163             allApps.unfreeze();
164         }
165     }
166 
167     @Test
168     @PortraitLandscape
testWorkspaceSwitchToAllApps()169     public void testWorkspaceSwitchToAllApps() {
170         assertNotNull("switchToAllApps() returned null",
171                 mLauncher.getWorkspace().switchToAllApps());
172         assertTrue("Launcher internal state is not All Apps",
173                 isInState(() -> LauncherState.ALL_APPS));
174     }
175 
176     @Test
testWorkspace()177     public void testWorkspace() throws Exception {
178         final Workspace workspace = mLauncher.getWorkspace();
179 
180         // Test that ensureWorkspaceIsScrollable adds a page by dragging an icon there.
181         executeOnLauncher(launcher -> assertFalse("Initial workspace state is scrollable",
182                 isWorkspaceScrollable(launcher)));
183         assertNull("Chrome app was found on empty workspace",
184                 workspace.tryGetWorkspaceAppIcon("Chrome"));
185 
186         workspace.ensureWorkspaceIsScrollable();
187 
188         executeOnLauncher(
189                 launcher -> assertEquals(
190                         "Ensuring workspace scrollable didn't switch to next screen",
191                         workspace.pagesPerScreen(), getCurrentWorkspacePage(launcher)));
192         executeOnLauncher(
193                 launcher -> assertTrue("ensureScrollable didn't make workspace scrollable",
194                         isWorkspaceScrollable(launcher)));
195         assertNotNull("ensureScrollable didn't add Chrome app",
196                 workspace.getWorkspaceAppIcon("Chrome"));
197 
198         // Test flinging workspace.
199         workspace.flingBackward();
200         assertTrue("Launcher internal state is not Home", isInState(() -> LauncherState.NORMAL));
201         executeOnLauncher(
202                 launcher -> assertEquals("Flinging back didn't switch workspace to page #0",
203                         0, getCurrentWorkspacePage(launcher)));
204 
205         workspace.flingForward();
206         executeOnLauncher(
207                 launcher -> assertEquals("Flinging forward didn't switch workspace to next screen",
208                         workspace.pagesPerScreen(), getCurrentWorkspacePage(launcher)));
209         assertTrue("Launcher internal state is not Home", isInState(() -> LauncherState.NORMAL));
210 
211         // Test starting a workspace app.
212         final AppIcon app = workspace.getWorkspaceAppIcon("Chrome");
213         assertNotNull("No Chrome app in workspace", app);
214     }
215 
runIconLaunchFromAllAppsTest(AbstractLauncherUiTest test, AllApps allApps)216     public static void runIconLaunchFromAllAppsTest(AbstractLauncherUiTest test, AllApps allApps) {
217         allApps.freeze();
218         try {
219             final AppIcon app = allApps.getAppIcon("TestActivity7");
220             assertNotNull("AppIcon.launch returned null", app.launch(getAppPackageName()));
221             test.executeOnLauncher(launcher -> assertTrue(
222                     "Launcher activity is the top activity; expecting another activity to be the "
223                             + "top "
224                             + "one",
225                     test.isInBackground(launcher)));
226         } finally {
227             allApps.unfreeze();
228         }
229     }
230 
231     @Test
232     @PortraitLandscape
testAppIconLaunchFromAllAppsFromHome()233     public void testAppIconLaunchFromAllAppsFromHome() throws Exception {
234         final AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
235         assertTrue("Launcher internal state is not All Apps",
236                 isInState(() -> LauncherState.ALL_APPS));
237 
238         runIconLaunchFromAllAppsTest(this, allApps);
239     }
240 
241     @Test
242     @PortraitLandscape
testWidgets()243     public void testWidgets() throws Exception {
244         // Test opening widgets.
245         executeOnLauncher(launcher ->
246                 assertTrue("Widgets is initially opened", getWidgetsView(launcher) == null));
247         Widgets widgets = mLauncher.getWorkspace().openAllWidgets();
248         assertNotNull("openAllWidgets() returned null", widgets);
249         widgets = mLauncher.getAllWidgets();
250         assertNotNull("getAllWidgets() returned null", widgets);
251         executeOnLauncher(launcher ->
252                 assertTrue("Widgets is not shown", getWidgetsView(launcher).isShown()));
253         executeOnLauncher(launcher -> assertEquals("Widgets is scrolled upon opening",
254                 0, getWidgetsScroll(launcher)));
255 
256         // Test flinging widgets.
257         widgets.flingForward();
258         Integer flingForwardY = getFromLauncher(launcher -> getWidgetsScroll(launcher));
259         executeOnLauncher(launcher -> assertTrue("Flinging forward didn't scroll widgets",
260                 flingForwardY > 0));
261 
262         widgets.flingBackward();
263         executeOnLauncher(launcher -> assertTrue("Flinging backward didn't scroll widgets",
264                 getWidgetsScroll(launcher) < flingForwardY));
265 
266         mLauncher.pressHome();
267         waitForLauncherCondition("Widgets were not closed",
268                 launcher -> getWidgetsView(launcher) == null);
269     }
270 
getWidgetsScroll(Launcher launcher)271     private int getWidgetsScroll(Launcher launcher) {
272         return getWidgetsView(launcher).getCurrentScrollY();
273     }
274 
isOptionsPopupVisible(Launcher launcher)275     private boolean isOptionsPopupVisible(Launcher launcher) {
276         final ArrowPopup<?> popup = launcher.getOptionsPopup();
277         return popup != null && popup.isShown();
278     }
279 
280     @Test
281     @PortraitLandscape
testLaunchMenuItem()282     public void testLaunchMenuItem() throws Exception {
283         final AllApps allApps = mLauncher.
284                 getWorkspace().
285                 switchToAllApps();
286         allApps.freeze();
287         try {
288             final AppIconMenu menu = allApps.
289                     getAppIcon(APP_NAME).
290                     openDeepShortcutMenu();
291 
292             executeOnLauncher(
293                     launcher -> assertTrue("Launcher internal state didn't switch to Showing Menu",
294                             isOptionsPopupVisible(launcher)));
295 
296             final AppIconMenuItem menuItem = menu.getMenuItem(1);
297             assertEquals("Wrong menu item", "Shortcut 2", menuItem.getText());
298             menuItem.launch(getAppPackageName());
299         } finally {
300             allApps.unfreeze();
301         }
302     }
303 
304     @Test
305     @PortraitLandscape
testDragAppIcon()306     public void testDragAppIcon() throws Throwable {
307         // 1. Open all apps and wait for load complete.
308         // 2. Drag icon to homescreen.
309         // 3. Verify that the icon works on homescreen.
310         final AllApps allApps = mLauncher.getWorkspace().
311                 switchToAllApps();
312         allApps.freeze();
313         try {
314             allApps.getAppIcon(APP_NAME).dragToWorkspace(false, false);
315             mLauncher.getWorkspace().getWorkspaceAppIcon(APP_NAME).launch(getAppPackageName());
316         } finally {
317             allApps.unfreeze();
318         }
319         executeOnLauncher(launcher -> assertTrue(
320                 "Launcher activity is the top activity; expecting another activity to be the top "
321                         + "one",
322                 isInBackground(launcher)));
323     }
324 
325     @Test
326     @PortraitLandscape
testDragShortcut()327     public void testDragShortcut() throws Throwable {
328         // 1. Open all apps and wait for load complete.
329         // 2. Find the app and long press it to show shortcuts.
330         // 3. Press icon center until shortcuts appear
331         final AllApps allApps = mLauncher
332                 .getWorkspace()
333                 .switchToAllApps();
334         allApps.freeze();
335         try {
336             final AppIconMenu menu = allApps
337                     .getAppIcon(APP_NAME)
338                     .openDeepShortcutMenu();
339             final AppIconMenuItem menuItem0 = menu.getMenuItem(0);
340             final AppIconMenuItem menuItem2 = menu.getMenuItem(2);
341 
342             final AppIconMenuItem menuItem;
343 
344             final String expectedShortcutName = "Shortcut 3";
345             if (menuItem0.getText().equals(expectedShortcutName)) {
346                 menuItem = menuItem0;
347             } else {
348                 final String shortcutName2 = menuItem2.getText();
349                 assertEquals("Wrong menu item", expectedShortcutName, shortcutName2);
350                 menuItem = menuItem2;
351             }
352 
353             menuItem.dragToWorkspace(false, false);
354             mLauncher.getWorkspace().getWorkspaceAppIcon(expectedShortcutName)
355                     .launch(getAppPackageName());
356         } finally {
357             allApps.unfreeze();
358         }
359     }
360 
createShortcutIfNotExist(String name)361     private AppIcon createShortcutIfNotExist(String name) {
362         AppIcon appIcon = mLauncher.getWorkspace().tryGetWorkspaceAppIcon(name);
363         if (appIcon == null) {
364             AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
365             allApps.freeze();
366             try {
367                 appIcon = allApps.getAppIcon(name);
368                 appIcon.dragToWorkspace(false, false);
369             } finally {
370                 allApps.unfreeze();
371             }
372             appIcon = mLauncher.getWorkspace().getWorkspaceAppIcon(name);
373         }
374         return appIcon;
375     }
376 
377     @Ignore("b/205014516")
378     @Test
379     @PortraitLandscape
testDragToFolder()380     public void testDragToFolder() throws Exception {
381         final AppIcon playStoreIcon = createShortcutIfNotExist("Play Store");
382         final AppIcon gmailIcon = createShortcutIfNotExist("Gmail");
383 
384         FolderIcon folderIcon = gmailIcon.dragToIcon(playStoreIcon);
385 
386         Folder folder = folderIcon.open();
387         folder.getAppIcon("Play Store");
388         folder.getAppIcon("Gmail");
389         Workspace workspace = folder.close();
390 
391         assertNull("Gmail should be moved to a folder.",
392                 workspace.tryGetWorkspaceAppIcon("Gmail"));
393         assertNull("Play Store should be moved to a folder.",
394                 workspace.tryGetWorkspaceAppIcon("Play Store"));
395 
396         final AppIcon youTubeIcon = createShortcutIfNotExist("YouTube");
397 
398         folderIcon = youTubeIcon.dragToIcon(folderIcon);
399         folder = folderIcon.open();
400         folder.getAppIcon("YouTube");
401         folder.close();
402     }
403 
404     @Ignore("b/205027405")
405     @Test
406     @PortraitLandscape
testPressBack()407     public void testPressBack() throws Exception {
408         mLauncher.getWorkspace().switchToAllApps();
409         mLauncher.pressBack();
410         mLauncher.getWorkspace();
411         waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
412 
413         AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
414         allApps.freeze();
415         try {
416             allApps.getAppIcon(APP_NAME).dragToWorkspace(false, false);
417         } finally {
418             allApps.unfreeze();
419         }
420         mLauncher.getWorkspace().getWorkspaceAppIcon(APP_NAME).launch(getAppPackageName());
421         mLauncher.pressBack();
422         mLauncher.getWorkspace();
423         waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
424     }
425 
426     @Test
427     @PortraitLandscape
testDeleteFromWorkspace()428     public void testDeleteFromWorkspace() throws Exception {
429         // test delete both built-in apps and user-installed app from workspace
430         for (String appName : new String[] {"Gmail", "Play Store", APP_NAME}) {
431             final AppIcon appIcon = createShortcutIfNotExist(appName);
432             Workspace workspace = mLauncher.getWorkspace().deleteAppIcon(appIcon);
433             assertNull(appName + " app was found after being deleted from workspace",
434                     workspace.tryGetWorkspaceAppIcon(appName));
435         }
436     }
437 
getAppPackageName()438     public static String getAppPackageName() {
439         return getInstrumentation().getContext().getPackageName();
440     }
441 }
442