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