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