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