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