• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.quickstep.inputconsumers;
18 
19 import static android.view.MotionEvent.ACTION_CANCEL;
20 import static android.view.MotionEvent.ACTION_DOWN;
21 import static android.view.MotionEvent.ACTION_MOVE;
22 import static android.view.MotionEvent.ACTION_UP;
23 
24 import static com.android.launcher3.Utilities.squaredHypot;
25 import static com.android.launcher3.testing.shared.ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE;
26 
27 import android.content.Context;
28 import android.graphics.Point;
29 import android.graphics.PointF;
30 import android.view.MotionEvent;
31 
32 import com.android.launcher3.R;
33 import com.android.launcher3.testing.shared.ResourceUtils;
34 import com.android.launcher3.util.DisplayController;
35 import com.android.quickstep.InputConsumer;
36 import com.android.quickstep.RecentsAnimationDeviceState;
37 import com.android.quickstep.SystemUiProxy;
38 import com.android.systemui.shared.system.InputMonitorCompat;
39 
40 /**
41  * Touch consumer for handling gesture event to launch one handed
42  * One handed gestural in quickstep only active on NO_BUTTON, TWO_BUTTONS, and portrait mode
43  */
44 public class OneHandedModeInputConsumer extends DelegateInputConsumer {
45 
46     private static final int ANGLE_MAX = 150;
47     private static final int ANGLE_MIN = 30;
48 
49     private final Context mContext;
50     private final Point mDisplaySize;
51     private final RecentsAnimationDeviceState mDeviceState;
52 
53     private final float mDragDistThreshold;
54     private final float mSquaredSlop;
55 
56     private final int mNavBarSize;
57 
58     private final PointF mDownPos = new PointF();
59     private final PointF mLastPos = new PointF();
60 
61     private boolean mPassedSlop;
62     private boolean mIsStopGesture;
63 
OneHandedModeInputConsumer(Context context, RecentsAnimationDeviceState deviceState, InputConsumer delegate, InputMonitorCompat inputMonitor)64     public OneHandedModeInputConsumer(Context context, RecentsAnimationDeviceState deviceState,
65             InputConsumer delegate, InputMonitorCompat inputMonitor) {
66         super(delegate, inputMonitor);
67         mContext = context;
68         mDeviceState = deviceState;
69         mDragDistThreshold = context.getResources().getDimensionPixelSize(
70                 R.dimen.gestures_onehanded_drag_threshold);
71         mSquaredSlop = mDeviceState.getSquaredTouchSlop();
72         mDisplaySize = DisplayController.INSTANCE.get(mContext).getInfo().currentSize;
73         mNavBarSize = ResourceUtils.getNavbarSize(NAVBAR_BOTTOM_GESTURE_SIZE,
74                 mContext.getResources());
75     }
76 
77     @Override
getType()78     public int getType() {
79         return TYPE_ONE_HANDED | mDelegate.getType();
80     }
81 
82     @Override
onMotionEvent(MotionEvent ev)83     public void onMotionEvent(MotionEvent ev) {
84         switch (ev.getActionMasked()) {
85             case ACTION_DOWN: {
86                 mDownPos.set(ev.getX(), ev.getY());
87                 mLastPos.set(mDownPos);
88                 break;
89             }
90             case ACTION_MOVE: {
91                 if (mState == STATE_DELEGATE_ACTIVE) {
92                     break;
93                 }
94                 if (!mDelegate.allowInterceptByParent()) {
95                     mState = STATE_DELEGATE_ACTIVE;
96                     break;
97                 }
98 
99                 mLastPos.set(ev.getX(), ev.getY());
100                 if (!mPassedSlop) {
101                     if (squaredHypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y)
102                             > mSquaredSlop) {
103                         if ((!mDeviceState.isOneHandedModeActive() && isValidStartAngle(
104                                 mDownPos.x - mLastPos.x, mDownPos.y - mLastPos.y))
105                                 || (mDeviceState.isOneHandedModeActive() && isValidExitAngle(
106                                 mDownPos.x - mLastPos.x, mDownPos.y - mLastPos.y))) {
107                             // To avoid mis-trigger when motion not touch system gesture region.
108                             mPassedSlop = isInSystemGestureRegion(mLastPos);
109                             setActive(ev);
110                         } else {
111                             mState = STATE_DELEGATE_ACTIVE;
112                         }
113                     }
114                 } else {
115                     float distance = (float) Math.hypot(mLastPos.x - mDownPos.x,
116                             mLastPos.y - mDownPos.y);
117                     if (distance > mDragDistThreshold && mPassedSlop) {
118                         mIsStopGesture = true;
119                     }
120                 }
121                 break;
122             }
123             case ACTION_UP: {
124                 if (mLastPos.y >= mDownPos.y && mPassedSlop) {
125                     onStartGestureDetected();
126                 } else if (mIsStopGesture) {
127                     onStopGestureDetected();
128                 }
129                 clearState();
130                 break;
131             }
132             case ACTION_CANCEL:
133                 clearState();
134                 break;
135         }
136 
137         if (mState != STATE_ACTIVE) {
138             mDelegate.onMotionEvent(ev);
139         }
140     }
141 
clearState()142     private void clearState() {
143         mPassedSlop = false;
144         mState = STATE_INACTIVE;
145         mIsStopGesture = false;
146     }
147 
onStartGestureDetected()148     private void onStartGestureDetected() {
149         if (mDeviceState.isSwipeToNotificationEnabled()) {
150             SystemUiProxy.INSTANCE.get(mContext).expandNotificationPanel();
151         } else if (!mDeviceState.isOneHandedModeActive()) {
152             SystemUiProxy.INSTANCE.get(mContext).startOneHandedMode();
153         }
154     }
155 
onStopGestureDetected()156     private void onStopGestureDetected() {
157         if (!mDeviceState.isOneHandedModeEnabled() || !mDeviceState.isOneHandedModeActive()) {
158             return;
159         }
160 
161         SystemUiProxy.INSTANCE.get(mContext).stopOneHandedMode();
162     }
163 
isInSystemGestureRegion(PointF lastPos)164     private boolean isInSystemGestureRegion(PointF lastPos) {
165         final int navBarUpperBound = mDisplaySize.y - mNavBarSize;
166         return mDeviceState.isGesturalNavMode() && lastPos.y > navBarUpperBound;
167     }
168 
isValidStartAngle(float deltaX, float deltaY)169     private boolean isValidStartAngle(float deltaX, float deltaY) {
170         final float angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX));
171         return angle > -(ANGLE_MAX) && angle < -(ANGLE_MIN);
172     }
173 
isValidExitAngle(float deltaX, float deltaY)174     private boolean isValidExitAngle(float deltaX, float deltaY) {
175         final float angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX));
176         return angle > ANGLE_MIN && angle < ANGLE_MAX;
177     }
178 
179     @Override
getDelegatorName()180     protected String getDelegatorName() {
181         return "OneHandedModeInputConsumer";
182     }
183 }
184