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