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