Home | History | Annotate | Download | only in sensors
      1 /*
      2  * Copyright (C) 2013 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.cts.verifier.sensors;
     18 
     19 import android.content.Context;
     20 import android.graphics.Canvas;
     21 import android.graphics.Color;
     22 import android.graphics.Paint;
     23 import android.graphics.PorterDuff;
     24 import android.graphics.PorterDuffXfermode;
     25 import android.graphics.RectF;
     26 import android.hardware.SensorManager;
     27 import android.util.AttributeSet;
     28 import android.util.Log;
     29 import android.view.Surface;
     30 import android.view.View;
     31 
     32 /**
     33  * A view class that draws the user prompt
     34  *
     35  * The following piece of code should show how to use this view.
     36  *
     37  *  public void testUI()  {
     38  *     final int MAX_TILT_ANGLE = 70; // +/- 70
     39  *
     40  *     final int TILT_ANGLE_STEP = 5; // 5 degree(s) per step
     41  *     final int YAW_ANGLE_STEP = 10; // 10 degree(s) per step
     42  *
     43  *     RangeCoveredRegister xCovered, yCovered, zCovered;
     44  *     xCovered = new RangeCoveredRegister(-MAX_TILT_ANGLE, +MAX_TILT_ANGLE, TILT_ANGLE_STEP);
     45  *
     46  *     yCovered = new RangeCoveredRegister(-MAX_TILT_ANGLE, +MAX_TILT_ANGLE, TILT_ANGLE_STEP);
     47  *     zCovered = new RangeCoveredRegister(YAW_ANGLE_STEP);
     48  *
     49  *     xCovered.update(40);
     50  *     xCovered.update(-40);
     51  *     xCovered.update(12);
     52  *
     53  *     yCovered.update(50);
     54  *     yCovered.update(-51);
     55  *
     56  *     zCovered.update(150);
     57  *     zCovered.update(42);
     58  *
     59  *     setDataProvider(xCovered, yCovered, zCovered);
     60  *     enableAxis(RVCVRecordActivity.AXIS_ALL); //debug mode, show all three axis
     61  * }
     62  */
     63 public class MotionIndicatorView extends View {
     64     private final String TAG = "MotionIndicatorView";
     65     private final boolean LOCAL_LOGV = false;
     66 
     67     private Paint mCursorPaint;
     68     private Paint mLimitPaint;
     69     private Paint mCoveredPaint;
     70     private Paint mRangePaint;
     71     private Paint mEraserPaint;
     72 
     73     // UI settings
     74     private final int XBAR_WIDTH = 50;
     75     private final int XBAR_MARGIN = 50;
     76     private final int XBAR_CURSOR_ADD = 20;
     77 
     78     private final int YBAR_WIDTH = 50;
     79     private final int YBAR_MARGIN = 50;
     80     private final int YBAR_CURSOR_ADD = 20;
     81 
     82     private final int ZRING_WIDTH = 50;
     83     private final int ZRING_CURSOR_ADD = 30;
     84 
     85 
     86     private int mXSize, mYSize;
     87     private RectF mZBoundOut, mZBoundOut2, mZBoundIn, mZBoundIn2;
     88 
     89     private RangeCoveredRegister mXCovered, mYCovered, mZCovered;
     90 
     91     private boolean mXEnabled, mYEnabled, mZEnabled;
     92 
     93     private boolean mIsDeviceRotated = false;
     94 
     95     /**
     96      * Constructor
     97      * @param context
     98      */
     99     public MotionIndicatorView(Context context) {
    100         super(context);
    101         init();
    102     }
    103 
    104     /**
    105      * Constructor
    106      * @param context Application context
    107      * @param attrs
    108      */
    109     public MotionIndicatorView(Context context, AttributeSet attrs) {
    110         super(context, attrs);
    111         init();
    112     }
    113 
    114     /**
    115      * Initialize the Paint objects
    116      */
    117     private void init() {
    118 
    119         mCursorPaint = new Paint();
    120         mCursorPaint.setColor(Color.BLUE);
    121 
    122         mLimitPaint = new Paint();
    123         mLimitPaint.setColor(Color.YELLOW);
    124 
    125         mCoveredPaint = new Paint();
    126         mCoveredPaint.setColor(Color.CYAN);
    127 
    128         mRangePaint = new Paint();
    129         mRangePaint.setColor(Color.DKGRAY);
    130 
    131         mEraserPaint = new Paint();
    132         mEraserPaint.setColor(Color.TRANSPARENT);
    133         // ensure the erasing effect
    134         mEraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
    135     }
    136 
    137     /**
    138      * Connect the view to certain data provider objects
    139      * @param x Data provider for x direction tilt angle
    140      * @param y Data provider for y direction tilt angle
    141      * @param z Data provider for z rotation
    142      */
    143     public void setDataProvider(RangeCoveredRegister x,
    144                                 RangeCoveredRegister y,
    145                                 RangeCoveredRegister z)    {
    146         mXCovered = x;
    147         mYCovered = y;
    148         mZCovered = z;
    149     }
    150 
    151     /**
    152      * Set the device's current rotation
    153      * @param rotation Surface.ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_180, or
    154      *                 Surface.ROTATION_270
    155      */
    156     public void setDeviceRotation(int rotation) {
    157         mIsDeviceRotated = (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270);
    158     }
    159 
    160     /**
    161      * Set the active axis for display
    162      *
    163      * @param axis AXIS_X, AXIS_Y, AXIS_Z for x, y, z axis indicators, or AXIS_ALL for all three.
    164      */
    165     public void enableAxis(int axis)  {
    166         mXEnabled = mYEnabled = mZEnabled = false;
    167 
    168         switch(axis)
    169         {
    170             case SensorManager.AXIS_X:
    171                 mXEnabled = true;
    172                 break;
    173             case SensorManager.AXIS_Y:
    174                 mYEnabled = true;
    175                 break;
    176             case SensorManager.AXIS_Z:
    177                 mZEnabled = true;
    178                 break;
    179             case RVCVRecordActivity.AXIS_ALL:
    180                 mXEnabled = mYEnabled = mZEnabled = true;
    181         }
    182     }
    183 
    184     /**
    185      * Doing some pre-calculation that only changes when view dimensions are changed.
    186      * @param w
    187      * @param h
    188      * @param oldw
    189      * @param oldh
    190      */
    191     @Override
    192     protected void onSizeChanged (int w, int h, int oldw, int oldh) {
    193         mXSize = w;
    194         mYSize = h;
    195 
    196         float halfSideLength = 0.4f * Math.min(w, h);
    197         float leftSide = w/2 - halfSideLength;
    198         float topSide = h/2 - halfSideLength;
    199         float rightSide = w/2 + halfSideLength;
    200         float bottomSide = h/2 + halfSideLength;
    201 
    202         mZBoundOut = new RectF(leftSide, topSide, rightSide, bottomSide);
    203         mZBoundOut2 = new RectF(
    204                 leftSide-ZRING_CURSOR_ADD, topSide-ZRING_CURSOR_ADD,
    205                 rightSide+ZRING_CURSOR_ADD, bottomSide+ZRING_CURSOR_ADD);
    206         mZBoundIn = new RectF(
    207                 leftSide+ZRING_WIDTH, topSide+ZRING_WIDTH,
    208                 rightSide-ZRING_WIDTH, bottomSide-ZRING_WIDTH);
    209         mZBoundIn2 = new RectF(
    210                 leftSide+ZRING_WIDTH+ZRING_CURSOR_ADD, topSide+ZRING_WIDTH+ZRING_CURSOR_ADD,
    211                 rightSide-ZRING_WIDTH-ZRING_CURSOR_ADD, bottomSide-ZRING_WIDTH-ZRING_CURSOR_ADD);
    212 
    213         if (LOCAL_LOGV) Log.v(TAG, "New view size = ("+w+", "+h+")");
    214     }
    215 
    216     /**
    217      * Draw UI depends on the selected axis and registered value
    218      *
    219      * @param canvas the canvas to draw on
    220      */
    221     @Override
    222     protected void onDraw(Canvas canvas) {
    223         super.onDraw(canvas);
    224         int i,t;
    225 
    226         Paint p = new Paint();
    227         p.setColor(Color.YELLOW);
    228         canvas.drawRect(10,10, 50, 50, p);
    229 
    230         // In order to determine which progress bar to draw, the device's rotation must be accounted
    231         // for since the accelerometer rotates with the display.
    232         boolean drawX = (mXEnabled && !mIsDeviceRotated) || (mYEnabled && mIsDeviceRotated);
    233         boolean drawY = (mYEnabled && !mIsDeviceRotated) || (mXEnabled && mIsDeviceRotated);
    234 
    235         if (drawX && mXCovered != null) {
    236             RangeCoveredRegister covered = mIsDeviceRotated ? mYCovered : mXCovered;
    237             int xNStep = covered.getNSteps() + 4; // two on each side as a buffer
    238             int xStepSize = mXSize * 3/4 / xNStep;
    239             int xLeft = mXSize * 1/8 + (mXSize * 3/4 % xNStep)/2;
    240 
    241             // base bar
    242             canvas.drawRect(xLeft, XBAR_MARGIN,
    243                     xLeft+xStepSize*xNStep-1, XBAR_WIDTH+XBAR_MARGIN, mRangePaint);
    244 
    245             // covered range
    246             for (i=0; i<covered.getNSteps(); ++i) {
    247                 if (covered.isCovered(i)) {
    248                     canvas.drawRect(
    249                             xLeft+xStepSize*(i+2), XBAR_MARGIN,
    250                             xLeft+xStepSize*(i+3)-1, XBAR_WIDTH + XBAR_MARGIN,
    251                             mCoveredPaint);
    252                 }
    253             }
    254 
    255             // limit
    256             canvas.drawRect(xLeft+xStepSize*2-4, XBAR_MARGIN,
    257                     xLeft+xStepSize*2+3, XBAR_WIDTH+XBAR_MARGIN, mLimitPaint);
    258             canvas.drawRect(xLeft+xStepSize*(xNStep-2)-4, XBAR_MARGIN,
    259                     xLeft+xStepSize*(xNStep-2)+3, XBAR_WIDTH+XBAR_MARGIN, mLimitPaint);
    260 
    261             // cursor
    262             t = (int)(xLeft+xStepSize*(covered.getLastValue()+2));
    263             canvas.drawRect(t-4, XBAR_MARGIN-XBAR_CURSOR_ADD, t+3,
    264                     XBAR_WIDTH+XBAR_MARGIN+XBAR_CURSOR_ADD, mCursorPaint);
    265         }
    266 
    267         if (drawY && mYCovered != null) {
    268             RangeCoveredRegister covered = mIsDeviceRotated ? mXCovered : mYCovered;
    269             int yNStep = covered.getNSteps() + 4; // two on each side as a buffer
    270             int yStepSize = mYSize * 3/4 / yNStep;
    271             int yLeft = mYSize * 1/8 + (mYSize * 3/4 % yNStep)/2;
    272 
    273             // base bar
    274             canvas.drawRect(YBAR_MARGIN, yLeft,
    275                     YBAR_WIDTH+YBAR_MARGIN, yLeft+yStepSize*yNStep-1, mRangePaint);
    276 
    277             // covered range
    278             for (i=0; i<covered.getNSteps(); ++i) {
    279                 if (covered.isCovered(i)) {
    280                     canvas.drawRect(
    281                             YBAR_MARGIN, yLeft+yStepSize*(i+2),
    282                             YBAR_WIDTH + YBAR_MARGIN, yLeft+yStepSize*(i+3)-1,
    283                             mCoveredPaint);
    284                 }
    285             }
    286 
    287             // limit
    288             canvas.drawRect(YBAR_MARGIN, yLeft + yStepSize * 2 - 4,
    289                     YBAR_WIDTH + YBAR_MARGIN, yLeft + yStepSize * 2 + 3, mLimitPaint);
    290             canvas.drawRect(YBAR_MARGIN, yLeft + yStepSize * (yNStep - 2) - 4,
    291                     YBAR_WIDTH + YBAR_MARGIN, yLeft + yStepSize * (yNStep - 2) + 3, mLimitPaint);
    292 
    293             // cursor
    294             t = (int)(yLeft+yStepSize*(covered.getLastValue()+2));
    295             canvas.drawRect( YBAR_MARGIN-YBAR_CURSOR_ADD, t-4,
    296                     YBAR_WIDTH+YBAR_MARGIN+YBAR_CURSOR_ADD, t+3, mCursorPaint);
    297         }
    298 
    299         if (mZEnabled && mZCovered != null) {
    300             float stepSize  = 360.0f/mZCovered.getNSteps();
    301 
    302             // base bar
    303             canvas.drawArc(mZBoundOut,0, 360, true, mRangePaint);
    304 
    305             // covered range
    306             for (i=0; i<mZCovered.getNSteps(); ++i) {
    307                 if (mZCovered.isCovered(i)) {
    308                     canvas.drawArc(mZBoundOut,i*stepSize-0.2f, stepSize+0.4f,
    309                             true, mCoveredPaint);
    310                 }
    311             }
    312             // clear center
    313             canvas.drawArc(mZBoundIn, 0, 360, true, mEraserPaint);
    314             // cursor
    315             canvas.drawArc(mZBoundOut2, mZCovered.getLastValue()*stepSize- 1, 2,
    316                     true, mCursorPaint);
    317             canvas.drawArc(mZBoundIn2, mZCovered.getLastValue()*stepSize-1.5f, 3,
    318                     true, mEraserPaint);
    319         }
    320     }
    321 }
    322 
    323 /**
    324  *  A range register class for the RVCVRecord Activity
    325  */
    326 class RangeCoveredRegister {
    327     enum MODE {
    328         LINEAR,
    329         ROTATE2D
    330     }
    331 
    332     private boolean[] mCovered;
    333     private MODE mMode;
    334     private int mStep;
    335     private int mLow, mHigh;
    336     private int mLastData;
    337 
    338     // high is not inclusive
    339     RangeCoveredRegister(int low, int high, int step) {
    340         mMode = MODE.LINEAR;
    341         mStep = step;
    342         mLow = low;
    343         mHigh = high;
    344         init();
    345     }
    346 
    347     RangeCoveredRegister(int step) {
    348         mMode = MODE.ROTATE2D;
    349         mStep = step;
    350         mLow = 0;
    351         mHigh = 360;
    352         init();
    353     }
    354 
    355     private void init() {
    356         if (mMode == MODE.LINEAR) {
    357             mCovered = new boolean[(mHigh-mLow)/mStep];
    358         }else {
    359             mCovered = new boolean[360/mStep];
    360         }
    361     }
    362 
    363     /**
    364      * Test if the range specified by (low, high) is covered.
    365      *
    366      * If it is LINEAR mode, the range will be quantized to nearest step boundary. If it is the
    367      * ROTATE2D mode, it is the same as isFullyCovered().
    368      *
    369      * @param low The low end of the range.
    370      * @param high The high end of the range.
    371      * @return if the specified range is covered, return true; otherwise false.
    372      */
    373     public boolean isRangeCovered(int low, int high) {
    374         if (mMode == MODE.LINEAR) {
    375             int iLow = Math.max(Math.round((low - mLow) / mStep), 0);
    376             int iHigh = Math.min(Math.round((high - mLow) / mStep), mCovered.length-1);
    377 
    378             for (int i = iLow; i <= iHigh; ++i) {
    379                 if (!mCovered[i]) {
    380                     return false;
    381                 }
    382             }
    383             return true;
    384 
    385         } else {
    386             return isFullyCovered();
    387         }
    388     }
    389 
    390     /**
    391      * Test if the range defined is fully covered.
    392      *
    393      * @return if the range is fully covered, return true; otherwise false.
    394      */
    395     public boolean isFullyCovered() {
    396         for (boolean i : mCovered) {
    397             if (!i) return false;
    398         }
    399         return true;
    400     }
    401 
    402     /**
    403      * Test if a specific step is covered.
    404      *
    405      * @param i the step number
    406      * @return if the step specified is covered, return true; otherwise false.
    407      */
    408     public boolean isCovered(int i) {
    409         return mCovered[i];
    410     }
    411 
    412     /**
    413      *
    414      *
    415      * @param data
    416      * @return if this update changes the status of
    417      */
    418     public boolean update(int data) {
    419         mLastData = data;
    420 
    421         if (mMode == MODE.ROTATE2D) {
    422             data %= 360;
    423         }
    424 
    425         int iStep = (data - mLow)/mStep;
    426 
    427         if (iStep>=0 && iStep<getNSteps()) {
    428             // only record valid data
    429             mLastData = data;
    430 
    431             if (mCovered[iStep]) {
    432                 return false;
    433             } else {
    434                 mCovered[iStep] = true;
    435                 return true;
    436             }
    437         }
    438         return false;
    439     }
    440 
    441     /**
    442      * Get the number of steps in this register
    443      *
    444      * @return The number of steps in this register
    445      */
    446     public int getNSteps() {
    447         //if (mCovered == null) {
    448         //return 0;
    449         //}
    450         return mCovered.length;
    451     }
    452 
    453     /**
    454      * Get the last value updated
    455      *
    456      * @return The last value updated
    457      */
    458     public float getLastValue() {
    459         // ensure float division
    460         return ((float)(mLastData - mLow))/mStep;
    461     }
    462 }
    463