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