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.launcher3.tapl;
18 
19 import static com.android.launcher3.tapl.BaseOverview.TASK_SELECTOR;
20 import static com.android.launcher3.tapl.OverviewTask.TASK_START_EVENT;
21 import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL;
22 
23 import android.graphics.Point;
24 import android.os.SystemClock;
25 import android.view.MotionEvent;
26 
27 import androidx.annotation.NonNull;
28 import androidx.test.uiautomator.UiObject2;
29 
30 import com.android.launcher3.tapl.LauncherInstrumentation.NavigationModel;
31 import com.android.launcher3.tapl.LauncherInstrumentation.TrackpadGestureType;
32 import com.android.launcher3.tapl.OverviewTask.TaskViewType;
33 import com.android.launcher3.testing.shared.TestProtocol;
34 
35 import java.util.List;
36 import java.util.regex.Pattern;
37 
38 /**
39  * Indicates the base state with a UI other than Overview running as foreground. It can also
40  * indicate Launcher as long as Launcher is not in Overview state.
41  */
42 public abstract class Background extends LauncherInstrumentation.VisibleContainer
43         implements KeyboardQuickSwitchSource {
44     private static final int ZERO_BUTTON_SWIPE_UP_GESTURE_DURATION = 500;
45     private static final Pattern SQUARE_BUTTON_EVENT = Pattern.compile("onOverviewToggle");
46 
Background(LauncherInstrumentation launcher)47     Background(LauncherInstrumentation launcher) {
48         super(launcher);
49     }
50 
51     @Override
getLauncher()52     public LauncherInstrumentation getLauncher() {
53         return mLauncher;
54     }
55 
56     @Override
getStartingContainerType()57     public LauncherInstrumentation.ContainerType getStartingContainerType() {
58         return getContainerType();
59     }
60 
61     /**
62      * Swipes up or presses the square button to switch to Overview.
63      * Returns the base overview, which can be either in Launcher or the fallback recents.
64      *
65      * @return the Overview panel object.
66      */
67     @NonNull
switchToOverview()68     public BaseOverview switchToOverview() {
69         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
70              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
71                      "want to switch from background to overview")) {
72             verifyActiveContainer();
73             goToOverviewUnchecked();
74             return mLauncher.is3PLauncher()
75                     ? new BaseOverview(mLauncher) : new Overview(mLauncher);
76         }
77     }
78 
79 
zeroButtonToOverviewGestureStateTransitionWhileHolding()80     protected boolean zeroButtonToOverviewGestureStateTransitionWhileHolding() {
81         return false;
82     }
83 
goToOverviewUnchecked()84     protected void goToOverviewUnchecked() {
85         if (mLauncher.getNavigationModel() == NavigationModel.ZERO_BUTTON
86                 || mLauncher.getTrackpadGestureType() == TrackpadGestureType.THREE_FINGER) {
87             final long downTime = SystemClock.uptimeMillis();
88             sendDownPointerToEnterOverviewToLauncher(downTime);
89             String swipeAndHoldToEnterOverviewActionName =
90                     "swiping and holding to enter overview";
91             // If swiping from an app (e.g. Overview is in Background), we pause and hold on
92             // swipe up to make overview appear, or else swiping without holding would take
93             // us to the Home state. If swiping up from Home (e.g. Overview in Home or
94             // Workspace state where the below condition is true), there is no need to pause,
95             // and we will not test for an intermediate carousel as one will not exist.
96             if (zeroButtonToOverviewGestureStateTransitionWhileHolding()) {
97                 mLauncher.runToState(
98                         () -> sendSwipeUpAndHoldToEnterOverviewGestureToLauncher(downTime),
99                         OVERVIEW_STATE_ORDINAL, swipeAndHoldToEnterOverviewActionName);
100                 sendUpPointerToEnterOverviewToLauncher(downTime);
101             } else {
102                 // If swiping up from an app to overview, pause on intermediate carousel
103                 // until snapshots are visible. No intermediate carousel when swiping from
104                 // Home. The task swiped up is not a snapshot but the TaskViewSimulator. If
105                 // only a single task exists, no snapshots will be available during swipe up.
106                 mLauncher.executeAndWaitForLauncherEvent(
107                         () -> sendSwipeUpAndHoldToEnterOverviewGestureToLauncher(downTime),
108                         event -> TestProtocol.PAUSE_DETECTED_MESSAGE.equals(
109                                 event.getClassName().toString()),
110                         () -> "Pause wasn't detected",
111                         swipeAndHoldToEnterOverviewActionName);
112                 try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
113                         "paused on swipe up to overview")) {
114                     if (mLauncher.getRecentTasks().size() > 1) {
115                         // When swiping up to grid-overview for tablets, the swiped tab will be
116                         // in the middle of the screen (TaskViewSimulator, not a snapshot), and
117                         // all remaining snapshots will be to the left of that task. In
118                         // non-tablet overview, snapshots can be on either side of the swiped
119                         // task, but we still check that they become visible after swiping and
120                         // pausing.
121                         mLauncher.waitForObjectBySelector(TASK_SELECTOR);
122                         if (mLauncher.isTablet()) {
123                             List<UiObject2> tasks = mLauncher.getDevice().findObjects(
124                                     TASK_SELECTOR);
125 
126                             final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
127                             UiObject2 centerTask = tasks.stream()
128                                     .filter(t -> t.getVisibleCenter().x == centerX)
129                                     .findFirst()
130                                     .orElse(null);
131 
132                             if (centerTask != null) {
133                                 mLauncher.assertTrue(
134                                         "Task(s) found to the right of the swiped task",
135                                         tasks.stream()
136                                                 .filter(t -> t != centerTask
137                                                         && OverviewTask.getType(t)
138                                                         != TaskViewType.DESKTOP)
139                                                 .allMatch(t -> t.getVisibleBounds().right
140                                                         < centerTask.getVisibleBounds().left));
141                                 mLauncher.assertTrue(
142                                         "DesktopTask(s) found to the left of the swiped task",
143                                         tasks.stream()
144                                                 .filter(t -> t != centerTask
145                                                         && OverviewTask.getType(t)
146                                                         == TaskViewType.DESKTOP)
147                                                 .allMatch(t -> t.getVisibleBounds().left
148                                                         > centerTask.getVisibleBounds().right));
149                             }
150                         }
151 
152                     }
153                     String upPointerToEnterOverviewActionName =
154                             "sending UP pointer to enter overview";
155                     mLauncher.runToState(() -> sendUpPointerToEnterOverviewToLauncher(downTime),
156                             OVERVIEW_STATE_ORDINAL, upPointerToEnterOverviewActionName);
157                 }
158             }
159         } else {
160             mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
161             mLauncher.runToState(
162                     () -> mLauncher.waitForNavigationUiObject("recent_apps").click(),
163                     OVERVIEW_STATE_ORDINAL, "clicking Recents button");
164         }
165         expectSwitchToOverviewEvents();
166     }
167 
expectSwitchToOverviewEvents()168     private void expectSwitchToOverviewEvents() {
169     }
170 
sendDownPointerToEnterOverviewToLauncher(long downTime)171     private void sendDownPointerToEnterOverviewToLauncher(long downTime) {
172         final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
173         final int startY = getSwipeStartY();
174         final Point start = new Point(centerX, startY);
175 
176         mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, start,
177                 LauncherInstrumentation.GestureScope.EXPECT_PILFER);
178 
179         if (!mLauncher.isLauncher3()) {
180             mLauncher.expectEvent(TestProtocol.SEQUENCE_PILFER,
181                     LauncherInstrumentation.EVENT_PILFER_POINTERS);
182         }
183     }
184 
sendSwipeUpAndHoldToEnterOverviewGestureToLauncher(long downTime)185     private void sendSwipeUpAndHoldToEnterOverviewGestureToLauncher(long downTime) {
186         final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
187         final int startY = getSwipeStartY();
188         final int swipeHeight = mLauncher.getTestInfo(getSwipeHeightRequestName()).getInt(
189                 TestProtocol.TEST_INFO_RESPONSE_FIELD);
190         final Point start = new Point(centerX, startY);
191         final Point end =
192                 new Point(centerX, startY - swipeHeight - mLauncher.getTouchSlop());
193 
194         mLauncher.movePointer(
195                 downTime,
196                 downTime,
197                 ZERO_BUTTON_SWIPE_UP_GESTURE_DURATION,
198                 start,
199                 end,
200                 LauncherInstrumentation.GestureScope.EXPECT_PILFER);
201     }
202 
sendUpPointerToEnterOverviewToLauncher(long downTime)203     private void sendUpPointerToEnterOverviewToLauncher(long downTime) {
204         final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
205         final int startY = getSwipeStartY();
206         final int swipeHeight = mLauncher.getTestInfo(getSwipeHeightRequestName()).getInt(
207                 TestProtocol.TEST_INFO_RESPONSE_FIELD);
208         final Point end =
209                 new Point(centerX, startY - swipeHeight - mLauncher.getTouchSlop());
210 
211         mLauncher.sendPointer(downTime, SystemClock.uptimeMillis(),
212                 MotionEvent.ACTION_UP, end,
213                 LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
214     }
215 
216     /**
217      * Quick switching to the app with swiping to right.
218      */
219     @NonNull
quickSwitchToPreviousApp()220     public LaunchedAppState quickSwitchToPreviousApp() {
221         quickSwitch(true /* toRight */);
222         return new LaunchedAppState(mLauncher);
223     }
224 
225     /**
226      * Quick switching to the app with swiping to left.
227      */
228     @NonNull
quickSwitchToPreviousAppSwipeLeft()229     public LaunchedAppState quickSwitchToPreviousAppSwipeLeft() {
230         quickSwitch(false /* toRight */);
231         return new LaunchedAppState(mLauncher);
232     }
233 
234     /**
235      * Making swipe gesture to quick-switch app tasks.
236      *
237      * @param toRight {@code true} means swiping right, {@code false} means swiping left.
238      * @throws {@link AssertionError} when failing to verify the visible UI in the container.
239      */
quickSwitch(boolean toRight)240     private void quickSwitch(boolean toRight) {
241         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
242              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
243                      "want to quick switch to the previous app")) {
244             verifyActiveContainer();
245             if (mLauncher.getNavigationModel() == NavigationModel.ZERO_BUTTON
246                     || mLauncher.getTrackpadGestureType() == TrackpadGestureType.FOUR_FINGER) {
247                 final int startX;
248                 final int startY;
249                 final int endX;
250                 final int endY;
251                 final int cornerRadius = (int) Math.ceil(mLauncher.getWindowCornerRadius());
252                 if (toRight) {
253                     // Swipe from the bottom left to the bottom right of the screen.
254                     startX = cornerRadius;
255                     startY = getSwipeStartY();
256                     endX = mLauncher.getDevice().getDisplayWidth() - cornerRadius;
257                     endY = startY;
258                 } else {
259                     // Swipe from the bottom right to the bottom left of the screen.
260                     startX = mLauncher.getDevice().getDisplayWidth() - cornerRadius;
261                     startY = getSwipeStartY();
262                     endX = cornerRadius;
263                     endY = startY;
264                 }
265 
266                 mLauncher.executeAndWaitForLauncherStop(
267                         () -> mLauncher.linearGesture(
268                                 startX, startY, endX, endY, 20, false,
269                                 LauncherInstrumentation.GestureScope.EXPECT_PILFER),
270                         "swiping");
271             } else {
272                 // Double press the recents button.
273                 UiObject2 recentsButton = mLauncher.waitForNavigationUiObject("recent_apps");
274                 mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
275                 mLauncher.runToState(() -> recentsButton.click(), OVERVIEW_STATE_ORDINAL,
276                         "clicking Recents button for the first time");
277                 mLauncher.getOverview();
278                 mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
279                 mLauncher.executeAndWaitForLauncherStop(
280                         () -> recentsButton.click(),
281                         "clicking Recents button for the second time");
282             }
283             mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT);
284         }
285     }
286 
getSwipeHeightRequestName()287     protected String getSwipeHeightRequestName() {
288         return TestProtocol.REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT;
289     }
290 
getSwipeStartX()291     protected int getSwipeStartX() {
292         return mLauncher.getRealDisplaySize().x - 1;
293     }
294 
getSwipeStartY()295     protected int getSwipeStartY() {
296         return mLauncher.getTrackpadGestureType() == TrackpadGestureType.THREE_FINGER
297                 ? mLauncher.getDevice().getDisplayHeight() * 3 / 4
298                 : mLauncher.getRealDisplaySize().y - 1;
299     }
300 }
301