• 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 
78         mSensorManager.registerDynamicSensorCallback(mCallback, mVirtualHeadTracker.handler);
79 
80         mVirtualHeadTracker.registerDevice(R.raw.head_tracker_main);
81 
82         try {
83             featureSupportedOrSkip();
84         } catch (SensorTestStateNotSupportedException e) {
85             return;
86         }
87     }
88 
89     @Override
tearDown()90     protected void tearDown() {
91         Log.d(TAG, "Teardown.");
92         mVirtualHeadTracker.closeDevice();
93 
94         configureHtSensorAccess(false);
95     }
96 
97     @CddTest(requirements = {"7.3"})
testIsNotDynamicSensorDiscoverySupported()98     public void testIsNotDynamicSensorDiscoverySupported() {
99 
100         if (!mHasSensorDynamicHeadTracker && mSensorManager.isDynamicSensorDiscoverySupported()) {
101             assertFalse(
102                     "Discovered head tracker sensor but feature flag is not set.",
103                     mCallback.waitForConnection(VIRTUAL_HEAD_TRACKER_PRIMARY));
104         }
105     }
106 
107     @CddTest(requirements = {"7.3"})
testIsDynamicSensorDiscoverySupported()108     public void testIsDynamicSensorDiscoverySupported() {
109         featureSupportedOrSkip();
110 
111         assertTrue("Cannot detect sensor connection.",
112                 mCallback.waitForConnection(VIRTUAL_HEAD_TRACKER_PRIMARY));
113 
114         assertTrue(
115                 "Dynamic sensor discovery is not supported.",
116                 mSensorManager.isDynamicSensorDiscoverySupported());
117     }
118 
119     @CddTest(requirements = {"7.3"})
testRegisterDynamicSensorCallback()120     public void testRegisterDynamicSensorCallback() {
121         featureSupportedOrSkip();
122 
123         mVirtualHeadTracker.closeDevice();
124 
125         Log.d(
126                 TAG,
127                 String.format(
128                         "Fake head tracker sensor has %d seconds to connect.",
129                         CONNECTION_TIMEOUT_SEC));
130 
131         mVirtualHeadTracker.registerDevice(R.raw.head_tracker_main);
132 
133         assertTrue("Cannot detect sensor connection.",
134                 mCallback.waitForConnection(VIRTUAL_HEAD_TRACKER_PRIMARY));
135 
136         assertTrue(
137                 String.format(
138                         "Sensor type is %d. Sensor type should be %d",
139                         mCallback.mSensor.getType(), Sensor.TYPE_HEAD_TRACKER),
140                 mCallback.isTypeHeadTracker());
141 
142         assertTrue("Sensor is a wake up sensor.", !mCallback.isNotWakeUpSensor());
143 
144         assertTrue("Sensor is not a dynamic sensor.", mCallback.isDynamicSensor());
145 
146         assertTrue(
147                 String.format("Sensor id is %d. Must not be 0 or 1.", mCallback.mSensor.getId()),
148                 mCallback.checkId());
149 
150         assertTrue(
151                 String.format(
152                         "Reporting mode is %d. Reporting mode should be %d.",
153                         mCallback.mSensor.getReportingMode(), Sensor.REPORTING_MODE_CONTINUOUS),
154                 mCallback.isReportingModeContinuous());
155 
156         assertTrue(
157                 String.format(
158                         "Sensor string type is %s. Sensor string should be %s.",
159                         mCallback.mSensor.getStringType(), Sensor.STRING_TYPE_HEAD_TRACKER),
160                 mCallback.isStringTypeHeadTracker());
161 
162         assertTrue("Sensor name is incorrect.", mCallback.isUhidName());
163 
164         assertTrue("Sensor is not in dynamic sensor list.", mCallback.isSensorInList());
165     }
166 
167     @CddTest(requirements = {"7.3"})
testArrayOfSixFloats()168     public void testArrayOfSixFloats() {
169         featureSupportedOrSkip();
170 
171         assertTrue("Cannot detect sensor connection.",
172                 mCallback.waitForConnection(VIRTUAL_HEAD_TRACKER_PRIMARY));
173 
174         mCallback.headTrackerData();
175     }
176 
177     @CddTest(requirements = {"7.3"})
testDiscontinuity()178     public void testDiscontinuity() {
179         featureSupportedOrSkip();
180 
181         assertTrue("Cannot detect sensor connection.",
182                 mCallback.waitForConnection(VIRTUAL_HEAD_TRACKER_PRIMARY));
183 
184         mVirtualHeadTracker.incDiscontinuityCount();
185 
186         assertTrue(
187                 "First event after discontinuity was not called.", mCallback.discontinuityCount());
188     }
189 
190     @CddTest(requirements = {"7.3"})
testSensorManagerFlush()191     public void testSensorManagerFlush() {
192         featureSupportedOrSkip();
193 
194         assertTrue("Cannot detect sensor connection.",
195                 mCallback.waitForConnection(VIRTUAL_HEAD_TRACKER_PRIMARY));
196 
197         assertTrue("Flush was not completed within five seconds.", mCallback.waitForFlush());
198     }
199 
200     @CddTest(requirements = {"7.3"})
testDisconnectionReconnection()201     public void testDisconnectionReconnection() {
202         featureSupportedOrSkip();
203 
204         assertTrue("Cannot detect sensor connection.",
205                 mCallback.waitForConnection(VIRTUAL_HEAD_TRACKER_PRIMARY));
206 
207         mSensorId = mCallback.getSensorId();
208 
209         mVirtualHeadTracker.closeDevice();
210 
211         assertTrue("Device was not disconnected.", mCallback.waitForDisconnection());
212 
213         mVirtualHeadTracker.registerDevice(R.raw.head_tracker_main);
214 
215         assertTrue("Cannot detect sensor reconnection.",
216                 mCallback.waitForConnection(VIRTUAL_HEAD_TRACKER_PRIMARY));
217 
218         Integer sensorId = mCallback.getSensorId();
219         boolean match =
220                 mSensorId != null
221                         && sensorId != null
222                         && sensorId.intValue() == mSensorId.intValue();
223 
224         assertTrue("ID mismatch for the reconnected sensor.", match);
225     }
226 
227     @CddTest(requirements = {"7.3"})
testAddSecondSensor()228     public void testAddSecondSensor() {
229         featureSupportedOrSkip();
230 
231         assertTrue(
232                 "Cannot detect sensor connection.",
233                 mCallback.waitForConnection(VIRTUAL_HEAD_TRACKER_PRIMARY));
234 
235         Callback secondCallback = new Callback();
236         VirtualHeadTracker virtualHeadTrackerTwo = new VirtualHeadTracker();
237 
238         mSensorManager.registerDynamicSensorCallback(secondCallback, virtualHeadTrackerTwo.handler);
239         virtualHeadTrackerTwo.registerDevice(R.raw.head_tracker_distinct_id);
240         try {
241             assertTrue(
242                     "Cannot detect second sensor connection.",
243                     secondCallback.waitForConnection(VIRTUAL_HEAD_TRACKER_SECONDARY));
244             assertTrue(
245                     String.format(
246                             "Sensor id is %d. Must not be 0 or 1.", mCallback.mSensor.getId()),
247                     mCallback.checkId());
248 
249             assertTrue(
250                     String.format(
251                             "Sensor id is %d. Must not be 0 or 1.", secondCallback.mSensor.getId()),
252                     secondCallback.checkId());
253 
254             assertNotEquals(
255                     "Sensors have the same id.",
256                     secondCallback.getSensorId(),
257                     mCallback.getSensorId());
258         } finally {
259             if (Objects.nonNull(virtualHeadTrackerTwo)) {
260                 virtualHeadTrackerTwo.closeDevice();
261             }
262         }
263     }
264 
265     private class Callback extends SensorManager.DynamicSensorCallback {
266 
267         Sensor mSensor = null;
268         private boolean mFirstDiscontinuityReported;
269         private int mDuplicateDiscontinuityCount = 0;
270         private String mExpectedSensorName;
271         private CountDownLatch mConnectLatch;
272         private CountDownLatch mDisconnectLatch;
273         private final String mEmulatorName = "Emulated HT";
274 
275         @Override
onDynamicSensorConnected(Sensor sensor)276         public void onDynamicSensorConnected(Sensor sensor) {
277             Log.d(TAG, "Sensor Connected: " + sensor);
278             if (mExpectedSensorName == null || mExpectedSensorName == sensor.getName()) {
279                 mSensor = sensor;
280                 if (mConnectLatch != null) {
281                     mConnectLatch.countDown();
282                 }
283             }
284         }
285 
286         @Override
onDynamicSensorDisconnected(Sensor sensor)287         public void onDynamicSensorDisconnected(Sensor sensor) {
288             Log.d(TAG, "Sensor disconnected: " + sensor);
289             if (mSensor == sensor) {
290                 mSensor = null;
291                 if (mDisconnectLatch != null) {
292                     mDisconnectLatch.countDown();
293                 }
294             }
295         }
296 
waitForConnection(String sensorName)297         public boolean waitForConnection(String sensorName) {
298             boolean ret;
299             mExpectedSensorName = sensorName;
300             mConnectLatch = new CountDownLatch(1);
301             try {
302                 ret = mConnectLatch.await(CONNECTION_TIMEOUT_SEC, TimeUnit.SECONDS);
303             } catch (InterruptedException e) {
304                 ret = false;
305                 Thread.currentThread().interrupt();
306             } finally {
307                 mConnectLatch = null;
308             }
309             return ret;
310         }
311 
waitForDisconnection()312         public boolean waitForDisconnection() {
313             boolean ret;
314             mDisconnectLatch = new CountDownLatch(1);
315             try {
316                 ret = mDisconnectLatch.await(DISCONNECTION_TIMEOUT_SEC, TimeUnit.SECONDS);
317             } catch (InterruptedException e) {
318                 ret = false;
319                 Thread.currentThread().interrupt();
320             } finally {
321                 mDisconnectLatch = null;
322             }
323             return ret;
324         }
325 
waitForFlush()326         public boolean waitForFlush() {
327             final CountDownLatch eventLatch = new CountDownLatch(1);
328 
329             SensorEventCallback eventCallback =
330                     new SensorEventCallback() {
331                         @Override
332                         public void onFlushCompleted(Sensor s) {
333                             eventLatch.countDown();
334                         }
335                     };
336 
337             assertTrue(
338                     "Register listener is false in waitForFlush().",
339                     mSensorManager.registerListener(
340                             eventCallback, mSensor, SensorManager.SENSOR_DELAY_NORMAL));
341 
342             mSensorManager.flush(eventCallback);
343 
344             boolean flush;
345             try {
346                 flush = eventLatch.await(FLUSH_COMPLETED_TIMEOUT_SEC, TimeUnit.SECONDS);
347             } catch (InterruptedException e) {
348                 flush = false;
349                 Thread.currentThread().interrupt();
350             } finally {
351                 mSensorManager.unregisterListener(eventCallback);
352             }
353             return flush;
354         }
355 
356         private class SensorEventCopy {
357             private float[] mValues;
358         }
359 
getOneSensorEvent()360         SensorEventCopy getOneSensorEvent() {
361             SensorEventCopy eventCopy = new SensorEventCopy();
362 
363             final CountDownLatch eventLatch = new CountDownLatch(1);
364             SensorEventCallback eventCallback =
365                     new SensorEventCallback() {
366                         public void onSensorChanged(SensorEvent event) {
367                             if (eventLatch.getCount() == 1) {
368                                 eventCopy.mValues = event.values.clone();
369                                 eventLatch.countDown();
370                             }
371                         }
372                     };
373 
374             assertTrue(
375                     "Register listener is false in getOneSensorEvent().",
376                     mSensorManager.registerListener(
377                             eventCallback, mSensor, SensorManager.SENSOR_DELAY_NORMAL));
378 
379             try {
380                 eventLatch.await(EVENT_TIMEOUT_SEC, TimeUnit.SECONDS);
381             } catch (InterruptedException e) {
382                 Thread.currentThread().interrupt();
383             } finally {
384                 mSensorManager.unregisterListener(eventCallback);
385             }
386 
387             return eventCopy;
388         }
389 
discontinuityCount()390         public boolean discontinuityCount() {
391             final CountDownLatch eventLatch = new CountDownLatch(4);
392             SensorEventCallback eventCallback =
393                     new SensorEventCallback() {
394                         public void onSensorChanged(SensorEvent event) {
395                             if (eventLatch.getCount() == 4) {
396                                 if (event.firstEventAfterDiscontinuity) {
397                                     mFirstDiscontinuityReported = true;
398                                     eventLatch.countDown();
399                                 } // else: ignore event
400                             } else {
401                                 if (event.firstEventAfterDiscontinuity) {
402                                     mDuplicateDiscontinuityCount++;
403                                 }
404                                 eventLatch.countDown();
405                             }
406                         }
407                     };
408 
409             assertTrue(
410                     "Register listener is returning false in discontinuityCount().",
411                     mSensorManager.registerListener(
412                             eventCallback, mSensor, SensorManager.SENSOR_DELAY_NORMAL));
413 
414             try {
415                 eventLatch.await(EVENT_TIMEOUT_SEC, TimeUnit.SECONDS);
416                 assertEquals(
417                         "mDuplicateDiscontinuityCount is not equal to expected.",
418                         0,
419                         mDuplicateDiscontinuityCount);
420             } catch (InterruptedException e) {
421                 Thread.currentThread().interrupt();
422                 return false;
423             } finally {
424                 mSensorManager.unregisterListener(eventCallback);
425             }
426 
427             return mFirstDiscontinuityReported;
428         }
429 
headTrackerData()430         public void headTrackerData() {
431             float threshold = 0.001f;
432 
433             SensorEventCopy event = mCallback.getOneSensorEvent();
434 
435             assertNotNull("Time out waiting on a SensorEvent", event);
436 
437             for (int i = 0; i < mVirtualHeadTracker.HEAD_TRACKER_VALUES.length; i++) {
438                 assertEquals(
439                         mVirtualHeadTracker.HEAD_TRACKER_VALUES[i], event.mValues[i], threshold);
440             }
441         }
442 
isTypeHeadTracker()443         public boolean isTypeHeadTracker() {
444             return assumeSensorIsSet() && mSensor.getType() == Sensor.TYPE_HEAD_TRACKER;
445         }
446 
isStringTypeHeadTracker()447         public boolean isStringTypeHeadTracker() {
448             return assumeSensorIsSet()
449                     && mSensor.getStringType() == Sensor.STRING_TYPE_HEAD_TRACKER;
450         }
451 
isNotWakeUpSensor()452         public boolean isNotWakeUpSensor() {
453             return assumeSensorIsSet() && mSensor.isWakeUpSensor();
454         }
455 
isDynamicSensor()456         public boolean isDynamicSensor() {
457             return assumeSensorIsSet() && mSensor.isDynamicSensor();
458         }
459 
checkId()460         public boolean checkId() {
461             return assumeSensorIsSet() && mSensor.getId() != 0 && mSensor.getId() != -1;
462         }
463 
isReportingModeContinuous()464         public boolean isReportingModeContinuous() {
465             return assumeSensorIsSet()
466                     && mSensor.getReportingMode() == Sensor.REPORTING_MODE_CONTINUOUS;
467         }
468 
isUhidName()469         public boolean isUhidName() {
470             return assumeSensorIsSet() && mSensor.getName() == mEmulatorName;
471         }
472 
isSensorInList()473         public boolean isSensorInList() {
474             return assumeSensorIsSet()
475                     && mSensorManager.getDynamicSensorList(Sensor.TYPE_ALL).contains(mSensor);
476         }
477 
isSensorInListOfSpecificType()478         public boolean isSensorInListOfSpecificType() {
479             return assumeSensorIsSet()
480                     && mSensorManager.getDynamicSensorList(mSensor.getType()).contains(mSensor);
481         }
482 
getSensorId()483         public Integer getSensorId() {
484             return assumeSensorIsSet() ? mSensor.getId() : null;
485         }
486 
assumeSensorIsSet()487         private boolean assumeSensorIsSet() {
488             if (mSensor == null) {
489                 Log.e(TAG, "Sensor is not set.");
490                 return false;
491             }
492             return true;
493         }
494     }
495 
featureSupportedOrSkip()496     private void featureSupportedOrSkip() {
497         if (!mHasSensorDynamicHeadTracker) {
498             throw new SensorTestStateNotSupportedException(
499                     "Dynamic sensor discovery not supported, skip.");
500         }
501     }
502 
configureHtSensorAccess(boolean enable)503     private static void configureHtSensorAccess(boolean enable) {
504         final String command = "cmd sensorservice " + ((enable) ? "un" : "") + "restrict-ht";
505         Log.d(TAG, "Running command " + command);
506         try {
507             SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
508         } catch (IOException e) {
509             Log.e(TAG, "Failed to run command " + command, e);
510         }
511     }
512 }
513