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