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