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 78 public void clearTrace() { 79 mTraceCount = 0; 80 } 81 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 136 public PointerLocationView(Context c) { 137 super(c); 138 setFocusableInTouchMode(true); 139 140 mIm = (InputManager)c.getSystemService(Context.INPUT_SERVICE); 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 188 public void setPrintCoords(boolean state) { 189 mPrintCoords = state; 190 } 191 192 @Override 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(); 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 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 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 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 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 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 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 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 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 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 715 public boolean onTrackballEvent(MotionEvent event) { 716 logMotionEvent("Trackball", event); 717 return true; 718 } 719 720 @Override 721 protected void onAttachedToWindow() { 722 super.onAttachedToWindow(); 723 724 mIm.registerInputDeviceListener(this, getHandler()); 725 logInputDevices(); 726 } 727 728 @Override 729 protected void onDetachedFromWindow() { 730 super.onDetachedFromWindow(); 731 732 mIm.unregisterInputDeviceListener(this); 733 } 734 735 @Override 736 public void onInputDeviceAdded(int deviceId) { 737 logInputDeviceState(deviceId, "Device Added"); 738 } 739 740 @Override 741 public void onInputDeviceChanged(int deviceId) { 742 logInputDeviceState(deviceId, "Device Changed"); 743 } 744 745 @Override 746 public void onInputDeviceRemoved(int deviceId) { 747 logInputDeviceState(deviceId, "Device Removed"); 748 } 749 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 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 775 public FasterStringBuilder() { 776 mChars = new char[64]; 777 } 778 779 public FasterStringBuilder clear() { 780 mLength = 0; 781 return this; 782 } 783 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 792 public FasterStringBuilder append(int value) { 793 return append(value, 0); 794 } 795 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