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 clearTrace()58 public void clearTrace() { 59 mTraceCount = 0; 60 } 61 addTrace(float x, float y)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 PointerLocationView(Context c)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 logInputDeviceCapabilities()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 setPrintCoords(boolean state)156 public void setPrintCoords(boolean state) { 157 mPrintCoords = state; 158 } 159 160 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)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(); drawOval(Canvas canvas, float x, float y, float major, float minor, float angle, Paint paint)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 onDraw(Canvas canvas)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 logMotionEvent(String type, MotionEvent event)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 logCoords(String type, int action, int index, MotionEvent.PointerCoords coords, int id, int toolType, int buttonState)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 addPointerEvent(MotionEvent event)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 onTouchEvent(MotionEvent event)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 onGenericMotionEvent(MotionEvent event)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 onKeyDown(int keyCode, KeyEvent event)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 onKeyUp(int keyCode, KeyEvent event)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 shouldLogKey(int keyCode)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 onTrackballEvent(MotionEvent event)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 FasterStringBuilder()648 public FasterStringBuilder() { 649 mChars = new char[64]; 650 } 651 clear()652 public FasterStringBuilder clear() { 653 mLength = 0; 654 return this; 655 } 656 append(String value)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 append(int value)665 public FasterStringBuilder append(int value) { 666 return append(value, 0); 667 } 668 append(int value, int zeroPadWidth)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