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