xref: /aosp_15_r20/cts/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SignificantMotionTestActivity.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
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 android.app.AlarmManager;
20 import android.app.PendingIntent;
21 import android.content.BroadcastReceiver;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.hardware.Sensor;
26 import android.hardware.SensorManager;
27 import android.hardware.TriggerEvent;
28 import android.hardware.TriggerEventListener;
29 import android.hardware.cts.helpers.SensorNotSupportedException;
30 import android.hardware.cts.helpers.SensorTestStateNotSupportedException;
31 import android.hardware.cts.helpers.SuspendStateMonitor;
32 import android.hardware.cts.helpers.TestSensorEnvironment;
33 import android.os.BatteryManager;
34 import android.os.Handler;
35 import android.os.Looper;
36 import android.os.PowerManager;
37 import android.os.PowerManager.WakeLock;
38 import android.os.SystemClock;
39 import android.os.Vibrator;
40 
41 import androidx.localbroadcastmanager.content.LocalBroadcastManager;
42 
43 import com.android.cts.verifier.R;
44 import com.android.cts.verifier.sensors.base.SensorCtsVerifierTestActivity;
45 import com.android.cts.verifier.sensors.helpers.SensorTestScreenManipulator;
46 
47 import junit.framework.Assert;
48 
49 import java.util.concurrent.CountDownLatch;
50 import java.util.concurrent.TimeUnit;
51 
52 /**
53  * Test cases for Significant Motion sensor.
54  * They use walking motion to change the location and trigger Significant Motion.
55  */
56 public class SignificantMotionTestActivity extends SensorCtsVerifierTestActivity {
SignificantMotionTestActivity()57     public SignificantMotionTestActivity() {
58         super(SignificantMotionTestActivity.class, true);
59     }
60 
61     // acceptable time difference between event time and system time
62     private static final long MAX_ACCEPTABLE_EVENT_TIME_DELAY_NANOS =
63             TimeUnit.MILLISECONDS.toNanos(500);
64 
65     // acceptable time difference between event time and AP wake up time.
66     private static final long MAX_ACCEPTABLE_DELAY_EVENT_AP_WAKE_UP_NS =
67             TimeUnit.MILLISECONDS.toNanos(2000);
68 
69     // time to wait for SMD after the device has gone into suspend. Even after
70     // 45 secs if SMD does not trigger, the test will fail.
71     private static final long ALARM_WAKE_TIME_DELAY_MS = TimeUnit.SECONDS.toMillis(45);
72 
73     // time for the test to wait for a trigger
74     private static final int TRIGGER_MAX_DELAY_SECONDS = 30;
75     private static final int VIBRATE_DURATION_MILLIS = 10000;
76 
77     private static final int EVENT_VALUES_LENGTH = 1;
78     private static final float EXPECTED_EVENT_VALUE = 1.0f;
79     private static String ACTION_ALARM = "SignificantMotionTestActivity.ACTION_ALARM";
80 
81     private SensorManager mSensorManager;
82     private Sensor mSensorSignificantMotion;
83     private TriggerVerifier mVerifier;
84     private SensorTestScreenManipulator mScreenManipulator;
85     private WakeLock mPartialWakeLock;
86 
87     /**
88      * Test cases.
89      */
90     @SuppressWarnings("unused")
testTrigger()91     public String testTrigger() throws Throwable {
92         return runTest(
93                 R.string.snsr_significant_motion_test_trigger,
94                 true /* isMotionExpected */,
95                 false /* cancelEventNotification */,
96                 false /* vibrate */);
97     }
98 
99     @SuppressWarnings("unused")
testNotTriggerAfterCancel()100     public String testNotTriggerAfterCancel() throws Throwable {
101         return runTest(
102                 R.string.snsr_significant_motion_test_cancel,
103                 false /* isMotionExpected */,
104                 true /* cancelEventNotification */,
105                 false /* vibrate */);
106     }
107 
108     /**
109      * Verifies that Significant Motion is not trigger by the vibrator motion.
110      */
111     @SuppressWarnings("unused")
testVibratorDoesNotTrigger()112     public String testVibratorDoesNotTrigger() throws Throwable {
113         Vibrator vibrator = (Vibrator) getApplicationContext().getSystemService(Context.VIBRATOR_SERVICE);
114         if (!vibrator.hasVibrator()) {
115             throw new SensorTestStateNotSupportedException("Vibrator not supported, skip.");
116         } else {
117             return runTest(
118                     R.string.snsr_significant_motion_test_vibration,
119                     false /* isMotionExpected */,
120                     false /* cancelEventNotification */,
121                     true /* vibrate */);
122         }
123     }
124 
125     /**
126      * Verifies that the natural motion of keeping the device in hand does not change the location.
127      * It ensures that Significant Motion will not trigger in that scenario.
128      */
129     @SuppressWarnings("unused")
testInHandDoesNotTrigger()130     public String testInHandDoesNotTrigger() throws Throwable {
131         return runTest(
132                 R.string.snsr_significant_motion_test_in_hand,
133                 false /* isMotionExpected */,
134                 false /* cancelEventNotification */,
135                 false /* vibrate */);
136     }
137 
138     @SuppressWarnings("unused")
testSittingDoesNotTrigger()139     public String testSittingDoesNotTrigger() throws Throwable {
140         return runTest(
141                 R.string.snsr_significant_motion_test_sitting,
142                 false /* isMotionExpected */,
143                 false /* cancelEventNotification */,
144                 false /* vibrate */);
145     }
146 
147     @SuppressWarnings("unused")
testTriggerDeactivation()148     public String testTriggerDeactivation() throws Throwable {
149 
150         setFirstExecutionInstruction(R.string.snsr_significant_motion_test_deactivation);
151 
152         TriggerVerifier verifier = new TriggerVerifier();
153         mSensorManager.requestTriggerSensor(verifier, mSensorSignificantMotion);
154         getTestLogger().logWaitForSound();
155 
156         String result;
157         try {
158             mPartialWakeLock.acquire();
159 
160             // wait for the first event to trigger
161             verifier.verifyEventTriggered();
162 
163             // wait for a second event not to trigger
164             result = verifier.verifyEventNotTriggered();
165         } finally {
166             playSound();
167             mScreenManipulator.turnScreenOn();
168             mPartialWakeLock.release();
169         }
170         return result;
171     }
172 
173     public static class AlarmReceiver extends BroadcastReceiver {
174         @Override
onReceive(Context context, Intent intent)175         public void onReceive(Context context, Intent intent) {
176             Intent alarm_intent = new Intent(context, SignificantMotionTestActivity.class);
177             alarm_intent.setAction(SignificantMotionTestActivity.ACTION_ALARM);
178             LocalBroadcastManager.getInstance(context).sendBroadcastSync(alarm_intent);
179         }
180     }
181 
182     public BroadcastReceiver myBroadCastReceiver = new BroadcastReceiver() {
183         @Override
184         public void onReceive(Context context, Intent intent) {
185             mVerifier.releaseLatch();
186             mScreenManipulator.turnScreenOn();
187             try {
188                 playSound();
189             } catch (InterruptedException e) {
190                 // Ignore ...
191             }
192         }
193     };
194 
195     @SuppressWarnings("unused")
testAPWakeUpOnSMDTrigger()196     public String testAPWakeUpOnSMDTrigger() throws Throwable {
197         // skip this test if the device does NOT support battery.
198         if (!deviceHasBattery()) {
199             throw new SensorTestStateNotSupportedException(
200                         getString(R.string.battery_saver_test_no_battery_detected));
201         }
202 
203         setFirstExecutionInstruction(R.string.snsr_significant_motion_ap_suspend);
204 
205         mVerifier = new TriggerVerifier();
206         mSensorManager.requestTriggerSensor(mVerifier, mSensorSignificantMotion);
207         long testStartTimeNs = SystemClock.elapsedRealtimeNanos();
208         Handler handler = new Handler(Looper.getMainLooper());
209         SuspendStateMonitor suspendStateMonitor = new SuspendStateMonitor();
210 
211         Intent intent = new Intent(this, AlarmReceiver.class);
212         PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
213 
214         AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
215         am.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
216                      SystemClock.elapsedRealtime() + ALARM_WAKE_TIME_DELAY_MS, pendingIntent);
217         try {
218             // Wait for the first event to trigger. Device is expected to go into suspend here.
219             mVerifier.verifyEventTriggered();
220             long eventTimeStampNs = mVerifier.getTimeStampForTriggerEvent();
221             long endTimeNs = SystemClock.elapsedRealtimeNanos();
222             long lastWakeupTimeNs = TimeUnit.MILLISECONDS.toNanos(
223                     suspendStateMonitor.getLastWakeUpTime());
224             Assert.assertTrue(getString(R.string.snsr_device_did_not_go_into_suspend),
225                               testStartTimeNs < lastWakeupTimeNs && lastWakeupTimeNs < endTimeNs);
226             long timestampDelta = Math.abs(lastWakeupTimeNs - eventTimeStampNs);
227             Assert.assertTrue(
228                     String.format(getString(R.string.snsr_device_did_not_wake_up_at_trigger),
229                               TimeUnit.NANOSECONDS.toMillis(lastWakeupTimeNs),
230                               TimeUnit.NANOSECONDS.toMillis(eventTimeStampNs)),
231                               timestampDelta < MAX_ACCEPTABLE_DELAY_EVENT_AP_WAKE_UP_NS);
232         } finally {
233             am.cancel(pendingIntent);
234             suspendStateMonitor.cancel();
235             mScreenManipulator.turnScreenOn();
236             playSound();
237         }
238         return null;
239     }
240 
241     /**
242      * @param instructionsResId Instruction to be shown to testers
243      * @param isMotionExpected Should the device detect significant motion event
244      *            for this test?
245      * @param cancelEventNotification If TRUE, motion notifications will be
246      *            requested first and request will be cancelled
247      * @param vibrate If TRUE, vibration will be concurrent with the test
248      * @throws Throwable
249      */
250     private String runTest(
251             int instructionsResId,
252             boolean isMotionExpected,
253             boolean cancelEventNotification,
254             boolean vibrate) throws Throwable {
255 
256         setFirstExecutionInstruction(instructionsResId);
257 
258         if (vibrate) {
259             vibrate(VIBRATE_DURATION_MILLIS);
260         }
261 
262         TriggerVerifier verifier = new TriggerVerifier();
263         boolean success = mSensorManager.requestTriggerSensor(verifier, mSensorSignificantMotion);
264         Assert.assertTrue(
265                 getString(R.string.snsr_significant_motion_registration, success),
266                 success);
267         if (cancelEventNotification) {
268             Assert.assertTrue(
269                     getString(R.string.snsr_significant_motion_cancelation),
270                     mSensorManager.cancelTriggerSensor(verifier, mSensorSignificantMotion));
271         }
272         getTestLogger().logWaitForSound();
273 
274         String result;
275         try {
276             mPartialWakeLock.acquire();
277 
278             if (isMotionExpected) {
279                 result = verifier.verifyEventTriggered();
280             } else {
281                 result = verifier.verifyEventNotTriggered();
282             }
283         } finally {
284             mSensorManager.cancelTriggerSensor(verifier, mSensorSignificantMotion);
285 
286             // notify user test finished
287             playSound();
288             mScreenManipulator.turnScreenOn();
289             mPartialWakeLock.release();
290         }
291         return result;
292     }
293 
294     @Override
295     protected void activitySetUp() {
296         mSensorManager = (SensorManager) getApplicationContext()
297                 .getSystemService(Context.SENSOR_SERVICE);
298         mSensorSignificantMotion = mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION);
299         if (mSensorSignificantMotion == null) {
300             throw new SensorNotSupportedException(Sensor.TYPE_SIGNIFICANT_MOTION);
301         }
302 
303         mScreenManipulator = new SensorTestScreenManipulator(this);
304         try {
305             mScreenManipulator.initialize(this);
306         } catch (InterruptedException e) {
307         }
308         PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
309         mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "SignificantMotionTestActivity");
310         LocalBroadcastManager.getInstance(this).registerReceiver(myBroadCastReceiver,
311                                             new IntentFilter(ACTION_ALARM));
312     }
313 
314     @Override
315     protected void activityCleanUp() {
316         if (mScreenManipulator != null) {
317             // after this screen does not have to be on constantly
318             mScreenManipulator.releaseScreenOn();
319         }
320         if (mPartialWakeLock != null && mPartialWakeLock.isHeld()) {
321             mPartialWakeLock.release();
322         }
323         LocalBroadcastManager.getInstance(this).unregisterReceiver(myBroadCastReceiver);
324     }
325 
326     @Override
327     protected void onDestroy() {
328         super.onDestroy();
329         if (mScreenManipulator != null){
330             mScreenManipulator.close();
331         }
332     }
333 
334     private boolean deviceHasBattery() {
335         final Intent batteryInfo = getApplicationContext().registerReceiver(null,
336                 new IntentFilter(Intent.ACTION_BATTERY_CHANGED), RECEIVER_EXPORTED);
337         return batteryInfo.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true);
338     }
339 
340     /**
341      * Helper Trigger listener for testing.
342      * It cannot be reused.
343      */
344     private class TriggerVerifier extends TriggerEventListener {
345         private volatile CountDownLatch mCountDownLatch;
346         private volatile TriggerEventRegistry mEventRegistry;
347         private volatile long mTimestampForTriggeredEvent = 0;
348 
349         // TODO: refactor out if needed
350         private class TriggerEventRegistry {
351             public final TriggerEvent triggerEvent;
352             public final long realtimeTimestampNanos;
353 
354             public TriggerEventRegistry(TriggerEvent event, long realtimeTimestampNanos) {
355                 this.triggerEvent = event;
356                 this.realtimeTimestampNanos = realtimeTimestampNanos;
357             }
358         }
359 
360         public void onTrigger(TriggerEvent event) {
361             long elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos();
362             mEventRegistry = new TriggerEventRegistry(event, elapsedRealtimeNanos);
363             mCountDownLatch.countDown();
364         }
365 
366         public void releaseLatch() {
367             if (mCountDownLatch != null) {
368                 mCountDownLatch.countDown();
369             }
370         }
371 
372         public long getTimeStampForTriggerEvent() {
373             return mTimestampForTriggeredEvent;
374         }
375 
376         public String verifyEventTriggered() throws Throwable {
377             TriggerEventRegistry registry = awaitForEvent();
378 
379             // verify an event arrived, and it is indeed a Significant Motion event
380             TriggerEvent event = registry.triggerEvent;
381             String eventArrivalMessage =
382                     getString(R.string.snsr_significant_motion_event_arrival, event != null);
383             Assert.assertNotNull(eventArrivalMessage, event);
384 
385             int eventType = event.sensor.getType();
386             String eventTypeMessage = getString(
387                     R.string.snsr_significant_motion_event_type,
388                     Sensor.TYPE_SIGNIFICANT_MOTION,
389                     eventType);
390             Assert.assertEquals(eventTypeMessage, Sensor.TYPE_SIGNIFICANT_MOTION, eventType);
391 
392             String sensorName = event.sensor.getName();
393             int valuesLength = event.values.length;
394             String valuesLengthMessage = getString(
395                     R.string.snsr_event_length,
396                     EVENT_VALUES_LENGTH,
397                     valuesLength,
398                     sensorName);
399             Assert.assertEquals(valuesLengthMessage, EVENT_VALUES_LENGTH, valuesLength);
400 
401             float value = event.values[0];
402             String valuesMessage = getString(
403                     R.string.snsr_event_value,
404                     EXPECTED_EVENT_VALUE,
405                     value,
406                     sensorName);
407             Assert.assertEquals(valuesMessage, EXPECTED_EVENT_VALUE, value);
408 
409             long deltaThreshold = MAX_ACCEPTABLE_EVENT_TIME_DELAY_NANOS
410                     + TestSensorEnvironment.getSensorMaxDetectionLatencyNs(event.sensor);
411             return assertTimestampSynchronization(
412                     event.timestamp,
413                     registry.realtimeTimestampNanos,
414                     deltaThreshold,
415                     sensorName);
416         }
417 
418         public String verifyEventNotTriggered() throws Throwable {
419             TriggerEventRegistry registry = awaitForEvent();
420 
421             TriggerEvent event = registry.triggerEvent;
422             String eventMessage =
423                     getString(R.string.snsr_significant_motion_event_unexpected, event != null);
424             Assert.assertNull(eventMessage, event);
425             return eventMessage;
426         }
427 
428         private TriggerEventRegistry awaitForEvent() throws InterruptedException {
429             mCountDownLatch = new CountDownLatch(1);
430             mCountDownLatch.await(TRIGGER_MAX_DELAY_SECONDS, TimeUnit.SECONDS);
431             TriggerEventRegistry registry = mEventRegistry;
432 
433             // Save the last timestamp when the event triggered.
434             if (mEventRegistry != null && mEventRegistry.triggerEvent != null) {
435                 mTimestampForTriggeredEvent = mEventRegistry.triggerEvent.timestamp;
436             }
437 
438             mEventRegistry = null;
439             return registry != null ? registry : new TriggerEventRegistry(null, 0);
440         }
441     }
442 }
443