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