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