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