• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 android.hardware.cts;
18 
19 import static org.junit.Assert.*;
20 
21 import android.content.Context;
22 import android.content.pm.PackageManager;
23 import android.hardware.Sensor;
24 import android.hardware.SensorEvent;
25 import android.hardware.SensorEventCallback;
26 import android.hardware.SensorManager;
27 import android.hardware.cts.accessories.VirtualHeadTracker;
28 import android.hardware.cts.helpers.SensorTestStateNotSupportedException;
29 import android.hardware.sensor.cts.R;
30 import android.os.Build;
31 import android.platform.test.annotations.AppModeFull;
32 import android.util.Log;
33 
34 import androidx.test.InstrumentationRegistry;
35 
36 import com.android.compatibility.common.util.CddTest;
37 import com.android.compatibility.common.util.PropertyUtil;
38 import com.android.compatibility.common.util.SystemUtil;
39 
40 import java.io.IOException;
41 import java.util.Objects;
42 import java.util.concurrent.CountDownLatch;
43 import java.util.concurrent.TimeUnit;
44 
45 @AppModeFull(reason = "Test requires special permission not available to instant apps")
46 public class SensorHeadTrackerTest extends SensorTestCase {
47     private static final String TAG = "SensorHeadTrackerTest";
48 
49     private SensorManager mSensorManager;
50     private boolean mHasSensorDynamicHeadTracker = false;
51     private Callback mCallback;
52     private Integer mSensorId;
53     private VirtualHeadTracker mVirtualHeadTracker;
54     private static final String VIRTUAL_HEAD_TRACKER_PRIMARY = "Emulated HT";
55     private static final String VIRTUAL_HEAD_TRACKER_SECONDARY = "Emulated HT Secondary";
56     private static final int CONNECTION_TIMEOUT_SEC = 5;
57     private static final int DISCONNECTION_TIMEOUT_SEC = 5;
58     private static final int FLUSH_COMPLETED_TIMEOUT_SEC = 5;
59     private static final int EVENT_TIMEOUT_SEC = 5;
60 
61     @Override
setUp()62     protected void setUp() throws InterruptedException {
63         if (PropertyUtil.getVsrApiLevel() < Build.VERSION_CODES.TIRAMISU) {
64             throw new SensorTestStateNotSupportedException(
65                     "Test only applies for HAL version T or later, skip.");
66         }
67         configureHtSensorAccess(true);
68         mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
69         mHasSensorDynamicHeadTracker =
70                 getContext()
71                         .getPackageManager()
72                         .hasSystemFeature(PackageManager.FEATURE_SENSOR_DYNAMIC_HEAD_TRACKER);
73         Log.d(TAG, String.format("mHasSensorDynamicHeadTracker %b", mHasSensorDynamicHeadTracker));
74 
75         mCallback = new Callback();
76         mVirtualHeadTracker = new VirtualHeadTracker();
77         mSensorManager.registerDynamicSensorCallback(mCallback, mVirtualHeadTracker.handler);
78         mVirtualHeadTracker.registerDevice(R.raw.head_tracker_main);
79 
80         try {
81             // Note that we didn't skip the test here completely since
82             // testIsNotDynamicSensorDiscoverySupported need to be ran without
83             // dynamic sensors support
84             featureSupportedOrSkip();
85         } catch (SensorTestStateNotSupportedException e) {
86             return;
87         }
88 
89         assertTrue(
90                 "Cannot detect sensor connection.",
91                 mCallback.waitForConnection(VIRTUAL_HEAD_TRACKER_PRIMARY));
92     }
93 
94     @Override
tearDown()95     protected void tearDown() {
96         Log.d(TAG, "Teardown.");
97         mVirtualHeadTracker.closeDevice();
98         mCallback.waitForDisconnection();
99         configureHtSensorAccess(false);
100         mSensorManager.unregisterDynamicSensorCallback(mCallback);
101     }
102 
103     @CddTest(requirements = {"7.3"})
testIsNotDynamicSensorDiscoverySupported()104     public void testIsNotDynamicSensorDiscoverySupported() {
105         if (!mHasSensorDynamicHeadTracker && mSensorManager.isDynamicSensorDiscoverySupported()) {
106             assertFalse(
107                     "Discovered head tracker sensor but feature flag is not set.",
108                     mCallback.waitForConnection(VIRTUAL_HEAD_TRACKER_PRIMARY));
109         }
110     }
111 
112     @CddTest(requirements = {"7.3"})
testIsDynamicSensorDiscoverySupported()113     public void testIsDynamicSensorDiscoverySupported() {
114         featureSupportedOrSkip();
115         assertTrue(
116                 "Dynamic sensor discovery is not supported.",
117                 mSensorManager.isDynamicSensorDiscoverySupported());
118     }
119 
120     @CddTest(requirements = {"7.3"})
testRegisterDynamicSensorCallback()121     public void testRegisterDynamicSensorCallback() {
122         featureSupportedOrSkip();
123         assertTrue(
124                 String.format(
125                         "Sensor type is %d. Sensor type should be %d",
126                         mCallback.mSensor.getType(), Sensor.TYPE_HEAD_TRACKER),
127                 mCallback.isTypeHeadTracker());
128 
129         assertTrue("Sensor is a wake up sensor.", !mCallback.isNotWakeUpSensor());
130 
131         assertTrue("Sensor is not a dynamic sensor.", mCallback.isDynamicSensor());
132 
133         assertTrue(
134                 String.format("Sensor id is %d. Must not be 0 or 1.", mCallback.mSensor.getId()),
135                 mCallback.checkId());
136 
137         assertTrue(
138                 String.format(
139                         "Reporting mode is %d. Reporting mode should be %d.",
140                         mCallback.mSensor.getReportingMode(), Sensor.REPORTING_MODE_CONTINUOUS),
141                 mCallback.isReportingModeContinuous());
142 
143         assertTrue(
144                 String.format(
145                         "Sensor string type is %s. Sensor string should be %s.",
146                         mCallback.mSensor.getStringType(), Sensor.STRING_TYPE_HEAD_TRACKER),
147                 mCallback.isStringTypeHeadTracker());
148 
149         assertTrue("Sensor name is incorrect.", mCallback.isUhidName());
150 
151         assertTrue("Sensor is not in dynamic sensor list.", mCallback.isSensorInList());
152     }
153 
154     @CddTest(requirements = {"7.3"})
testArrayOfSixFloats()155     public void testArrayOfSixFloats() {
156         featureSupportedOrSkip();
157         mCallback.headTrackerData();
158     }
159 
160     @CddTest(requirements = {"7.3"})
testDiscontinuity()161     public void testDiscontinuity() {
162         featureSupportedOrSkip();
163         mVirtualHeadTracker.incDiscontinuityCount();
164 
165         assertTrue(
166                 "First event after discontinuity was not called.", mCallback.discontinuityCount());
167     }
168 
169     @CddTest(requirements = {"7.3"})
testSensorManagerFlush()170     public void testSensorManagerFlush() {
171         featureSupportedOrSkip();
172         assertTrue("Flush was not completed within five seconds.", mCallback.waitForFlush());
173     }
174 
175     @CddTest(requirements = {"7.3"})
testDisconnectionReconnection()176     public void testDisconnectionReconnection() {
177         featureSupportedOrSkip();
178         mSensorId = mCallback.getSensorId();
179 
180         mVirtualHeadTracker.closeDevice();
181 
182         assertTrue("Device was not disconnected.", mCallback.waitForDisconnection());
183 
184         mVirtualHeadTracker.registerDevice(R.raw.head_tracker_main);
185 
186         assertTrue("Cannot detect sensor reconnection.",
187                 mCallback.waitForConnection(VIRTUAL_HEAD_TRACKER_PRIMARY));
188 
189         Integer sensorId = mCallback.getSensorId();
190         boolean match =
191                 mSensorId != null
192                         && sensorId != null
193                         && sensorId.intValue() == mSensorId.intValue();
194 
195         assertTrue("ID mismatch for the reconnected sensor.", match);
196     }
197 
198     @CddTest(requirements = {"7.3"})
testAddSecondSensor()199     public void testAddSecondSensor() {
200         featureSupportedOrSkip();
201         Callback secondCallback = new Callback();
202         VirtualHeadTracker virtualHeadTrackerTwo = new VirtualHeadTracker();
203         mSensorManager.registerDynamicSensorCallback(secondCallback, virtualHeadTrackerTwo.handler);
204         virtualHeadTrackerTwo.registerDevice(R.raw.head_tracker_distinct_id);
205         try {
206             assertTrue(
207                     "Cannot detect second sensor connection.",
208                     secondCallback.waitForConnection(VIRTUAL_HEAD_TRACKER_SECONDARY));
209             assertTrue(
210                     String.format(
211                             "Sensor id is %d. Must not be 0 or 1.", mCallback.mSensor.getId()),
212                     mCallback.checkId());
213 
214             assertTrue(
215                     String.format(
216                             "Sensor id is %d. Must not be 0 or 1.", secondCallback.mSensor.getId()),
217                     secondCallback.checkId());
218 
219             assertNotEquals(
220                     "Sensors have the same id.",
221                     secondCallback.getSensorId(),
222                     mCallback.getSensorId());
223         } finally {
224             if (Objects.nonNull(virtualHeadTrackerTwo)) {
225                 virtualHeadTrackerTwo.closeDevice();
226                 assertTrue(
227                         "Did not get disconnect callback from the second sensor.",
228                         secondCallback.waitForDisconnection());
229             }
230             mSensorManager.unregisterDynamicSensorCallback(secondCallback);
231         }
232     }
233 
234     private class Callback extends SensorManager.DynamicSensorCallback {
235 
236         Sensor mSensor = null;
237         private boolean mFirstDiscontinuityReported;
238         private int mDuplicateDiscontinuityCount = 0;
239         private String mExpectedSensorName;
240         private CountDownLatch mConnectLatch;
241         private CountDownLatch mDisconnectLatch;
242         private final String mEmulatorName = "Emulated HT";
243 
244         @Override
onDynamicSensorConnected(Sensor sensor)245         public void onDynamicSensorConnected(Sensor sensor) {
246             Log.d(TAG, "Sensor Connected: " + sensor);
247             if (mExpectedSensorName == null || mExpectedSensorName == sensor.getName()) {
248                 mSensor = sensor;
249                 if (mConnectLatch != null) {
250                     mConnectLatch.countDown();
251                 }
252             }
253         }
254 
255         @Override
onDynamicSensorDisconnected(Sensor sensor)256         public void onDynamicSensorDisconnected(Sensor sensor) {
257             Log.d(TAG, "Sensor disconnected: " + sensor);
258             if (mSensor == sensor) {
259                 mSensor = null;
260                 if (mDisconnectLatch != null) {
261                     mDisconnectLatch.countDown();
262                 }
263             }
264         }
265 
waitForConnection(String sensorName)266         public boolean waitForConnection(String sensorName) {
267             boolean ret;
268             mExpectedSensorName = sensorName;
269             mConnectLatch = new CountDownLatch(1);
270             try {
271                 ret = mConnectLatch.await(CONNECTION_TIMEOUT_SEC, TimeUnit.SECONDS);
272             } catch (InterruptedException e) {
273                 ret = false;
274                 Thread.currentThread().interrupt();
275             } finally {
276                 mConnectLatch = null;
277             }
278             return ret;
279         }
280 
waitForDisconnection()281         public boolean waitForDisconnection() {
282             boolean ret;
283             mDisconnectLatch = new CountDownLatch(1);
284             try {
285                 ret = mDisconnectLatch.await(DISCONNECTION_TIMEOUT_SEC, TimeUnit.SECONDS);
286             } catch (InterruptedException e) {
287                 ret = false;
288                 Thread.currentThread().interrupt();
289             } finally {
290                 mDisconnectLatch = null;
291             }
292             return ret;
293         }
294 
waitForFlush()295         public boolean waitForFlush() {
296             final CountDownLatch eventLatch = new CountDownLatch(1);
297 
298             SensorEventCallback eventCallback =
299                     new SensorEventCallback() {
300                         @Override
301                         public void onFlushCompleted(Sensor s) {
302                             eventLatch.countDown();
303                         }
304                     };
305 
306             assertTrue(
307                     "Register listener is false in waitForFlush().",
308                     mSensorManager.registerListener(
309                             eventCallback, mSensor, SensorManager.SENSOR_DELAY_NORMAL));
310 
311             mSensorManager.flush(eventCallback);
312 
313             boolean flush;
314             try {
315                 flush = eventLatch.await(FLUSH_COMPLETED_TIMEOUT_SEC, TimeUnit.SECONDS);
316             } catch (InterruptedException e) {
317                 flush = false;
318                 Thread.currentThread().interrupt();
319             } finally {
320                 mSensorManager.unregisterListener(eventCallback);
321             }
322             return flush;
323         }
324 
325         private class SensorEventCopy {
326             private float[] mValues;
327         }
328 
getOneSensorEvent()329         SensorEventCopy getOneSensorEvent() {
330             SensorEventCopy eventCopy = new SensorEventCopy();
331 
332             final CountDownLatch eventLatch = new CountDownLatch(1);
333             SensorEventCallback eventCallback =
334                     new SensorEventCallback() {
335                         public void onSensorChanged(SensorEvent event) {
336                             if (eventLatch.getCount() == 1) {
337                                 eventCopy.mValues = event.values.clone();
338                                 eventLatch.countDown();
339                             }
340                         }
341                     };
342 
343             assertTrue(
344                     "Register listener is false in getOneSensorEvent().",
345                     mSensorManager.registerListener(
346                             eventCallback, mSensor, SensorManager.SENSOR_DELAY_NORMAL));
347 
348             try {
349                 eventLatch.await(EVENT_TIMEOUT_SEC, TimeUnit.SECONDS);
350             } catch (InterruptedException e) {
351                 Thread.currentThread().interrupt();
352             } finally {
353                 mSensorManager.unregisterListener(eventCallback);
354             }
355 
356             return eventCopy;
357         }
358 
discontinuityCount()359         public boolean discontinuityCount() {
360             final CountDownLatch eventLatch = new CountDownLatch(4);
361             SensorEventCallback eventCallback =
362                     new SensorEventCallback() {
363                         public void onSensorChanged(SensorEvent event) {
364                             if (eventLatch.getCount() == 4) {
365                                 if (event.firstEventAfterDiscontinuity) {
366                                     mFirstDiscontinuityReported = true;
367                                     eventLatch.countDown();
368                                 } // else: ignore event
369                             } else {
370                                 if (event.firstEventAfterDiscontinuity) {
371                                     mDuplicateDiscontinuityCount++;
372                                 }
373                                 eventLatch.countDown();
374                             }
375                         }
376                     };
377 
378             assertTrue(
379                     "Register listener is returning false in discontinuityCount().",
380                     mSensorManager.registerListener(
381                             eventCallback, mSensor, SensorManager.SENSOR_DELAY_NORMAL));
382 
383             try {
384                 eventLatch.await(EVENT_TIMEOUT_SEC, TimeUnit.SECONDS);
385                 assertEquals(
386                         "mDuplicateDiscontinuityCount is not equal to expected.",
387                         0,
388                         mDuplicateDiscontinuityCount);
389             } catch (InterruptedException e) {
390                 Thread.currentThread().interrupt();
391                 return false;
392             } finally {
393                 mSensorManager.unregisterListener(eventCallback);
394             }
395 
396             return mFirstDiscontinuityReported;
397         }
398 
headTrackerData()399         public void headTrackerData() {
400             float threshold = 0.001f;
401 
402             SensorEventCopy event = mCallback.getOneSensorEvent();
403 
404             assertNotNull("Time out waiting on a SensorEvent", event);
405 
406             for (int i = 0; i < mVirtualHeadTracker.HEAD_TRACKER_VALUES.length; i++) {
407                 assertEquals(
408                         mVirtualHeadTracker.HEAD_TRACKER_VALUES[i], event.mValues[i], threshold);
409             }
410         }
411 
isTypeHeadTracker()412         public boolean isTypeHeadTracker() {
413             return assumeSensorIsSet() && mSensor.getType() == Sensor.TYPE_HEAD_TRACKER;
414         }
415 
isStringTypeHeadTracker()416         public boolean isStringTypeHeadTracker() {
417             return assumeSensorIsSet()
418                     && mSensor.getStringType() == Sensor.STRING_TYPE_HEAD_TRACKER;
419         }
420 
isNotWakeUpSensor()421         public boolean isNotWakeUpSensor() {
422             return assumeSensorIsSet() && mSensor.isWakeUpSensor();
423         }
424 
isDynamicSensor()425         public boolean isDynamicSensor() {
426             return assumeSensorIsSet() && mSensor.isDynamicSensor();
427         }
428 
checkId()429         public boolean checkId() {
430             return assumeSensorIsSet() && mSensor.getId() != 0 && mSensor.getId() != -1;
431         }
432 
isReportingModeContinuous()433         public boolean isReportingModeContinuous() {
434             return assumeSensorIsSet()
435                     && mSensor.getReportingMode() == Sensor.REPORTING_MODE_CONTINUOUS;
436         }
437 
isUhidName()438         public boolean isUhidName() {
439             return assumeSensorIsSet() && mSensor.getName() == mEmulatorName;
440         }
441 
isSensorInList()442         public boolean isSensorInList() {
443             return assumeSensorIsSet()
444                     && mSensorManager.getDynamicSensorList(Sensor.TYPE_ALL).contains(mSensor);
445         }
446 
isSensorInListOfSpecificType()447         public boolean isSensorInListOfSpecificType() {
448             return assumeSensorIsSet()
449                     && mSensorManager.getDynamicSensorList(mSensor.getType()).contains(mSensor);
450         }
451 
getSensorId()452         public Integer getSensorId() {
453             return assumeSensorIsSet() ? mSensor.getId() : null;
454         }
455 
assumeSensorIsSet()456         private boolean assumeSensorIsSet() {
457             if (mSensor == null) {
458                 Log.e(TAG, "Sensor is not set.");
459                 return false;
460             }
461             return true;
462         }
463     }
464 
featureSupportedOrSkip()465     private void featureSupportedOrSkip() {
466         if (!mHasSensorDynamicHeadTracker) {
467             throw new SensorTestStateNotSupportedException(
468                     "Dynamic sensor discovery not supported, skip.");
469         }
470     }
471 
configureHtSensorAccess(boolean enable)472     private static void configureHtSensorAccess(boolean enable) {
473         final String command = "cmd sensorservice " + ((enable) ? "un" : "") + "restrict-ht";
474         Log.d(TAG, "Running command " + command);
475         try {
476             SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
477         } catch (IOException e) {
478             Log.e(TAG, "Failed to run command " + command, e);
479         }
480     }
481 }
482