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.util.Log;
     25 import android.view.InputDevice;
     26 import android.view.MotionEvent;
     27 import android.view.VelocityTracker;
     28 import android.view.View;
     29 import android.view.ViewConfiguration;
     30 
     31 import java.util.ArrayList;
     32 
     33 public class PointerLocationView extends View {
     34     private static final String TAG = "Pointer";
     35 
     36     public static class PointerState {
     37         // Trace of previous points.
     38         private float[] mTraceX = new float[32];
     39         private float[] mTraceY = new float[32];
     40         private int mTraceCount;
     41 
     42         // True if the pointer is down.
     43         private boolean mCurDown;
     44 
     45         // Most recent coordinates.
     46         private MotionEvent.PointerCoords mCoords = new MotionEvent.PointerCoords();
     47 
     48         // Most recent velocity.
     49         private float mXVelocity;
     50         private float mYVelocity;
     51 
     52         public void clearTrace() {
     53             mTraceCount = 0;
     54         }
     55 
     56         public void addTrace(float x, float y) {
     57             int traceCapacity = mTraceX.length;
     58             if (mTraceCount == traceCapacity) {
     59                 traceCapacity *= 2;
     60                 float[] newTraceX = new float[traceCapacity];
     61                 System.arraycopy(mTraceX, 0, newTraceX, 0, mTraceCount);
     62                 mTraceX = newTraceX;
     63 
     64                 float[] newTraceY = new float[traceCapacity];
     65                 System.arraycopy(mTraceY, 0, newTraceY, 0, mTraceCount);
     66                 mTraceY = newTraceY;
     67             }
     68 
     69             mTraceX[mTraceCount] = x;
     70             mTraceY[mTraceCount] = y;
     71             mTraceCount += 1;
     72         }
     73     }
     74 
     75     private final ViewConfiguration mVC;
     76     private final Paint mTextPaint;
     77     private final Paint mTextBackgroundPaint;
     78     private final Paint mTextLevelPaint;
     79     private final Paint mPaint;
     80     private final Paint mTargetPaint;
     81     private final Paint mPathPaint;
     82     private final FontMetricsInt mTextMetrics = new FontMetricsInt();
     83     private int mHeaderBottom;
     84     private boolean mCurDown;
     85     private int mCurNumPointers;
     86     private int mMaxNumPointers;
     87     private int mActivePointerId;
     88     private final ArrayList<PointerState> mPointers = new ArrayList<PointerState>();
     89 
     90     private final VelocityTracker mVelocity;
     91 
     92     private final FasterStringBuilder mText = new FasterStringBuilder();
     93 
     94     private boolean mPrintCoords = true;
     95 
     96     public PointerLocationView(Context c) {
     97         super(c);
     98         setFocusable(true);
     99         mVC = ViewConfiguration.get(c);
    100         mTextPaint = new Paint();
    101         mTextPaint.setAntiAlias(true);
    102         mTextPaint.setTextSize(10
    103                 * getResources().getDisplayMetrics().density);
    104         mTextPaint.setARGB(255, 0, 0, 0);
    105         mTextBackgroundPaint = new Paint();
    106         mTextBackgroundPaint.setAntiAlias(false);
    107         mTextBackgroundPaint.setARGB(128, 255, 255, 255);
    108         mTextLevelPaint = new Paint();
    109         mTextLevelPaint.setAntiAlias(false);
    110         mTextLevelPaint.setARGB(192, 255, 0, 0);
    111         mPaint = new Paint();
    112         mPaint.setAntiAlias(true);
    113         mPaint.setARGB(255, 255, 255, 255);
    114         mPaint.setStyle(Paint.Style.STROKE);
    115         mPaint.setStrokeWidth(2);
    116         mTargetPaint = new Paint();
    117         mTargetPaint.setAntiAlias(false);
    118         mTargetPaint.setARGB(255, 0, 0, 192);
    119         mPathPaint = new Paint();
    120         mPathPaint.setAntiAlias(false);
    121         mPathPaint.setARGB(255, 0, 96, 255);
    122         mPaint.setStyle(Paint.Style.STROKE);
    123         mPaint.setStrokeWidth(1);
    124 
    125         PointerState ps = new PointerState();
    126         mPointers.add(ps);
    127         mActivePointerId = 0;
    128 
    129         mVelocity = VelocityTracker.obtain();
    130 
    131         logInputDeviceCapabilities();
    132     }
    133 
    134     private void logInputDeviceCapabilities() {
    135         int[] deviceIds = InputDevice.getDeviceIds();
    136         for (int i = 0; i < deviceIds.length; i++) {
    137             InputDevice device = InputDevice.getDevice(deviceIds[i]);
    138             if (device != null) {
    139                 Log.i(TAG, device.toString());
    140             }
    141         }
    142     }
    143 
    144     public void setPrintCoords(boolean state) {
    145         mPrintCoords = state;
    146     }
    147 
    148     @Override
    149     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    150         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    151         mTextPaint.getFontMetricsInt(mTextMetrics);
    152         mHeaderBottom = -mTextMetrics.ascent+mTextMetrics.descent+2;
    153         if (false) {
    154             Log.i("foo", "Metrics: ascent=" + mTextMetrics.ascent
    155                     + " descent=" + mTextMetrics.descent
    156                     + " leading=" + mTextMetrics.leading
    157                     + " top=" + mTextMetrics.top
    158                     + " bottom=" + mTextMetrics.bottom);
    159         }
    160     }
    161 
    162     // Draw an oval.  When angle is 0 radians, orients the major axis vertically,
    163     // angles less than or greater than 0 radians rotate the major axis left or right.
    164     private RectF mReusableOvalRect = new RectF();
    165     private void drawOval(Canvas canvas, float x, float y, float major, float minor,
    166             float angle, Paint paint) {
    167         canvas.save(Canvas.MATRIX_SAVE_FLAG);
    168         canvas.rotate((float) (angle * 180 / Math.PI), x, y);
    169         mReusableOvalRect.left = x - minor / 2;
    170         mReusableOvalRect.right = x + minor / 2;
    171         mReusableOvalRect.top = y - major / 2;
    172         mReusableOvalRect.bottom = y + major / 2;
    173         canvas.drawOval(mReusableOvalRect, paint);
    174         canvas.restore();
    175     }
    176 
    177     @Override
    178     protected void onDraw(Canvas canvas) {
    179         synchronized (mPointers) {
    180             final int w = getWidth();
    181             final int itemW = w/7;
    182             final int base = -mTextMetrics.ascent+1;
    183             final int bottom = mHeaderBottom;
    184 
    185             final int NP = mPointers.size();
    186 
    187             // Labels
    188             if (mActivePointerId >= 0) {
    189                 final PointerState ps = mPointers.get(mActivePointerId);
    190 
    191                 canvas.drawRect(0, 0, itemW-1, bottom,mTextBackgroundPaint);
    192                 canvas.drawText(mText.clear()
    193                         .append("P: ").append(mCurNumPointers)
    194                         .append(" / ").append(mMaxNumPointers)
    195                         .toString(), 1, base, mTextPaint);
    196 
    197                 final int N = ps.mTraceCount;
    198                 if ((mCurDown && ps.mCurDown) || N == 0) {
    199                     canvas.drawRect(itemW, 0, (itemW * 2) - 1, bottom, mTextBackgroundPaint);
    200                     canvas.drawText(mText.clear()
    201                             .append("X: ").append(ps.mCoords.x, 1)
    202                             .toString(), 1 + itemW, base, mTextPaint);
    203                     canvas.drawRect(itemW * 2, 0, (itemW * 3) - 1, bottom, mTextBackgroundPaint);
    204                     canvas.drawText(mText.clear()
    205                             .append("Y: ").append(ps.mCoords.y, 1)
    206                             .toString(), 1 + itemW * 2, base, mTextPaint);
    207                 } else {
    208                     float dx = ps.mTraceX[N - 1] - ps.mTraceX[0];
    209                     float dy = ps.mTraceY[N - 1] - ps.mTraceY[0];
    210                     canvas.drawRect(itemW, 0, (itemW * 2) - 1, bottom,
    211                             Math.abs(dx) < mVC.getScaledTouchSlop()
    212                             ? mTextBackgroundPaint : mTextLevelPaint);
    213                     canvas.drawText(mText.clear()
    214                             .append("dX: ").append(dx, 1)
    215                             .toString(), 1 + itemW, base, mTextPaint);
    216                     canvas.drawRect(itemW * 2, 0, (itemW * 3) - 1, bottom,
    217                             Math.abs(dy) < mVC.getScaledTouchSlop()
    218                             ? mTextBackgroundPaint : mTextLevelPaint);
    219                     canvas.drawText(mText.clear()
    220                             .append("dY: ").append(dy, 1)
    221                             .toString(), 1 + itemW * 2, base, mTextPaint);
    222                 }
    223 
    224                 canvas.drawRect(itemW * 3, 0, (itemW * 4) - 1, bottom, mTextBackgroundPaint);
    225                 canvas.drawText(mText.clear()
    226                         .append("Xv: ").append(ps.mXVelocity, 3)
    227                         .toString(), 1 + itemW * 3, base, mTextPaint);
    228 
    229                 canvas.drawRect(itemW * 4, 0, (itemW * 5) - 1, bottom, mTextBackgroundPaint);
    230                 canvas.drawText(mText.clear()
    231                         .append("Yv: ").append(ps.mYVelocity, 3)
    232                         .toString(), 1 + itemW * 4, base, mTextPaint);
    233 
    234                 canvas.drawRect(itemW * 5, 0, (itemW * 6) - 1, bottom, mTextBackgroundPaint);
    235                 canvas.drawRect(itemW * 5, 0, (itemW * 5) + (ps.mCoords.pressure * itemW) - 1,
    236                         bottom, mTextLevelPaint);
    237                 canvas.drawText(mText.clear()
    238                         .append("Prs: ").append(ps.mCoords.pressure, 2)
    239                         .toString(), 1 + itemW * 5, base, mTextPaint);
    240 
    241                 canvas.drawRect(itemW * 6, 0, w, bottom, mTextBackgroundPaint);
    242                 canvas.drawRect(itemW * 6, 0, (itemW * 6) + (ps.mCoords.size * itemW) - 1,
    243                         bottom, mTextLevelPaint);
    244                 canvas.drawText(mText.clear()
    245                         .append("Size: ").append(ps.mCoords.size, 2)
    246                         .toString(), 1 + itemW * 6, base, mTextPaint);
    247             }
    248 
    249             // Pointer trace.
    250             for (int p = 0; p < NP; p++) {
    251                 final PointerState ps = mPointers.get(p);
    252 
    253                 // Draw path.
    254                 final int N = ps.mTraceCount;
    255                 float lastX = 0, lastY = 0;
    256                 boolean haveLast = false;
    257                 boolean drawn = false;
    258                 mPaint.setARGB(255, 128, 255, 255);
    259                 for (int i=0; i < N; i++) {
    260                     float x = ps.mTraceX[i];
    261                     float y = ps.mTraceY[i];
    262                     if (Float.isNaN(x)) {
    263                         haveLast = false;
    264                         continue;
    265                     }
    266                     if (haveLast) {
    267                         canvas.drawLine(lastX, lastY, x, y, mPathPaint);
    268                         canvas.drawPoint(lastX, lastY, mPaint);
    269                         drawn = true;
    270                     }
    271                     lastX = x;
    272                     lastY = y;
    273                     haveLast = true;
    274                 }
    275 
    276                 // Draw velocity vector.
    277                 if (drawn) {
    278                     mPaint.setARGB(255, 255, 64, 128);
    279                     float xVel = ps.mXVelocity * (1000 / 60);
    280                     float yVel = ps.mYVelocity * (1000 / 60);
    281                     canvas.drawLine(lastX, lastY, lastX + xVel, lastY + yVel, mPaint);
    282                 }
    283 
    284                 if (mCurDown && ps.mCurDown) {
    285                     // Draw crosshairs.
    286                     canvas.drawLine(0, ps.mCoords.y, getWidth(), ps.mCoords.y, mTargetPaint);
    287                     canvas.drawLine(ps.mCoords.x, 0, ps.mCoords.x, getHeight(), mTargetPaint);
    288 
    289                     // Draw current point.
    290                     int pressureLevel = (int)(ps.mCoords.pressure * 255);
    291                     mPaint.setARGB(255, pressureLevel, 255, 255 - pressureLevel);
    292                     canvas.drawPoint(ps.mCoords.x, ps.mCoords.y, mPaint);
    293 
    294                     // Draw current touch ellipse.
    295                     mPaint.setARGB(255, pressureLevel, 255 - pressureLevel, 128);
    296                     drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.touchMajor,
    297                             ps.mCoords.touchMinor, ps.mCoords.orientation, mPaint);
    298 
    299                     // Draw current tool ellipse.
    300                     mPaint.setARGB(255, pressureLevel, 128, 255 - pressureLevel);
    301                     drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.toolMajor,
    302                             ps.mCoords.toolMinor, ps.mCoords.orientation, mPaint);
    303                 }
    304             }
    305         }
    306     }
    307 
    308     private void logPointerCoords(MotionEvent.PointerCoords coords, int id) {
    309         Log.i(TAG, mText.clear()
    310                 .append("Pointer ").append(id + 1)
    311                 .append(": (").append(coords.x, 3).append(", ").append(coords.y, 3)
    312                 .append(") Pressure=").append(coords.pressure, 3)
    313                 .append(" Size=").append(coords.size, 3)
    314                 .append(" TouchMajor=").append(coords.touchMajor, 3)
    315                 .append(" TouchMinor=").append(coords.touchMinor, 3)
    316                 .append(" ToolMajor=").append(coords.toolMajor, 3)
    317                 .append(" ToolMinor=").append(coords.toolMinor, 3)
    318                 .append(" Orientation=").append((float)(coords.orientation * 180 / Math.PI), 1)
    319                 .append("deg").toString());
    320     }
    321 
    322     public void addTouchEvent(MotionEvent event) {
    323         synchronized (mPointers) {
    324             int action = event.getAction();
    325 
    326             //Log.i(TAG, "Motion: action=0x" + Integer.toHexString(action)
    327             //        + " pointers=" + event.getPointerCount());
    328 
    329             int NP = mPointers.size();
    330 
    331             //mRect.set(0, 0, getWidth(), mHeaderBottom+1);
    332             //invalidate(mRect);
    333             //if (mCurDown) {
    334             //    mRect.set(mCurX-mCurWidth-3, mCurY-mCurWidth-3,
    335             //            mCurX+mCurWidth+3, mCurY+mCurWidth+3);
    336             //} else {
    337             //    mRect.setEmpty();
    338             //}
    339             if (action == MotionEvent.ACTION_DOWN
    340                     || (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_DOWN) {
    341                 final int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK)
    342                         >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; // will be 0 for down
    343                 if (action == MotionEvent.ACTION_DOWN) {
    344                     for (int p=0; p<NP; p++) {
    345                         final PointerState ps = mPointers.get(p);
    346                         ps.clearTrace();
    347                         ps.mCurDown = false;
    348                     }
    349                     mCurDown = true;
    350                     mMaxNumPointers = 0;
    351                     mVelocity.clear();
    352                 }
    353 
    354                 final int id = event.getPointerId(index);
    355                 while (NP <= id) {
    356                     PointerState ps = new PointerState();
    357                     mPointers.add(ps);
    358                     NP++;
    359                 }
    360 
    361                 if (mActivePointerId < 0 ||
    362                         ! mPointers.get(mActivePointerId).mCurDown) {
    363                     mActivePointerId = id;
    364                 }
    365 
    366                 final PointerState ps = mPointers.get(id);
    367                 ps.mCurDown = true;
    368                 if (mPrintCoords) {
    369                     Log.i(TAG, mText.clear().append("Pointer ")
    370                             .append(id + 1).append(": DOWN").toString());
    371                 }
    372             }
    373 
    374             final int NI = event.getPointerCount();
    375 
    376             mCurDown = action != MotionEvent.ACTION_UP
    377                     && action != MotionEvent.ACTION_CANCEL;
    378             mCurNumPointers = mCurDown ? NI : 0;
    379             if (mMaxNumPointers < mCurNumPointers) {
    380                 mMaxNumPointers = mCurNumPointers;
    381             }
    382 
    383             mVelocity.addMovement(event);
    384             mVelocity.computeCurrentVelocity(1);
    385 
    386             for (int i=0; i<NI; i++) {
    387                 final int id = event.getPointerId(i);
    388                 final PointerState ps = mPointers.get(id);
    389                 final int N = event.getHistorySize();
    390                 for (int j=0; j<N; j++) {
    391                     event.getHistoricalPointerCoords(i, j, ps.mCoords);
    392                     if (mPrintCoords) {
    393                         logPointerCoords(ps.mCoords, id);
    394                     }
    395                     ps.addTrace(event.getHistoricalX(i, j), event.getHistoricalY(i, j));
    396                 }
    397                 event.getPointerCoords(i, ps.mCoords);
    398                 if (mPrintCoords) {
    399                     logPointerCoords(ps.mCoords, id);
    400                 }
    401                 ps.addTrace(ps.mCoords.x, ps.mCoords.y);
    402                 ps.mXVelocity = mVelocity.getXVelocity(id);
    403                 ps.mYVelocity = mVelocity.getYVelocity(id);
    404             }
    405 
    406             if (action == MotionEvent.ACTION_UP
    407                     || action == MotionEvent.ACTION_CANCEL
    408                     || (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP) {
    409                 final int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK)
    410                         >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; // will be 0 for UP
    411 
    412                 final int id = event.getPointerId(index);
    413                 final PointerState ps = mPointers.get(id);
    414                 ps.mCurDown = false;
    415                 if (mPrintCoords) {
    416                     Log.i(TAG, mText.clear().append("Pointer ")
    417                             .append(id + 1).append(": UP").toString());
    418                 }
    419 
    420                 if (action == MotionEvent.ACTION_UP
    421                         || action == MotionEvent.ACTION_CANCEL) {
    422                     mCurDown = false;
    423                 } else {
    424                     if (mActivePointerId == id) {
    425                         mActivePointerId = event.getPointerId(index == 0 ? 1 : 0);
    426                     }
    427                     ps.addTrace(Float.NaN, Float.NaN);
    428                 }
    429             }
    430 
    431             //if (mCurDown) {
    432             //    mRect.union(mCurX-mCurWidth-3, mCurY-mCurWidth-3,
    433             //            mCurX+mCurWidth+3, mCurY+mCurWidth+3);
    434             //}
    435             //invalidate(mRect);
    436             postInvalidate();
    437         }
    438     }
    439 
    440     @Override
    441     public boolean onTouchEvent(MotionEvent event) {
    442         addTouchEvent(event);
    443         return true;
    444     }
    445 
    446     @Override
    447     public boolean onTrackballEvent(MotionEvent event) {
    448         Log.i(TAG, "Trackball: " + event);
    449         return super.onTrackballEvent(event);
    450     }
    451 
    452     // HACK
    453     // A quick and dirty string builder implementation optimized for GC.
    454     // Using String.format causes the application grind to a halt when
    455     // more than a couple of pointers are down due to the number of
    456     // temporary objects allocated while formatting strings for drawing or logging.
    457     private static final class FasterStringBuilder {
    458         private char[] mChars;
    459         private int mLength;
    460 
    461         public FasterStringBuilder() {
    462             mChars = new char[64];
    463         }
    464 
    465         public FasterStringBuilder clear() {
    466             mLength = 0;
    467             return this;
    468         }
    469 
    470         public FasterStringBuilder append(String value) {
    471             final int valueLength = value.length();
    472             final int index = reserve(valueLength);
    473             value.getChars(0, valueLength, mChars, index);
    474             mLength += valueLength;
    475             return this;
    476         }
    477 
    478         public FasterStringBuilder append(int value) {
    479             return append(value, 0);
    480         }
    481 
    482         public FasterStringBuilder append(int value, int zeroPadWidth) {
    483             final boolean negative = value < 0;
    484             if (negative) {
    485                 value = - value;
    486                 if (value < 0) {
    487                     append("-2147483648");
    488                     return this;
    489                 }
    490             }
    491 
    492             int index = reserve(11);
    493             final char[] chars = mChars;
    494 
    495             if (value == 0) {
    496                 chars[index++] = '0';
    497                 mLength += 1;
    498                 return this;
    499             }
    500 
    501             if (negative) {
    502                 chars[index++] = '-';
    503             }
    504 
    505             int divisor = 1000000000;
    506             int numberWidth = 10;
    507             while (value < divisor) {
    508                 divisor /= 10;
    509                 numberWidth -= 1;
    510                 if (numberWidth < zeroPadWidth) {
    511                     chars[index++] = '0';
    512                 }
    513             }
    514 
    515             do {
    516                 int digit = value / divisor;
    517                 value -= digit * divisor;
    518                 divisor /= 10;
    519                 chars[index++] = (char) (digit + '0');
    520             } while (divisor != 0);
    521 
    522             mLength = index;
    523             return this;
    524         }
    525 
    526         public FasterStringBuilder append(float value, int precision) {
    527             int scale = 1;
    528             for (int i = 0; i < precision; i++) {
    529                 scale *= 10;
    530             }
    531             value = (float) (Math.rint(value * scale) / scale);
    532 
    533             append((int) value);
    534 
    535             if (precision != 0) {
    536                 append(".");
    537                 value = Math.abs(value);
    538                 value -= Math.floor(value);
    539                 append((int) (value * scale), precision);
    540             }
    541 
    542             return this;
    543         }
    544 
    545         @Override
    546         public String toString() {
    547             return new String(mChars, 0, mLength);
    548         }
    549 
    550         private int reserve(int length) {
    551             final int oldLength = mLength;
    552             final int newLength = mLength + length;
    553             final char[] oldChars = mChars;
    554             final int oldCapacity = oldChars.length;
    555             if (newLength > oldCapacity) {
    556                 final int newCapacity = oldCapacity * 2;
    557                 final char[] newChars = new char[newCapacity];
    558                 System.arraycopy(oldChars, 0, newChars, 0, oldLength);
    559                 mChars = newChars;
    560             }
    561             return oldLength;
    562         }
    563     }
    564 }
    565