1 /*
2  * Copyright (C) 2023 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 package com.android.launcher3.dragging;
17 
18 import static com.android.launcher3.testing.shared.TestProtocol.ICON_MISSING;
19 import static com.android.launcher3.testing.shared.TestProtocol.UIOBJECT_STALE_ELEMENT;
20 import static com.android.launcher3.util.TestConstants.AppNames.DUMMY_APP_NAME;
21 import static com.android.launcher3.util.TestConstants.AppNames.GMAIL_APP_NAME;
22 import static com.android.launcher3.util.TestConstants.AppNames.MAPS_APP_NAME;
23 import static com.android.launcher3.util.TestConstants.AppNames.STORE_APP_NAME;
24 import static com.android.launcher3.util.TestConstants.AppNames.TEST_APP_NAME;
25 import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
26 import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
27 
28 import static com.google.common.truth.Truth.assertThat;
29 
30 import android.graphics.Point;
31 import android.platform.test.annotations.PlatinumTest;
32 import android.util.Log;
33 
34 import com.android.launcher3.Launcher;
35 import com.android.launcher3.tapl.HomeAllApps;
36 import com.android.launcher3.tapl.HomeAppIcon;
37 import com.android.launcher3.tapl.Workspace;
38 import com.android.launcher3.ui.AbstractLauncherUiTest;
39 import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
40 import com.android.launcher3.util.TestUtil;
41 import com.android.launcher3.util.Wait;
42 import com.android.launcher3.util.rule.ScreenRecordRule;
43 import com.android.launcher3.util.rule.TestStabilityRule;
44 
45 import org.junit.Test;
46 
47 import java.io.IOException;
48 import java.util.Arrays;
49 import java.util.Map;
50 
51 /**
52  * Test runs in Out of process (Oop) and In process (Ipc)
53  * Test the behaviour of uninstalling and removing apps both from AllApps, Workspace and Hotseat.
54  */
55 public class TaplUninstallRemoveTest extends AbstractLauncherUiTest<Launcher> {
56 
57     /**
58      * Deletes app both built-in and user-installed from the Workspace and makes sure it's no longer
59      * in the Workspace.
60      */
61     @Test
62     @PortraitLandscape
testDeleteFromWorkspace()63     public void testDeleteFromWorkspace() {
64         for (String appName : new String[]{GMAIL_APP_NAME, STORE_APP_NAME, TEST_APP_NAME}) {
65             final HomeAppIcon homeAppIcon = createShortcutInCenterIfNotExist(appName);
66             Workspace workspace = mLauncher.getWorkspace().deleteAppIcon(homeAppIcon);
67             workspace.verifyWorkspaceAppIconIsGone(
68                     appName + " app was found after being deleted from workspace",
69                     appName);
70         }
71     }
72 
verifyAppUninstalledFromAllApps(Workspace workspace, String appName)73     private void verifyAppUninstalledFromAllApps(Workspace workspace, String appName) {
74         final HomeAllApps allApps = workspace.switchToAllApps();
75         Wait.atMost(appName + " app was found on all apps after being uninstalled",
76                 () -> allApps.tryGetAppIcon(appName) == null,
77                 DEFAULT_UI_TIMEOUT, mLauncher);
78     }
79 
installDummyAppAndWaitForUIUpdate()80     private void installDummyAppAndWaitForUIUpdate() throws IOException {
81         TestUtil.installDummyApp();
82         waitForLauncherUIUpdate();
83     }
84 
waitForLauncherUIUpdate()85     private void waitForLauncherUIUpdate() {
86         // Wait for model thread completion as it may be processing
87         // the install event from the SystemService
88         mLauncher.waitForModelQueueCleared();
89         // Wait for Launcher UI thread completion, as it may be processing updating the UI in
90         // response to the model update. Not that `waitForLauncherInitialized` is just a proxy
91         // method, we can use any method which touches Launcher UI thread,
92         mLauncher.waitForLauncherInitialized();
93     }
94 
95     /**
96      * Makes sure you can uninstall an app from the Workspace.
97      */
98     @Test
99     @PortraitLandscape
100     @PlatinumTest(focusArea = "launcher")
101     @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/311099513
testUninstallFromWorkspace()102     public void testUninstallFromWorkspace() throws Exception {
103         installDummyAppAndWaitForUIUpdate();
104         try {
105             verifyAppUninstalledFromAllApps(
106                     createShortcutInCenterIfNotExist(DUMMY_APP_NAME).uninstall(), DUMMY_APP_NAME);
107         } finally {
108             TestUtil.uninstallDummyApp();
109         }
110     }
111 
112     /**
113      * Makes sure you can uninstall an app from AllApps.
114      */
115     @Test
116     @PortraitLandscape
117     @PlatinumTest(focusArea = "launcher")
testUninstallFromAllApps()118     public void testUninstallFromAllApps() throws Exception {
119         installDummyAppAndWaitForUIUpdate();
120         try {
121             Workspace workspace = mLauncher.getWorkspace();
122             final HomeAllApps allApps = workspace.switchToAllApps();
123             workspace = allApps.getAppIcon(DUMMY_APP_NAME).uninstall();
124             verifyAppUninstalledFromAllApps(workspace, DUMMY_APP_NAME);
125         } finally {
126             TestUtil.uninstallDummyApp();
127         }
128     }
129 
130     /**
131      * Adds three icons to the workspace and removes one of them by dragging to uninstall.
132      */
133     @Test
134     @PlatinumTest(focusArea = "launcher")
135     @ScreenRecordRule.ScreenRecord // b/319501259
uninstallWorkspaceIcon()136     public void uninstallWorkspaceIcon() throws IOException {
137         Point[] gridPositions = TestUtil.getCornersAndCenterPositions(mLauncher);
138         StringBuilder sb = new StringBuilder();
139         for (Point p : gridPositions) {
140             sb.append(p).append(", ");
141         }
142         Log.d(ICON_MISSING, "allGridPositions: " + sb);
143         try {
144             installDummyAppAndWaitForUIUpdate();
145 
146             final String[] appNameCandidates = {DUMMY_APP_NAME, MAPS_APP_NAME, STORE_APP_NAME};
147 
148             // List of test apps trimmed down to the length of grid positions.
149             final String[] appNames = Arrays.copyOfRange(
150                     appNameCandidates,
151                     0, Math.min(gridPositions.length, appNameCandidates.length));
152 
153             for (int i = 0; i < appNames.length; ++i) {
154                 Log.d(UIOBJECT_STALE_ELEMENT, "creatingShortcut for: " + appNames[i]);
155                 createShortcutIfNotExist(appNames[i], gridPositions[i]);
156             }
157 
158             Map<String, Point> initialPositions =
159                     mLauncher.getWorkspace().getWorkspaceIconsPositions();
160             assertThat(initialPositions.keySet()).containsAtLeastElementsIn(appNames);
161 
162             mLauncher.getWorkspace().getWorkspaceAppIcon(DUMMY_APP_NAME).uninstall();
163             mLauncher.getWorkspace().verifyWorkspaceAppIconIsGone(
164                     DUMMY_APP_NAME + " was expected to disappear after uninstall.", DUMMY_APP_NAME);
165 
166             Log.d(UIOBJECT_STALE_ELEMENT, "second getWorkspaceIconsPositions()");
167             Map<String, Point> finalPositions =
168                     mLauncher.getWorkspace().getWorkspaceIconsPositions();
169             assertThat(finalPositions).doesNotContainKey(DUMMY_APP_NAME);
170         } finally {
171             TestUtil.uninstallDummyApp();
172         }
173     }
174 
175     /**
176      * Drag icon from the Hotseat to the delete drop target
177      */
178     @Test
179     @PortraitLandscape
180     @ScreenRecordRule.ScreenRecord // b/338869019
181     @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/338869019
testAddDeleteShortcutOnHotseat()182     public void testAddDeleteShortcutOnHotseat() {
183         mLauncher.getWorkspace()
184                 .deleteAppIcon(mLauncher.getWorkspace().getHotseatAppIcon(0))
185                 .switchToAllApps()
186                 .getAppIcon(TEST_APP_NAME)
187                 .dragToHotseat(0);
188         mLauncher.getWorkspace().deleteAppIcon(
189                 mLauncher.getWorkspace().getHotseatAppIcon(TEST_APP_NAME));
190     }
191 }
192