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