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