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