1 /* 2 * Copyright (C) 2010 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.internal.widget; 18 19 import android.compat.annotation.UnsupportedAppUsage; 20 import android.content.Context; 21 import android.graphics.Canvas; 22 import android.graphics.Paint; 23 import android.graphics.Paint.FontMetricsInt; 24 import android.graphics.Path; 25 import android.graphics.RectF; 26 import android.graphics.Region; 27 import android.hardware.input.InputManager; 28 import android.hardware.input.InputManager.InputDeviceListener; 29 import android.os.Handler; 30 import android.os.RemoteException; 31 import android.os.SystemProperties; 32 import android.util.Log; 33 import android.util.Slog; 34 import android.view.ISystemGestureExclusionListener; 35 import android.view.InputDevice; 36 import android.view.KeyEvent; 37 import android.view.MotionEvent; 38 import android.view.MotionEvent.PointerCoords; 39 import android.view.VelocityTracker; 40 import android.view.View; 41 import android.view.ViewConfiguration; 42 import android.view.WindowInsets; 43 import android.view.WindowManagerGlobal; 44 import android.view.WindowManagerPolicyConstants.PointerEventListener; 45 46 import java.util.ArrayList; 47 48 public class PointerLocationView extends View implements InputDeviceListener, 49 PointerEventListener { 50 private static final String TAG = "Pointer"; 51 52 // The system property key used to specify an alternate velocity tracker strategy 53 // to plot alongside the default one. Useful for testing and comparison purposes. 54 private static final String ALT_STRATEGY_PROPERY_KEY = "debug.velocitytracker.alt"; 55 56 /** 57 * If set to a positive value between 1-255, shows an overlay with the approved (red) and 58 * rejected (blue) exclusions. 59 */ 60 private static final String GESTURE_EXCLUSION_PROP = "debug.pointerlocation.showexclusion"; 61 62 public static class PointerState { 63 // Trace of previous points. 64 private float[] mTraceX = new float[32]; 65 private float[] mTraceY = new float[32]; 66 private boolean[] mTraceCurrent = new boolean[32]; 67 private int mTraceCount; 68 69 // True if the pointer is down. 70 @UnsupportedAppUsage 71 private boolean mCurDown; 72 73 // Most recent coordinates. 74 private PointerCoords mCoords = new PointerCoords(); 75 private int mToolType; 76 77 // Most recent velocity. 78 private float mXVelocity; 79 private float mYVelocity; 80 private float mAltXVelocity; 81 private float mAltYVelocity; 82 83 // Current bounding box, if any 84 private boolean mHasBoundingBox; 85 private float mBoundingLeft; 86 private float mBoundingTop; 87 private float mBoundingRight; 88 private float mBoundingBottom; 89 90 // Position estimator. 91 private VelocityTracker.Estimator mEstimator = new VelocityTracker.Estimator(); 92 private VelocityTracker.Estimator mAltEstimator = new VelocityTracker.Estimator(); 93 94 @UnsupportedAppUsage PointerState()95 public PointerState() { 96 } 97 clearTrace()98 public void clearTrace() { 99 mTraceCount = 0; 100 } 101 addTrace(float x, float y, boolean current)102 public void addTrace(float x, float y, boolean current) { 103 int traceCapacity = mTraceX.length; 104 if (mTraceCount == traceCapacity) { 105 traceCapacity *= 2; 106 float[] newTraceX = new float[traceCapacity]; 107 System.arraycopy(mTraceX, 0, newTraceX, 0, mTraceCount); 108 mTraceX = newTraceX; 109 110 float[] newTraceY = new float[traceCapacity]; 111 System.arraycopy(mTraceY, 0, newTraceY, 0, mTraceCount); 112 mTraceY = newTraceY; 113 114 boolean[] newTraceCurrent = new boolean[traceCapacity]; 115 System.arraycopy(mTraceCurrent, 0, newTraceCurrent, 0, mTraceCount); 116 mTraceCurrent= newTraceCurrent; 117 } 118 119 mTraceX[mTraceCount] = x; 120 mTraceY[mTraceCount] = y; 121 mTraceCurrent[mTraceCount] = current; 122 mTraceCount += 1; 123 } 124 } 125 126 private final InputManager mIm; 127 128 private final ViewConfiguration mVC; 129 private final Paint mTextPaint; 130 private final Paint mTextBackgroundPaint; 131 private final Paint mTextLevelPaint; 132 private final Paint mPaint; 133 private final Paint mCurrentPointPaint; 134 private final Paint mTargetPaint; 135 private final Paint mPathPaint; 136 private final FontMetricsInt mTextMetrics = new FontMetricsInt(); 137 private int mHeaderBottom; 138 private int mHeaderPaddingTop = 0; 139 @UnsupportedAppUsage 140 private boolean mCurDown; 141 @UnsupportedAppUsage 142 private int mCurNumPointers; 143 @UnsupportedAppUsage 144 private int mMaxNumPointers; 145 private int mActivePointerId; 146 @UnsupportedAppUsage 147 private final ArrayList<PointerState> mPointers = new ArrayList<PointerState>(); 148 private final PointerCoords mTempCoords = new PointerCoords(); 149 150 private final Region mSystemGestureExclusion = new Region(); 151 private final Region mSystemGestureExclusionRejected = new Region(); 152 private final Path mSystemGestureExclusionPath = new Path(); 153 private final Paint mSystemGestureExclusionPaint; 154 private final Paint mSystemGestureExclusionRejectedPaint; 155 156 private final VelocityTracker mVelocity; 157 private final VelocityTracker mAltVelocity; 158 159 private final FasterStringBuilder mText = new FasterStringBuilder(); 160 161 @UnsupportedAppUsage 162 private boolean mPrintCoords = true; 163 PointerLocationView(Context c)164 public PointerLocationView(Context c) { 165 super(c); 166 setFocusableInTouchMode(true); 167 168 mIm = c.getSystemService(InputManager.class); 169 170 mVC = ViewConfiguration.get(c); 171 mTextPaint = new Paint(); 172 mTextPaint.setAntiAlias(true); 173 mTextPaint.setTextSize(10 174 * getResources().getDisplayMetrics().density); 175 mTextPaint.setARGB(255, 0, 0, 0); 176 mTextBackgroundPaint = new Paint(); 177 mTextBackgroundPaint.setAntiAlias(false); 178 mTextBackgroundPaint.setARGB(128, 255, 255, 255); 179 mTextLevelPaint = new Paint(); 180 mTextLevelPaint.setAntiAlias(false); 181 mTextLevelPaint.setARGB(192, 255, 0, 0); 182 mPaint = new Paint(); 183 mPaint.setAntiAlias(true); 184 mPaint.setARGB(255, 255, 255, 255); 185 mPaint.setStyle(Paint.Style.STROKE); 186 mPaint.setStrokeWidth(2); 187 mCurrentPointPaint = new Paint(); 188 mCurrentPointPaint.setAntiAlias(true); 189 mCurrentPointPaint.setARGB(255, 255, 0, 0); 190 mCurrentPointPaint.setStyle(Paint.Style.STROKE); 191 mCurrentPointPaint.setStrokeWidth(2); 192 mTargetPaint = new Paint(); 193 mTargetPaint.setAntiAlias(false); 194 mTargetPaint.setARGB(255, 0, 0, 192); 195 mPathPaint = new Paint(); 196 mPathPaint.setAntiAlias(false); 197 mPathPaint.setARGB(255, 0, 96, 255); 198 mPaint.setStyle(Paint.Style.STROKE); 199 mPaint.setStrokeWidth(1); 200 201 mSystemGestureExclusionPaint = new Paint(); 202 mSystemGestureExclusionPaint.setARGB(25, 255, 0, 0); 203 mSystemGestureExclusionPaint.setStyle(Paint.Style.FILL_AND_STROKE); 204 205 mSystemGestureExclusionRejectedPaint = new Paint(); 206 mSystemGestureExclusionRejectedPaint.setARGB(25, 0, 0, 255); 207 mSystemGestureExclusionRejectedPaint.setStyle(Paint.Style.FILL_AND_STROKE); 208 209 PointerState ps = new PointerState(); 210 mPointers.add(ps); 211 mActivePointerId = 0; 212 213 mVelocity = VelocityTracker.obtain(); 214 215 String altStrategy = SystemProperties.get(ALT_STRATEGY_PROPERY_KEY); 216 if (altStrategy.length() != 0) { 217 Log.d(TAG, "Comparing default velocity tracker strategy with " + altStrategy); 218 mAltVelocity = VelocityTracker.obtain(altStrategy); 219 } else { 220 mAltVelocity = null; 221 } 222 } 223 setPrintCoords(boolean state)224 public void setPrintCoords(boolean state) { 225 mPrintCoords = state; 226 } 227 228 @Override onApplyWindowInsets(WindowInsets insets)229 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 230 if (insets.getDisplayCutout() != null) { 231 mHeaderPaddingTop = insets.getDisplayCutout().getSafeInsetTop(); 232 } else { 233 mHeaderPaddingTop = 0; 234 } 235 return super.onApplyWindowInsets(insets); 236 } 237 238 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)239 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 240 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 241 mTextPaint.getFontMetricsInt(mTextMetrics); 242 mHeaderBottom = mHeaderPaddingTop-mTextMetrics.ascent+mTextMetrics.descent+2; 243 if (false) { 244 Log.i("foo", "Metrics: ascent=" + mTextMetrics.ascent 245 + " descent=" + mTextMetrics.descent 246 + " leading=" + mTextMetrics.leading 247 + " top=" + mTextMetrics.top 248 + " bottom=" + mTextMetrics.bottom); 249 } 250 } 251 252 // Draw an oval. When angle is 0 radians, orients the major axis vertically, 253 // angles less than or greater than 0 radians rotate the major axis left or right. 254 private RectF mReusableOvalRect = new RectF(); drawOval(Canvas canvas, float x, float y, float major, float minor, float angle, Paint paint)255 private void drawOval(Canvas canvas, float x, float y, float major, float minor, 256 float angle, Paint paint) { 257 canvas.save(Canvas.MATRIX_SAVE_FLAG); 258 canvas.rotate((float) (angle * 180 / Math.PI), x, y); 259 mReusableOvalRect.left = x - minor / 2; 260 mReusableOvalRect.right = x + minor / 2; 261 mReusableOvalRect.top = y - major / 2; 262 mReusableOvalRect.bottom = y + major / 2; 263 canvas.drawOval(mReusableOvalRect, paint); 264 canvas.restore(); 265 } 266 267 @Override onDraw(Canvas canvas)268 protected void onDraw(Canvas canvas) { 269 final int w = getWidth(); 270 final int itemW = w/7; 271 final int base = mHeaderPaddingTop-mTextMetrics.ascent+1; 272 final int bottom = mHeaderBottom; 273 274 final int NP = mPointers.size(); 275 276 if (!mSystemGestureExclusion.isEmpty()) { 277 mSystemGestureExclusionPath.reset(); 278 mSystemGestureExclusion.getBoundaryPath(mSystemGestureExclusionPath); 279 canvas.drawPath(mSystemGestureExclusionPath, mSystemGestureExclusionPaint); 280 } 281 282 if (!mSystemGestureExclusionRejected.isEmpty()) { 283 mSystemGestureExclusionPath.reset(); 284 mSystemGestureExclusionRejected.getBoundaryPath(mSystemGestureExclusionPath); 285 canvas.drawPath(mSystemGestureExclusionPath, mSystemGestureExclusionRejectedPaint); 286 } 287 288 // Labels 289 if (mActivePointerId >= 0) { 290 final PointerState ps = mPointers.get(mActivePointerId); 291 292 canvas.drawRect(0, mHeaderPaddingTop, itemW-1, bottom,mTextBackgroundPaint); 293 canvas.drawText(mText.clear() 294 .append("P: ").append(mCurNumPointers) 295 .append(" / ").append(mMaxNumPointers) 296 .toString(), 1, base, mTextPaint); 297 298 final int N = ps.mTraceCount; 299 if ((mCurDown && ps.mCurDown) || N == 0) { 300 canvas.drawRect(itemW, mHeaderPaddingTop, (itemW * 2) - 1, bottom, 301 mTextBackgroundPaint); 302 canvas.drawText(mText.clear() 303 .append("X: ").append(ps.mCoords.x, 1) 304 .toString(), 1 + itemW, base, mTextPaint); 305 canvas.drawRect(itemW * 2, mHeaderPaddingTop, (itemW * 3) - 1, bottom, 306 mTextBackgroundPaint); 307 canvas.drawText(mText.clear() 308 .append("Y: ").append(ps.mCoords.y, 1) 309 .toString(), 1 + itemW * 2, base, mTextPaint); 310 } else { 311 float dx = ps.mTraceX[N - 1] - ps.mTraceX[0]; 312 float dy = ps.mTraceY[N - 1] - ps.mTraceY[0]; 313 canvas.drawRect(itemW, mHeaderPaddingTop, (itemW * 2) - 1, bottom, 314 Math.abs(dx) < mVC.getScaledTouchSlop() 315 ? mTextBackgroundPaint : mTextLevelPaint); 316 canvas.drawText(mText.clear() 317 .append("dX: ").append(dx, 1) 318 .toString(), 1 + itemW, base, mTextPaint); 319 canvas.drawRect(itemW * 2, mHeaderPaddingTop, (itemW * 3) - 1, bottom, 320 Math.abs(dy) < mVC.getScaledTouchSlop() 321 ? mTextBackgroundPaint : mTextLevelPaint); 322 canvas.drawText(mText.clear() 323 .append("dY: ").append(dy, 1) 324 .toString(), 1 + itemW * 2, base, mTextPaint); 325 } 326 327 canvas.drawRect(itemW * 3, mHeaderPaddingTop, (itemW * 4) - 1, bottom, 328 mTextBackgroundPaint); 329 canvas.drawText(mText.clear() 330 .append("Xv: ").append(ps.mXVelocity, 3) 331 .toString(), 1 + itemW * 3, base, mTextPaint); 332 333 canvas.drawRect(itemW * 4, mHeaderPaddingTop, (itemW * 5) - 1, bottom, 334 mTextBackgroundPaint); 335 canvas.drawText(mText.clear() 336 .append("Yv: ").append(ps.mYVelocity, 3) 337 .toString(), 1 + itemW * 4, base, mTextPaint); 338 339 canvas.drawRect(itemW * 5, mHeaderPaddingTop, (itemW * 6) - 1, bottom, 340 mTextBackgroundPaint); 341 canvas.drawRect(itemW * 5, mHeaderPaddingTop, 342 (itemW * 5) + (ps.mCoords.pressure * itemW) - 1, bottom, mTextLevelPaint); 343 canvas.drawText(mText.clear() 344 .append("Prs: ").append(ps.mCoords.pressure, 2) 345 .toString(), 1 + itemW * 5, base, mTextPaint); 346 347 canvas.drawRect(itemW * 6, mHeaderPaddingTop, w, bottom, mTextBackgroundPaint); 348 canvas.drawRect(itemW * 6, mHeaderPaddingTop, 349 (itemW * 6) + (ps.mCoords.size * itemW) - 1, bottom, mTextLevelPaint); 350 canvas.drawText(mText.clear() 351 .append("Size: ").append(ps.mCoords.size, 2) 352 .toString(), 1 + itemW * 6, base, mTextPaint); 353 } 354 355 // Pointer trace. 356 for (int p = 0; p < NP; p++) { 357 final PointerState ps = mPointers.get(p); 358 359 // Draw path. 360 final int N = ps.mTraceCount; 361 float lastX = 0, lastY = 0; 362 boolean haveLast = false; 363 boolean drawn = false; 364 mPaint.setARGB(255, 128, 255, 255); 365 for (int i=0; i < N; i++) { 366 float x = ps.mTraceX[i]; 367 float y = ps.mTraceY[i]; 368 if (Float.isNaN(x)) { 369 haveLast = false; 370 continue; 371 } 372 if (haveLast) { 373 canvas.drawLine(lastX, lastY, x, y, mPathPaint); 374 final Paint paint = ps.mTraceCurrent[i - 1] ? mCurrentPointPaint : mPaint; 375 canvas.drawPoint(lastX, lastY, paint); 376 drawn = true; 377 } 378 lastX = x; 379 lastY = y; 380 haveLast = true; 381 } 382 383 if (drawn) { 384 // Draw velocity vector. 385 mPaint.setARGB(255, 255, 64, 128); 386 float xVel = ps.mXVelocity * (1000 / 60); 387 float yVel = ps.mYVelocity * (1000 / 60); 388 canvas.drawLine(lastX, lastY, lastX + xVel, lastY + yVel, mPaint); 389 390 // Draw velocity vector using an alternate VelocityTracker strategy. 391 if (mAltVelocity != null) { 392 mPaint.setARGB(255, 64, 255, 128); 393 xVel = ps.mAltXVelocity * (1000 / 60); 394 yVel = ps.mAltYVelocity * (1000 / 60); 395 canvas.drawLine(lastX, lastY, lastX + xVel, lastY + yVel, mPaint); 396 } 397 } 398 399 if (mCurDown && ps.mCurDown) { 400 // Draw crosshairs. 401 canvas.drawLine(0, ps.mCoords.y, getWidth(), ps.mCoords.y, mTargetPaint); 402 canvas.drawLine(ps.mCoords.x, 0, ps.mCoords.x, getHeight(), mTargetPaint); 403 404 // Draw current point. 405 int pressureLevel = (int)(ps.mCoords.pressure * 255); 406 mPaint.setARGB(255, pressureLevel, 255, 255 - pressureLevel); 407 canvas.drawPoint(ps.mCoords.x, ps.mCoords.y, mPaint); 408 409 // Draw current touch ellipse. 410 mPaint.setARGB(255, pressureLevel, 255 - pressureLevel, 128); 411 drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.touchMajor, 412 ps.mCoords.touchMinor, ps.mCoords.orientation, mPaint); 413 414 // Draw current tool ellipse. 415 mPaint.setARGB(255, pressureLevel, 128, 255 - pressureLevel); 416 drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.toolMajor, 417 ps.mCoords.toolMinor, ps.mCoords.orientation, mPaint); 418 419 // Draw the orientation arrow. 420 float arrowSize = ps.mCoords.toolMajor * 0.7f; 421 if (arrowSize < 20) { 422 arrowSize = 20; 423 } 424 mPaint.setARGB(255, pressureLevel, 255, 0); 425 float orientationVectorX = (float) (Math.sin(ps.mCoords.orientation) 426 * arrowSize); 427 float orientationVectorY = (float) (-Math.cos(ps.mCoords.orientation) 428 * arrowSize); 429 if (ps.mToolType == MotionEvent.TOOL_TYPE_STYLUS 430 || ps.mToolType == MotionEvent.TOOL_TYPE_ERASER) { 431 // Show full circle orientation. 432 canvas.drawLine(ps.mCoords.x, ps.mCoords.y, 433 ps.mCoords.x + orientationVectorX, 434 ps.mCoords.y + orientationVectorY, 435 mPaint); 436 } else { 437 // Show half circle orientation. 438 canvas.drawLine( 439 ps.mCoords.x - orientationVectorX, 440 ps.mCoords.y - orientationVectorY, 441 ps.mCoords.x + orientationVectorX, 442 ps.mCoords.y + orientationVectorY, 443 mPaint); 444 } 445 446 // Draw the tilt point along the orientation arrow. 447 float tiltScale = (float) Math.sin( 448 ps.mCoords.getAxisValue(MotionEvent.AXIS_TILT)); 449 canvas.drawCircle( 450 ps.mCoords.x + orientationVectorX * tiltScale, 451 ps.mCoords.y + orientationVectorY * tiltScale, 452 3.0f, mPaint); 453 454 // Draw the current bounding box 455 if (ps.mHasBoundingBox) { 456 canvas.drawRect(ps.mBoundingLeft, ps.mBoundingTop, 457 ps.mBoundingRight, ps.mBoundingBottom, mPaint); 458 } 459 } 460 } 461 } 462 logMotionEvent(String type, MotionEvent event)463 private void logMotionEvent(String type, MotionEvent event) { 464 final int action = event.getAction(); 465 final int N = event.getHistorySize(); 466 final int NI = event.getPointerCount(); 467 for (int historyPos = 0; historyPos < N; historyPos++) { 468 for (int i = 0; i < NI; i++) { 469 final int id = event.getPointerId(i); 470 event.getHistoricalPointerCoords(i, historyPos, mTempCoords); 471 logCoords(type, action, i, mTempCoords, id, event); 472 } 473 } 474 for (int i = 0; i < NI; i++) { 475 final int id = event.getPointerId(i); 476 event.getPointerCoords(i, mTempCoords); 477 logCoords(type, action, i, mTempCoords, id, event); 478 } 479 } 480 logCoords(String type, int action, int index, MotionEvent.PointerCoords coords, int id, MotionEvent event)481 private void logCoords(String type, int action, int index, 482 MotionEvent.PointerCoords coords, int id, MotionEvent event) { 483 final int toolType = event.getToolType(index); 484 final int buttonState = event.getButtonState(); 485 final String prefix; 486 switch (action & MotionEvent.ACTION_MASK) { 487 case MotionEvent.ACTION_DOWN: 488 prefix = "DOWN"; 489 break; 490 case MotionEvent.ACTION_UP: 491 prefix = "UP"; 492 break; 493 case MotionEvent.ACTION_MOVE: 494 prefix = "MOVE"; 495 break; 496 case MotionEvent.ACTION_CANCEL: 497 prefix = "CANCEL"; 498 break; 499 case MotionEvent.ACTION_OUTSIDE: 500 prefix = "OUTSIDE"; 501 break; 502 case MotionEvent.ACTION_POINTER_DOWN: 503 if (index == ((action & MotionEvent.ACTION_POINTER_INDEX_MASK) 504 >> MotionEvent.ACTION_POINTER_INDEX_SHIFT)) { 505 prefix = "DOWN"; 506 } else { 507 prefix = "MOVE"; 508 } 509 break; 510 case MotionEvent.ACTION_POINTER_UP: 511 if (index == ((action & MotionEvent.ACTION_POINTER_INDEX_MASK) 512 >> MotionEvent.ACTION_POINTER_INDEX_SHIFT)) { 513 prefix = "UP"; 514 } else { 515 prefix = "MOVE"; 516 } 517 break; 518 case MotionEvent.ACTION_HOVER_MOVE: 519 prefix = "HOVER MOVE"; 520 break; 521 case MotionEvent.ACTION_HOVER_ENTER: 522 prefix = "HOVER ENTER"; 523 break; 524 case MotionEvent.ACTION_HOVER_EXIT: 525 prefix = "HOVER EXIT"; 526 break; 527 case MotionEvent.ACTION_SCROLL: 528 prefix = "SCROLL"; 529 break; 530 default: 531 prefix = Integer.toString(action); 532 break; 533 } 534 535 Log.i(TAG, mText.clear() 536 .append(type).append(" id ").append(id + 1) 537 .append(": ") 538 .append(prefix) 539 .append(" (").append(coords.x, 3).append(", ").append(coords.y, 3) 540 .append(") Pressure=").append(coords.pressure, 3) 541 .append(" Size=").append(coords.size, 3) 542 .append(" TouchMajor=").append(coords.touchMajor, 3) 543 .append(" TouchMinor=").append(coords.touchMinor, 3) 544 .append(" ToolMajor=").append(coords.toolMajor, 3) 545 .append(" ToolMinor=").append(coords.toolMinor, 3) 546 .append(" Orientation=").append((float)(coords.orientation * 180 / Math.PI), 1) 547 .append("deg") 548 .append(" Tilt=").append((float)( 549 coords.getAxisValue(MotionEvent.AXIS_TILT) * 180 / Math.PI), 1) 550 .append("deg") 551 .append(" Distance=").append(coords.getAxisValue(MotionEvent.AXIS_DISTANCE), 1) 552 .append(" VScroll=").append(coords.getAxisValue(MotionEvent.AXIS_VSCROLL), 1) 553 .append(" HScroll=").append(coords.getAxisValue(MotionEvent.AXIS_HSCROLL), 1) 554 .append(" BoundingBox=[(") 555 .append(event.getAxisValue(MotionEvent.AXIS_GENERIC_1), 3) 556 .append(", ").append(event.getAxisValue(MotionEvent.AXIS_GENERIC_2), 3).append(")") 557 .append(", (").append(event.getAxisValue(MotionEvent.AXIS_GENERIC_3), 3) 558 .append(", ").append(event.getAxisValue(MotionEvent.AXIS_GENERIC_4), 3) 559 .append(")]") 560 .append(" ToolType=").append(MotionEvent.toolTypeToString(toolType)) 561 .append(" ButtonState=").append(MotionEvent.buttonStateToString(buttonState)) 562 .toString()); 563 } 564 565 @Override onPointerEvent(MotionEvent event)566 public void onPointerEvent(MotionEvent event) { 567 final int action = event.getAction(); 568 int NP = mPointers.size(); 569 570 if (action == MotionEvent.ACTION_DOWN 571 || (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_DOWN) { 572 final int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) 573 >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; // will be 0 for down 574 if (action == MotionEvent.ACTION_DOWN) { 575 for (int p=0; p<NP; p++) { 576 final PointerState ps = mPointers.get(p); 577 ps.clearTrace(); 578 ps.mCurDown = false; 579 } 580 mCurDown = true; 581 mCurNumPointers = 0; 582 mMaxNumPointers = 0; 583 mVelocity.clear(); 584 if (mAltVelocity != null) { 585 mAltVelocity.clear(); 586 } 587 } 588 589 mCurNumPointers += 1; 590 if (mMaxNumPointers < mCurNumPointers) { 591 mMaxNumPointers = mCurNumPointers; 592 } 593 594 final int id = event.getPointerId(index); 595 while (NP <= id) { 596 PointerState ps = new PointerState(); 597 mPointers.add(ps); 598 NP++; 599 } 600 601 if (mActivePointerId < 0 || 602 !mPointers.get(mActivePointerId).mCurDown) { 603 mActivePointerId = id; 604 } 605 606 final PointerState ps = mPointers.get(id); 607 ps.mCurDown = true; 608 InputDevice device = InputDevice.getDevice(event.getDeviceId()); 609 ps.mHasBoundingBox = device != null && 610 device.getMotionRange(MotionEvent.AXIS_GENERIC_1) != null; 611 } 612 613 final int NI = event.getPointerCount(); 614 615 mVelocity.addMovement(event); 616 mVelocity.computeCurrentVelocity(1); 617 if (mAltVelocity != null) { 618 mAltVelocity.addMovement(event); 619 mAltVelocity.computeCurrentVelocity(1); 620 } 621 622 final int N = event.getHistorySize(); 623 for (int historyPos = 0; historyPos < N; historyPos++) { 624 for (int i = 0; i < NI; i++) { 625 final int id = event.getPointerId(i); 626 final PointerState ps = mCurDown ? mPointers.get(id) : null; 627 final PointerCoords coords = ps != null ? ps.mCoords : mTempCoords; 628 event.getHistoricalPointerCoords(i, historyPos, coords); 629 if (mPrintCoords) { 630 logCoords("Pointer", action, i, coords, id, event); 631 } 632 if (ps != null) { 633 ps.addTrace(coords.x, coords.y, false); 634 } 635 } 636 } 637 for (int i = 0; i < NI; i++) { 638 final int id = event.getPointerId(i); 639 final PointerState ps = mCurDown ? mPointers.get(id) : null; 640 final PointerCoords coords = ps != null ? ps.mCoords : mTempCoords; 641 event.getPointerCoords(i, coords); 642 if (mPrintCoords) { 643 logCoords("Pointer", action, i, coords, id, event); 644 } 645 if (ps != null) { 646 ps.addTrace(coords.x, coords.y, true); 647 ps.mXVelocity = mVelocity.getXVelocity(id); 648 ps.mYVelocity = mVelocity.getYVelocity(id); 649 mVelocity.getEstimator(id, ps.mEstimator); 650 if (mAltVelocity != null) { 651 ps.mAltXVelocity = mAltVelocity.getXVelocity(id); 652 ps.mAltYVelocity = mAltVelocity.getYVelocity(id); 653 mAltVelocity.getEstimator(id, ps.mAltEstimator); 654 } 655 ps.mToolType = event.getToolType(i); 656 657 if (ps.mHasBoundingBox) { 658 ps.mBoundingLeft = event.getAxisValue(MotionEvent.AXIS_GENERIC_1, i); 659 ps.mBoundingTop = event.getAxisValue(MotionEvent.AXIS_GENERIC_2, i); 660 ps.mBoundingRight = event.getAxisValue(MotionEvent.AXIS_GENERIC_3, i); 661 ps.mBoundingBottom = event.getAxisValue(MotionEvent.AXIS_GENERIC_4, i); 662 } 663 } 664 } 665 666 if (action == MotionEvent.ACTION_UP 667 || action == MotionEvent.ACTION_CANCEL 668 || (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP) { 669 final int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) 670 >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; // will be 0 for UP 671 672 final int id = event.getPointerId(index); 673 if (id >= NP) { 674 Slog.wtf(TAG, "Got pointer ID out of bounds: id=" + id + " arraysize=" 675 + NP + " pointerindex=" + index 676 + " action=0x" + Integer.toHexString(action)); 677 return; 678 } 679 final PointerState ps = mPointers.get(id); 680 ps.mCurDown = false; 681 682 if (action == MotionEvent.ACTION_UP 683 || action == MotionEvent.ACTION_CANCEL) { 684 mCurDown = false; 685 mCurNumPointers = 0; 686 } else { 687 mCurNumPointers -= 1; 688 if (mActivePointerId == id) { 689 mActivePointerId = event.getPointerId(index == 0 ? 1 : 0); 690 } 691 ps.addTrace(Float.NaN, Float.NaN, false); 692 } 693 } 694 695 invalidate(); 696 } 697 698 @Override onTouchEvent(MotionEvent event)699 public boolean onTouchEvent(MotionEvent event) { 700 onPointerEvent(event); 701 702 if (event.getAction() == MotionEvent.ACTION_DOWN && !isFocused()) { 703 requestFocus(); 704 } 705 return true; 706 } 707 708 @Override onGenericMotionEvent(MotionEvent event)709 public boolean onGenericMotionEvent(MotionEvent event) { 710 final int source = event.getSource(); 711 if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) { 712 onPointerEvent(event); 713 } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { 714 logMotionEvent("Joystick", event); 715 } else if ((source & InputDevice.SOURCE_CLASS_POSITION) != 0) { 716 logMotionEvent("Position", event); 717 } else { 718 logMotionEvent("Generic", event); 719 } 720 return true; 721 } 722 723 @Override onKeyDown(int keyCode, KeyEvent event)724 public boolean onKeyDown(int keyCode, KeyEvent event) { 725 if (shouldLogKey(keyCode)) { 726 final int repeatCount = event.getRepeatCount(); 727 if (repeatCount == 0) { 728 Log.i(TAG, "Key Down: " + event); 729 } else { 730 Log.i(TAG, "Key Repeat #" + repeatCount + ": " + event); 731 } 732 return true; 733 } 734 return super.onKeyDown(keyCode, event); 735 } 736 737 @Override onKeyUp(int keyCode, KeyEvent event)738 public boolean onKeyUp(int keyCode, KeyEvent event) { 739 if (shouldLogKey(keyCode)) { 740 Log.i(TAG, "Key Up: " + event); 741 return true; 742 } 743 return super.onKeyUp(keyCode, event); 744 } 745 shouldLogKey(int keyCode)746 private static boolean shouldLogKey(int keyCode) { 747 switch (keyCode) { 748 case KeyEvent.KEYCODE_DPAD_UP: 749 case KeyEvent.KEYCODE_DPAD_DOWN: 750 case KeyEvent.KEYCODE_DPAD_LEFT: 751 case KeyEvent.KEYCODE_DPAD_RIGHT: 752 case KeyEvent.KEYCODE_DPAD_CENTER: 753 return true; 754 default: 755 return KeyEvent.isGamepadButton(keyCode) 756 || KeyEvent.isModifierKey(keyCode); 757 } 758 } 759 760 @Override onTrackballEvent(MotionEvent event)761 public boolean onTrackballEvent(MotionEvent event) { 762 logMotionEvent("Trackball", event); 763 return true; 764 } 765 766 @Override onAttachedToWindow()767 protected void onAttachedToWindow() { 768 super.onAttachedToWindow(); 769 770 mIm.registerInputDeviceListener(this, getHandler()); 771 if (shouldShowSystemGestureExclusion()) { 772 try { 773 WindowManagerGlobal.getWindowManagerService() 774 .registerSystemGestureExclusionListener(mSystemGestureExclusionListener, 775 mContext.getDisplayId()); 776 } catch (RemoteException e) { 777 throw e.rethrowFromSystemServer(); 778 } 779 final int alpha = systemGestureExclusionOpacity(); 780 mSystemGestureExclusionPaint.setAlpha(alpha); 781 mSystemGestureExclusionRejectedPaint.setAlpha(alpha); 782 } else { 783 mSystemGestureExclusion.setEmpty(); 784 } 785 logInputDevices(); 786 } 787 788 @Override onDetachedFromWindow()789 protected void onDetachedFromWindow() { 790 super.onDetachedFromWindow(); 791 792 mIm.unregisterInputDeviceListener(this); 793 try { 794 WindowManagerGlobal.getWindowManagerService().unregisterSystemGestureExclusionListener( 795 mSystemGestureExclusionListener, mContext.getDisplayId()); 796 } catch (RemoteException e) { 797 throw e.rethrowFromSystemServer(); 798 } 799 } 800 801 @Override onInputDeviceAdded(int deviceId)802 public void onInputDeviceAdded(int deviceId) { 803 logInputDeviceState(deviceId, "Device Added"); 804 } 805 806 @Override onInputDeviceChanged(int deviceId)807 public void onInputDeviceChanged(int deviceId) { 808 logInputDeviceState(deviceId, "Device Changed"); 809 } 810 811 @Override onInputDeviceRemoved(int deviceId)812 public void onInputDeviceRemoved(int deviceId) { 813 logInputDeviceState(deviceId, "Device Removed"); 814 } 815 logInputDevices()816 private void logInputDevices() { 817 int[] deviceIds = InputDevice.getDeviceIds(); 818 for (int i = 0; i < deviceIds.length; i++) { 819 logInputDeviceState(deviceIds[i], "Device Enumerated"); 820 } 821 } 822 logInputDeviceState(int deviceId, String state)823 private void logInputDeviceState(int deviceId, String state) { 824 InputDevice device = mIm.getInputDevice(deviceId); 825 if (device != null) { 826 Log.i(TAG, state + ": " + device); 827 } else { 828 Log.i(TAG, state + ": " + deviceId); 829 } 830 } 831 shouldShowSystemGestureExclusion()832 private static boolean shouldShowSystemGestureExclusion() { 833 return systemGestureExclusionOpacity() > 0; 834 } 835 systemGestureExclusionOpacity()836 private static int systemGestureExclusionOpacity() { 837 int x = SystemProperties.getInt(GESTURE_EXCLUSION_PROP, 0); 838 return x >= 0 && x <= 255 ? x : 0; 839 } 840 841 // HACK 842 // A quick and dirty string builder implementation optimized for GC. 843 // Using String.format causes the application grind to a halt when 844 // more than a couple of pointers are down due to the number of 845 // temporary objects allocated while formatting strings for drawing or logging. 846 private static final class FasterStringBuilder { 847 private char[] mChars; 848 private int mLength; 849 FasterStringBuilder()850 public FasterStringBuilder() { 851 mChars = new char[64]; 852 } 853 clear()854 public FasterStringBuilder clear() { 855 mLength = 0; 856 return this; 857 } 858 append(String value)859 public FasterStringBuilder append(String value) { 860 final int valueLength = value.length(); 861 final int index = reserve(valueLength); 862 value.getChars(0, valueLength, mChars, index); 863 mLength += valueLength; 864 return this; 865 } 866 append(int value)867 public FasterStringBuilder append(int value) { 868 return append(value, 0); 869 } 870 append(int value, int zeroPadWidth)871 public FasterStringBuilder append(int value, int zeroPadWidth) { 872 final boolean negative = value < 0; 873 if (negative) { 874 value = - value; 875 if (value < 0) { 876 append("-2147483648"); 877 return this; 878 } 879 } 880 881 int index = reserve(11); 882 final char[] chars = mChars; 883 884 if (value == 0) { 885 chars[index++] = '0'; 886 mLength += 1; 887 return this; 888 } 889 890 if (negative) { 891 chars[index++] = '-'; 892 } 893 894 int divisor = 1000000000; 895 int numberWidth = 10; 896 while (value < divisor) { 897 divisor /= 10; 898 numberWidth -= 1; 899 if (numberWidth < zeroPadWidth) { 900 chars[index++] = '0'; 901 } 902 } 903 904 do { 905 int digit = value / divisor; 906 value -= digit * divisor; 907 divisor /= 10; 908 chars[index++] = (char) (digit + '0'); 909 } while (divisor != 0); 910 911 mLength = index; 912 return this; 913 } 914 915 public FasterStringBuilder append(float value, int precision) { 916 int scale = 1; 917 for (int i = 0; i < precision; i++) { 918 scale *= 10; 919 } 920 value = (float) (Math.rint(value * scale) / scale); 921 922 // Corner case: (int)-0.1 will become zero, so the negative sign gets lost 923 if ((int) value == 0 && value < 0) { 924 append("-"); 925 } 926 append((int) value); 927 928 if (precision != 0) { 929 append("."); 930 value = Math.abs(value); 931 value -= Math.floor(value); 932 append((int) (value * scale), precision); 933 } 934 935 return this; 936 } 937 938 @Override 939 public String toString() { 940 return new String(mChars, 0, mLength); 941 } 942 943 private int reserve(int length) { 944 final int oldLength = mLength; 945 final int newLength = mLength + length; 946 final char[] oldChars = mChars; 947 final int oldCapacity = oldChars.length; 948 if (newLength > oldCapacity) { 949 final int newCapacity = oldCapacity * 2; 950 final char[] newChars = new char[newCapacity]; 951 System.arraycopy(oldChars, 0, newChars, 0, oldLength); 952 mChars = newChars; 953 } 954 return oldLength; 955 } 956 } 957 958 private ISystemGestureExclusionListener mSystemGestureExclusionListener = 959 new ISystemGestureExclusionListener.Stub() { 960 @Override 961 public void onSystemGestureExclusionChanged(int displayId, Region systemGestureExclusion, 962 Region systemGestureExclusionUnrestricted) { 963 Region exclusion = Region.obtain(systemGestureExclusion); 964 Region rejected = Region.obtain(); 965 if (systemGestureExclusionUnrestricted != null) { 966 rejected.set(systemGestureExclusionUnrestricted); 967 rejected.op(exclusion, Region.Op.DIFFERENCE); 968 } 969 Handler handler = getHandler(); 970 if (handler != null) { 971 handler.post(() -> { 972 mSystemGestureExclusion.set(exclusion); 973 mSystemGestureExclusionRejected.set(rejected); 974 exclusion.recycle(); 975 invalidate(); 976 }); 977 } 978 } 979 }; 980 } 981