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