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