1 /* 2 * Copyright (C) 2007 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.app.Activity; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.hardware.Camera; 23 import android.hardware.Sensor; 24 import android.hardware.SensorEvent; 25 import android.hardware.SensorEventListener; 26 import android.hardware.SensorManager; 27 import android.media.AudioManager; 28 import android.media.CamcorderProfile; 29 import android.media.MediaRecorder; 30 import android.media.SoundPool; 31 import android.net.Uri; 32 import android.os.Bundle; 33 import android.os.Environment; 34 import android.util.JsonWriter; 35 import android.util.Log; 36 import android.view.Window; 37 import android.widget.ImageView; 38 import android.widget.Toast; 39 40 import com.android.cts.verifier.R; 41 42 import java.io.File; 43 import java.io.FileNotFoundException; 44 import java.io.FileOutputStream; 45 import java.io.IOException; 46 import java.io.OutputStreamWriter; 47 import java.text.SimpleDateFormat; 48 import java.util.Date; 49 import java.util.HashMap; 50 import java.util.List; 51 import java.util.Map; 52 53 // ---------------------------------------------------------------------- 54 55 /** 56 * An activity that does recording of the camera video and rotation vector data at the same time. 57 */ 58 public class RVCVRecordActivity extends Activity { 59 private static final String TAG = "RVCVRecordActivity"; 60 private static final boolean LOCAL_LOGV = false; 61 62 private MotionIndicatorView mIndicatorView; 63 64 private SoundPool mSoundPool; 65 private Map<String, Integer> mSoundMap; 66 67 private File mRecordDir; 68 private RecordProcedureController mController; 69 private VideoRecorder mVideoRecorder; 70 private RVSensorLogger mRVSensorLogger; 71 private CoverageManager mCoverManager; 72 private CameraContext mCameraContext; 73 74 public static final int AXIS_NONE = 0; 75 public static final int AXIS_ALL = SensorManager.AXIS_X + 76 SensorManager.AXIS_Y + 77 SensorManager.AXIS_Z; 78 79 // For Rotation Vector algorithm research use 80 private final static boolean LOG_RAW_SENSORS = false; 81 private RawSensorLogger mRawSensorLogger; 82 83 @Override 84 public void onCreate(Bundle savedInstanceState) { 85 super.onCreate(savedInstanceState); 86 87 // Hide the window title. 88 requestWindowFeature(Window.FEATURE_NO_TITLE); 89 90 // inflate xml 91 setContentView(R.layout.cam_preview_overlay); 92 93 // locate views 94 mIndicatorView = (MotionIndicatorView) findViewById(R.id.cam_indicator); 95 96 initStoragePath(); 97 } 98 99 @Override 100 protected void onPause() { 101 super.onPause(); 102 mController.quit(); 103 104 mCameraContext.end(); 105 endSoundPool(); 106 } 107 108 @Override 109 protected void onResume() { 110 super.onResume(); 111 // delay the initialization as much as possible 112 init(); 113 } 114 115 /** display toast message 116 * 117 * @param msg Message content 118 */ 119 private void message(String msg) { 120 121 Context context = getApplicationContext(); 122 int duration = Toast.LENGTH_SHORT; 123 124 Toast toast = Toast.makeText(context, msg, duration); 125 toast.show(); 126 } 127 128 /** 129 * Initialize components 130 * 131 */ 132 private void init() { 133 mCameraContext = new CameraContext(); 134 mCameraContext.init(); 135 136 mCoverManager = new CoverageManager(); 137 mIndicatorView.setDataProvider( 138 mCoverManager.getAxis(SensorManager.AXIS_X), 139 mCoverManager.getAxis(SensorManager.AXIS_Y), 140 mCoverManager.getAxis(SensorManager.AXIS_Z) ); 141 142 initSoundPool(); 143 mRVSensorLogger = new RVSensorLogger(this); 144 145 mVideoRecorder = new VideoRecorder(mCameraContext.getCamera(), mCameraContext.getProfile()); 146 147 if (LOG_RAW_SENSORS) { 148 mRawSensorLogger = new RawSensorLogger(mRecordDir); 149 } 150 151 mController = new RecordProcedureController(this); 152 } 153 154 /** 155 * Notify recording is completed. This is the successful exit. 156 */ 157 public void notifyComplete() { 158 message("Capture completed!"); 159 160 Uri resultUri = Uri.fromFile(mRecordDir); 161 Intent result = new Intent(); 162 result.setData(resultUri); 163 setResult(Activity.RESULT_OK, result); 164 165 finish(); 166 } 167 168 /** 169 * Notify the user what to do next in text 170 * 171 * @param axis SensorManager.AXIS_X or SensorManager.AXIS_Y or SensorManager.AXIS_Z 172 */ 173 private void notifyPrompt(int axis) { 174 // It is not XYZ because of earlier design have different definition of 175 // X and Y 176 final String axisName = "YXZ"; 177 178 message("Manipulate the device in " + axisName.charAt(axis - 1) + 179 " axis (as illustrated) about the pattern."); 180 } 181 182 /** 183 * Ask indicator view to redraw 184 */ 185 private void redrawIndicator() { 186 mIndicatorView.invalidate(); 187 } 188 189 /** 190 * Switch to a different axis for display and logging 191 * @param axis 192 */ 193 private void switchAxis(int axis) { 194 ImageView imageView = (ImageView) findViewById(R.id.cam_overlay); 195 196 final int [] prompts = {R.drawable.prompt_x, R.drawable.prompt_y, R.drawable.prompt_z}; 197 198 if (axis >=SensorManager.AXIS_X && axis <=SensorManager.AXIS_Z) { 199 imageView.setImageResource(prompts[axis-1]); 200 mIndicatorView.enableAxis(axis); 201 mRVSensorLogger.updateRegister(mCoverManager.getAxis(axis), axis); 202 notifyPrompt(axis); 203 } else { 204 imageView.setImageDrawable(null); 205 mIndicatorView.enableAxis(AXIS_NONE); 206 } 207 redrawIndicator(); 208 } 209 210 /** 211 * Asynchronized way to call switchAxis. Use this if caller is not on UI thread. 212 * @param axis @param axis SensorManager.AXIS_X or SensorManager.AXIS_Y or SensorManager.AXIS_Z 213 */ 214 public void switchAxisAsync(int axis) { 215 // intended to be called from a non-UI thread 216 final int fAxis = axis; 217 runOnUiThread(new Runnable() { 218 public void run() { 219 // UI code goes here 220 switchAxis(fAxis); 221 } 222 }); 223 } 224 225 /** 226 * Initialize sound pool for user notification 227 */ 228 private void initSoundPool() { 229 mSoundPool = new SoundPool(1 /*maxStreams*/, AudioManager.STREAM_MUSIC, 0); 230 mSoundMap = new HashMap<>(); 231 232 // TODO: add different sound into this 233 mSoundMap.put("start", mSoundPool.load(this, R.raw.start_axis, 1)); 234 mSoundMap.put("end", mSoundPool.load(this, R.raw.next_axis, 1)); 235 mSoundMap.put("half-way", mSoundPool.load(this, R.raw.half_way, 1)); 236 } 237 private void endSoundPool() { 238 mSoundPool.release(); 239 } 240 241 /** 242 * Play notify sound to user 243 * @param name name of the sound to be played 244 */ 245 public void playNotifySound(String name) { 246 Integer id = mSoundMap.get(name); 247 if (id != null) { 248 mSoundPool.play(id.intValue(), 0.75f/*left vol*/, 0.75f/*right vol*/, 0 /*priority*/, 249 0/*loop play*/, 1/*rate*/); 250 } 251 } 252 253 /** 254 * Start the sensor recording 255 */ 256 public void startRecordSensor() { 257 runOnUiThread(new Runnable() { 258 public void run() { 259 mRVSensorLogger.init(); 260 if (LOG_RAW_SENSORS) { 261 mRawSensorLogger.init(); 262 } 263 } 264 }); 265 } 266 267 /** 268 * Stop the sensor recording 269 */ 270 public void stopRecordSensor() { 271 runOnUiThread(new Runnable() { 272 public void run() { 273 mRVSensorLogger.end(); 274 if (LOG_RAW_SENSORS) { 275 mRawSensorLogger.end(); 276 } 277 } 278 }); 279 } 280 281 /** 282 * Start video recording 283 */ 284 public void startRecordVideo() { 285 mVideoRecorder.init(); 286 } 287 288 /** 289 * Stop video recording 290 */ 291 public void stopRecordVideo() { 292 mVideoRecorder.end(); 293 } 294 295 /** 296 * Wait until a sensor recording for a certain axis is fully covered 297 * @param axis 298 */ 299 public void waitUntilCovered(int axis) { 300 mCoverManager.waitUntilCovered(axis); 301 } 302 303 /** 304 * Wait until a sensor recording for a certain axis is halfway covered 305 * @param axis 306 */ 307 public void waitUntilHalfCovered(int axis) { 308 mCoverManager.waitUntilHalfCovered(axis); 309 } 310 311 /** 312 * 313 */ 314 private void initStoragePath() { 315 File rxcvRecDataDir = new File(Environment.getExternalStorageDirectory(),"RVCVRecData"); 316 317 // Create the storage directory if it does not exist 318 if (! rxcvRecDataDir.exists()) { 319 if (! rxcvRecDataDir.mkdirs()) { 320 Log.e(TAG, "failed to create main data directory"); 321 } 322 } 323 324 mRecordDir = new File(rxcvRecDataDir, new SimpleDateFormat("yyMMdd-hhmmss").format(new Date())); 325 326 if (! mRecordDir.mkdirs()) { 327 Log.e(TAG, "failed to create rec data directory"); 328 } 329 } 330 331 /** 332 * Get the sensor log file path 333 * @return Path of the sensor log file 334 */ 335 public String getSensorLogFilePath() { 336 return new File(mRecordDir, "sensor.log").getPath(); 337 } 338 339 /** 340 * Get the video recording file path 341 * @return Path of the video recording file 342 */ 343 public String getVideoRecFilePath() { 344 return new File(mRecordDir, "video.mp4").getPath(); 345 } 346 347 /** 348 * Write out important camera/video information to a JSON file 349 * @param width width of frame 350 * @param height height of frame 351 * @param frameRate frame rate in fps 352 * @param fovW field of view in width direction 353 * @param fovH field of view in height direction 354 */ 355 public void writeVideoMetaInfo(int width, int height, float frameRate, float fovW, float fovH) { 356 try { 357 JsonWriter writer = 358 new JsonWriter( 359 new OutputStreamWriter( 360 new FileOutputStream( 361 new File(mRecordDir, "videometa.json").getPath() 362 ) 363 ) 364 ); 365 writer.beginObject(); 366 writer.name("fovW").value(fovW); 367 writer.name("fovH").value(fovH); 368 writer.name("width").value(width); 369 writer.name("height").value(height); 370 writer.name("frameRate").value(frameRate); 371 writer.endObject(); 372 373 writer.close(); 374 }catch (FileNotFoundException e) { 375 // Not very likely to happen 376 e.printStackTrace(); 377 }catch (IOException e) { 378 // do nothing 379 e.printStackTrace(); 380 Log.e(TAG, "Writing video meta data failed."); 381 } 382 } 383 384 /** 385 * Camera preview control class 386 */ 387 class CameraContext { 388 private Camera mCamera; 389 private CamcorderProfile mProfile; 390 private Camera.CameraInfo mCameraInfo; 391 392 private int [] mPreferredProfiles = { 393 CamcorderProfile.QUALITY_480P, // smaller -> faster 394 CamcorderProfile.QUALITY_720P, 395 CamcorderProfile.QUALITY_1080P, 396 CamcorderProfile.QUALITY_HIGH // existence guaranteed 397 }; 398 399 CameraContext() { 400 try { 401 mCamera = Camera.open(); // attempt to get a default Camera instance (0) 402 mProfile = null; 403 if (mCamera != null) { 404 mCameraInfo = new Camera.CameraInfo(); 405 Camera.getCameraInfo(0, mCameraInfo); 406 setupCamera(); 407 } 408 } 409 catch (Exception e){ 410 // Camera is not available (in use or does not exist) 411 Log.e(TAG, "Cannot obtain Camera!"); 412 } 413 } 414 415 /** 416 * Find a preferred camera profile and set preview and picture size property accordingly. 417 */ 418 void setupCamera() { 419 CamcorderProfile profile = null; 420 Camera.Parameters param = mCamera.getParameters(); 421 List<Camera.Size> pre_sz = param.getSupportedPreviewSizes(); 422 List<Camera.Size> pic_sz = param.getSupportedPictureSizes(); 423 424 for (int i : mPreferredProfiles) { 425 if (CamcorderProfile.hasProfile(i)) { 426 profile = CamcorderProfile.get(i); 427 428 int valid = 0; 429 for (Camera.Size j : pre_sz) { 430 if (j.width == profile.videoFrameWidth && 431 j.height == profile.videoFrameHeight) { 432 ++valid; 433 break; 434 } 435 } 436 for (Camera.Size j : pic_sz) { 437 if (j.width == profile.videoFrameWidth && 438 j.height == profile.videoFrameHeight) { 439 ++valid; 440 break; 441 } 442 } 443 if (valid == 2) { 444 param.setPreviewSize(profile.videoFrameWidth, profile.videoFrameHeight); 445 param.setPictureSize(profile.videoFrameWidth, profile.videoFrameHeight); 446 mCamera.setParameters(param); 447 break; 448 } else { 449 profile = null; 450 } 451 } 452 } 453 if (profile != null) { 454 param = mCamera.getParameters(); //acquire proper fov after change the picture size 455 float fovW = param.getHorizontalViewAngle(); 456 float fovH = param.getVerticalViewAngle(); 457 writeVideoMetaInfo(profile.videoFrameWidth, profile.videoFrameHeight, 458 profile.videoFrameRate, fovW, fovH); 459 } else { 460 Log.e(TAG, "Cannot find a proper video profile"); 461 } 462 mProfile = profile; 463 464 } 465 466 467 /** 468 * Get sensor information of the camera being used 469 */ 470 public Camera.CameraInfo getCameraInfo() { 471 return mCameraInfo; 472 } 473 474 /** 475 * Get the camera to be previewed 476 * @return Reference to Camera used 477 */ 478 public Camera getCamera() { 479 return mCamera; 480 } 481 482 /** 483 * Get the camera profile to be used 484 * @return Reference to Camera profile 485 */ 486 public CamcorderProfile getProfile() { 487 return mProfile; 488 } 489 490 /** 491 * Setup the camera 492 */ 493 public void init() { 494 if (mCamera != null) { 495 double alpha = mCamera.getParameters().getHorizontalViewAngle()*Math.PI/180.0; 496 int width = mProfile.videoFrameWidth; 497 double fx = width/2/Math.tan(alpha/2.0); 498 499 if (LOCAL_LOGV) Log.v(TAG, "View angle=" 500 + mCamera.getParameters().getHorizontalViewAngle() +" Estimated fx = "+fx); 501 502 RVCVCameraPreview cameraPreview = 503 (RVCVCameraPreview) findViewById(R.id.cam_preview); 504 cameraPreview.init(mCamera, 505 (float)mProfile.videoFrameWidth/mProfile.videoFrameHeight, 506 mCameraInfo.orientation); 507 } else { 508 message("Cannot open camera!"); 509 finish(); 510 } 511 } 512 513 /** 514 * End the camera preview 515 */ 516 public void end() { 517 if (mCamera != null) { 518 mCamera.release(); // release the camera for other applications 519 mCamera = null; 520 } 521 } 522 } 523 524 /** 525 * Manage a set of RangeCoveredRegister objects 526 */ 527 class CoverageManager { 528 // settings 529 private final int MAX_TILT_ANGLE = 50; // +/- 50 530 //private final int REQUIRED_TILT_ANGLE = 50; // +/- 50 531 private final int TILT_ANGLE_STEP = 5; // 5 degree(s) per step 532 private final int YAW_ANGLE_STEP = 10; // 10 degree(s) per step 533 534 RangeCoveredRegister[] mAxisCovered; 535 536 CoverageManager() { 537 mAxisCovered = new RangeCoveredRegister[3]; 538 // X AXIS 539 mAxisCovered[0] = new RangeCoveredRegister( 540 -MAX_TILT_ANGLE, +MAX_TILT_ANGLE, TILT_ANGLE_STEP); 541 // Y AXIS 542 mAxisCovered[1] = new RangeCoveredRegister( 543 -MAX_TILT_ANGLE, +MAX_TILT_ANGLE, TILT_ANGLE_STEP); 544 // Z AXIS 545 mAxisCovered[2] = new RangeCoveredRegister(YAW_ANGLE_STEP); 546 } 547 548 public RangeCoveredRegister getAxis(int axis) { 549 // SensorManager.AXIS_X = 1, need offset -1 for mAxisCovered array 550 return mAxisCovered[axis-1]; 551 } 552 553 public void waitUntilHalfCovered(int axis) { 554 if (axis == SensorManager.AXIS_Z) { 555 waitUntilCovered(axis); 556 } 557 558 // SensorManager.AXIS_X = 1, need offset -1 for mAxisCovered array 559 while(!(mAxisCovered[axis-1].isRangeCovered(-MAX_TILT_ANGLE, -MAX_TILT_ANGLE/2) || 560 mAxisCovered[axis-1].isRangeCovered(MAX_TILT_ANGLE/2, MAX_TILT_ANGLE) ) ) { 561 try { 562 Thread.sleep(500); 563 } catch (InterruptedException e) { 564 if (LOCAL_LOGV) { 565 Log.v(TAG, "waitUntilHalfCovered axis = "+ axis + " is interrupted"); 566 } 567 Thread.currentThread().interrupt(); 568 } 569 } 570 } 571 572 public void waitUntilCovered(int axis) { 573 // SensorManager.AXIS_X = 1, need offset -1 for mAxisCovered array 574 while(!mAxisCovered[axis-1].isFullyCovered()) { 575 try { 576 Thread.sleep(500); 577 } catch (InterruptedException e) { 578 if (LOCAL_LOGV) { 579 Log.v(TAG, "waitUntilCovered axis = "+ axis + " is interrupted"); 580 } 581 Thread.currentThread().interrupt(); 582 } 583 } 584 } 585 } 586 //////////////////////////////////////////////////////////////////////////////////////////////// 587 588 /** 589 * A class controls the video recording 590 */ 591 class VideoRecorder 592 { 593 private MediaRecorder mRecorder; 594 private CamcorderProfile mProfile; 595 private Camera mCamera; 596 private boolean mRunning = false; 597 598 VideoRecorder(Camera camera, CamcorderProfile profile){ 599 mCamera = camera; 600 mProfile = profile; 601 } 602 603 /** 604 * Initialize and start recording 605 */ 606 public void init() { 607 if (mCamera == null || mProfile ==null){ 608 return; 609 } 610 611 mRecorder = new MediaRecorder(); 612 mCamera.unlock(); 613 mRecorder.setCamera(mCamera); 614 615 mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 616 mRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT); 617 618 mRecorder.setProfile(mProfile); 619 620 try { 621 mRecorder.setOutputFile(getVideoRecFilePath()); 622 mRecorder.prepare(); 623 } catch (IOException e) { 624 Log.e(TAG, "Preparation for recording failed."); 625 return; 626 } 627 628 try { 629 mRecorder.start(); 630 } catch (RuntimeException e) { 631 Log.e(TAG, "Starting recording failed."); 632 mRecorder.reset(); 633 mRecorder.release(); 634 mCamera.lock(); 635 return; 636 } 637 mRunning = true; 638 } 639 640 /** 641 * Stop recording 642 */ 643 public void end() { 644 if (mRunning) { 645 try { 646 mRecorder.stop(); 647 mRecorder.reset(); 648 mRecorder.release(); 649 mCamera.lock(); 650 } catch (RuntimeException e) { 651 e.printStackTrace(); 652 Log.e(TAG, "Runtime error in stopping recording."); 653 } 654 } 655 mRecorder = null; 656 } 657 658 } 659 660 //////////////////////////////////////////////////////////////////////////////////////////////// 661 662 /** 663 * Log all raw sensor readings, for Rotation Vector sensor algorithms research 664 */ 665 class RawSensorLogger implements SensorEventListener { 666 private final String TAG = "RawSensorLogger"; 667 668 private final static int SENSOR_RATE = SensorManager.SENSOR_DELAY_FASTEST; 669 private File mRecPath; 670 671 SensorManager mSensorManager; 672 Sensor mAccSensor, mGyroSensor, mMagSensor; 673 OutputStreamWriter mAccLogWriter, mGyroLogWriter, mMagLogWriter; 674 675 private float[] mRTemp = new float[16]; 676 677 RawSensorLogger(File recPath) { 678 mRecPath = recPath; 679 } 680 681 /** 682 * Initialize and start recording 683 */ 684 public void init() { 685 mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE); 686 687 mAccSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); 688 mGyroSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE_UNCALIBRATED); 689 mMagSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED); 690 691 mSensorManager.registerListener(this, mAccSensor, SENSOR_RATE); 692 mSensorManager.registerListener(this, mGyroSensor, SENSOR_RATE); 693 mSensorManager.registerListener(this, mMagSensor, SENSOR_RATE); 694 695 try { 696 mAccLogWriter= new OutputStreamWriter( 697 new FileOutputStream(new File(mRecPath, "raw_acc.log"))); 698 mGyroLogWriter= new OutputStreamWriter( 699 new FileOutputStream(new File(mRecPath, "raw_uncal_gyro.log"))); 700 mMagLogWriter= new OutputStreamWriter( 701 new FileOutputStream(new File(mRecPath, "raw_uncal_mag.log"))); 702 703 } catch (FileNotFoundException e) { 704 Log.e(TAG, "Sensor log file open failed: " + e.toString()); 705 } 706 } 707 708 /** 709 * Stop recording and clean up 710 */ 711 public void end() { 712 mSensorManager.flush(this); 713 mSensorManager.unregisterListener(this); 714 715 try { 716 if (mAccLogWriter != null) { 717 OutputStreamWriter writer = mAccLogWriter; 718 mAccLogWriter = null; 719 writer.close(); 720 } 721 if (mGyroLogWriter != null) { 722 OutputStreamWriter writer = mGyroLogWriter; 723 mGyroLogWriter = null; 724 writer.close(); 725 } 726 if (mMagLogWriter != null) { 727 OutputStreamWriter writer = mMagLogWriter; 728 mMagLogWriter = null; 729 writer.close(); 730 } 731 732 } catch (IOException e) { 733 Log.e(TAG, "Sensor log file close failed: " + e.toString()); 734 } 735 } 736 737 @Override 738 public void onAccuracyChanged(Sensor sensor, int i) { 739 // do not care 740 } 741 742 @Override 743 public void onSensorChanged(SensorEvent event) { 744 OutputStreamWriter writer=null; 745 switch(event.sensor.getType()) { 746 case Sensor.TYPE_ACCELEROMETER: 747 writer = mAccLogWriter; 748 break; 749 case Sensor.TYPE_GYROSCOPE_UNCALIBRATED: 750 writer = mGyroLogWriter; 751 break; 752 case Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED: 753 writer = mMagLogWriter; 754 break; 755 756 } 757 if (writer!=null) { 758 float[] data = event.values; 759 try { 760 if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { 761 writer.write(String.format("%d %f %f %f\r\n", 762 event.timestamp, data[0], data[1], data[2])); 763 }else // TYPE_GYROSCOPE_UNCALIBRATED and TYPE_MAGNETIC_FIELD_UNCALIBRATED 764 { 765 writer.write(String.format("%d %f %f %f %f %f %f\r\n", event.timestamp, 766 data[0], data[1], data[2], data[3], data[4], data[5])); 767 } 768 }catch (IOException e) 769 { 770 Log.e(TAG, "Write to raw sensor log file failed."); 771 } 772 773 } 774 } 775 } 776 777 /** 778 * Rotation sensor logger class 779 */ 780 class RVSensorLogger implements SensorEventListener { 781 private final String TAG = "RVSensorLogger"; 782 783 private final static int SENSOR_RATE = SensorManager.SENSOR_DELAY_FASTEST; 784 RangeCoveredRegister mRegister; 785 int mAxis; 786 RVCVRecordActivity mActivity; 787 788 SensorManager mSensorManager; 789 Sensor mRVSensor; 790 OutputStreamWriter mLogWriter; 791 792 private float[] mRTemp = new float[16]; 793 794 RVSensorLogger(RVCVRecordActivity activity) { 795 mActivity = activity; 796 } 797 798 /** 799 * Initialize and start recording 800 */ 801 public void init() { 802 mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE); 803 if (mSensorManager == null) { 804 Log.e(TAG,"SensorManager is null!"); 805 } 806 mRVSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR); 807 if (mRVSensor != null) { 808 if (LOCAL_LOGV) Log.v(TAG, "Got RV Sensor"); 809 }else { 810 Log.e(TAG, "Did not get RV sensor"); 811 } 812 if(mSensorManager.registerListener(this, mRVSensor, SENSOR_RATE)) { 813 if (LOCAL_LOGV) Log.v(TAG,"Register listener successfull"); 814 } else { 815 Log.e(TAG,"Register listener failed"); 816 } 817 818 try { 819 mLogWriter= new OutputStreamWriter( 820 new FileOutputStream(mActivity.getSensorLogFilePath())); 821 } catch (FileNotFoundException e) { 822 Log.e(TAG, "Sensor log file open failed: " + e.toString()); 823 } 824 } 825 826 /** 827 * Stop recording and clean up 828 */ 829 public void end() { 830 mSensorManager.flush(this); 831 mSensorManager.unregisterListener(this); 832 833 try { 834 if (mLogWriter != null) { 835 OutputStreamWriter writer = mLogWriter; 836 mLogWriter = null; 837 writer.close(); 838 } 839 } catch (IOException e) { 840 Log.e(TAG, "Sensor log file close failed: " + e.toString()); 841 } 842 843 updateRegister(null, AXIS_NONE); 844 } 845 846 private void onNewData(float[] data, long timestamp) { 847 // LOG 848 try { 849 if (mLogWriter != null) { 850 mLogWriter.write(String.format("%d %f %f %f %f\r\n", timestamp, 851 data[3], data[0], data[1], data[2])); 852 } 853 } catch (IOException e) { 854 Log.e(TAG, "Sensor log file write failed: " + e.toString()); 855 } 856 857 // Update UI 858 if (mRegister != null) { 859 int d = 0; 860 int dx, dy, dz; 861 boolean valid = false; 862 SensorManager.getRotationMatrixFromVector(mRTemp, data); 863 864 dx = (int)(Math.asin(mRTemp[8])*(180.0/Math.PI)); 865 dy = (int)(Math.asin(mRTemp[9])*(180.0/Math.PI)); 866 dz = (int)((Math.atan2(mRTemp[4], mRTemp[0])+Math.PI)*(180.0/Math.PI)); 867 868 switch(mAxis) { 869 case SensorManager.AXIS_X: 870 d = dx; 871 valid = (Math.abs(dy) < 30); 872 break; 873 case SensorManager.AXIS_Y: 874 d = dy; 875 valid = (Math.abs(dx) < 30); 876 break; 877 case SensorManager.AXIS_Z: 878 d = dz; 879 valid = (Math.abs(dx) < 20 && Math.abs(dy) < 20); 880 break; 881 } 882 883 if (valid) { 884 mRegister.update(d); 885 mActivity.redrawIndicator(); 886 } 887 } 888 889 } 890 891 public void updateRegister(RangeCoveredRegister reg, int axis) { 892 mRegister = reg; 893 mAxis = axis; 894 } 895 896 897 @Override 898 public void onAccuracyChanged(Sensor sensor, int i) { 899 // do not care 900 } 901 902 @Override 903 public void onSensorChanged(SensorEvent event) { 904 if (event.sensor.getType() == Sensor.TYPE_ROTATION_VECTOR) { 905 onNewData(event.values, event.timestamp); 906 } 907 } 908 } 909 910 911 //////////////////////////////////////////////////////////////////////////////////////////////// 912 913 /** 914 * Controls the over all logic of record procedure: first x-direction, then y-direction and 915 * then z-direction. 916 */ 917 class RecordProcedureController implements Runnable { 918 private static final boolean LOCAL_LOGV = false; 919 920 private final RVCVRecordActivity mActivity; 921 private Thread mThread = null; 922 923 RecordProcedureController(RVCVRecordActivity activity) { 924 mActivity = activity; 925 mThread = new Thread(this); 926 mThread.start(); 927 } 928 929 /** 930 * Run the record procedure 931 */ 932 public void run() { 933 if (LOCAL_LOGV) Log.v(TAG, "Controller Thread Started."); 934 //start recording & logging 935 delay(2000); 936 937 init(); 938 if (LOCAL_LOGV) Log.v(TAG, "Controller Thread init() finished."); 939 940 // test 3 axis 941 // It is in YXZ order because UI element design use opposite definition 942 // of XY axis. To ensure the user see X Y Z, it is flipped here. 943 recordAxis(SensorManager.AXIS_Y); 944 if (LOCAL_LOGV) Log.v(TAG, "Controller Thread axis 0 finished."); 945 946 recordAxis(SensorManager.AXIS_X); 947 if (LOCAL_LOGV) Log.v(TAG, "Controller Thread axis 1 finished."); 948 949 recordAxis(SensorManager.AXIS_Z); 950 if (LOCAL_LOGV) Log.v(TAG, "Controller Thread axis 2 finished."); 951 952 delay(1000); 953 end(); 954 if (LOCAL_LOGV) Log.v(TAG, "Controller Thread End."); 955 } 956 957 private void delay(int milli) { 958 try{ 959 Thread.sleep(milli); 960 } catch(InterruptedException e) { 961 if (LOCAL_LOGV) Log.v(TAG, "Controller Thread Interrupted."); 962 } 963 } 964 private void init() { 965 // start video recording 966 mActivity.startRecordVideo(); 967 968 // start sensor logging & listening 969 mActivity.startRecordSensor(); 970 } 971 972 private void end() { 973 // stop video recording 974 mActivity.stopRecordVideo(); 975 976 // stop sensor logging 977 mActivity.stopRecordSensor(); 978 979 // notify ui complete 980 runOnUiThread(new Runnable(){ 981 public void run() { 982 mActivity.notifyComplete(); 983 } 984 }); 985 } 986 987 private void recordAxis(int axis) { 988 // delay 2 seconds? 989 delay(1000); 990 991 // change ui 992 mActivity.switchAxisAsync(axis); 993 994 // play start sound 995 mActivity.playNotifySound("start"); 996 997 if (axis != SensorManager.AXIS_Z) { 998 // wait until axis half covered 999 mActivity.waitUntilHalfCovered(axis); 1000 1001 // play half way sound 1002 mActivity.playNotifySound("half-way"); 1003 } 1004 1005 // wait until axis covered 1006 mActivity.waitUntilCovered(axis); 1007 1008 // play stop sound 1009 mActivity.playNotifySound("end"); 1010 } 1011 1012 /** 1013 * Force quit 1014 */ 1015 public void quit() { 1016 mThread.interrupt(); 1017 try { 1018 if (LOCAL_LOGV) Log.v(TAG, "Wait for controller to end"); 1019 1020 // stop video recording 1021 mActivity.stopRecordVideo(); 1022 1023 // stop sensor logging 1024 mActivity.stopRecordSensor(); 1025 1026 } catch (Exception e) 1027 { 1028 e.printStackTrace(); 1029 } 1030 } 1031 } 1032 1033 } 1034