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