PointerLocationView.java revision b59ab9f41faafb358afb4f951de96f34a656e0b4
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.widget;
18
19import android.content.Context;
20import android.graphics.Canvas;
21import android.graphics.Paint;
22import android.graphics.RectF;
23import android.graphics.Paint.FontMetricsInt;
24import android.util.Log;
25import android.view.InputDevice;
26import android.view.KeyEvent;
27import android.view.MotionEvent;
28import android.view.VelocityTracker;
29import android.view.View;
30import android.view.ViewConfiguration;
31import android.view.MotionEvent.PointerCoords;
32
33import java.util.ArrayList;
34
35public 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
58        public void clearTrace() {
59            mTraceCount = 0;
60        }
61
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
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
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
156    public void setPrintCoords(boolean state) {
157        mPrintCoords = state;
158    }
159
160    @Override
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();
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
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
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
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
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
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
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
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
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
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
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
648        public FasterStringBuilder() {
649            mChars = new char[64];
650        }
651
652        public FasterStringBuilder clear() {
653            mLength = 0;
654            return this;
655        }
656
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
665        public FasterStringBuilder append(int value) {
666            return append(value, 0);
667        }
668
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