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