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