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