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 onCreate(Bundle savedInstanceState)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 onPause()100 protected void onPause() { 101 super.onPause(); 102 mController.quit(); 103 104 mCameraContext.end(); 105 endSoundPool(); 106 } 107 108 @Override onResume()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 */ message(String msg)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 */ init()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 */ notifyComplete()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 */ notifyPrompt(int axis)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 */ redrawIndicator()185 private void redrawIndicator() { 186 mIndicatorView.invalidate(); 187 } 188 189 /** 190 * Switch to a different axis for display and logging 191 * @param axis 192 */ switchAxis(int axis)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 */ switchAxisAsync(int axis)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 */ initSoundPool()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 } endSoundPool()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 */ playNotifySound(String name)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 */ startRecordSensor()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 */ stopRecordSensor()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 */ startRecordVideo()284 public void startRecordVideo() { 285 mVideoRecorder.init(); 286 } 287 288 /** 289 * Stop video recording 290 */ stopRecordVideo()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 */ waitUntilCovered(int axis)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 */ waitUntilHalfCovered(int axis)307 public void waitUntilHalfCovered(int axis) { 308 mCoverManager.waitUntilHalfCovered(axis); 309 } 310 311 /** 312 * 313 */ initStoragePath()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 */ getSensorLogFilePath()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 */ getVideoRecFilePath()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 */ writeVideoMetaInfo(int width, int height, float frameRate, float fovW, float fovH)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 private String [] mPreferredFocusMode = { 400 Camera.Parameters.FOCUS_MODE_FIXED, 401 Camera.Parameters.FOCUS_MODE_INFINITY, 402 // the following two modes are more likely to mess up recording 403 // but they are still better than FOCUS_MODE_AUTO, which requires 404 // calling autoFocus explicitly to focus. 405 Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, 406 Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE 407 }; 408 CameraContext()409 CameraContext() { 410 try { 411 mCamera = Camera.open(); // attempt to get a default Camera instance (0) 412 mProfile = null; 413 if (mCamera != null) { 414 mCameraInfo = new Camera.CameraInfo(); 415 Camera.getCameraInfo(0, mCameraInfo); 416 setupCamera(); 417 } 418 } 419 catch (Exception e){ 420 // Camera is not available (in use or does not exist) 421 Log.e(TAG, "Cannot obtain Camera!"); 422 } 423 } 424 425 /** 426 * Find a preferred camera profile and set preview and picture size property accordingly. 427 */ setupCamera()428 void setupCamera() { 429 CamcorderProfile profile = null; 430 boolean isSetNeeded = false; 431 Camera.Parameters param = mCamera.getParameters(); 432 List<Camera.Size> pre_sz = param.getSupportedPreviewSizes(); 433 List<Camera.Size> pic_sz = param.getSupportedPictureSizes(); 434 435 for (int i : mPreferredProfiles) { 436 if (CamcorderProfile.hasProfile(i)) { 437 profile = CamcorderProfile.get(i); 438 439 int valid = 0; 440 for (Camera.Size j : pre_sz) { 441 if (j.width == profile.videoFrameWidth && 442 j.height == profile.videoFrameHeight) { 443 ++valid; 444 break; 445 } 446 } 447 for (Camera.Size j : pic_sz) { 448 if (j.width == profile.videoFrameWidth && 449 j.height == profile.videoFrameHeight) { 450 ++valid; 451 break; 452 } 453 } 454 if (valid == 2) { 455 param.setPreviewSize(profile.videoFrameWidth, profile.videoFrameHeight); 456 param.setPictureSize(profile.videoFrameWidth, profile.videoFrameHeight); 457 isSetNeeded = true; 458 break; 459 } else { 460 profile = null; 461 } 462 } 463 } 464 465 for (String i : mPreferredFocusMode) { 466 if (param.getSupportedFocusModes().contains(i)){ 467 param.setFocusMode(i); 468 isSetNeeded = true; 469 break; 470 } 471 } 472 473 if (isSetNeeded) { 474 mCamera.setParameters(param); 475 } 476 477 if (profile != null) { 478 param = mCamera.getParameters(); //acquire proper fov after change the picture size 479 float fovW = param.getHorizontalViewAngle(); 480 float fovH = param.getVerticalViewAngle(); 481 writeVideoMetaInfo(profile.videoFrameWidth, profile.videoFrameHeight, 482 profile.videoFrameRate, fovW, fovH); 483 } else { 484 Log.e(TAG, "Cannot find a proper video profile"); 485 } 486 mProfile = profile; 487 488 } 489 490 491 /** 492 * Get sensor information of the camera being used 493 */ getCameraInfo()494 public Camera.CameraInfo getCameraInfo() { 495 return mCameraInfo; 496 } 497 498 /** 499 * Get the camera to be previewed 500 * @return Reference to Camera used 501 */ getCamera()502 public Camera getCamera() { 503 return mCamera; 504 } 505 506 /** 507 * Get the camera profile to be used 508 * @return Reference to Camera profile 509 */ getProfile()510 public CamcorderProfile getProfile() { 511 return mProfile; 512 } 513 514 /** 515 * Setup the camera 516 */ init()517 public void init() { 518 if (mCamera != null) { 519 double alpha = mCamera.getParameters().getHorizontalViewAngle()*Math.PI/180.0; 520 int width = mProfile.videoFrameWidth; 521 double fx = width/2/Math.tan(alpha/2.0); 522 523 if (LOCAL_LOGV) Log.v(TAG, "View angle=" 524 + mCamera.getParameters().getHorizontalViewAngle() +" Estimated fx = "+fx); 525 526 RVCVCameraPreview cameraPreview = 527 (RVCVCameraPreview) findViewById(R.id.cam_preview); 528 cameraPreview.init(mCamera, 529 (float)mProfile.videoFrameWidth/mProfile.videoFrameHeight, 530 mCameraInfo.orientation); 531 } else { 532 message("Cannot open camera!"); 533 finish(); 534 } 535 } 536 537 /** 538 * End the camera preview 539 */ end()540 public void end() { 541 if (mCamera != null) { 542 mCamera.release(); // release the camera for other applications 543 mCamera = null; 544 } 545 } 546 } 547 548 /** 549 * Manage a set of RangeCoveredRegister objects 550 */ 551 class CoverageManager { 552 // settings 553 private final int MAX_TILT_ANGLE = 50; // +/- 50 554 //private final int REQUIRED_TILT_ANGLE = 50; // +/- 50 555 private final int TILT_ANGLE_STEP = 5; // 5 degree(s) per step 556 private final int YAW_ANGLE_STEP = 10; // 10 degree(s) per step 557 558 RangeCoveredRegister[] mAxisCovered; 559 CoverageManager()560 CoverageManager() { 561 mAxisCovered = new RangeCoveredRegister[3]; 562 // X AXIS 563 mAxisCovered[0] = new RangeCoveredRegister( 564 -MAX_TILT_ANGLE, +MAX_TILT_ANGLE, TILT_ANGLE_STEP); 565 // Y AXIS 566 mAxisCovered[1] = new RangeCoveredRegister( 567 -MAX_TILT_ANGLE, +MAX_TILT_ANGLE, TILT_ANGLE_STEP); 568 // Z AXIS 569 mAxisCovered[2] = new RangeCoveredRegister(YAW_ANGLE_STEP); 570 } 571 getAxis(int axis)572 public RangeCoveredRegister getAxis(int axis) { 573 // SensorManager.AXIS_X = 1, need offset -1 for mAxisCovered array 574 return mAxisCovered[axis-1]; 575 } 576 waitUntilHalfCovered(int axis)577 public void waitUntilHalfCovered(int axis) { 578 if (axis == SensorManager.AXIS_Z) { 579 waitUntilCovered(axis); 580 } 581 582 // SensorManager.AXIS_X = 1, need offset -1 for mAxisCovered array 583 while(!(mAxisCovered[axis-1].isRangeCovered(-MAX_TILT_ANGLE, -MAX_TILT_ANGLE/2) || 584 mAxisCovered[axis-1].isRangeCovered(MAX_TILT_ANGLE/2, MAX_TILT_ANGLE) ) ) { 585 try { 586 Thread.sleep(500); 587 } catch (InterruptedException e) { 588 if (LOCAL_LOGV) { 589 Log.v(TAG, "waitUntilHalfCovered axis = "+ axis + " is interrupted"); 590 } 591 Thread.currentThread().interrupt(); 592 } 593 } 594 } 595 waitUntilCovered(int axis)596 public void waitUntilCovered(int axis) { 597 // SensorManager.AXIS_X = 1, need offset -1 for mAxisCovered array 598 while(!mAxisCovered[axis-1].isFullyCovered()) { 599 try { 600 Thread.sleep(500); 601 } catch (InterruptedException e) { 602 if (LOCAL_LOGV) { 603 Log.v(TAG, "waitUntilCovered axis = "+ axis + " is interrupted"); 604 } 605 Thread.currentThread().interrupt(); 606 } 607 } 608 } 609 } 610 //////////////////////////////////////////////////////////////////////////////////////////////// 611 612 /** 613 * A class controls the video recording 614 */ 615 class VideoRecorder 616 { 617 private MediaRecorder mRecorder; 618 private CamcorderProfile mProfile; 619 private Camera mCamera; 620 private boolean mRunning = false; 621 VideoRecorder(Camera camera, CamcorderProfile profile)622 VideoRecorder(Camera camera, CamcorderProfile profile){ 623 mCamera = camera; 624 mProfile = profile; 625 } 626 627 /** 628 * Initialize and start recording 629 */ init()630 public void init() { 631 if (mCamera == null || mProfile ==null){ 632 return; 633 } 634 635 mRecorder = new MediaRecorder(); 636 mCamera.unlock(); 637 mRecorder.setCamera(mCamera); 638 639 mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 640 mRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT); 641 642 mRecorder.setProfile(mProfile); 643 644 try { 645 mRecorder.setOutputFile(getVideoRecFilePath()); 646 mRecorder.prepare(); 647 } catch (IOException e) { 648 Log.e(TAG, "Preparation for recording failed."); 649 return; 650 } 651 652 try { 653 mRecorder.start(); 654 } catch (RuntimeException e) { 655 Log.e(TAG, "Starting recording failed."); 656 mRecorder.reset(); 657 mRecorder.release(); 658 mCamera.lock(); 659 return; 660 } 661 mRunning = true; 662 } 663 664 /** 665 * Stop recording 666 */ end()667 public void end() { 668 if (mRunning) { 669 try { 670 mRecorder.stop(); 671 mRecorder.reset(); 672 mRecorder.release(); 673 mCamera.lock(); 674 } catch (RuntimeException e) { 675 e.printStackTrace(); 676 Log.e(TAG, "Runtime error in stopping recording."); 677 } 678 } 679 mRecorder = null; 680 } 681 682 } 683 684 //////////////////////////////////////////////////////////////////////////////////////////////// 685 686 /** 687 * Log all raw sensor readings, for Rotation Vector sensor algorithms research 688 */ 689 class RawSensorLogger implements SensorEventListener { 690 private final String TAG = "RawSensorLogger"; 691 692 private final static int SENSOR_RATE = SensorManager.SENSOR_DELAY_FASTEST; 693 private File mRecPath; 694 695 SensorManager mSensorManager; 696 Sensor mAccSensor, mGyroSensor, mMagSensor; 697 OutputStreamWriter mAccLogWriter, mGyroLogWriter, mMagLogWriter; 698 699 private float[] mRTemp = new float[16]; 700 RawSensorLogger(File recPath)701 RawSensorLogger(File recPath) { 702 mRecPath = recPath; 703 } 704 705 /** 706 * Initialize and start recording 707 */ init()708 public void init() { 709 mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE); 710 711 mAccSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); 712 mGyroSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE_UNCALIBRATED); 713 mMagSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED); 714 715 mSensorManager.registerListener(this, mAccSensor, SENSOR_RATE); 716 mSensorManager.registerListener(this, mGyroSensor, SENSOR_RATE); 717 mSensorManager.registerListener(this, mMagSensor, SENSOR_RATE); 718 719 try { 720 mAccLogWriter= new OutputStreamWriter( 721 new FileOutputStream(new File(mRecPath, "raw_acc.log"))); 722 mGyroLogWriter= new OutputStreamWriter( 723 new FileOutputStream(new File(mRecPath, "raw_uncal_gyro.log"))); 724 mMagLogWriter= new OutputStreamWriter( 725 new FileOutputStream(new File(mRecPath, "raw_uncal_mag.log"))); 726 727 } catch (FileNotFoundException e) { 728 Log.e(TAG, "Sensor log file open failed: " + e.toString()); 729 } 730 } 731 732 /** 733 * Stop recording and clean up 734 */ end()735 public void end() { 736 mSensorManager.flush(this); 737 mSensorManager.unregisterListener(this); 738 739 try { 740 if (mAccLogWriter != null) { 741 OutputStreamWriter writer = mAccLogWriter; 742 mAccLogWriter = null; 743 writer.close(); 744 } 745 if (mGyroLogWriter != null) { 746 OutputStreamWriter writer = mGyroLogWriter; 747 mGyroLogWriter = null; 748 writer.close(); 749 } 750 if (mMagLogWriter != null) { 751 OutputStreamWriter writer = mMagLogWriter; 752 mMagLogWriter = null; 753 writer.close(); 754 } 755 756 } catch (IOException e) { 757 Log.e(TAG, "Sensor log file close failed: " + e.toString()); 758 } 759 } 760 761 @Override onAccuracyChanged(Sensor sensor, int i)762 public void onAccuracyChanged(Sensor sensor, int i) { 763 // do not care 764 } 765 766 @Override onSensorChanged(SensorEvent event)767 public void onSensorChanged(SensorEvent event) { 768 OutputStreamWriter writer=null; 769 switch(event.sensor.getType()) { 770 case Sensor.TYPE_ACCELEROMETER: 771 writer = mAccLogWriter; 772 break; 773 case Sensor.TYPE_GYROSCOPE_UNCALIBRATED: 774 writer = mGyroLogWriter; 775 break; 776 case Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED: 777 writer = mMagLogWriter; 778 break; 779 780 } 781 if (writer!=null) { 782 float[] data = event.values; 783 try { 784 if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { 785 writer.write(String.format("%d %f %f %f\r\n", 786 event.timestamp, data[0], data[1], data[2])); 787 }else // TYPE_GYROSCOPE_UNCALIBRATED and TYPE_MAGNETIC_FIELD_UNCALIBRATED 788 { 789 writer.write(String.format("%d %f %f %f %f %f %f\r\n", event.timestamp, 790 data[0], data[1], data[2], data[3], data[4], data[5])); 791 } 792 }catch (IOException e) 793 { 794 Log.e(TAG, "Write to raw sensor log file failed."); 795 } 796 797 } 798 } 799 } 800 801 /** 802 * Rotation sensor logger class 803 */ 804 class RVSensorLogger implements SensorEventListener { 805 private final String TAG = "RVSensorLogger"; 806 807 private final static int SENSOR_RATE = SensorManager.SENSOR_DELAY_FASTEST; 808 RangeCoveredRegister mRegister; 809 int mAxis; 810 RVCVRecordActivity mActivity; 811 812 SensorManager mSensorManager; 813 Sensor mRVSensor; 814 OutputStreamWriter mLogWriter; 815 816 private float[] mRTemp = new float[16]; 817 RVSensorLogger(RVCVRecordActivity activity)818 RVSensorLogger(RVCVRecordActivity activity) { 819 mActivity = activity; 820 } 821 822 /** 823 * Initialize and start recording 824 */ init()825 public void init() { 826 mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE); 827 if (mSensorManager == null) { 828 Log.e(TAG,"SensorManager is null!"); 829 } 830 mRVSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR); 831 if (mRVSensor != null) { 832 if (LOCAL_LOGV) Log.v(TAG, "Got RV Sensor"); 833 }else { 834 Log.e(TAG, "Did not get RV sensor"); 835 } 836 if(mSensorManager.registerListener(this, mRVSensor, SENSOR_RATE)) { 837 if (LOCAL_LOGV) Log.v(TAG,"Register listener successfull"); 838 } else { 839 Log.e(TAG,"Register listener failed"); 840 } 841 842 try { 843 mLogWriter= new OutputStreamWriter( 844 new FileOutputStream(mActivity.getSensorLogFilePath())); 845 } catch (FileNotFoundException e) { 846 Log.e(TAG, "Sensor log file open failed: " + e.toString()); 847 } 848 } 849 850 /** 851 * Stop recording and clean up 852 */ end()853 public void end() { 854 mSensorManager.flush(this); 855 mSensorManager.unregisterListener(this); 856 857 try { 858 if (mLogWriter != null) { 859 OutputStreamWriter writer = mLogWriter; 860 mLogWriter = null; 861 writer.close(); 862 } 863 } catch (IOException e) { 864 Log.e(TAG, "Sensor log file close failed: " + e.toString()); 865 } 866 867 updateRegister(null, AXIS_NONE); 868 } 869 onNewData(float[] data, long timestamp)870 private void onNewData(float[] data, long timestamp) { 871 // LOG 872 try { 873 if (mLogWriter != null) { 874 mLogWriter.write(String.format("%d %f %f %f %f\r\n", timestamp, 875 data[3], data[0], data[1], data[2])); 876 } 877 } catch (IOException e) { 878 Log.e(TAG, "Sensor log file write failed: " + e.toString()); 879 } 880 881 // Update UI 882 if (mRegister != null) { 883 int d = 0; 884 int dx, dy, dz; 885 boolean valid = false; 886 SensorManager.getRotationMatrixFromVector(mRTemp, data); 887 888 dx = (int)(Math.asin(mRTemp[8])*(180.0/Math.PI)); 889 dy = (int)(Math.asin(mRTemp[9])*(180.0/Math.PI)); 890 dz = (int)((Math.atan2(mRTemp[4], mRTemp[0])+Math.PI)*(180.0/Math.PI)); 891 892 switch(mAxis) { 893 case SensorManager.AXIS_X: 894 d = dx; 895 valid = (Math.abs(dy) < 30); 896 break; 897 case SensorManager.AXIS_Y: 898 d = dy; 899 valid = (Math.abs(dx) < 30); 900 break; 901 case SensorManager.AXIS_Z: 902 d = dz; 903 valid = (Math.abs(dx) < 20 && Math.abs(dy) < 20); 904 break; 905 } 906 907 if (valid) { 908 mRegister.update(d); 909 mActivity.redrawIndicator(); 910 } 911 } 912 913 } 914 updateRegister(RangeCoveredRegister reg, int axis)915 public void updateRegister(RangeCoveredRegister reg, int axis) { 916 mRegister = reg; 917 mAxis = axis; 918 } 919 920 921 @Override onAccuracyChanged(Sensor sensor, int i)922 public void onAccuracyChanged(Sensor sensor, int i) { 923 // do not care 924 } 925 926 @Override onSensorChanged(SensorEvent event)927 public void onSensorChanged(SensorEvent event) { 928 if (event.sensor.getType() == Sensor.TYPE_ROTATION_VECTOR) { 929 onNewData(event.values, event.timestamp); 930 } 931 } 932 } 933 934 935 //////////////////////////////////////////////////////////////////////////////////////////////// 936 937 /** 938 * Controls the over all logic of record procedure: first x-direction, then y-direction and 939 * then z-direction. 940 */ 941 class RecordProcedureController implements Runnable { 942 private static final boolean LOCAL_LOGV = false; 943 944 private final RVCVRecordActivity mActivity; 945 private Thread mThread = null; 946 RecordProcedureController(RVCVRecordActivity activity)947 RecordProcedureController(RVCVRecordActivity activity) { 948 mActivity = activity; 949 mThread = new Thread(this); 950 mThread.start(); 951 } 952 953 /** 954 * Run the record procedure 955 */ run()956 public void run() { 957 if (LOCAL_LOGV) Log.v(TAG, "Controller Thread Started."); 958 //start recording & logging 959 delay(2000); 960 961 init(); 962 if (LOCAL_LOGV) Log.v(TAG, "Controller Thread init() finished."); 963 964 // test 3 axis 965 // It is in YXZ order because UI element design use opposite definition 966 // of XY axis. To ensure the user see X Y Z, it is flipped here. 967 recordAxis(SensorManager.AXIS_Y); 968 if (LOCAL_LOGV) Log.v(TAG, "Controller Thread axis 0 finished."); 969 970 recordAxis(SensorManager.AXIS_X); 971 if (LOCAL_LOGV) Log.v(TAG, "Controller Thread axis 1 finished."); 972 973 recordAxis(SensorManager.AXIS_Z); 974 if (LOCAL_LOGV) Log.v(TAG, "Controller Thread axis 2 finished."); 975 976 delay(1000); 977 end(); 978 if (LOCAL_LOGV) Log.v(TAG, "Controller Thread End."); 979 } 980 delay(int milli)981 private void delay(int milli) { 982 try{ 983 Thread.sleep(milli); 984 } catch(InterruptedException e) { 985 if (LOCAL_LOGV) Log.v(TAG, "Controller Thread Interrupted."); 986 } 987 } init()988 private void init() { 989 // start video recording 990 mActivity.startRecordVideo(); 991 992 // start sensor logging & listening 993 mActivity.startRecordSensor(); 994 } 995 end()996 private void end() { 997 // stop video recording 998 mActivity.stopRecordVideo(); 999 1000 // stop sensor logging 1001 mActivity.stopRecordSensor(); 1002 1003 // notify ui complete 1004 runOnUiThread(new Runnable(){ 1005 public void run() { 1006 mActivity.notifyComplete(); 1007 } 1008 }); 1009 } 1010 recordAxis(int axis)1011 private void recordAxis(int axis) { 1012 // delay 2 seconds? 1013 delay(1000); 1014 1015 // change ui 1016 mActivity.switchAxisAsync(axis); 1017 1018 // play start sound 1019 mActivity.playNotifySound("start"); 1020 1021 if (axis != SensorManager.AXIS_Z) { 1022 // wait until axis half covered 1023 mActivity.waitUntilHalfCovered(axis); 1024 1025 // play half way sound 1026 mActivity.playNotifySound("half-way"); 1027 } 1028 1029 // wait until axis covered 1030 mActivity.waitUntilCovered(axis); 1031 1032 // play stop sound 1033 mActivity.playNotifySound("end"); 1034 } 1035 1036 /** 1037 * Force quit 1038 */ quit()1039 public void quit() { 1040 mThread.interrupt(); 1041 try { 1042 if (LOCAL_LOGV) Log.v(TAG, "Wait for controller to end"); 1043 1044 // stop video recording 1045 mActivity.stopRecordVideo(); 1046 1047 // stop sensor logging 1048 mActivity.stopRecordSensor(); 1049 1050 } catch (Exception e) 1051 { 1052 e.printStackTrace(); 1053 } 1054 } 1055 } 1056 1057 } 1058