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 
CameraContext()399         CameraContext() {
400             try {
401                 mCamera = Camera.open(); // attempt to get a default Camera instance (0)
402                 mProfile = null;
403                 if (mCamera != null) {
404                     mCameraInfo = new Camera.CameraInfo();
405                     Camera.getCameraInfo(0, mCameraInfo);
406                     setupCamera();
407                 }
408             }
409             catch (Exception e){
410                 // Camera is not available (in use or does not exist)
411                 Log.e(TAG, "Cannot obtain Camera!");
412             }
413         }
414 
415         /**
416          * Find a preferred camera profile and set preview and picture size property accordingly.
417          */
setupCamera()418         void setupCamera() {
419             CamcorderProfile profile = null;
420             Camera.Parameters param = mCamera.getParameters();
421             List<Camera.Size> pre_sz = param.getSupportedPreviewSizes();
422             List<Camera.Size> pic_sz = param.getSupportedPictureSizes();
423 
424             for (int i : mPreferredProfiles) {
425                 if (CamcorderProfile.hasProfile(i)) {
426                     profile = CamcorderProfile.get(i);
427 
428                     int valid = 0;
429                     for (Camera.Size j : pre_sz) {
430                         if (j.width == profile.videoFrameWidth &&
431                                 j.height == profile.videoFrameHeight) {
432                             ++valid;
433                             break;
434                         }
435                     }
436                     for (Camera.Size j : pic_sz) {
437                         if (j.width == profile.videoFrameWidth &&
438                                 j.height == profile.videoFrameHeight) {
439                             ++valid;
440                             break;
441                         }
442                     }
443                     if (valid == 2) {
444                         param.setPreviewSize(profile.videoFrameWidth, profile.videoFrameHeight);
445                         param.setPictureSize(profile.videoFrameWidth, profile.videoFrameHeight);
446                         mCamera.setParameters(param);
447                         break;
448                     } else {
449                         profile = null;
450                     }
451                 }
452             }
453             if (profile != null) {
454                 param = mCamera.getParameters(); //acquire proper fov after change the picture size
455                 float fovW = param.getHorizontalViewAngle();
456                 float fovH = param.getVerticalViewAngle();
457                 writeVideoMetaInfo(profile.videoFrameWidth, profile.videoFrameHeight,
458                         profile.videoFrameRate, fovW, fovH);
459             } else {
460                 Log.e(TAG, "Cannot find a proper video profile");
461             }
462             mProfile = profile;
463 
464         }
465 
466 
467         /**
468          * Get sensor information of the camera being used
469          */
getCameraInfo()470         public Camera.CameraInfo getCameraInfo() {
471             return mCameraInfo;
472         }
473 
474         /**
475          * Get the camera to be previewed
476          * @return Reference to Camera used
477          */
getCamera()478         public Camera getCamera() {
479             return mCamera;
480         }
481 
482         /**
483          * Get the camera profile to be used
484          * @return Reference to Camera profile
485          */
getProfile()486         public CamcorderProfile getProfile() {
487             return mProfile;
488         }
489 
490         /**
491          * Setup the camera
492          */
init()493         public void init() {
494             if (mCamera != null) {
495                 double alpha = mCamera.getParameters().getHorizontalViewAngle()*Math.PI/180.0;
496                 int width = mProfile.videoFrameWidth;
497                 double fx = width/2/Math.tan(alpha/2.0);
498 
499                 if (LOCAL_LOGV) Log.v(TAG, "View angle="
500                         + mCamera.getParameters().getHorizontalViewAngle() +"  Estimated fx = "+fx);
501 
502                 RVCVCameraPreview cameraPreview =
503                         (RVCVCameraPreview) findViewById(R.id.cam_preview);
504                 cameraPreview.init(mCamera,
505                         (float)mProfile.videoFrameWidth/mProfile.videoFrameHeight,
506                         mCameraInfo.orientation);
507             } else {
508                 message("Cannot open camera!");
509                 finish();
510             }
511         }
512 
513         /**
514          * End the camera preview
515          */
end()516         public void end() {
517             if (mCamera != null) {
518                 mCamera.release();        // release the camera for other applications
519                 mCamera = null;
520             }
521         }
522     }
523 
524     /**
525      * Manage a set of RangeCoveredRegister objects
526      */
527     class CoverageManager {
528         // settings
529         private final int MAX_TILT_ANGLE = 50; // +/- 50
530         //private final int REQUIRED_TILT_ANGLE = 50; // +/- 50
531         private final int TILT_ANGLE_STEP = 5; // 5 degree(s) per step
532         private final int YAW_ANGLE_STEP = 10; // 10 degree(s) per step
533 
534         RangeCoveredRegister[] mAxisCovered;
535 
CoverageManager()536         CoverageManager() {
537             mAxisCovered = new RangeCoveredRegister[3];
538             // X AXIS
539             mAxisCovered[0] = new RangeCoveredRegister(
540                     -MAX_TILT_ANGLE, +MAX_TILT_ANGLE, TILT_ANGLE_STEP);
541             // Y AXIS
542             mAxisCovered[1] = new RangeCoveredRegister(
543                     -MAX_TILT_ANGLE, +MAX_TILT_ANGLE, TILT_ANGLE_STEP);
544             // Z AXIS
545             mAxisCovered[2] = new RangeCoveredRegister(YAW_ANGLE_STEP);
546         }
547 
getAxis(int axis)548         public RangeCoveredRegister getAxis(int axis) {
549             // SensorManager.AXIS_X = 1, need offset -1 for mAxisCovered array
550             return mAxisCovered[axis-1];
551         }
552 
waitUntilHalfCovered(int axis)553         public void waitUntilHalfCovered(int axis) {
554             if (axis == SensorManager.AXIS_Z) {
555                 waitUntilCovered(axis);
556             }
557 
558             // SensorManager.AXIS_X = 1, need offset -1 for mAxisCovered array
559             while(!(mAxisCovered[axis-1].isRangeCovered(-MAX_TILT_ANGLE, -MAX_TILT_ANGLE/2) ||
560                         mAxisCovered[axis-1].isRangeCovered(MAX_TILT_ANGLE/2, MAX_TILT_ANGLE) ) ) {
561                 try {
562                     Thread.sleep(500);
563                 } catch (InterruptedException e) {
564                     if (LOCAL_LOGV) {
565                         Log.v(TAG, "waitUntilHalfCovered axis = "+ axis + " is interrupted");
566                     }
567                     Thread.currentThread().interrupt();
568                 }
569             }
570         }
571 
waitUntilCovered(int axis)572         public void waitUntilCovered(int axis) {
573             // SensorManager.AXIS_X = 1, need offset -1 for mAxisCovered array
574             while(!mAxisCovered[axis-1].isFullyCovered()) {
575                 try {
576                     Thread.sleep(500);
577                 } catch (InterruptedException e) {
578                     if (LOCAL_LOGV) {
579                         Log.v(TAG, "waitUntilCovered axis = "+ axis + " is interrupted");
580                     }
581                     Thread.currentThread().interrupt();
582                 }
583             }
584         }
585     }
586     ////////////////////////////////////////////////////////////////////////////////////////////////
587 
588     /**
589      * A class controls the video recording
590      */
591     class VideoRecorder
592     {
593         private MediaRecorder mRecorder;
594         private CamcorderProfile mProfile;
595         private Camera mCamera;
596         private boolean mRunning = false;
597 
VideoRecorder(Camera camera, CamcorderProfile profile)598         VideoRecorder(Camera camera, CamcorderProfile profile){
599             mCamera = camera;
600             mProfile = profile;
601         }
602 
603         /**
604          * Initialize and start recording
605          */
init()606         public void init() {
607             if (mCamera == null  || mProfile ==null){
608                 return;
609             }
610 
611             mRecorder = new MediaRecorder();
612             mCamera.unlock();
613             mRecorder.setCamera(mCamera);
614 
615             mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
616             mRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
617 
618             mRecorder.setProfile(mProfile);
619 
620             try {
621                 mRecorder.setOutputFile(getVideoRecFilePath());
622                 mRecorder.prepare();
623             } catch (IOException e) {
624                 Log.e(TAG, "Preparation for recording failed.");
625                 return;
626             }
627 
628             try {
629                 mRecorder.start();
630             } catch (RuntimeException e) {
631                 Log.e(TAG, "Starting recording failed.");
632                 mRecorder.reset();
633                 mRecorder.release();
634                 mCamera.lock();
635                 return;
636             }
637             mRunning = true;
638         }
639 
640         /**
641          * Stop recording
642          */
end()643         public void end() {
644             if (mRunning) {
645                 try {
646                     mRecorder.stop();
647                     mRecorder.reset();
648                     mRecorder.release();
649                     mCamera.lock();
650                 } catch (RuntimeException e) {
651                     e.printStackTrace();
652                     Log.e(TAG, "Runtime error in stopping recording.");
653                 }
654             }
655             mRecorder = null;
656         }
657 
658     }
659 
660     ////////////////////////////////////////////////////////////////////////////////////////////////
661 
662     /**
663      *  Log all raw sensor readings, for Rotation Vector sensor algorithms research
664      */
665     class RawSensorLogger implements SensorEventListener {
666         private final String TAG = "RawSensorLogger";
667 
668         private final static int SENSOR_RATE = SensorManager.SENSOR_DELAY_FASTEST;
669         private File mRecPath;
670 
671         SensorManager mSensorManager;
672         Sensor mAccSensor, mGyroSensor, mMagSensor;
673         OutputStreamWriter mAccLogWriter, mGyroLogWriter, mMagLogWriter;
674 
675         private float[] mRTemp = new float[16];
676 
RawSensorLogger(File recPath)677         RawSensorLogger(File recPath) {
678             mRecPath = recPath;
679         }
680 
681         /**
682          * Initialize and start recording
683          */
init()684         public void init() {
685             mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
686 
687             mAccSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
688             mGyroSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE_UNCALIBRATED);
689             mMagSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED);
690 
691             mSensorManager.registerListener(this, mAccSensor, SENSOR_RATE);
692             mSensorManager.registerListener(this, mGyroSensor, SENSOR_RATE);
693             mSensorManager.registerListener(this, mMagSensor, SENSOR_RATE);
694 
695             try {
696                 mAccLogWriter= new OutputStreamWriter(
697                         new FileOutputStream(new File(mRecPath, "raw_acc.log")));
698                 mGyroLogWriter= new OutputStreamWriter(
699                         new FileOutputStream(new File(mRecPath, "raw_uncal_gyro.log")));
700                 mMagLogWriter= new OutputStreamWriter(
701                         new FileOutputStream(new File(mRecPath, "raw_uncal_mag.log")));
702 
703             } catch (FileNotFoundException e) {
704                 Log.e(TAG, "Sensor log file open failed: " + e.toString());
705             }
706         }
707 
708         /**
709          * Stop recording and clean up
710          */
end()711         public void end() {
712             mSensorManager.flush(this);
713             mSensorManager.unregisterListener(this);
714 
715             try {
716                 if (mAccLogWriter != null) {
717                     OutputStreamWriter writer = mAccLogWriter;
718                     mAccLogWriter = null;
719                     writer.close();
720                 }
721                 if (mGyroLogWriter != null) {
722                     OutputStreamWriter writer = mGyroLogWriter;
723                     mGyroLogWriter = null;
724                     writer.close();
725                 }
726                 if (mMagLogWriter != null) {
727                     OutputStreamWriter writer = mMagLogWriter;
728                     mMagLogWriter = null;
729                     writer.close();
730                 }
731 
732             } catch (IOException e) {
733                 Log.e(TAG, "Sensor log file close failed: " + e.toString());
734             }
735         }
736 
737         @Override
onAccuracyChanged(Sensor sensor, int i)738         public void onAccuracyChanged(Sensor sensor, int i) {
739             // do not care
740         }
741 
742         @Override
onSensorChanged(SensorEvent event)743         public void onSensorChanged(SensorEvent event) {
744             OutputStreamWriter writer=null;
745             switch(event.sensor.getType()) {
746                 case Sensor.TYPE_ACCELEROMETER:
747                     writer = mAccLogWriter;
748                     break;
749                 case Sensor.TYPE_GYROSCOPE_UNCALIBRATED:
750                     writer = mGyroLogWriter;
751                     break;
752                 case Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED:
753                     writer = mMagLogWriter;
754                     break;
755 
756             }
757             if (writer!=null)  {
758                 float[] data = event.values;
759                 try {
760                     if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
761                         writer.write(String.format("%d %f %f %f\r\n",
762                                 event.timestamp, data[0], data[1], data[2]));
763                     }else // TYPE_GYROSCOPE_UNCALIBRATED and TYPE_MAGNETIC_FIELD_UNCALIBRATED
764                     {
765                         writer.write(String.format("%d %f %f %f %f %f %f\r\n", event.timestamp,
766                                 data[0], data[1], data[2], data[3], data[4], data[5]));
767                     }
768                 }catch (IOException e)
769                 {
770                     Log.e(TAG, "Write to raw sensor log file failed.");
771                 }
772 
773             }
774         }
775     }
776 
777     /**
778      *  Rotation sensor logger class
779      */
780     class RVSensorLogger implements SensorEventListener {
781         private final String TAG = "RVSensorLogger";
782 
783         private final static int SENSOR_RATE = SensorManager.SENSOR_DELAY_FASTEST;
784         RangeCoveredRegister mRegister;
785         int mAxis;
786         RVCVRecordActivity mActivity;
787 
788         SensorManager mSensorManager;
789         Sensor mRVSensor;
790         OutputStreamWriter mLogWriter;
791 
792         private float[] mRTemp = new float[16];
793 
RVSensorLogger(RVCVRecordActivity activity)794         RVSensorLogger(RVCVRecordActivity activity) {
795             mActivity = activity;
796         }
797 
798         /**
799          * Initialize and start recording
800          */
init()801         public void init() {
802             mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
803             if (mSensorManager == null) {
804                 Log.e(TAG,"SensorManager is null!");
805             }
806             mRVSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
807             if (mRVSensor != null) {
808                 if (LOCAL_LOGV) Log.v(TAG, "Got RV Sensor");
809             }else {
810                 Log.e(TAG, "Did not get RV sensor");
811             }
812             if(mSensorManager.registerListener(this, mRVSensor, SENSOR_RATE)) {
813                 if (LOCAL_LOGV) Log.v(TAG,"Register listener successfull");
814             } else {
815                 Log.e(TAG,"Register listener failed");
816             }
817 
818             try {
819                 mLogWriter= new OutputStreamWriter(
820                         new FileOutputStream(mActivity.getSensorLogFilePath()));
821             } catch (FileNotFoundException e) {
822                 Log.e(TAG, "Sensor log file open failed: " + e.toString());
823             }
824         }
825 
826         /**
827          * Stop recording and clean up
828          */
end()829         public void end() {
830             mSensorManager.flush(this);
831             mSensorManager.unregisterListener(this);
832 
833             try {
834                 if (mLogWriter != null) {
835                     OutputStreamWriter writer = mLogWriter;
836                     mLogWriter = null;
837                     writer.close();
838                 }
839             } catch (IOException e) {
840                 Log.e(TAG, "Sensor log file close failed: " + e.toString());
841             }
842 
843             updateRegister(null, AXIS_NONE);
844         }
845 
onNewData(float[] data, long timestamp)846         private void onNewData(float[] data, long timestamp) {
847             // LOG
848             try {
849                 if (mLogWriter != null) {
850                     mLogWriter.write(String.format("%d %f %f %f %f\r\n", timestamp,
851                             data[3], data[0], data[1], data[2]));
852                 }
853             } catch (IOException e) {
854                 Log.e(TAG, "Sensor log file write failed: " + e.toString());
855             }
856 
857             // Update UI
858             if (mRegister != null) {
859                 int d = 0;
860                 int dx, dy, dz;
861                 boolean valid = false;
862                 SensorManager.getRotationMatrixFromVector(mRTemp, data);
863 
864                 dx = (int)(Math.asin(mRTemp[8])*(180.0/Math.PI));
865                 dy = (int)(Math.asin(mRTemp[9])*(180.0/Math.PI));
866                 dz = (int)((Math.atan2(mRTemp[4], mRTemp[0])+Math.PI)*(180.0/Math.PI));
867 
868                 switch(mAxis) {
869                     case SensorManager.AXIS_X:
870                         d = dx;
871                         valid = (Math.abs(dy) < 30);
872                         break;
873                     case SensorManager.AXIS_Y:
874                         d = dy;
875                         valid = (Math.abs(dx) < 30);
876                         break;
877                     case SensorManager.AXIS_Z:
878                         d = dz;
879                         valid = (Math.abs(dx) < 20 && Math.abs(dy) < 20);
880                         break;
881                 }
882 
883                 if (valid) {
884                     mRegister.update(d);
885                     mActivity.redrawIndicator();
886                 }
887             }
888 
889         }
890 
updateRegister(RangeCoveredRegister reg, int axis)891         public void updateRegister(RangeCoveredRegister reg, int axis) {
892             mRegister = reg;
893             mAxis = axis;
894         }
895 
896 
897         @Override
onAccuracyChanged(Sensor sensor, int i)898         public void onAccuracyChanged(Sensor sensor, int i) {
899             // do not care
900         }
901 
902         @Override
onSensorChanged(SensorEvent event)903         public void onSensorChanged(SensorEvent event) {
904             if (event.sensor.getType() == Sensor.TYPE_ROTATION_VECTOR) {
905                 onNewData(event.values, event.timestamp);
906             }
907         }
908     }
909 
910 
911     ////////////////////////////////////////////////////////////////////////////////////////////////
912 
913     /**
914      * Controls the over all logic of record procedure: first x-direction, then y-direction and
915      * then z-direction.
916      */
917     class RecordProcedureController implements Runnable {
918         private static final boolean LOCAL_LOGV = false;
919 
920         private final RVCVRecordActivity mActivity;
921         private Thread mThread = null;
922 
RecordProcedureController(RVCVRecordActivity activity)923         RecordProcedureController(RVCVRecordActivity activity) {
924             mActivity = activity;
925             mThread = new Thread(this);
926             mThread.start();
927         }
928 
929         /**
930          * Run the record procedure
931          */
run()932         public void run() {
933             if (LOCAL_LOGV) Log.v(TAG, "Controller Thread Started.");
934             //start recording & logging
935             delay(2000);
936 
937             init();
938             if (LOCAL_LOGV) Log.v(TAG, "Controller Thread init() finished.");
939 
940             // test 3 axis
941             // It is in YXZ order because UI element design use opposite definition
942             // of XY axis. To ensure the user see X Y Z, it is flipped here.
943             recordAxis(SensorManager.AXIS_Y);
944             if (LOCAL_LOGV) Log.v(TAG, "Controller Thread axis 0 finished.");
945 
946             recordAxis(SensorManager.AXIS_X);
947             if (LOCAL_LOGV) Log.v(TAG, "Controller Thread axis 1 finished.");
948 
949             recordAxis(SensorManager.AXIS_Z);
950             if (LOCAL_LOGV) Log.v(TAG, "Controller Thread axis 2 finished.");
951 
952             delay(1000);
953             end();
954             if (LOCAL_LOGV) Log.v(TAG, "Controller Thread End.");
955         }
956 
delay(int milli)957         private void delay(int milli) {
958             try{
959                 Thread.sleep(milli);
960             } catch(InterruptedException e) {
961                 if (LOCAL_LOGV) Log.v(TAG, "Controller Thread Interrupted.");
962             }
963         }
init()964         private void init() {
965             // start video recording
966             mActivity.startRecordVideo();
967 
968             // start sensor logging & listening
969             mActivity.startRecordSensor();
970         }
971 
end()972         private void end() {
973             // stop video recording
974             mActivity.stopRecordVideo();
975 
976             // stop sensor logging
977             mActivity.stopRecordSensor();
978 
979             // notify ui complete
980             runOnUiThread(new Runnable(){
981                 public void run() {
982                     mActivity.notifyComplete();
983                 }
984             });
985         }
986 
recordAxis(int axis)987         private void recordAxis(int axis) {
988             // delay 2 seconds?
989             delay(1000);
990 
991             // change ui
992             mActivity.switchAxisAsync(axis);
993 
994             // play start sound
995             mActivity.playNotifySound("start");
996 
997             if (axis != SensorManager.AXIS_Z) {
998                 // wait until axis half covered
999                 mActivity.waitUntilHalfCovered(axis);
1000 
1001                 // play half way sound
1002                 mActivity.playNotifySound("half-way");
1003             }
1004 
1005             // wait until axis covered
1006             mActivity.waitUntilCovered(axis);
1007 
1008             // play stop sound
1009             mActivity.playNotifySound("end");
1010         }
1011 
1012         /**
1013          * Force quit
1014          */
quit()1015         public void quit() {
1016             mThread.interrupt();
1017             try {
1018                 if (LOCAL_LOGV) Log.v(TAG, "Wait for controller to end");
1019 
1020                 // stop video recording
1021                 mActivity.stopRecordVideo();
1022 
1023                 // stop sensor logging
1024                 mActivity.stopRecordSensor();
1025 
1026             } catch (Exception e)
1027             {
1028                 e.printStackTrace();
1029             }
1030         }
1031     }
1032 
1033 }
1034