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