• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 package com.android.quickstep.inputconsumers;
17 
18 import static android.view.MotionEvent.ACTION_CANCEL;
19 import static android.view.MotionEvent.ACTION_MOVE;
20 import static android.view.MotionEvent.ACTION_UP;
21 import static android.view.MotionEvent.INVALID_POINTER_ID;
22 
23 import static com.android.launcher3.Flags.enableCursorHoverStates;
24 import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
25 import static com.android.launcher3.MotionEventsUtils.isTrackpadMotionEvent;
26 import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_TOUCHING;
27 
28 import android.content.Context;
29 import android.content.res.Resources;
30 import android.graphics.PointF;
31 import android.graphics.Rect;
32 import android.os.Handler;
33 import android.os.Looper;
34 import android.view.InputDevice;
35 import android.view.MotionEvent;
36 import android.view.VelocityTracker;
37 import android.view.ViewConfiguration;
38 
39 import androidx.annotation.Nullable;
40 
41 import com.android.launcher3.DeviceProfile;
42 import com.android.launcher3.R;
43 import com.android.launcher3.taskbar.TaskbarActivityContext;
44 import com.android.launcher3.taskbar.TaskbarThresholdUtils;
45 import com.android.launcher3.taskbar.TaskbarTranslationController.TransitionCallback;
46 import com.android.launcher3.taskbar.bubbles.BubbleControllers;
47 import com.android.launcher3.touch.OverScroll;
48 import com.android.launcher3.util.DisplayController;
49 import com.android.quickstep.GestureState;
50 import com.android.quickstep.InputConsumer;
51 import com.android.quickstep.OverviewCommandHelper;
52 import com.android.systemui.shared.system.InputMonitorCompat;
53 
54 /**
55  * Listens for touch (swipe) and hover events to unstash the Taskbar.
56  */
57 public class TaskbarUnstashInputConsumer extends DelegateInputConsumer {
58 
59     private static final int HOVER_TASKBAR_UNSTASH_TIMEOUT = 500;
60 
61     private static final int NUM_MOTION_MOVE_THRESHOLD = 3;
62 
63     private static final Handler sUnstashHandler = new Handler(Looper.getMainLooper());
64 
65     private final TaskbarActivityContext mTaskbarActivityContext;
66     private final OverviewCommandHelper mOverviewCommandHelper;
67     private final float mUnstashArea;
68     private final int mTaskbarNavThreshold;
69     private final int mTaskbarNavThresholdY;
70     private final boolean mIsTaskbarAllAppsOpen;
71     private boolean mHasPassedTaskbarNavThreshold;
72     private boolean mIsInBubbleBarArea;
73     private boolean mIsVerticalGestureOverBubbleBar;
74     private boolean mIsPassedBubbleBarSlop;
75     private final int mTouchSlop;
76 
77     private final PointF mDownPos = new PointF();
78     private final PointF mLastPos = new PointF();
79     private int mActivePointerId = INVALID_POINTER_ID;
80 
81     private final boolean mIsTransientTaskbar;
82 
83     private boolean mIsStashedTaskbarHovered = false;
84     private final Rect mStashedTaskbarHandleBounds = new Rect();
85     private final Rect mBottomEdgeBounds = new Rect();
86     private final int mBottomScreenEdge;
87     private final int mStashedTaskbarBottomEdge;
88 
89     private final @Nullable TransitionCallback mTransitionCallback;
90     private final GestureState mGestureState;
91     private VelocityTracker mVelocityTracker;
92     private boolean mCanPlayTaskbarBgAlphaAnimation = true;
93     private int mMotionMoveCount = 0;
94     // Velocity defined as dp per s
95     private float mTaskbarSlowVelocityYThreshold;
96 
TaskbarUnstashInputConsumer(Context context, InputConsumer delegate, InputMonitorCompat inputMonitor, TaskbarActivityContext taskbarActivityContext, OverviewCommandHelper overviewCommandHelper, GestureState gestureState)97     public TaskbarUnstashInputConsumer(Context context, InputConsumer delegate,
98             InputMonitorCompat inputMonitor, TaskbarActivityContext taskbarActivityContext,
99             OverviewCommandHelper overviewCommandHelper, GestureState gestureState) {
100         super(delegate, inputMonitor);
101         mTaskbarActivityContext = taskbarActivityContext;
102         mOverviewCommandHelper = overviewCommandHelper;
103         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
104 
105         Resources res = context.getResources();
106         mUnstashArea = res.getDimensionPixelSize(R.dimen.taskbar_unstash_input_area);
107         mTaskbarNavThreshold = TaskbarThresholdUtils.getFromNavThreshold(res,
108                 taskbarActivityContext.getDeviceProfile());
109         mTaskbarNavThresholdY = taskbarActivityContext.getDeviceProfile().heightPx
110                 - mTaskbarNavThreshold;
111         mIsTaskbarAllAppsOpen = mTaskbarActivityContext.isTaskbarAllAppsOpen();
112 
113         mIsTransientTaskbar = DisplayController.isTransientTaskbar(context);
114         mTaskbarSlowVelocityYThreshold =
115                 res.getDimensionPixelSize(R.dimen.taskbar_slow_velocity_y_threshold);
116 
117         mBottomScreenEdge = res.getDimensionPixelSize(
118                 R.dimen.taskbar_stashed_screen_edge_hover_deadzone_height);
119         mStashedTaskbarBottomEdge =
120                 res.getDimensionPixelSize(R.dimen.taskbar_stashed_below_hover_deadzone_height);
121 
122         mTransitionCallback = mIsTransientTaskbar
123                 ? taskbarActivityContext.getTranslationCallbacks()
124                 : null;
125         mGestureState = gestureState;
126     }
127 
128     @Override
getType()129     public int getType() {
130         return TYPE_TASKBAR_STASH | TYPE_CURSOR_HOVER | mDelegate.getType();
131     }
132 
133     @Override
allowInterceptByParent()134     public boolean allowInterceptByParent() {
135         return super.allowInterceptByParent() && !mHasPassedTaskbarNavThreshold;
136     }
137 
138     @Override
onMotionEvent(MotionEvent ev)139     public void onMotionEvent(MotionEvent ev) {
140         if (enableScalingRevealHomeAnimation() && mIsTransientTaskbar) {
141             checkVelocityForTaskbarBackground(ev);
142         }
143         if (mState != STATE_ACTIVE) {
144             boolean isStashedTaskbarHovered = isMouseEvent(ev)
145                     && isStashedTaskbarHovered((int) ev.getX(), (int) ev.getY());
146             // Only show the transient task bar if the touch events are on the screen.
147             if (!isTrackpadMotionEvent(ev)) {
148                 final float x = ev.getRawX();
149                 final float y = ev.getRawY();
150                 switch (ev.getAction()) {
151                     case MotionEvent.ACTION_DOWN:
152                         mActivePointerId = ev.getPointerId(0);
153                         mDownPos.set(ev.getX(), ev.getY());
154                         mLastPos.set(mDownPos);
155 
156                         mHasPassedTaskbarNavThreshold = false;
157                         mTaskbarActivityContext.setAutohideSuspendFlag(
158                                 FLAG_AUTOHIDE_SUSPEND_TOUCHING, true);
159                         if (mTransitionCallback != null && !mIsTaskbarAllAppsOpen) {
160                             mTransitionCallback.onActionDown();
161                         }
162                         if (mIsTransientTaskbar && isInBubbleBarArea(x)) {
163                             mIsInBubbleBarArea = true;
164                         }
165                         break;
166                     case MotionEvent.ACTION_POINTER_UP:
167                         int ptrIdx = ev.getActionIndex();
168                         int ptrId = ev.getPointerId(ptrIdx);
169                         if (ptrId == mActivePointerId) {
170                             final int newPointerIdx = ptrIdx == 0 ? 1 : 0;
171                             mDownPos.set(
172                                     ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x),
173                                     ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y));
174                             mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx));
175                             mActivePointerId = ev.getPointerId(newPointerIdx);
176                         }
177                         break;
178                     case MotionEvent.ACTION_MOVE:
179                         int pointerIndex = ev.findPointerIndex(mActivePointerId);
180                         if (pointerIndex == INVALID_POINTER_ID) {
181                             break;
182                         }
183                         mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
184 
185                         float dX = mLastPos.x - mDownPos.x;
186                         float dY = mLastPos.y - mDownPos.y;
187 
188                         if (!mIsPassedBubbleBarSlop && mIsInBubbleBarArea) {
189                             boolean passedSlop =
190                                     Math.abs(dY) > mTouchSlop || Math.abs(dX) > mTouchSlop;
191                             if (passedSlop) {
192                                 mIsPassedBubbleBarSlop = true;
193                                 mIsVerticalGestureOverBubbleBar = Math.abs(dY) > Math.abs(dX);
194                                 if (mIsVerticalGestureOverBubbleBar) {
195                                     setActive(ev);
196                                 }
197                             }
198                         }
199 
200                         if (mIsTransientTaskbar) {
201                             boolean passedTaskbarNavThreshold = dY < 0
202                                     && Math.abs(dY) >= mTaskbarNavThreshold;
203 
204                             if (!mHasPassedTaskbarNavThreshold && passedTaskbarNavThreshold
205                                     && !mGestureState.isInExtendedSlopRegion()) {
206                                 mHasPassedTaskbarNavThreshold = true;
207                                 if (mIsInBubbleBarArea && mIsVerticalGestureOverBubbleBar) {
208                                     mTaskbarActivityContext.onSwipeToOpenBubblebar();
209                                 } else {
210                                     mTaskbarActivityContext.onSwipeToUnstashTaskbar();
211                                 }
212                             }
213 
214                             if (dY < 0) {
215                                 dY = -OverScroll.dampedScroll(-dY, mTaskbarNavThresholdY);
216                                 if (mTransitionCallback != null && !mIsTaskbarAllAppsOpen) {
217                                     mTransitionCallback.onActionMove(dY);
218                                 }
219                             }
220                         }
221                         break;
222                     case MotionEvent.ACTION_UP:
223                     case MotionEvent.ACTION_CANCEL:
224                         cleanupAfterMotionEvent();
225                         break;
226                     case MotionEvent.ACTION_BUTTON_RELEASE:
227                         if (isStashedTaskbarHovered) {
228                             mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_HOME);
229                         }
230                         break;
231                 }
232             }
233             boolean isMovingInBubbleBarArea = mIsInBubbleBarArea && ev.getAction() == ACTION_MOVE;
234             if (!isStashedTaskbarHovered) {
235                 // if we're moving in the bubble bar area but we haven't passed the slop yet, don't
236                 // propagate to the delegate, until we can determine the direction of the gesture.
237                 if (!isMovingInBubbleBarArea || mIsPassedBubbleBarSlop) {
238                     mDelegate.onMotionEvent(ev);
239                 }
240             }
241         } else if (mIsVerticalGestureOverBubbleBar) {
242             // if we get here then this gesture is a vertical swipe over the bubble bar.
243             // we're also active and there's no need to delegate any additional motion events. the
244             // rest of the gesture will be handled here.
245             switch (ev.getAction()) {
246                 case ACTION_MOVE:
247                     int pointerIndex = ev.findPointerIndex(mActivePointerId);
248                     if (pointerIndex == INVALID_POINTER_ID) {
249                         break;
250                     }
251                     mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
252 
253                     float dY = mLastPos.y - mDownPos.y;
254 
255                     // bubble bar swipe gesture uses the same threshold as the taskbar.
256                     boolean passedTaskbarNavThreshold = dY < 0
257                             && Math.abs(dY) >= mTaskbarNavThreshold;
258 
259                     if (!mHasPassedTaskbarNavThreshold && passedTaskbarNavThreshold) {
260                         mHasPassedTaskbarNavThreshold = true;
261                         mTaskbarActivityContext.onSwipeToOpenBubblebar();
262                     }
263                     break;
264                 case ACTION_UP:
265                 case ACTION_CANCEL:
266                     cleanupAfterMotionEvent();
267                     break;
268             }
269         }
270     }
271 
checkVelocityForTaskbarBackground(MotionEvent ev)272     private void checkVelocityForTaskbarBackground(MotionEvent ev) {
273         int actionMasked = ev.getActionMasked();
274         if (actionMasked == MotionEvent.ACTION_DOWN && mVelocityTracker != null) {
275             mVelocityTracker.clear();
276         }
277         if (mVelocityTracker == null) {
278             mVelocityTracker = VelocityTracker.obtain();
279         }
280         mVelocityTracker.addMovement(ev);
281 
282         mVelocityTracker.computeCurrentVelocity(1000);
283         if (ev.getAction() == ACTION_MOVE) {
284             mMotionMoveCount++;
285         }
286 
287         float velocityYPxPerS = mVelocityTracker.getYVelocity();
288         if (mCanPlayTaskbarBgAlphaAnimation
289                 && mMotionMoveCount >= NUM_MOTION_MOVE_THRESHOLD // Arbitrary value
290                 && velocityYPxPerS != 0 // Ignore these
291                 && velocityYPxPerS >= mTaskbarSlowVelocityYThreshold) {
292             mTaskbarActivityContext.playTaskbarBackgroundAlphaAnimation();
293             mCanPlayTaskbarBgAlphaAnimation = false;
294         }
295     }
296 
cleanupAfterMotionEvent()297     private void cleanupAfterMotionEvent() {
298         mTaskbarActivityContext.setAutohideSuspendFlag(
299                 FLAG_AUTOHIDE_SUSPEND_TOUCHING, false);
300         if (mTransitionCallback != null) {
301             mTransitionCallback.onActionEnd();
302         }
303         mHasPassedTaskbarNavThreshold = false;
304         mIsInBubbleBarArea = false;
305         mIsVerticalGestureOverBubbleBar = false;
306         mIsPassedBubbleBarSlop = false;
307 
308         if (mVelocityTracker != null) {
309             mVelocityTracker.recycle();
310         }
311         mVelocityTracker = null;
312         mCanPlayTaskbarBgAlphaAnimation = true;
313         mMotionMoveCount = 0;
314     }
315 
isInBubbleBarArea(float x)316     private boolean isInBubbleBarArea(float x) {
317         if (mTaskbarActivityContext == null || !mIsTransientTaskbar) {
318             return false;
319         }
320         BubbleControllers controllers = mTaskbarActivityContext.getBubbleControllers();
321         if (controllers == null) {
322             return false;
323         }
324         if (controllers.bubbleStashController.isStashed()) {
325             return controllers.bubbleStashedHandleViewController.containsX((int) x);
326         } else {
327             Rect bubbleBarBounds = controllers.bubbleBarViewController.getBubbleBarBounds();
328             return x >= bubbleBarBounds.left && x <= bubbleBarBounds.right;
329         }
330     }
331 
332     /**
333      * Listen for hover events for the stashed taskbar.
334      *
335      * <p>When hovered over the stashed taskbar handle, show the unstash hint.
336      * <p>When the cursor is touching the bottom edge below the stashed taskbar, unstash it.
337      * <p>When the cursor is within a defined threshold of the screen's bottom edge outside of
338      * the stashed taskbar, unstash it.
339      */
340     @Override
onHoverEvent(MotionEvent ev)341     public void onHoverEvent(MotionEvent ev) {
342         if (!enableCursorHoverStates() || mTaskbarActivityContext == null
343                 || !mTaskbarActivityContext.isTaskbarStashed()) {
344             return;
345         }
346 
347         if (mIsStashedTaskbarHovered) {
348             updateHoveredTaskbarState((int) ev.getX(), (int) ev.getY());
349         } else {
350             updateUnhoveredTaskbarState((int) ev.getX(), (int) ev.getY());
351         }
352     }
353 
updateHoveredTaskbarState(int x, int y)354     private void updateHoveredTaskbarState(int x, int y) {
355         DeviceProfile dp = mTaskbarActivityContext.getDeviceProfile();
356         mBottomEdgeBounds.set(
357                 (dp.widthPx - (int) mUnstashArea) / 2,
358                 dp.heightPx - mStashedTaskbarBottomEdge,
359                 (int) (((dp.widthPx - mUnstashArea) / 2) + mUnstashArea),
360                 dp.heightPx);
361 
362         if (mBottomEdgeBounds.contains(x, y)) {
363             // start a single unstash timeout if hovering bottom edge under the hinted taskbar.
364             if (!sUnstashHandler.hasMessagesOrCallbacks()) {
365                 sUnstashHandler.postDelayed(() -> {
366                     mTaskbarActivityContext.onSwipeToUnstashTaskbar();
367                     mIsStashedTaskbarHovered = false;
368                 }, HOVER_TASKBAR_UNSTASH_TIMEOUT);
369             }
370         } else if (!isStashedTaskbarHovered(x, y)) {
371             // If exit hovering stashed taskbar, remove hint and clear pending unstash calls.
372             sUnstashHandler.removeCallbacksAndMessages(null);
373             startStashedTaskbarHover(/* isHovered = */ false);
374         } else {
375             sUnstashHandler.removeCallbacksAndMessages(null);
376         }
377     }
378 
updateUnhoveredTaskbarState(int x, int y)379     private void updateUnhoveredTaskbarState(int x, int y) {
380         sUnstashHandler.removeCallbacksAndMessages(null);
381 
382         DeviceProfile dp = mTaskbarActivityContext.getDeviceProfile();
383         mBottomEdgeBounds.set(
384                 0,
385                 dp.heightPx - mBottomScreenEdge,
386                 dp.widthPx,
387                 dp.heightPx);
388 
389         if (isStashedTaskbarHovered(x, y)) {
390             // If enter hovering stashed taskbar, start hint.
391             startStashedTaskbarHover(/* isHovered = */ true);
392         } else if (mBottomEdgeBounds.contains(x, y)) {
393             // If hover screen's bottom edge not below the stashed taskbar, unstash it.
394             mTaskbarActivityContext.onSwipeToUnstashTaskbar();
395         }
396     }
397 
startStashedTaskbarHover(boolean isHovered)398     private void startStashedTaskbarHover(boolean isHovered) {
399         mTaskbarActivityContext.startTaskbarUnstashHint(isHovered);
400         mIsStashedTaskbarHovered = isHovered;
401     }
402 
isStashedTaskbarHovered(int x, int y)403     private boolean isStashedTaskbarHovered(int x, int y) {
404         if (!mTaskbarActivityContext.isTaskbarStashed()
405                 || mTaskbarActivityContext.isTaskbarAllAppsOpen()
406                 || !enableCursorHoverStates()) {
407             return false;
408         }
409         DeviceProfile dp = mTaskbarActivityContext.getDeviceProfile();
410         mStashedTaskbarHandleBounds.set(
411                 (dp.widthPx - (int) mUnstashArea) / 2,
412                 dp.heightPx - dp.stashedTaskbarHeight,
413                 (int) (((dp.widthPx - mUnstashArea) / 2) + mUnstashArea),
414                 dp.heightPx);
415         return mStashedTaskbarHandleBounds.contains(x, y);
416     }
417 
isMouseEvent(MotionEvent event)418     private boolean isMouseEvent(MotionEvent event) {
419         return event.getSource() == InputDevice.SOURCE_MOUSE;
420     }
421 
422     @Override
getDelegatorName()423     protected String getDelegatorName() {
424         return "TaskbarUnstashInputConsumer";
425     }
426 }
427