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 75 public void clearTrace() { 76 mTraceCount = 0; 77 } 78 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 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 174 public void setPrintCoords(boolean state) { 175 mPrintCoords = state; 176 } 177 178 @Override 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(); 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 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 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 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 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 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 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 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 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 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 698 public boolean onTrackballEvent(MotionEvent event) { 699 logMotionEvent("Trackball", event); 700 return true; 701 } 702 703 @Override 704 protected void onAttachedToWindow() { 705 super.onAttachedToWindow(); 706 707 mIm.registerInputDeviceListener(this, getHandler()); 708 logInputDevices(); 709 } 710 711 @Override 712 protected void onDetachedFromWindow() { 713 super.onDetachedFromWindow(); 714 715 mIm.unregisterInputDeviceListener(this); 716 } 717 718 @Override 719 public void onInputDeviceAdded(int deviceId) { 720 logInputDeviceState(deviceId, "Device Added"); 721 } 722 723 @Override 724 public void onInputDeviceChanged(int deviceId) { 725 logInputDeviceState(deviceId, "Device Changed"); 726 } 727 728 @Override 729 public void onInputDeviceRemoved(int deviceId) { 730 logInputDeviceState(deviceId, "Device Removed"); 731 } 732 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 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 758 public FasterStringBuilder() { 759 mChars = new char[64]; 760 } 761 762 public FasterStringBuilder clear() { 763 mLength = 0; 764 return this; 765 } 766 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 775 public FasterStringBuilder append(int value) { 776 return append(value, 0); 777 } 778 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