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