1 /*
2  * Copyright (C) 2022 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.LauncherInstrumentation.DEFAULT_POLL_INTERVAL;
20 import static com.android.launcher3.tapl.LauncherInstrumentation.TASKBAR_RES_ID;
21 import static com.android.launcher3.tapl.LauncherInstrumentation.WAIT_TIME_MS;
22 import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_DISABLE_BLOCK_TIMEOUT;
23 import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_ENABLE_BLOCK_TIMEOUT;
24 import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_SHELL_DRAG_READY;
25 import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_STASHED_TASKBAR_SCALE;
26 import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_TASKBAR_FROM_NAV_THRESHOLD;
27 import static com.android.launcher3.testing.shared.TestProtocol.TEST_INFO_RESPONSE_FIELD;
28 
29 import android.graphics.Point;
30 import android.graphics.Rect;
31 import android.os.SystemClock;
32 import android.view.InputDevice;
33 import android.view.MotionEvent;
34 import android.view.ViewConfiguration;
35 
36 import androidx.test.uiautomator.Condition;
37 import androidx.test.uiautomator.UiDevice;
38 
39 import com.android.launcher3.testing.shared.ResourceUtils;
40 import com.android.launcher3.testing.shared.TestProtocol;
41 
42 /**
43  * Background state operations specific to when an app has been launched.
44  */
45 public final class LaunchedAppState extends Background {
46 
47     // More drag steps than Launchables to give the window manager time to register the drag.
48     private static final int DEFAULT_DRAG_STEPS = 35;
49 
50     // UNSTASHED_TASKBAR_HANDLE_HINT_SCALE value from TaskbarStashController.
51     private static final float UNSTASHED_TASKBAR_HANDLE_HINT_SCALE = 1.1f;
52 
53     private static final int STASHED_TASKBAR_BOTTOM_EDGE_DP = 1;
54 
55     private final Condition<UiDevice, Boolean> mStashedTaskbarHintScaleCondition =
56             device -> Math.abs(mLauncher.getTestInfo(REQUEST_STASHED_TASKBAR_SCALE).getFloat(
57                     TestProtocol.TEST_INFO_RESPONSE_FIELD) - UNSTASHED_TASKBAR_HANDLE_HINT_SCALE)
58                     < 0.00001f;
59 
60     private final Condition<UiDevice, Boolean> mStashedTaskbarDefaultScaleCondition =
61             device -> Math.abs(mLauncher.getTestInfo(REQUEST_STASHED_TASKBAR_SCALE).getFloat(
62                     TestProtocol.TEST_INFO_RESPONSE_FIELD) - 1f) < 0.00001f;
63 
64     LaunchedAppState(LauncherInstrumentation launcher) {
65         super(launcher);
66     }
67 
68     @Override
69     protected LauncherInstrumentation.ContainerType getContainerType() {
70         return LauncherInstrumentation.ContainerType.LAUNCHED_APP;
71     }
72 
73     @Override
74     public boolean isHomeState() {
75         return false;
76     }
77 
78     /**
79      * Returns the taskbar.
80      *
81      * The taskbar must already be visible when calling this method.
82      */
83     public Taskbar getTaskbar() {
84         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
85                 "want to get the taskbar")) {
86             return new Taskbar(mLauncher);
87         }
88     }
89 
90     /**
91      * Waits for the taskbar to be hidden, or fails.
92      */
93     public void assertTaskbarHidden() {
94         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
95                 "waiting for taskbar to be hidden")) {
96             mLauncher.waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID);
97         }
98     }
99 
100     /**
101      * Waits for the taskbar to be visible, or fails.
102      */
103     public void assertTaskbarVisible() {
104         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
105                 "waiting for taskbar to be visible")) {
106             mLauncher.waitForSystemLauncherObject(TASKBAR_RES_ID);
107         }
108     }
109 
110     /**
111      * Returns the Taskbar in a visible state.
112      *
113      * The taskbar must already be hidden and in transient mode when calling this method.
114      */
115     public Taskbar swipeUpToUnstashTaskbar() {
116         mLauncher.assertTrue("Taskbar is not transient, swipe up not supported",
117                 mLauncher.isTransientTaskbar());
118 
119         mLauncher.getTestInfo(REQUEST_ENABLE_BLOCK_TIMEOUT);
120 
121         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
122              LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
123                      "want to swipe up to unstash the taskbar")) {
124             mLauncher.waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID);
125 
126             int taskbarFromNavThreshold = mLauncher.getTestInfo(REQUEST_TASKBAR_FROM_NAV_THRESHOLD)
127                     .getInt(TEST_INFO_RESPONSE_FIELD);
128             int startX = mLauncher.getRealDisplaySize().x / 2;
129             int startY = mLauncher.getRealDisplaySize().y - 1;
130             int endX = startX;
131             int endY = startY - taskbarFromNavThreshold;
132 
133             mLauncher.executeAndWaitForLauncherStop(
134                     () -> mLauncher.linearGesture(startX, startY, endX, endY, 10,
135                             /* slowDown= */ true,
136                             LauncherInstrumentation.GestureScope.EXPECT_PILFER),
137                     "swiping");
138             LauncherInstrumentation.log("swipeUpToUnstashTaskbar: sent linear swipe up gesture");
139 
140             return new Taskbar(mLauncher);
141         } finally {
142             mLauncher.getTestInfo(REQUEST_DISABLE_BLOCK_TIMEOUT);
143         }
144     }
145 
146     static void dragToSplitscreen(
147             LauncherInstrumentation launcher,
148             Launchable launchable,
149             String expectedNewPackageName,
150             String expectedExistingPackageName) {
151         try (LauncherInstrumentation.Closable c1 = launcher.addContextLayer(
152                 "want to drag taskbar item to splitscreen")) {
153             final Point displaySize = launcher.getRealDisplaySize();
154             // Drag to the center of the top-left quadrant of the screen, this point will work in
155             // both portrait and landscape.
156             final Point endPoint = new Point(displaySize.x / 4, displaySize.y / 4);
157             final long downTime = SystemClock.uptimeMillis();
158             // Use mObject before starting drag since the system drag and drop moves the original
159             // view.
160             Point itemVisibleCenter = launchable.mObject.getVisibleCenter();
161             Rect itemVisibleBounds = launcher.getVisibleBounds(launchable.mObject);
162             String itemLabel = launchable.mObject.getText();
163 
164             Point dragStart = launchable.startDrag(
165                     downTime,
166                     launchable::addExpectedEventsForLongClick,
167                     /* runToSpringLoadedState= */ false);
168 
169             try (LauncherInstrumentation.Closable c2 = launcher.addContextLayer(
170                     "started item drag")) {
171                 launcher.assertTrue("Shell drag not marked as ready", launcher.waitAndGet(() -> {
172                     LauncherInstrumentation.log("Checking shell drag ready");
173                     return launcher.getTestInfo(REQUEST_SHELL_DRAG_READY)
174                             .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, false);
175                 }, WAIT_TIME_MS, DEFAULT_POLL_INTERVAL));
176 
177                 launcher.movePointer(
178                         dragStart,
179                         endPoint,
180                         DEFAULT_DRAG_STEPS,
181                         /* isDecelerating= */ true,
182                         downTime,
183                         SystemClock.uptimeMillis(),
184                         /* slowDown= */ false,
185                         LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
186 
187                 try (LauncherInstrumentation.Closable c3 = launcher.addContextLayer(
188                         "moved pointer to drop point")) {
189                     LauncherInstrumentation.log("SplitscreenDragSource.dragToSplitscreen: "
190                             + "before drop " + itemVisibleCenter + " in " + itemVisibleBounds);
191                     launcher.sendPointer(
192                             downTime,
193                             SystemClock.uptimeMillis(),
194                             MotionEvent.ACTION_UP,
195                             endPoint,
196                             LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
197                     LauncherInstrumentation.log("SplitscreenDragSource.dragToSplitscreen: "
198                             + "after drop");
199 
200                     try (LauncherInstrumentation.Closable c4 = launcher.addContextLayer(
201                             "dropped item")) {
202                         launcher.assertAppLaunched(expectedNewPackageName);
203                         launcher.assertAppLaunched(expectedExistingPackageName);
204                     }
205                 }
206             }
207         }
208     }
209 
210     /**
211      * Emulate the cursor hovering the screen edge to unstash the taskbar.
212      *
213      * <p>This unstashing occurs when not actively hovering the taskbar.
214      */
215     public Taskbar hoverScreenBottomEdgeToUnstashTaskbar() {
216         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
217              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
218                      "cursor hover entering screen edge to unstash taskbar")) {
219             mLauncher.getDevice().wait(mStashedTaskbarDefaultScaleCondition,
220                     ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT);
221 
222             long downTime = SystemClock.uptimeMillis();
223             int leftEdge = 10;
224             Point taskbarUnstashArea = new Point(leftEdge, mLauncher.getRealDisplaySize().y - 1);
225             mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_HOVER_ENTER,
226                     new Point(taskbarUnstashArea.x, taskbarUnstashArea.y), null,
227                     InputDevice.SOURCE_MOUSE);
228 
229             mLauncher.waitForSystemLauncherObject(TASKBAR_RES_ID);
230 
231             mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_HOVER_EXIT,
232                     new Point(taskbarUnstashArea.x, taskbarUnstashArea.y), null,
233                     InputDevice.SOURCE_MOUSE);
234 
235             return new Taskbar(mLauncher);
236         }
237     }
238 
239     /**
240      * Emulate the cursor hovering the taskbar to get unstash hint, then hovering below to unstash.
241      */
242     public Taskbar hoverBelowHintedTaskbarToUnstash() {
243         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
244              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
245                      "cursor hover entering stashed taskbar")) {
246             long downTime = SystemClock.uptimeMillis();
247             Point stashedTaskbarHintArea = new Point(mLauncher.getRealDisplaySize().x / 2,
248                     mLauncher.getRealDisplaySize().y - 1);
249             mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_HOVER_ENTER,
250                     new Point(stashedTaskbarHintArea.x, stashedTaskbarHintArea.y), null,
251                     InputDevice.SOURCE_MOUSE);
252 
253             mLauncher.getDevice().wait(mStashedTaskbarHintScaleCondition,
254                     LauncherInstrumentation.WAIT_TIME_MS);
255 
256             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
257                          "cursor hover enter below taskbar to unstash")) {
258                 downTime = SystemClock.uptimeMillis();
259                 Point taskbarUnstashArea = new Point(mLauncher.getRealDisplaySize().x / 2,
260                         mLauncher.getRealDisplaySize().y - 1);
261                 mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_HOVER_EXIT,
262                         new Point(taskbarUnstashArea.x, taskbarUnstashArea.y), null,
263                         InputDevice.SOURCE_MOUSE);
264 
265                 mLauncher.waitForSystemLauncherObject(TASKBAR_RES_ID);
266                 return new Taskbar(mLauncher);
267             }
268         }
269     }
270 
271     /**
272      * Emulate the cursor entering and exiting a hover over the taskbar.
273      */
274     public void hoverToShowTaskbarUnstashHint() {
275         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
276              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
277                      "cursor hover entering stashed taskbar")) {
278             long downTime = SystemClock.uptimeMillis();
279             Point stashedTaskbarHintArea = new Point(mLauncher.getRealDisplaySize().x / 2,
280                     mLauncher.getRealDisplaySize().y - 1);
281             mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_HOVER_ENTER,
282                     new Point(stashedTaskbarHintArea.x, stashedTaskbarHintArea.y), null,
283                     InputDevice.SOURCE_MOUSE);
284 
285             mLauncher.getDevice().wait(mStashedTaskbarHintScaleCondition,
286                     LauncherInstrumentation.WAIT_TIME_MS);
287 
288             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
289                          "cursor hover exiting stashed taskbar")) {
290                 Point outsideStashedTaskbarHintArea = new Point(
291                         mLauncher.getRealDisplaySize().x / 2,
292                         mLauncher.getRealDisplaySize().y - 500);
293                 mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_HOVER_EXIT,
294                         new Point(outsideStashedTaskbarHintArea.x, outsideStashedTaskbarHintArea.y),
295                         null, InputDevice.SOURCE_MOUSE);
296 
297                 mLauncher.getDevice().wait(mStashedTaskbarDefaultScaleCondition,
298                         LauncherInstrumentation.WAIT_TIME_MS);
299             }
300         }
301     }
302 
303     /**
304      * Emulate the cursor clicking the stashed taskbar to go home.
305      */
306     public Workspace clickStashedTaskbarToGoHome() {
307         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
308              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
309                      "cursor hover entering stashed taskbar")) {
310             long downTime = SystemClock.uptimeMillis();
311             int stashedTaskbarBottomEdge = ResourceUtils.pxFromDp(STASHED_TASKBAR_BOTTOM_EDGE_DP,
312                     mLauncher.getResources().getDisplayMetrics());
313             Point stashedTaskbarHintArea = new Point(mLauncher.getRealDisplaySize().x / 2,
314                     mLauncher.getRealDisplaySize().y - stashedTaskbarBottomEdge - 1);
315             mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_HOVER_ENTER,
316                     new Point(stashedTaskbarHintArea.x, stashedTaskbarHintArea.y), null,
317                     InputDevice.SOURCE_MOUSE);
318 
319             mLauncher.getDevice().wait(mStashedTaskbarHintScaleCondition,
320                     LauncherInstrumentation.WAIT_TIME_MS);
321 
322             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
323                     "cursor clicking stashed taskbar to go home")) {
324                 mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_HOVER_EXIT,
325                         new Point(stashedTaskbarHintArea.x, stashedTaskbarHintArea.y),
326                         null, InputDevice.SOURCE_MOUSE);
327                 mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN,
328                         new Point(stashedTaskbarHintArea.x, stashedTaskbarHintArea.y),
329                         LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER,
330                         InputDevice.SOURCE_MOUSE);
331                 mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_BUTTON_PRESS,
332                         new Point(stashedTaskbarHintArea.x, stashedTaskbarHintArea.y),
333                         null, InputDevice.SOURCE_MOUSE);
334                 mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_BUTTON_RELEASE,
335                         new Point(stashedTaskbarHintArea.x, stashedTaskbarHintArea.y),
336                         null, InputDevice.SOURCE_MOUSE);
337                 mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_UP,
338                         new Point(stashedTaskbarHintArea.x, stashedTaskbarHintArea.y),
339                         LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER,
340                         InputDevice.SOURCE_MOUSE);
341 
342                 return mLauncher.getWorkspace();
343             }
344         }
345     }
346 
347     /** Send the "back" gesture to go to workspace. */
348     public Workspace pressBackToWorkspace() {
349         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
350              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
351                      "want to press back from launched app to workspace")) {
352             if (mLauncher.isLauncher3()) {
353                 mLauncher.pressBackImpl();
354             } else {
355                 mLauncher.executeAndWaitForWallpaperAnimation(
356                         () -> mLauncher.pressBackImpl(),
357                         "pressing back"
358                 );
359             }
360             return new Workspace(mLauncher);
361         }
362     }
363 }
364