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