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