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      */
MotionIndicatorView(Context context)99     public MotionIndicatorView(Context context) {
100         super(context);
101         init();
102     }
103 
104     /**
105      * Constructor
106      * @param context Application context
107      * @param attrs
108      */
MotionIndicatorView(Context context, AttributeSet attrs)109     public MotionIndicatorView(Context context, AttributeSet attrs) {
110         super(context, attrs);
111         init();
112     }
113 
114     /**
115      * Initialize the Paint objects
116      */
init()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      */
setDataProvider(RangeCoveredRegister x, RangeCoveredRegister y, RangeCoveredRegister z)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      */
setDeviceRotation(int rotation)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      */
enableAxis(int axis)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
onSizeChanged(int w, int h, int oldw, int oldh)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
onDraw(Canvas canvas)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
RangeCoveredRegister(int low, int high, int step)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 
RangeCoveredRegister(int step)347     RangeCoveredRegister(int step) {
348         mMode = MODE.ROTATE2D;
349         mStep = step;
350         mLow = 0;
351         mHigh = 360;
352         init();
353     }
354 
init()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      */
isRangeCovered(int low, int high)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      */
isFullyCovered()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      */
isCovered(int i)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      */
update(int data)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      */
getNSteps()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      */
getLastValue()458     public float getLastValue() {
459         // ensure float division
460         return ((float)(mLastData - mLow))/mStep;
461     }
462 }
463