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