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 package com.android.quickstep.inputconsumers;
17 
18 import static android.view.MotionEvent.ACTION_CANCEL;
19 import static android.view.MotionEvent.ACTION_DOWN;
20 import static android.view.MotionEvent.ACTION_MOVE;
21 import static android.view.MotionEvent.ACTION_POINTER_DOWN;
22 import static android.view.MotionEvent.ACTION_POINTER_UP;
23 import static android.view.MotionEvent.ACTION_UP;
24 
25 import android.content.Context;
26 import android.view.Display;
27 import android.view.MotionEvent;
28 import android.view.VelocityTracker;
29 import android.view.ViewConfiguration;
30 
31 import com.android.launcher3.R;
32 import com.android.quickstep.InputConsumer;
33 import com.android.quickstep.RecentsAnimationDeviceState;
34 import com.android.quickstep.SystemUiProxy;
35 import com.android.quickstep.util.MotionPauseDetector;
36 import com.android.systemui.shared.system.InputMonitorCompat;
37 
38 /**
39  * Input consumer for two finger swipe actions for accessibility actions
40  */
41 public class AccessibilityInputConsumer extends DelegateInputConsumer {
42 
43     private static final String TAG = "A11yInputConsumer";
44 
45     private final Context mContext;
46     private final VelocityTracker mVelocityTracker;
47     private final MotionPauseDetector mMotionPauseDetector;
48     private final RecentsAnimationDeviceState mDeviceState;
49 
50     private final float mMinGestureDistance;
51     private final float mMinFlingVelocity;
52 
53     private int mActivePointerId = -1;
54     private float mDownY;
55     private float mTotalY;
56 
AccessibilityInputConsumer(Context context, RecentsAnimationDeviceState deviceState, InputConsumer delegate, InputMonitorCompat inputMonitor)57     public AccessibilityInputConsumer(Context context, RecentsAnimationDeviceState deviceState,
58             InputConsumer delegate, InputMonitorCompat inputMonitor) {
59         super(delegate, inputMonitor);
60         mContext = context;
61         mVelocityTracker = VelocityTracker.obtain();
62         mMinGestureDistance = context.getResources()
63                 .getDimension(R.dimen.accessibility_gesture_min_swipe_distance);
64         mMinFlingVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity();
65         mDeviceState = deviceState;
66 
67         mMotionPauseDetector = new MotionPauseDetector(context);
68     }
69 
70     @Override
getType()71     public int getType() {
72         return TYPE_ACCESSIBILITY | mDelegate.getType();
73     }
74 
75     @Override
onMotionEvent(MotionEvent ev)76     public void onMotionEvent(MotionEvent ev) {
77         if (mState != STATE_DELEGATE_ACTIVE) {
78             mVelocityTracker.addMovement(ev);
79         }
80 
81         switch (ev.getActionMasked()) {
82             case ACTION_DOWN: {
83                 break;
84             }
85             case ACTION_POINTER_UP: {
86                 if (mState == STATE_ACTIVE) {
87                     int pointerIndex = ev.getActionIndex();
88                     int pointerId = ev.getPointerId(pointerIndex);
89                     if (pointerId == mActivePointerId) {
90                         final int newPointerIdx = pointerIndex == 0 ? 1 : 0;
91 
92                         mTotalY += (ev.getY(pointerIndex) - mDownY);
93                         mDownY = ev.getY(newPointerIdx);
94                         mActivePointerId = ev.getPointerId(newPointerIdx);
95                     }
96                 }
97                 break;
98             }
99             case ACTION_POINTER_DOWN: {
100                 if (mState == STATE_INACTIVE) {
101                     int pointerIndex = ev.getActionIndex();
102                     if (mDeviceState.getRotationTouchHelper()
103                             .isInSwipeUpTouchRegion(ev, pointerIndex)
104                             && mDelegate.allowInterceptByParent()) {
105                         setActive(ev);
106 
107                         mActivePointerId = ev.getPointerId(pointerIndex);
108                         mDownY = ev.getY(pointerIndex);
109                     } else {
110                         mState = STATE_DELEGATE_ACTIVE;
111                     }
112                 }
113                 break;
114             }
115             case ACTION_MOVE: {
116                 if (mState == STATE_ACTIVE && mDeviceState.isAccessibilityMenuShortcutAvailable()) {
117                     int pointerIndex = ev.findPointerIndex(mActivePointerId);
118                     if (pointerIndex == -1) {
119                         break;
120                     }
121                     mMotionPauseDetector.addPosition(ev, pointerIndex);
122                 }
123                 break;
124             }
125             case ACTION_UP:
126                 if (mState == STATE_ACTIVE) {
127                     if (mDeviceState.isAccessibilityMenuShortcutAvailable()
128                             && mMotionPauseDetector.isPaused()) {
129                         SystemUiProxy.INSTANCE.get(mContext).notifyAccessibilityButtonLongClicked();
130                     } else {
131                         mTotalY += (ev.getY() - mDownY);
132                         mVelocityTracker.computeCurrentVelocity(1000);
133 
134                         if ((-mTotalY) > mMinGestureDistance
135                                 || (-mVelocityTracker.getYVelocity()) > mMinFlingVelocity) {
136                             SystemUiProxy.INSTANCE.get(mContext).notifyAccessibilityButtonClicked(
137                                     Display.DEFAULT_DISPLAY);
138                         }
139                     }
140                 }
141                 // Follow through
142             case ACTION_CANCEL: {
143                 mVelocityTracker.recycle();
144                 mMotionPauseDetector.clear();
145                 break;
146             }
147         }
148 
149         if (mState != STATE_ACTIVE) {
150             mDelegate.onMotionEvent(ev);
151         }
152     }
153 }
154