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 android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
20 
21 import static com.android.launcher3.tapl.OverviewTask.TASK_START_EVENT;
22 import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
23 
24 import android.graphics.Point;
25 import android.os.SystemClock;
26 import android.view.MotionEvent;
27 
28 import androidx.annotation.NonNull;
29 import androidx.test.uiautomator.UiObject2;
30 
31 import com.android.launcher3.testing.TestProtocol;
32 
33 import java.util.List;
34 import java.util.regex.Pattern;
35 
36 /**
37  * Indicates the base state with a UI other than Overview running as foreground. It can also
38  * indicate Launcher as long as Launcher is not in Overview state.
39  */
40 public class Background extends LauncherInstrumentation.VisibleContainer {
41     private static final int ZERO_BUTTON_SWIPE_UP_GESTURE_DURATION = 500;
42     private static final Pattern SQUARE_BUTTON_EVENT = Pattern.compile("onOverviewToggle");
43 
Background(LauncherInstrumentation launcher)44     Background(LauncherInstrumentation launcher) {
45         super(launcher);
46     }
47 
48     @Override
getContainerType()49     protected LauncherInstrumentation.ContainerType getContainerType() {
50         return LauncherInstrumentation.ContainerType.BACKGROUND;
51     }
52 
53     /**
54      * Swipes up or presses the square button to switch to Overview.
55      * Returns the base overview, which can be either in Launcher or the fallback recents.
56      *
57      * @return the Overview panel object.
58      */
59     @NonNull
switchToOverview()60     public BaseOverview switchToOverview() {
61         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
62              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
63                      "want to switch from background to overview")) {
64             verifyActiveContainer();
65             goToOverviewUnchecked();
66             return mLauncher.isFallbackOverview()
67                     ? new BaseOverview(mLauncher) : new Overview(mLauncher);
68         }
69     }
70 
71 
zeroButtonToOverviewGestureStartsInLauncher()72     protected boolean zeroButtonToOverviewGestureStartsInLauncher() {
73         return mLauncher.isTablet();
74     }
75 
zeroButtonToOverviewGestureStateTransitionWhileHolding()76     protected boolean zeroButtonToOverviewGestureStateTransitionWhileHolding() {
77         return false;
78     }
79 
goToOverviewUnchecked()80     protected void goToOverviewUnchecked() {
81         switch (mLauncher.getNavigationModel()) {
82             case ZERO_BUTTON: {
83                 sendDownPointerToEnterOverviewToLauncher();
84                 String swipeAndHoldToEnterOverviewActionName =
85                         "swiping and holding to enter overview";
86                 // If swiping from an app (e.g. Overview is in Background), we pause and hold on
87                 // swipe up to make overview appear, or else swiping without holding would take
88                 // us to the Home state. If swiping up from Home (e.g. Overview in Home or
89                 // Workspace state where the below condition is true), there is no need to pause,
90                 // and we will not test for an intermediate carousel as one will not exist.
91                 if (zeroButtonToOverviewGestureStateTransitionWhileHolding()) {
92                     mLauncher.runToState(this::sendSwipeUpAndHoldToEnterOverviewGestureToLauncher,
93                             OVERVIEW_STATE_ORDINAL, swipeAndHoldToEnterOverviewActionName);
94                     sendUpPointerToEnterOverviewToLauncher();
95                 } else {
96                     // If swiping up from an app to overview, pause on intermediate carousel
97                     // until snapshots are visible. No intermediate carousel when swiping from
98                     // Home. The task swiped up is not a snapshot but the TaskViewSimulator. If
99                     // only a single task exists, no snapshots will be available during swipe up.
100                     mLauncher.executeAndWaitForLauncherEvent(
101                             this::sendSwipeUpAndHoldToEnterOverviewGestureToLauncher,
102                             event -> TestProtocol.PAUSE_DETECTED_MESSAGE.equals(
103                                     event.getClassName().toString()),
104                             () -> "Pause wasn't detected",
105                             swipeAndHoldToEnterOverviewActionName);
106                     try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
107                             "paused on swipe up to overview")) {
108                         if (mLauncher.getRecentTasks().size() > 1) {
109                             // When swiping up to grid-overview for tablets, the swiped tab will be
110                             // in the middle of the screen (TaskViewSimulator, not a snapshot), and
111                             // all remaining snapshots will be to the left of that task. In
112                             // non-tablet overview, snapshots can be on either side of the swiped
113                             // task, but we still check that they become visible after swiping and
114                             // pausing.
115                             mLauncher.waitForOverviewObject("snapshot");
116                             if (mLauncher.isTablet()) {
117                                 List<UiObject2> tasks = mLauncher.getDevice().findObjects(
118                                         mLauncher.getOverviewObjectSelector("snapshot"));
119                                 final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
120                                 mLauncher.assertTrue(
121                                         "All tasks not to the left of the swiped task",
122                                         tasks.stream()
123                                                 .allMatch(
124                                                         t -> t.getVisibleBounds().right < centerX));
125                             }
126 
127                         }
128                         String upPointerToEnterOverviewActionName =
129                                 "sending UP pointer to enter overview";
130                         mLauncher.runToState(this::sendUpPointerToEnterOverviewToLauncher,
131                                 OVERVIEW_STATE_ORDINAL, upPointerToEnterOverviewActionName);
132                     }
133                 }
134                 break;
135             }
136 
137             case TWO_BUTTON: {
138                 final int startX;
139                 final int startY;
140                 final int endX;
141                 final int endY;
142                 final int swipeLength = mLauncher.getTestInfo(getSwipeHeightRequestName()).
143                         getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD) + mLauncher.getTouchSlop();
144 
145                 if (mLauncher.getDevice().isNaturalOrientation()) {
146                     startX = endX = mLauncher.getDevice().getDisplayWidth() / 2;
147                     startY = getSwipeStartY();
148                     endY = startY - swipeLength;
149                 } else {
150                     startX = getSwipeStartX();
151                     // TODO(b/184059820) make horizontal swipe use swipe width not height, for the
152                     // moment just double the swipe length.
153                     endX = startX - swipeLength * 2;
154                     startY = endY = mLauncher.getDevice().getDisplayHeight() / 2;
155                 }
156 
157                 mLauncher.swipeToState(startX, startY, endX, endY, 10, OVERVIEW_STATE_ORDINAL,
158                         LauncherInstrumentation.GestureScope.OUTSIDE_WITH_PILFER);
159                 break;
160             }
161 
162             case THREE_BUTTON:
163                 if (mLauncher.isTablet()) {
164                     mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN,
165                             LauncherInstrumentation.EVENT_TOUCH_DOWN);
166                     mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN,
167                             LauncherInstrumentation.EVENT_TOUCH_UP);
168                 }
169                 mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
170                 mLauncher.runToState(
171                         () -> mLauncher.waitForNavigationUiObject("recent_apps").click(),
172                         OVERVIEW_STATE_ORDINAL, "clicking Recents button");
173                 break;
174         }
175         expectSwitchToOverviewEvents();
176     }
177 
expectSwitchToOverviewEvents()178     private void expectSwitchToOverviewEvents() {
179     }
180 
sendDownPointerToEnterOverviewToLauncher()181     private void sendDownPointerToEnterOverviewToLauncher() {
182         final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
183         final int startY = getSwipeStartY();
184         final Point start = new Point(centerX, startY);
185         final long downTime = SystemClock.uptimeMillis();
186         final LauncherInstrumentation.GestureScope gestureScope =
187                 zeroButtonToOverviewGestureStartsInLauncher()
188                         ? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE
189                         : LauncherInstrumentation.GestureScope.OUTSIDE_WITH_PILFER;
190 
191         mLauncher.sendPointer(
192                 downTime, downTime, MotionEvent.ACTION_DOWN, start, gestureScope);
193     }
194 
sendSwipeUpAndHoldToEnterOverviewGestureToLauncher()195     private void sendSwipeUpAndHoldToEnterOverviewGestureToLauncher() {
196         final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
197         final int startY = getSwipeStartY();
198         final int swipeHeight = mLauncher.getTestInfo(getSwipeHeightRequestName()).getInt(
199                 TestProtocol.TEST_INFO_RESPONSE_FIELD);
200         final Point start = new Point(centerX, startY);
201         final Point end =
202                 new Point(centerX, startY - swipeHeight - mLauncher.getTouchSlop());
203         final long downTime = SystemClock.uptimeMillis();
204         final LauncherInstrumentation.GestureScope gestureScope =
205                 zeroButtonToOverviewGestureStartsInLauncher()
206                         ? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE
207                         : LauncherInstrumentation.GestureScope.OUTSIDE_WITH_PILFER;
208 
209         mLauncher.movePointer(
210                 downTime,
211                 downTime,
212                 ZERO_BUTTON_SWIPE_UP_GESTURE_DURATION,
213                 start,
214                 end,
215                 gestureScope);
216     }
217 
sendUpPointerToEnterOverviewToLauncher()218     private void sendUpPointerToEnterOverviewToLauncher() {
219         final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
220         final int startY = getSwipeStartY();
221         final int swipeHeight = mLauncher.getTestInfo(getSwipeHeightRequestName()).getInt(
222                 TestProtocol.TEST_INFO_RESPONSE_FIELD);
223         final Point end =
224                 new Point(centerX, startY - swipeHeight - mLauncher.getTouchSlop());
225         final long downTime = SystemClock.uptimeMillis();
226         final LauncherInstrumentation.GestureScope gestureScope =
227                 zeroButtonToOverviewGestureStartsInLauncher()
228                         ? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE
229                         : LauncherInstrumentation.GestureScope.OUTSIDE_WITH_PILFER;
230 
231         mLauncher.sendPointer(downTime, SystemClock.uptimeMillis(),
232                 MotionEvent.ACTION_UP, end, gestureScope);
233     }
234 
235     @NonNull
quickSwitchToPreviousApp()236     public Background quickSwitchToPreviousApp() {
237         boolean toRight = true;
238         quickSwitch(toRight);
239         return new Background(mLauncher);
240     }
241 
242     @NonNull
quickSwitchToPreviousAppSwipeLeft()243     public Background quickSwitchToPreviousAppSwipeLeft() {
244         boolean toRight = false;
245         quickSwitch(toRight);
246         return new Background(mLauncher);
247     }
248 
249     @NonNull
quickSwitch(boolean toRight)250     private void quickSwitch(boolean toRight) {
251         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
252              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
253                      "want to quick switch to the previous app")) {
254             verifyActiveContainer();
255             final boolean launcherWasVisible = mLauncher.isLauncherVisible();
256             boolean transposeInLandscape = false;
257             switch (mLauncher.getNavigationModel()) {
258                 case TWO_BUTTON:
259                     transposeInLandscape = true;
260                     // Fall through, zero button and two button modes behave the same.
261                 case ZERO_BUTTON: {
262                     final int startX;
263                     final int startY;
264                     final int endX;
265                     final int endY;
266                     final int cornerRadius = (int) Math.ceil(mLauncher.getWindowCornerRadius());
267                     if (toRight) {
268                         if (mLauncher.getDevice().isNaturalOrientation() || !transposeInLandscape) {
269                             // Swipe from the bottom left to the bottom right of the screen.
270                             startX = cornerRadius;
271                             startY = getSwipeStartY();
272                             endX = mLauncher.getDevice().getDisplayWidth() - cornerRadius;
273                             endY = startY;
274                         } else {
275                             // Swipe from the bottom right to the top right of the screen.
276                             startX = getSwipeStartX();
277                             startY = mLauncher.getRealDisplaySize().y - 1 - cornerRadius;
278                             endX = startX;
279                             endY = cornerRadius;
280                         }
281                     } else {
282                         if (mLauncher.getDevice().isNaturalOrientation() || !transposeInLandscape) {
283                             // Swipe from the bottom right to the bottom left of the screen.
284                             startX = mLauncher.getDevice().getDisplayWidth() - cornerRadius;
285                             startY = getSwipeStartY();
286                             endX = cornerRadius;
287                             endY = startY;
288                         } else {
289                             // Swipe from the bottom left to the top left of the screen.
290                             startX = getSwipeStartX();
291                             startY = cornerRadius;
292                             endX = startX;
293                             endY = mLauncher.getRealDisplaySize().y - 1 - cornerRadius;
294                         }
295                     }
296 
297                     final boolean isZeroButton = mLauncher.getNavigationModel()
298                             == LauncherInstrumentation.NavigationModel.ZERO_BUTTON;
299                     LauncherInstrumentation.GestureScope gestureScope =
300                             launcherWasVisible && isZeroButton
301                                     ? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE
302                                     : LauncherInstrumentation.GestureScope.OUTSIDE_WITH_PILFER;
303                     mLauncher.executeAndWaitForEvent(
304                             () -> mLauncher.linearGesture(
305                                     startX, startY, endX, endY, 20, false, gestureScope),
306                             event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
307                             () -> "Quick switch gesture didn't change window state", "swiping");
308                     break;
309                 }
310 
311                 case THREE_BUTTON:
312                     // Double press the recents button.
313                     UiObject2 recentsButton = mLauncher.waitForNavigationUiObject("recent_apps");
314                     if (mLauncher.isTablet()) {
315                         mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN,
316                                 LauncherInstrumentation.EVENT_TOUCH_DOWN);
317                         mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN,
318                                 LauncherInstrumentation.EVENT_TOUCH_UP);
319                     }
320                     mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
321                     mLauncher.runToState(() -> recentsButton.click(), OVERVIEW_STATE_ORDINAL,
322                             "clicking Recents button for the first time");
323                     mLauncher.getOverview();
324                     if (mLauncher.isTablet()) {
325                         mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN,
326                                 LauncherInstrumentation.EVENT_TOUCH_DOWN);
327                         mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN,
328                                 LauncherInstrumentation.EVENT_TOUCH_UP);
329                     }
330                     mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
331                     mLauncher.executeAndWaitForEvent(
332                             () -> recentsButton.click(),
333                             event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
334                             () -> "Pressing recents button didn't change window state",
335                             "clicking Recents button for the second time");
336                     break;
337             }
338             mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT);
339             return;
340         }
341     }
342 
getSwipeHeightRequestName()343     protected String getSwipeHeightRequestName() {
344         return TestProtocol.REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT;
345     }
346 
getSwipeStartX()347     protected int getSwipeStartX() {
348         return mLauncher.getRealDisplaySize().x - 1;
349     }
350 
getSwipeStartY()351     protected int getSwipeStartY() {
352         return mLauncher.getRealDisplaySize().y - 1;
353     }
354 }
355