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