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