• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.server.wm;
18 
19 import android.content.Context;
20 import android.content.res.Resources;
21 import android.graphics.Rect;
22 import android.graphics.Region;
23 import android.hardware.display.DisplayManagerGlobal;
24 import android.os.Handler;
25 import android.os.SystemClock;
26 import android.util.Slog;
27 import android.view.Display;
28 import android.view.DisplayCutout;
29 import android.view.DisplayInfo;
30 import android.view.GestureDetector;
31 import android.view.InputDevice;
32 import android.view.MotionEvent;
33 import android.view.WindowManagerPolicyConstants.PointerEventListener;
34 import android.widget.OverScroller;
35 
36 /**
37  * Listens for system-wide input gestures, firing callbacks when detected.
38  * @hide
39  */
40 class SystemGesturesPointerEventListener implements PointerEventListener {
41     private static final String TAG = "SystemGestures";
42     private static final boolean DEBUG = false;
43     private static final long SWIPE_TIMEOUT_MS = 500;
44     private static final int MAX_TRACKED_POINTERS = 32;  // max per input system
45     private static final int UNTRACKED_POINTER = -1;
46     private static final int MAX_FLING_TIME_MILLIS = 5000;
47 
48     private static final int SWIPE_NONE = 0;
49     private static final int SWIPE_FROM_TOP = 1;
50     private static final int SWIPE_FROM_BOTTOM = 2;
51     private static final int SWIPE_FROM_RIGHT = 3;
52     private static final int SWIPE_FROM_LEFT = 4;
53 
54     private final Context mContext;
55     private final Handler mHandler;
56     private int mDisplayCutoutTouchableRegionSize;
57     private int mSwipeStartThreshold;
58     private int mSwipeDistanceThreshold;
59     private final Callbacks mCallbacks;
60     private final int[] mDownPointerId = new int[MAX_TRACKED_POINTERS];
61     private final float[] mDownX = new float[MAX_TRACKED_POINTERS];
62     private final float[] mDownY = new float[MAX_TRACKED_POINTERS];
63     private final long[] mDownTime = new long[MAX_TRACKED_POINTERS];
64 
65     private GestureDetector mGestureDetector;
66 
67     int screenHeight;
68     int screenWidth;
69     private DisplayInfo mTmpDisplayInfo = new DisplayInfo();
70     private int mDownPointers;
71     private boolean mSwipeFireable;
72     private boolean mDebugFireable;
73     private boolean mMouseHoveringAtEdge;
74     private long mLastFlingTime;
75 
SystemGesturesPointerEventListener(Context context, Handler handler, Callbacks callbacks)76     SystemGesturesPointerEventListener(Context context, Handler handler, Callbacks callbacks) {
77         mContext = checkNull("context", context);
78         mHandler = handler;
79         mCallbacks = checkNull("callbacks", callbacks);
80         onConfigurationChanged();
81     }
82 
onDisplayInfoChanged(DisplayInfo info)83     void onDisplayInfoChanged(DisplayInfo info) {
84         screenWidth = info.logicalWidth;
85         screenHeight = info.logicalHeight;
86         onConfigurationChanged();
87     }
88 
onConfigurationChanged()89     void onConfigurationChanged() {
90         final Resources r = mContext.getResources();
91         final Display display = DisplayManagerGlobal.getInstance()
92                 .getRealDisplay(Display.DEFAULT_DISPLAY);
93         display.getDisplayInfo(mTmpDisplayInfo);
94         mSwipeStartThreshold = mTmpDisplayInfo.logicalWidth > mTmpDisplayInfo.logicalHeight
95                 ? r.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height_landscape)
96                 : r.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height_portrait);
97 
98         final DisplayCutout displayCutout = display.getCutout();
99         if (displayCutout != null) {
100             final Rect bounds = displayCutout.getBoundingRectTop();
101             if (!bounds.isEmpty()) {
102                 // Expand swipe start threshold such that we can catch touches that just start below
103                 // the notch area
104                 mDisplayCutoutTouchableRegionSize = r.getDimensionPixelSize(
105                         com.android.internal.R.dimen.display_cutout_touchable_region_size);
106                 mSwipeStartThreshold += mDisplayCutoutTouchableRegionSize;
107             }
108         }
109         mSwipeDistanceThreshold = mSwipeStartThreshold;
110         if (DEBUG) Slog.d(TAG,  "mSwipeStartThreshold=" + mSwipeStartThreshold
111             + " mSwipeDistanceThreshold=" + mSwipeDistanceThreshold);
112     }
113 
checkNull(String name, T arg)114     private static <T> T checkNull(String name, T arg) {
115         if (arg == null) {
116             throw new IllegalArgumentException(name + " must not be null");
117         }
118         return arg;
119     }
120 
systemReady()121     public void systemReady() {
122         // GestureDetector records statistics about gesture classification events to inform gesture
123         // usage trends. SystemGesturesPointerEventListener creates a lot of noise in these
124         // statistics because it passes every touch event though a GestureDetector. By creating an
125         // anonymous subclass of GestureDetector, these statistics will be recorded with a unique
126         // source name that can be filtered.
127 
128         // GestureDetector would get a ViewConfiguration instance by context, that may also
129         // create a new WindowManagerImpl for the new display, and lock WindowManagerGlobal
130         // temporarily in the constructor that would make a deadlock.
131         mHandler.post(() -> {
132             final int displayId = mContext.getDisplayId();
133             final DisplayInfo info = DisplayManagerGlobal.getInstance().getDisplayInfo(displayId);
134             if (info == null) {
135                 // Display already removed, stop here.
136                 Slog.w(TAG, "Cannot create GestureDetector, display removed:" + displayId);
137                 return;
138             }
139             mGestureDetector = new GestureDetector(mContext, new FlingGestureDetector(), mHandler) {
140             };
141         });
142     }
143 
144     @Override
onPointerEvent(MotionEvent event)145     public void onPointerEvent(MotionEvent event) {
146         if (mGestureDetector != null && event.isTouchEvent()) {
147             mGestureDetector.onTouchEvent(event);
148         }
149         switch (event.getActionMasked()) {
150             case MotionEvent.ACTION_DOWN:
151                 mSwipeFireable = true;
152                 mDebugFireable = true;
153                 mDownPointers = 0;
154                 captureDown(event, 0);
155                 if (mMouseHoveringAtEdge) {
156                     mMouseHoveringAtEdge = false;
157                     mCallbacks.onMouseLeaveFromEdge();
158                 }
159                 mCallbacks.onDown();
160                 break;
161             case MotionEvent.ACTION_POINTER_DOWN:
162                 captureDown(event, event.getActionIndex());
163                 if (mDebugFireable) {
164                     mDebugFireable = event.getPointerCount() < 5;
165                     if (!mDebugFireable) {
166                         if (DEBUG) Slog.d(TAG, "Firing debug");
167                         mCallbacks.onDebug();
168                     }
169                 }
170                 break;
171             case MotionEvent.ACTION_MOVE:
172                 if (mSwipeFireable) {
173                     final int swipe = detectSwipe(event);
174                     mSwipeFireable = swipe == SWIPE_NONE;
175                     if (swipe == SWIPE_FROM_TOP) {
176                         if (DEBUG) Slog.d(TAG, "Firing onSwipeFromTop");
177                         mCallbacks.onSwipeFromTop();
178                     } else if (swipe == SWIPE_FROM_BOTTOM) {
179                         if (DEBUG) Slog.d(TAG, "Firing onSwipeFromBottom");
180                         mCallbacks.onSwipeFromBottom();
181                     } else if (swipe == SWIPE_FROM_RIGHT) {
182                         if (DEBUG) Slog.d(TAG, "Firing onSwipeFromRight");
183                         mCallbacks.onSwipeFromRight();
184                     } else if (swipe == SWIPE_FROM_LEFT) {
185                         if (DEBUG) Slog.d(TAG, "Firing onSwipeFromLeft");
186                         mCallbacks.onSwipeFromLeft();
187                     }
188                 }
189                 break;
190             case MotionEvent.ACTION_HOVER_MOVE:
191                 if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
192                     if (!mMouseHoveringAtEdge && event.getY() == 0) {
193                         mCallbacks.onMouseHoverAtTop();
194                         mMouseHoveringAtEdge = true;
195                     } else if (!mMouseHoveringAtEdge && event.getY() >= screenHeight - 1) {
196                         mCallbacks.onMouseHoverAtBottom();
197                         mMouseHoveringAtEdge = true;
198                     } else if (mMouseHoveringAtEdge
199                             && (event.getY() > 0 && event.getY() < screenHeight - 1)) {
200                         mCallbacks.onMouseLeaveFromEdge();
201                         mMouseHoveringAtEdge = false;
202                     }
203                 }
204                 break;
205             case MotionEvent.ACTION_UP:
206             case MotionEvent.ACTION_CANCEL:
207                 mSwipeFireable = false;
208                 mDebugFireable = false;
209                 mCallbacks.onUpOrCancel();
210                 break;
211             default:
212                 if (DEBUG) Slog.d(TAG, "Ignoring " + event);
213         }
214     }
215 
captureDown(MotionEvent event, int pointerIndex)216     private void captureDown(MotionEvent event, int pointerIndex) {
217         final int pointerId = event.getPointerId(pointerIndex);
218         final int i = findIndex(pointerId);
219         if (DEBUG) Slog.d(TAG, "pointer " + pointerId
220                 + " down pointerIndex=" + pointerIndex + " trackingIndex=" + i);
221         if (i != UNTRACKED_POINTER) {
222             mDownX[i] = event.getX(pointerIndex);
223             mDownY[i] = event.getY(pointerIndex);
224             mDownTime[i] = event.getEventTime();
225             if (DEBUG) Slog.d(TAG, "pointer " + pointerId
226                     + " down x=" + mDownX[i] + " y=" + mDownY[i]);
227         }
228     }
229 
currentGestureStartedInRegion(Region r)230     protected boolean currentGestureStartedInRegion(Region r) {
231         return r.contains((int) mDownX[0], (int) mDownY[0]);
232     }
233 
findIndex(int pointerId)234     private int findIndex(int pointerId) {
235         for (int i = 0; i < mDownPointers; i++) {
236             if (mDownPointerId[i] == pointerId) {
237                 return i;
238             }
239         }
240         if (mDownPointers == MAX_TRACKED_POINTERS || pointerId == MotionEvent.INVALID_POINTER_ID) {
241             return UNTRACKED_POINTER;
242         }
243         mDownPointerId[mDownPointers++] = pointerId;
244         return mDownPointers - 1;
245     }
246 
detectSwipe(MotionEvent move)247     private int detectSwipe(MotionEvent move) {
248         final int historySize = move.getHistorySize();
249         final int pointerCount = move.getPointerCount();
250         for (int p = 0; p < pointerCount; p++) {
251             final int pointerId = move.getPointerId(p);
252             final int i = findIndex(pointerId);
253             if (i != UNTRACKED_POINTER) {
254                 for (int h = 0; h < historySize; h++) {
255                     final long time = move.getHistoricalEventTime(h);
256                     final float x = move.getHistoricalX(p, h);
257                     final float y = move.getHistoricalY(p,  h);
258                     final int swipe = detectSwipe(i, time, x, y);
259                     if (swipe != SWIPE_NONE) {
260                         return swipe;
261                     }
262                 }
263                 final int swipe = detectSwipe(i, move.getEventTime(), move.getX(p), move.getY(p));
264                 if (swipe != SWIPE_NONE) {
265                     return swipe;
266                 }
267             }
268         }
269         return SWIPE_NONE;
270     }
271 
detectSwipe(int i, long time, float x, float y)272     private int detectSwipe(int i, long time, float x, float y) {
273         final float fromX = mDownX[i];
274         final float fromY = mDownY[i];
275         final long elapsed = time - mDownTime[i];
276         if (DEBUG) Slog.d(TAG, "pointer " + mDownPointerId[i]
277                 + " moved (" + fromX + "->" + x + "," + fromY + "->" + y + ") in " + elapsed);
278         if (fromY <= mSwipeStartThreshold
279                 && y > fromY + mSwipeDistanceThreshold
280                 && elapsed < SWIPE_TIMEOUT_MS) {
281             return SWIPE_FROM_TOP;
282         }
283         if (fromY >= screenHeight - mSwipeStartThreshold
284                 && y < fromY - mSwipeDistanceThreshold
285                 && elapsed < SWIPE_TIMEOUT_MS) {
286             return SWIPE_FROM_BOTTOM;
287         }
288         if (fromX >= screenWidth - mSwipeStartThreshold
289                 && x < fromX - mSwipeDistanceThreshold
290                 && elapsed < SWIPE_TIMEOUT_MS) {
291             return SWIPE_FROM_RIGHT;
292         }
293         if (fromX <= mSwipeStartThreshold
294                 && x > fromX + mSwipeDistanceThreshold
295                 && elapsed < SWIPE_TIMEOUT_MS) {
296             return SWIPE_FROM_LEFT;
297         }
298         return SWIPE_NONE;
299     }
300 
301     private final class FlingGestureDetector extends GestureDetector.SimpleOnGestureListener {
302 
303         private OverScroller mOverscroller;
304 
FlingGestureDetector()305         FlingGestureDetector() {
306             mOverscroller = new OverScroller(mContext);
307         }
308 
309         @Override
onSingleTapUp(MotionEvent e)310         public boolean onSingleTapUp(MotionEvent e) {
311             if (!mOverscroller.isFinished()) {
312                 mOverscroller.forceFinished(true);
313             }
314             return true;
315         }
316         @Override
onFling(MotionEvent down, MotionEvent up, float velocityX, float velocityY)317         public boolean onFling(MotionEvent down, MotionEvent up,
318                 float velocityX, float velocityY) {
319             mOverscroller.computeScrollOffset();
320             long now = SystemClock.uptimeMillis();
321 
322             if (mLastFlingTime != 0 && now > mLastFlingTime + MAX_FLING_TIME_MILLIS) {
323                 mOverscroller.forceFinished(true);
324             }
325             mOverscroller.fling(0, 0, (int)velocityX, (int)velocityY,
326                     Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
327             int duration = mOverscroller.getDuration();
328             if (duration > MAX_FLING_TIME_MILLIS) {
329                 duration = MAX_FLING_TIME_MILLIS;
330             }
331             mLastFlingTime = now;
332             mCallbacks.onFling(duration);
333             return true;
334         }
335     }
336 
337     interface Callbacks {
338         void onSwipeFromTop();
339         void onSwipeFromBottom();
340         void onSwipeFromRight();
341         void onSwipeFromLeft();
342         void onFling(int durationMs);
343         void onDown();
344         void onUpOrCancel();
345         void onMouseHoverAtTop();
346         void onMouseHoverAtBottom();
347         void onMouseLeaveFromEdge();
348         void onDebug();
349     }
350 }
351