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