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