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