PointerLocationView.java revision 6f2fba428ca5e77a26d991ad728e346cc47609ee
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.MotionEvent; 27import android.view.VelocityTracker; 28import android.view.View; 29import android.view.ViewConfiguration; 30 31import java.util.ArrayList; 32 33public class PointerLocationView extends View { 34 private static final String TAG = "Pointer"; 35 36 public static class PointerState { 37 // Trace of previous points. 38 private float[] mTraceX = new float[32]; 39 private float[] mTraceY = new float[32]; 40 private int mTraceCount; 41 42 // True if the pointer is down. 43 private boolean mCurDown; 44 45 // Most recent coordinates. 46 private MotionEvent.PointerCoords mCoords = new MotionEvent.PointerCoords(); 47 48 // Most recent velocity. 49 private float mXVelocity; 50 private float mYVelocity; 51 52 public void clearTrace() { 53 mTraceCount = 0; 54 } 55 56 public void addTrace(float x, float y) { 57 int traceCapacity = mTraceX.length; 58 if (mTraceCount == traceCapacity) { 59 traceCapacity *= 2; 60 float[] newTraceX = new float[traceCapacity]; 61 System.arraycopy(mTraceX, 0, newTraceX, 0, mTraceCount); 62 mTraceX = newTraceX; 63 64 float[] newTraceY = new float[traceCapacity]; 65 System.arraycopy(mTraceY, 0, newTraceY, 0, mTraceCount); 66 mTraceY = newTraceY; 67 } 68 69 mTraceX[mTraceCount] = x; 70 mTraceY[mTraceCount] = y; 71 mTraceCount += 1; 72 } 73 } 74 75 private final ViewConfiguration mVC; 76 private final Paint mTextPaint; 77 private final Paint mTextBackgroundPaint; 78 private final Paint mTextLevelPaint; 79 private final Paint mPaint; 80 private final Paint mTargetPaint; 81 private final Paint mPathPaint; 82 private final FontMetricsInt mTextMetrics = new FontMetricsInt(); 83 private int mHeaderBottom; 84 private boolean mCurDown; 85 private int mCurNumPointers; 86 private int mMaxNumPointers; 87 private int mActivePointerId; 88 private final ArrayList<PointerState> mPointers = new ArrayList<PointerState>(); 89 90 private final VelocityTracker mVelocity; 91 92 private final FasterStringBuilder mText = new FasterStringBuilder(); 93 94 private boolean mPrintCoords = true; 95 96 public PointerLocationView(Context c) { 97 super(c); 98 setFocusable(true); 99 mVC = ViewConfiguration.get(c); 100 mTextPaint = new Paint(); 101 mTextPaint.setAntiAlias(true); 102 mTextPaint.setTextSize(10 103 * getResources().getDisplayMetrics().density); 104 mTextPaint.setARGB(255, 0, 0, 0); 105 mTextBackgroundPaint = new Paint(); 106 mTextBackgroundPaint.setAntiAlias(false); 107 mTextBackgroundPaint.setARGB(128, 255, 255, 255); 108 mTextLevelPaint = new Paint(); 109 mTextLevelPaint.setAntiAlias(false); 110 mTextLevelPaint.setARGB(192, 255, 0, 0); 111 mPaint = new Paint(); 112 mPaint.setAntiAlias(true); 113 mPaint.setARGB(255, 255, 255, 255); 114 mPaint.setStyle(Paint.Style.STROKE); 115 mPaint.setStrokeWidth(2); 116 mTargetPaint = new Paint(); 117 mTargetPaint.setAntiAlias(false); 118 mTargetPaint.setARGB(255, 0, 0, 192); 119 mPathPaint = new Paint(); 120 mPathPaint.setAntiAlias(false); 121 mPathPaint.setARGB(255, 0, 96, 255); 122 mPaint.setStyle(Paint.Style.STROKE); 123 mPaint.setStrokeWidth(1); 124 125 PointerState ps = new PointerState(); 126 mPointers.add(ps); 127 mActivePointerId = 0; 128 129 mVelocity = VelocityTracker.obtain(); 130 131 logInputDeviceCapabilities(); 132 } 133 134 private void logInputDeviceCapabilities() { 135 int[] deviceIds = InputDevice.getDeviceIds(); 136 for (int i = 0; i < deviceIds.length; i++) { 137 InputDevice device = InputDevice.getDevice(deviceIds[i]); 138 if (device != null) { 139 Log.i(TAG, device.toString()); 140 } 141 } 142 } 143 144 public void setPrintCoords(boolean state) { 145 mPrintCoords = state; 146 } 147 148 @Override 149 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 150 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 151 mTextPaint.getFontMetricsInt(mTextMetrics); 152 mHeaderBottom = -mTextMetrics.ascent+mTextMetrics.descent+2; 153 if (false) { 154 Log.i("foo", "Metrics: ascent=" + mTextMetrics.ascent 155 + " descent=" + mTextMetrics.descent 156 + " leading=" + mTextMetrics.leading 157 + " top=" + mTextMetrics.top 158 + " bottom=" + mTextMetrics.bottom); 159 } 160 } 161 162 // Draw an oval. When angle is 0 radians, orients the major axis vertically, 163 // angles less than or greater than 0 radians rotate the major axis left or right. 164 private RectF mReusableOvalRect = new RectF(); 165 private void drawOval(Canvas canvas, float x, float y, float major, float minor, 166 float angle, Paint paint) { 167 canvas.save(Canvas.MATRIX_SAVE_FLAG); 168 canvas.rotate((float) (angle * 180 / Math.PI), x, y); 169 mReusableOvalRect.left = x - minor / 2; 170 mReusableOvalRect.right = x + minor / 2; 171 mReusableOvalRect.top = y - major / 2; 172 mReusableOvalRect.bottom = y + major / 2; 173 canvas.drawOval(mReusableOvalRect, paint); 174 canvas.restore(); 175 } 176 177 @Override 178 protected void onDraw(Canvas canvas) { 179 synchronized (mPointers) { 180 final int w = getWidth(); 181 final int itemW = w/7; 182 final int base = -mTextMetrics.ascent+1; 183 final int bottom = mHeaderBottom; 184 185 final int NP = mPointers.size(); 186 187 // Labels 188 if (mActivePointerId >= 0) { 189 final PointerState ps = mPointers.get(mActivePointerId); 190 191 canvas.drawRect(0, 0, itemW-1, bottom,mTextBackgroundPaint); 192 canvas.drawText(mText.clear() 193 .append("P: ").append(mCurNumPointers) 194 .append(" / ").append(mMaxNumPointers) 195 .toString(), 1, base, mTextPaint); 196 197 final int N = ps.mTraceCount; 198 if ((mCurDown && ps.mCurDown) || N == 0) { 199 canvas.drawRect(itemW, 0, (itemW * 2) - 1, bottom, mTextBackgroundPaint); 200 canvas.drawText(mText.clear() 201 .append("X: ").append(ps.mCoords.x, 1) 202 .toString(), 1 + itemW, base, mTextPaint); 203 canvas.drawRect(itemW * 2, 0, (itemW * 3) - 1, bottom, mTextBackgroundPaint); 204 canvas.drawText(mText.clear() 205 .append("Y: ").append(ps.mCoords.y, 1) 206 .toString(), 1 + itemW * 2, base, mTextPaint); 207 } else { 208 float dx = ps.mTraceX[N - 1] - ps.mTraceX[0]; 209 float dy = ps.mTraceY[N - 1] - ps.mTraceY[0]; 210 canvas.drawRect(itemW, 0, (itemW * 2) - 1, bottom, 211 Math.abs(dx) < mVC.getScaledTouchSlop() 212 ? mTextBackgroundPaint : mTextLevelPaint); 213 canvas.drawText(mText.clear() 214 .append("dX: ").append(dx, 1) 215 .toString(), 1 + itemW, base, mTextPaint); 216 canvas.drawRect(itemW * 2, 0, (itemW * 3) - 1, bottom, 217 Math.abs(dy) < mVC.getScaledTouchSlop() 218 ? mTextBackgroundPaint : mTextLevelPaint); 219 canvas.drawText(mText.clear() 220 .append("dY: ").append(dy, 1) 221 .toString(), 1 + itemW * 2, base, mTextPaint); 222 } 223 224 canvas.drawRect(itemW * 3, 0, (itemW * 4) - 1, bottom, mTextBackgroundPaint); 225 canvas.drawText(mText.clear() 226 .append("Xv: ").append(ps.mXVelocity, 3) 227 .toString(), 1 + itemW * 3, base, mTextPaint); 228 229 canvas.drawRect(itemW * 4, 0, (itemW * 5) - 1, bottom, mTextBackgroundPaint); 230 canvas.drawText(mText.clear() 231 .append("Yv: ").append(ps.mYVelocity, 3) 232 .toString(), 1 + itemW * 4, base, mTextPaint); 233 234 canvas.drawRect(itemW * 5, 0, (itemW * 6) - 1, bottom, mTextBackgroundPaint); 235 canvas.drawRect(itemW * 5, 0, (itemW * 5) + (ps.mCoords.pressure * itemW) - 1, 236 bottom, mTextLevelPaint); 237 canvas.drawText(mText.clear() 238 .append("Prs: ").append(ps.mCoords.pressure, 2) 239 .toString(), 1 + itemW * 5, base, mTextPaint); 240 241 canvas.drawRect(itemW * 6, 0, w, bottom, mTextBackgroundPaint); 242 canvas.drawRect(itemW * 6, 0, (itemW * 6) + (ps.mCoords.size * itemW) - 1, 243 bottom, mTextLevelPaint); 244 canvas.drawText(mText.clear() 245 .append("Size: ").append(ps.mCoords.size, 2) 246 .toString(), 1 + itemW * 6, base, mTextPaint); 247 } 248 249 // Pointer trace. 250 for (int p = 0; p < NP; p++) { 251 final PointerState ps = mPointers.get(p); 252 253 // Draw path. 254 final int N = ps.mTraceCount; 255 float lastX = 0, lastY = 0; 256 boolean haveLast = false; 257 boolean drawn = false; 258 mPaint.setARGB(255, 128, 255, 255); 259 for (int i=0; i < N; i++) { 260 float x = ps.mTraceX[i]; 261 float y = ps.mTraceY[i]; 262 if (Float.isNaN(x)) { 263 haveLast = false; 264 continue; 265 } 266 if (haveLast) { 267 canvas.drawLine(lastX, lastY, x, y, mPathPaint); 268 canvas.drawPoint(lastX, lastY, mPaint); 269 drawn = true; 270 } 271 lastX = x; 272 lastY = y; 273 haveLast = true; 274 } 275 276 // Draw velocity vector. 277 if (drawn) { 278 mPaint.setARGB(255, 255, 64, 128); 279 float xVel = ps.mXVelocity * (1000 / 60); 280 float yVel = ps.mYVelocity * (1000 / 60); 281 canvas.drawLine(lastX, lastY, lastX + xVel, lastY + yVel, mPaint); 282 } 283 284 if (mCurDown && ps.mCurDown) { 285 // Draw crosshairs. 286 canvas.drawLine(0, ps.mCoords.y, getWidth(), ps.mCoords.y, mTargetPaint); 287 canvas.drawLine(ps.mCoords.x, 0, ps.mCoords.x, getHeight(), mTargetPaint); 288 289 // Draw current point. 290 int pressureLevel = (int)(ps.mCoords.pressure * 255); 291 mPaint.setARGB(255, pressureLevel, 255, 255 - pressureLevel); 292 canvas.drawPoint(ps.mCoords.x, ps.mCoords.y, mPaint); 293 294 // Draw current touch ellipse. 295 mPaint.setARGB(255, pressureLevel, 255 - pressureLevel, 128); 296 drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.touchMajor, 297 ps.mCoords.touchMinor, ps.mCoords.orientation, mPaint); 298 299 // Draw current tool ellipse. 300 mPaint.setARGB(255, pressureLevel, 128, 255 - pressureLevel); 301 drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.toolMajor, 302 ps.mCoords.toolMinor, ps.mCoords.orientation, mPaint); 303 304 // Draw the orientation arrow. 305 mPaint.setARGB(255, pressureLevel, 255, 0); 306 float orientationVectorX = (float) (Math.sin(-ps.mCoords.orientation) 307 * ps.mCoords.toolMajor * 0.7); 308 float orientationVectorY = (float) (Math.cos(-ps.mCoords.orientation) 309 * ps.mCoords.toolMajor * 0.7); 310 canvas.drawLine( 311 ps.mCoords.x - orientationVectorX, ps.mCoords.y - orientationVectorY, 312 ps.mCoords.x + orientationVectorX, ps.mCoords.y + orientationVectorY, 313 mPaint); 314 } 315 } 316 } 317 } 318 319 private void logPointerCoords(MotionEvent.PointerCoords coords, int id) { 320 Log.i(TAG, mText.clear() 321 .append("Pointer ").append(id + 1) 322 .append(": (").append(coords.x, 3).append(", ").append(coords.y, 3) 323 .append(") Pressure=").append(coords.pressure, 3) 324 .append(" Size=").append(coords.size, 3) 325 .append(" TouchMajor=").append(coords.touchMajor, 3) 326 .append(" TouchMinor=").append(coords.touchMinor, 3) 327 .append(" ToolMajor=").append(coords.toolMajor, 3) 328 .append(" ToolMinor=").append(coords.toolMinor, 3) 329 .append(" Orientation=").append((float)(coords.orientation * 180 / Math.PI), 1) 330 .append("deg") 331 .append(" VScroll=").append(coords.getAxisValue(MotionEvent.AXIS_VSCROLL), 1) 332 .append(" HScroll=").append(coords.getAxisValue(MotionEvent.AXIS_HSCROLL), 1) 333 .toString()); 334 } 335 336 public void addTouchEvent(MotionEvent event) { 337 synchronized (mPointers) { 338 int action = event.getAction(); 339 340 //Log.i(TAG, "Motion: action=0x" + Integer.toHexString(action) 341 // + " pointers=" + event.getPointerCount()); 342 343 int NP = mPointers.size(); 344 345 //mRect.set(0, 0, getWidth(), mHeaderBottom+1); 346 //invalidate(mRect); 347 //if (mCurDown) { 348 // mRect.set(mCurX-mCurWidth-3, mCurY-mCurWidth-3, 349 // mCurX+mCurWidth+3, mCurY+mCurWidth+3); 350 //} else { 351 // mRect.setEmpty(); 352 //} 353 if (action == MotionEvent.ACTION_DOWN 354 || (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_DOWN) { 355 final int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) 356 >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; // will be 0 for down 357 if (action == MotionEvent.ACTION_DOWN) { 358 for (int p=0; p<NP; p++) { 359 final PointerState ps = mPointers.get(p); 360 ps.clearTrace(); 361 ps.mCurDown = false; 362 } 363 mCurDown = true; 364 mMaxNumPointers = 0; 365 mVelocity.clear(); 366 } 367 368 final int id = event.getPointerId(index); 369 while (NP <= id) { 370 PointerState ps = new PointerState(); 371 mPointers.add(ps); 372 NP++; 373 } 374 375 if (mActivePointerId < 0 || 376 ! mPointers.get(mActivePointerId).mCurDown) { 377 mActivePointerId = id; 378 } 379 380 final PointerState ps = mPointers.get(id); 381 ps.mCurDown = true; 382 if (mPrintCoords) { 383 Log.i(TAG, mText.clear().append("Pointer ") 384 .append(id + 1).append(": DOWN").toString()); 385 } 386 } 387 388 final int NI = event.getPointerCount(); 389 390 mCurDown = action != MotionEvent.ACTION_UP 391 && action != MotionEvent.ACTION_CANCEL; 392 mCurNumPointers = mCurDown ? NI : 0; 393 if (mMaxNumPointers < mCurNumPointers) { 394 mMaxNumPointers = mCurNumPointers; 395 } 396 397 mVelocity.addMovement(event); 398 mVelocity.computeCurrentVelocity(1); 399 400 for (int i=0; i<NI; i++) { 401 final int id = event.getPointerId(i); 402 final PointerState ps = mPointers.get(id); 403 final int N = event.getHistorySize(); 404 for (int j=0; j<N; j++) { 405 event.getHistoricalPointerCoords(i, j, ps.mCoords); 406 if (mPrintCoords) { 407 logPointerCoords(ps.mCoords, id); 408 } 409 ps.addTrace(event.getHistoricalX(i, j), event.getHistoricalY(i, j)); 410 } 411 event.getPointerCoords(i, ps.mCoords); 412 if (mPrintCoords) { 413 logPointerCoords(ps.mCoords, id); 414 } 415 ps.addTrace(ps.mCoords.x, ps.mCoords.y); 416 ps.mXVelocity = mVelocity.getXVelocity(id); 417 ps.mYVelocity = mVelocity.getYVelocity(id); 418 } 419 420 if (action == MotionEvent.ACTION_UP 421 || action == MotionEvent.ACTION_CANCEL 422 || (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP) { 423 final int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) 424 >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; // will be 0 for UP 425 426 final int id = event.getPointerId(index); 427 final PointerState ps = mPointers.get(id); 428 ps.mCurDown = false; 429 if (mPrintCoords) { 430 Log.i(TAG, mText.clear().append("Pointer ") 431 .append(id + 1).append(": UP").toString()); 432 } 433 434 if (action == MotionEvent.ACTION_UP 435 || action == MotionEvent.ACTION_CANCEL) { 436 mCurDown = false; 437 } else { 438 if (mActivePointerId == id) { 439 mActivePointerId = event.getPointerId(index == 0 ? 1 : 0); 440 } 441 ps.addTrace(Float.NaN, Float.NaN); 442 } 443 } 444 445 //if (mCurDown) { 446 // mRect.union(mCurX-mCurWidth-3, mCurY-mCurWidth-3, 447 // mCurX+mCurWidth+3, mCurY+mCurWidth+3); 448 //} 449 //invalidate(mRect); 450 postInvalidate(); 451 } 452 } 453 454 @Override 455 public boolean onTouchEvent(MotionEvent event) { 456 addTouchEvent(event); 457 return true; 458 } 459 460 @Override 461 public boolean onTrackballEvent(MotionEvent event) { 462 Log.i(TAG, "Trackball: " + event); 463 return super.onTrackballEvent(event); 464 } 465 466 // HACK 467 // A quick and dirty string builder implementation optimized for GC. 468 // Using String.format causes the application grind to a halt when 469 // more than a couple of pointers are down due to the number of 470 // temporary objects allocated while formatting strings for drawing or logging. 471 private static final class FasterStringBuilder { 472 private char[] mChars; 473 private int mLength; 474 475 public FasterStringBuilder() { 476 mChars = new char[64]; 477 } 478 479 public FasterStringBuilder clear() { 480 mLength = 0; 481 return this; 482 } 483 484 public FasterStringBuilder append(String value) { 485 final int valueLength = value.length(); 486 final int index = reserve(valueLength); 487 value.getChars(0, valueLength, mChars, index); 488 mLength += valueLength; 489 return this; 490 } 491 492 public FasterStringBuilder append(int value) { 493 return append(value, 0); 494 } 495 496 public FasterStringBuilder append(int value, int zeroPadWidth) { 497 final boolean negative = value < 0; 498 if (negative) { 499 value = - value; 500 if (value < 0) { 501 append("-2147483648"); 502 return this; 503 } 504 } 505 506 int index = reserve(11); 507 final char[] chars = mChars; 508 509 if (value == 0) { 510 chars[index++] = '0'; 511 mLength += 1; 512 return this; 513 } 514 515 if (negative) { 516 chars[index++] = '-'; 517 } 518 519 int divisor = 1000000000; 520 int numberWidth = 10; 521 while (value < divisor) { 522 divisor /= 10; 523 numberWidth -= 1; 524 if (numberWidth < zeroPadWidth) { 525 chars[index++] = '0'; 526 } 527 } 528 529 do { 530 int digit = value / divisor; 531 value -= digit * divisor; 532 divisor /= 10; 533 chars[index++] = (char) (digit + '0'); 534 } while (divisor != 0); 535 536 mLength = index; 537 return this; 538 } 539 540 public FasterStringBuilder append(float value, int precision) { 541 int scale = 1; 542 for (int i = 0; i < precision; i++) { 543 scale *= 10; 544 } 545 value = (float) (Math.rint(value * scale) / scale); 546 547 append((int) value); 548 549 if (precision != 0) { 550 append("."); 551 value = Math.abs(value); 552 value -= Math.floor(value); 553 append((int) (value * scale), precision); 554 } 555 556 return this; 557 } 558 559 @Override 560 public String toString() { 561 return new String(mChars, 0, mLength); 562 } 563 564 private int reserve(int length) { 565 final int oldLength = mLength; 566 final int newLength = mLength + length; 567 final char[] oldChars = mChars; 568 final int oldCapacity = oldChars.length; 569 if (newLength > oldCapacity) { 570 final int newCapacity = oldCapacity * 2; 571 final char[] newChars = new char[newCapacity]; 572 System.arraycopy(oldChars, 0, newChars, 0, oldLength); 573 mChars = newChars; 574 } 575 return oldLength; 576 } 577 } 578} 579