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