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