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