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