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