1 
2 package com.android.cts.verifier.sensors;
3 
4 import com.android.cts.verifier.sensors.base.SensorCtsVerifierTestActivity;
5 
6 import junit.framework.Assert;
7 
8 import android.annotation.TargetApi;
9 import android.content.Context;
10 import android.hardware.Sensor;
11 import android.hardware.SensorEvent;
12 import android.hardware.SensorEventListener;
13 import android.hardware.SensorManager;
14 import android.hardware.cts.helpers.TestSensorEvent;
15 import android.os.Build;
16 
17 import java.util.ArrayList;
18 import java.util.List;
19 
20 /**
21  * Test cross-sensor timestamp alignment by detecting major change in each
22  * sensor and comparing timestamps of that change.
23  */
24 @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
25 public class SensorSynchronizationTestActivity
26         extends SensorCtsVerifierTestActivity
27         implements SensorEventListener {
SensorSynchronizationTestActivity()28     public SensorSynchronizationTestActivity() {
29         super(SensorSynchronizationTestActivity.class);
30     }
31 
32     private final double NANOS_PER_MILLI = 1e6;
33     private final int DATA_COLLECTION_TIME_IN_MS = 5000;
34     private final int RATE_100HZ_IN_US = 10000;
35     private final int MAX_CROSS_SENSOR_DELAY_MILLIS = 125;
36     private final double THRESH_DEGREES = 10.0;
37     private final double THRESH_RPS = 1.0;
38 
39     private SensorManager mSensorManager = null;
40     private List<TestSensorEvent> mSensorEvents = new ArrayList<TestSensorEvent>();
41 
startDataCollection()42     private void startDataCollection() {
43         mSensorEvents.clear();
44 
45         mSensorManager = (SensorManager) getApplicationContext()
46                 .getSystemService(Context.SENSOR_SERVICE);
47         mSensorManager.registerListener(this,
48                 mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
49                 RATE_100HZ_IN_US);
50         mSensorManager.registerListener(this,
51                 mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD),
52                 RATE_100HZ_IN_US);
53         mSensorManager.registerListener(this,
54                 mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE),
55                 RATE_100HZ_IN_US);
56     }
57 
stopDataCollection()58     private void stopDataCollection() {
59         mSensorManager.unregisterListener(this);
60     }
61 
analyzeData()62     private void analyzeData() {
63         int numberOfCollectedEvents = mSensorEvents.size();
64         Assert.assertTrue("No sensor events collected", numberOfCollectedEvents > 2);
65 
66         boolean accMovementDetected = false;
67         boolean magMovementDetected = false;
68         boolean gyrMovementDetected = false;
69         long accMovementTimestamp = 0, magMovementTimestamp = 0, gyrMovementTimestamp = 0;
70         float[] accInitValues = null, magInitValues = null, gyrInitValues = null;
71 
72         for (int i = 0; i < numberOfCollectedEvents; i++) {
73             TestSensorEvent event = mSensorEvents.get(i);
74 
75             if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
76                 if (accInitValues == null) {
77                     accInitValues = event.values.clone();
78                 } else if (angleBetweenVecsDegrees(accInitValues, event.values) > THRESH_DEGREES
79                         && !accMovementDetected) {
80                     accMovementDetected = true;
81                     accMovementTimestamp = event.timestamp;
82                 }
83             } else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
84                 if (magInitValues == null) {
85                     magInitValues = event.values.clone();
86                 } else if (angleBetweenVecsDegrees(magInitValues, event.values) > THRESH_DEGREES
87                         && !magMovementDetected) {
88                     magMovementDetected = true;
89                     magMovementTimestamp = event.timestamp;
90                 }
91             }
92             if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE) {
93                 if (gyrInitValues == null) {
94                     gyrInitValues = event.values.clone();
95                 } else if (normVec(event.values) > THRESH_RPS && !gyrMovementDetected) {
96                     gyrMovementDetected = true;
97                     gyrMovementTimestamp = event.timestamp;
98                 }
99             }
100 
101             if (accMovementDetected && magMovementDetected && gyrMovementDetected) {
102                 double maxTimestamp = Math.max(accMovementTimestamp,
103                         magMovementTimestamp);
104                 maxTimestamp = Math.max(gyrMovementTimestamp, maxTimestamp);
105 
106                 double minTimestamp = Math.min(accMovementTimestamp,
107                         magMovementTimestamp);
108                 minTimestamp = Math.min(gyrMovementTimestamp, minTimestamp);
109 
110                 double timeDifferenceBetweenMovementMillis =
111                         (maxTimestamp - minTimestamp) / NANOS_PER_MILLI;
112 
113                 appendText(String.format("\nSensor  |  Relative Timestamp (msec)\n"
114                         + "Accelerometer | %4.1f\nMagnetometer | %4.1f\nGyroscope | %4.1f\n",
115                         (accMovementTimestamp - minTimestamp) / NANOS_PER_MILLI,
116                         (magMovementTimestamp - minTimestamp) / NANOS_PER_MILLI,
117                         (gyrMovementTimestamp - minTimestamp) / NANOS_PER_MILLI));
118                 Assert.assertEquals(String.format(
119                         "Cross sensor timestamp alignment off by more than %d msec.",
120                         MAX_CROSS_SENSOR_DELAY_MILLIS),
121                         0, timeDifferenceBetweenMovementMillis, MAX_CROSS_SENSOR_DELAY_MILLIS);
122                 appendText(String.format(
123                         "Maximum cross sensor time between movement: %4.1f msec is within "
124                                 + "required tolerance of %4.1f msec",
125                         timeDifferenceBetweenMovementMillis,
126                         (float) MAX_CROSS_SENSOR_DELAY_MILLIS));
127                 break;
128             }
129         }
130 
131         Assert.assertTrue("Accelerometer did not detect any movement", accMovementDetected);
132         Assert.assertTrue("Magnetometer did not detect any movement", magMovementDetected);
133         Assert.assertTrue("Gyroscope did not detect any movement", gyrMovementDetected);
134     }
135 
testCrossSensorSynchronization()136     public String testCrossSensorSynchronization() throws Throwable {
137         appendText("This test provides a rough indication of cross-sensor timestamp synchronization.");
138         appendText("Hold device still in hand and click 'Next'");
139         waitForUserToBegin();
140         clearText();
141         appendText("Quickly twist device upside-down and back");
142 
143         startDataCollection();
144         Thread.sleep(DATA_COLLECTION_TIME_IN_MS);
145 
146         stopDataCollection();
147         analyzeData();
148         return null;
149     }
150 
angleBetweenVecsDegrees(float[] vec1, float[] vec2)151     protected double angleBetweenVecsDegrees(float[] vec1, float[] vec2) {
152         return Math.toDegrees(Math.acos((vec1[0] * vec2[0] + vec1[1] * vec2[1] + vec1[2] * vec2[2])
153                 / normVec(vec1) / normVec(vec2)));
154     }
155 
normVec(float[] vec1)156     protected double normVec(float[] vec1) {
157         return Math.sqrt(vec1[0] * vec1[0] + vec1[1] * vec1[1] + vec1[2] * vec1[2]);
158     }
159 
160     @Override
onSensorChanged(SensorEvent sensorEvent)161     public void onSensorChanged(SensorEvent sensorEvent) {
162         mSensorEvents.add(new TestSensorEvent(sensorEvent));
163     }
164 
165     @Override
onAccuracyChanged(Sensor sensor, int accuracy)166     public void onAccuracyChanged(Sensor sensor, int accuracy) {
167     }
168 }
169