1 /*
2  * Copyright (C) 2014 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 com.android.cts.verifier.R;
20 import com.android.cts.verifier.sensors.base.SensorCtsVerifierTestActivity;
21 
22 import junit.framework.Assert;
23 
24 import android.content.Context;
25 import android.hardware.Sensor;
26 import android.hardware.SensorEvent;
27 import android.hardware.SensorEventListener;
28 import android.hardware.SensorManager;
29 import android.hardware.cts.helpers.MovementDetectorHelper;
30 import android.hardware.cts.helpers.SensorTestStateNotSupportedException;
31 import android.hardware.cts.helpers.TestSensorEnvironment;
32 import android.hardware.cts.helpers.TestSensorEvent;
33 import android.os.SystemClock;
34 import android.view.MotionEvent;
35 import android.view.View;
36 
37 import java.util.ArrayList;
38 import java.util.List;
39 import java.util.concurrent.TimeUnit;
40 
41 public class StepCounterTestActivity
42         extends SensorCtsVerifierTestActivity
43         implements SensorEventListener {
StepCounterTestActivity()44     public StepCounterTestActivity() {
45         super(StepCounterTestActivity.class);
46     }
47 
48     private static final int TEST_DURATION_SECONDS = 20;
49     private static final long TIMESTAMP_SYNCHRONIZATION_THRESHOLD_NANOS =
50             TimeUnit.MILLISECONDS.toNanos(500);
51 
52     private static final int MIN_NUM_STEPS_PER_TEST = 10;
53     private static final int MAX_STEP_DISCREPANCY = 5;
54     private static final long MAX_TOLERANCE_STEP_TIME_NANOS = TimeUnit.SECONDS.toNanos(10);
55 
56     private static final long[] VIBRATE_PATTERN = {
57             1000L, 500L, 1000L, 750L, 1000L, 500L, 1000L, 750L, 1000L, 1000L, 500L, 1000L,
58             750L, 1000L, 500L, 1000L };
59 
60     private SensorManager mSensorManager;
61     private Sensor mSensorStepCounter;
62     private Sensor mSensorStepDetector;
63     private MovementDetectorHelper mMovementDetectorHelper;
64 
65     private volatile boolean mMoveDetected;
66 
67     private final List<Long> mTimestampsUserReported = new ArrayList<Long>();
68     private final List<TestSensorEvent> mStepCounterEvents = new ArrayList<TestSensorEvent>();
69     private final List<TestSensorEvent> mStepDetectorEvents = new ArrayList<TestSensorEvent>();
70 
71     /**
72      * A flag that indicates if the test is interested in registering steps reported by the
73      * operator. The registration of events happens by tapping the screen throughout the test.
74      */
75     private volatile boolean mCheckForMotion;
76 
77     @Override
activitySetUp()78     protected void activitySetUp() {
79         mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
80         mSensorStepCounter = mSensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER);
81         mSensorStepDetector = mSensorManager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR);
82 
83         if (mSensorStepCounter == null && mSensorStepDetector == null) {
84             throw new SensorTestStateNotSupportedException(
85                     "Sensors Step Counter/Detector are not supported.");
86         }
87 
88         setLogScrollViewListener(new View.OnTouchListener() {
89             @Override
90             public boolean onTouch(View v, MotionEvent event) {
91                 // during movement of the device, the ScrollView will detect user taps as attempts
92                 // to scroll, when in reality they are taps in the layout
93                 // to overcome the fact that a ScrollView cannot be disabled from scrolling, we
94                 // listen for ACTION_UP events instead of click events in the child layout
95                 long elapsedTime = SystemClock.elapsedRealtimeNanos();
96                 if (event.getAction() != MotionEvent.ACTION_UP) {
97                     return false;
98                 }
99 
100                 try {
101                     logUserReportedStep(elapsedTime);
102                 } catch (InterruptedException e) {
103                     // we cannot propagate the exception in the main thread, so we just catch and
104                     // restore the status, we don't need to log as we are terminating anyways
105                     Thread.currentThread().interrupt();
106                 }
107                 return false;
108             }
109         });
110     }
111 
112     @Override
activityCleanUp()113     protected void activityCleanUp() {
114         setLogScrollViewListener(null /* listener */);
115     }
116 
testWalking()117     public String testWalking() throws Throwable {
118         return runTest(
119                 R.string.snsr_step_counter_test_walking,
120                 MIN_NUM_STEPS_PER_TEST,
121                 false /* vibrate */);
122     }
123 
testStill()124     public String testStill() throws Throwable {
125         return runTest(
126                 R.string.snsr_step_counter_test_still,
127                 0 /* expectedSteps */,
128                 true /* vibrate */);
129     }
130 
131     /**
132      * @param instructionsResId Resource ID containing instruction to be shown to testers
133      * @param expectedSteps Number of steps expected in this test
134      * @param vibrate If TRUE, vibration will be concurrent with the test
135      */
runTest(int instructionsResId, int expectedSteps, boolean vibrate)136     private String runTest(int instructionsResId, int expectedSteps, boolean vibrate)
137             throws Throwable {
138         mTimestampsUserReported.clear();
139         mStepCounterEvents.clear();
140         mStepDetectorEvents.clear();
141 
142         mMoveDetected = false;
143         mCheckForMotion = false;
144 
145         getTestLogger().logInstructions(instructionsResId);
146         waitForUserToBegin();
147 
148         mCheckForMotion = (expectedSteps > 0);
149         if (vibrate) {
150             vibrate(VIBRATE_PATTERN);
151         }
152         startMeasurements();
153         getTestLogger().logWaitForSound();
154 
155         Thread.sleep(TimeUnit.SECONDS.toMillis(TEST_DURATION_SECONDS));
156         mCheckForMotion = false;
157         playSound();
158 
159         return verifyMeasurements(expectedSteps);
160     }
161 
startMeasurements()162     private void startMeasurements() {
163         if (mSensorStepCounter != null) {
164             mSensorManager.registerListener(this, mSensorStepCounter,
165                     SensorManager.SENSOR_DELAY_NORMAL);
166         }
167 
168         if (mSensorStepDetector != null) {
169             mSensorManager.registerListener(this, mSensorStepDetector,
170                     SensorManager.SENSOR_DELAY_NORMAL);
171         }
172 
173         mMovementDetectorHelper = new MovementDetectorHelper(getApplicationContext()) {
174             @Override
175             protected void onMovementDetected() {
176                 mMoveDetected = true;
177             }
178         };
179         mMovementDetectorHelper.start();
180     }
181 
verifyMeasurements(int stepsExpected)182     private String verifyMeasurements(int stepsExpected) {
183         mSensorManager.unregisterListener(this);
184         mMovementDetectorHelper.stop();
185 
186         if (mCheckForMotion) {
187             Assert.assertTrue(
188                     getString(R.string.snsr_movement_expected, mMoveDetected),
189                     mMoveDetected);
190         }
191 
192         final int userReportedSteps = mTimestampsUserReported.size();
193         String stepsReportedMessage = getString(
194                 R.string.snsr_step_counter_expected_steps,
195                 stepsExpected,
196                 userReportedSteps);
197         Assert.assertFalse(stepsReportedMessage, userReportedSteps < stepsExpected);
198 
199         // TODO: split test cases for step detector and counter
200         verifyStepDetectorMeasurements();
201         verifyStepCounterMeasurements();
202         return null;
203     }
204 
verifyStepCounterMeasurements()205     private void verifyStepCounterMeasurements() {
206         if (mSensorStepCounter == null) {
207             // sensor not supported, so no-op
208             return;
209         }
210 
211         final int userReportedSteps = mTimestampsUserReported.size();
212         int totalStepsCounted = 0;
213         int initialStepCount = -1;
214         for (TestSensorEvent counterEvent : mStepCounterEvents) {
215             String sensorName = counterEvent.sensor.getName();
216             float[] values = counterEvent.values;
217 
218             final int expectedLength = 1;
219             int valuesLength = values.length;
220             String eventLengthMessage = getString(
221                     R.string.snsr_event_length,
222                     expectedLength,
223                     valuesLength,
224                     sensorName);
225             Assert.assertEquals(eventLengthMessage, expectedLength, valuesLength);
226 
227             int stepValue = (int) values[0];
228             if (initialStepCount == -1) {
229                 initialStepCount = stepValue;
230             } else {
231                 int stepsCounted = stepValue - initialStepCount;
232                 int countDelta = stepsCounted - totalStepsCounted;
233 
234                 String eventTriggered = getString(
235                         R.string.snsr_step_counter_event_changed,
236                         countDelta,
237                         counterEvent.timestamp);
238                 Assert.assertTrue(eventTriggered, countDelta > 0);
239 
240                 // TODO: abstract this into an ISensorVerification
241 
242                 long deltaThreshold = TIMESTAMP_SYNCHRONIZATION_THRESHOLD_NANOS
243                         + TestSensorEnvironment.getSensorMaxDetectionLatencyNs(counterEvent.sensor);
244                 assertTimestampSynchronization(
245                         counterEvent.timestamp,
246                         counterEvent.receivedTimestamp,
247                         deltaThreshold,
248                         counterEvent.sensor.getName());
249 
250                 totalStepsCounted = stepsCounted;
251             }
252         }
253 
254         int stepsCountedDelta = Math.abs(totalStepsCounted - userReportedSteps);
255         String stepsDeltaMessage = getString(
256                 R.string.snsr_step_counter_detected_reported,
257                 userReportedSteps,
258                 totalStepsCounted,
259                 stepsCountedDelta,
260                 MAX_STEP_DISCREPANCY);
261         Assert.assertFalse(stepsDeltaMessage, stepsCountedDelta > MAX_STEP_DISCREPANCY);
262 
263         int stepCounterLength = mStepCounterEvents.size();
264         for (int i = 0; i < userReportedSteps && i < stepCounterLength; ++i) {
265             long userReportedTimestamp = mTimestampsUserReported.get(i);
266             TestSensorEvent counterEvent = mStepCounterEvents.get(i);
267 
268             assertTimestampSynchronization(
269                     counterEvent.timestamp,
270                     userReportedTimestamp,
271                     MAX_TOLERANCE_STEP_TIME_NANOS,
272                     counterEvent.sensor.getName());
273         }
274     }
275 
verifyStepDetectorMeasurements()276     private void verifyStepDetectorMeasurements() {
277         if (mSensorStepDetector == null) {
278             // sensor not supported, so no-op
279             return;
280         }
281 
282         final int userReportedSteps = mTimestampsUserReported.size();
283         int stepsDetected = mStepDetectorEvents.size();
284         int stepsDetectedDelta = Math.abs(stepsDetected - userReportedSteps);
285         String stepsDetectedMessage = getString(
286                 R.string.snsr_step_detector_detected_reported,
287                 userReportedSteps,
288                 stepsDetected,
289                 stepsDetectedDelta,
290                 MAX_STEP_DISCREPANCY);
291         Assert.assertFalse(stepsDetectedMessage, stepsDetectedDelta > MAX_STEP_DISCREPANCY);
292 
293         for (TestSensorEvent detectorEvent : mStepDetectorEvents) {
294             String sensorName = detectorEvent.sensor.getName();
295             float[] values = detectorEvent.values;
296 
297             final int expectedLength = 1;
298             int valuesLength = values.length;
299             String eventLengthMessage = getString(
300                     R.string.snsr_event_length,
301                     expectedLength,
302                     valuesLength,
303                     sensorName);
304             Assert.assertEquals(eventLengthMessage, expectedLength, valuesLength);
305 
306             final float expectedValue = 1.0f;
307             float value0 = values[0];
308             String eventValueMessage =
309                     getString(R.string.snsr_event_value, expectedValue, value0, sensorName);
310             Assert.assertEquals(eventValueMessage, expectedValue, value0);
311         }
312 
313         // TODO: verify correlation of events with steps from user
314     }
315 
316     @Override
onAccuracyChanged(Sensor sensor, int accuracy)317     public final void onAccuracyChanged(Sensor sensor, int accuracy) {
318     }
319 
onSensorChanged(SensorEvent event)320     public final void onSensorChanged(SensorEvent event) {
321         long elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos();
322         int type = event.sensor.getType();
323         if (type == Sensor.TYPE_STEP_COUNTER) {
324             mStepCounterEvents.add(new TestSensorEvent(event, elapsedRealtimeNanos));
325             getTestLogger().logMessage(
326                     R.string.snsr_step_counter_event,
327                     elapsedRealtimeNanos,
328                     (int) event.values[0]);
329         } else if (type == Sensor.TYPE_STEP_DETECTOR) {
330             mStepDetectorEvents.add(new TestSensorEvent(event, elapsedRealtimeNanos));
331             getTestLogger().logMessage(R.string.snsr_step_detector_event, elapsedRealtimeNanos);
332 
333         }
334         // TODO: with delayed assertions check events of other types are tracked
335     }
336 
logUserReportedStep(long timestamp)337     private void logUserReportedStep(long timestamp) throws InterruptedException {
338         if (!mCheckForMotion) {
339             return;
340         }
341         playSound();
342         mTimestampsUserReported.add(timestamp);
343         getTestLogger().logMessage(R.string.snsr_step_reported, timestamp);
344     }
345 }
346