1 /* 2 * Copyright (C) 2021 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.media.audio.cts; 18 19 import static android.media.audio.Flags.FLAG_FEATURE_SPATIAL_AUDIO_HEADTRACKING_LOW_LATENCY; 20 21 import static org.junit.Assert.assertThrows; 22 23 import android.annotation.NonNull; 24 import android.content.pm.PackageManager; 25 import android.media.AudioAttributes; 26 import android.media.AudioDeviceAttributes; 27 import android.media.AudioDeviceInfo; 28 import android.media.AudioFormat; 29 import android.media.AudioManager; 30 import android.media.Spatializer; 31 import android.platform.test.annotations.RequiresFlagsEnabled; 32 import android.util.Log; 33 34 import com.android.compatibility.common.util.CtsAndroidTestCase; 35 import com.android.compatibility.common.util.NonMainlineTest; 36 37 import org.junit.Assert; 38 39 import java.util.Arrays; 40 import java.util.List; 41 import java.util.concurrent.Executors; 42 import java.util.concurrent.LinkedBlockingQueue; 43 import java.util.concurrent.TimeUnit; 44 45 @NonMainlineTest 46 public class SpatializerTest extends CtsAndroidTestCase { 47 48 private AudioManager mAudioManager; 49 private static final String TAG = "SpatializerTest"; 50 private static final int LISTENER_WAIT_TIMEOUT_MS = 3000; 51 52 @Override setUp()53 protected void setUp() throws Exception { 54 super.setUp(); 55 mAudioManager = (AudioManager) getContext().getSystemService(AudioManager.class); 56 } 57 58 @Override tearDown()59 protected void tearDown() throws Exception { 60 getInstrumentation().getUiAutomation().dropShellPermissionIdentity(); 61 } 62 testGetSpatializer()63 public void testGetSpatializer() throws Exception { 64 Spatializer spat = mAudioManager.getSpatializer(); 65 assertNotNull("Spatializer shouldn't be null", spat); 66 } 67 testUnsupported()68 public void testUnsupported() throws Exception { 69 Spatializer spat = mAudioManager.getSpatializer(); 70 if (spat.getImmersiveAudioLevel() != Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE) { 71 Log.i(TAG, "skipping testUnsupported, functionality supported"); 72 return; 73 } 74 assertFalse(spat.isEnabled()); 75 assertFalse(spat.isAvailable()); 76 } 77 testSupportedDevices()78 public void testSupportedDevices() throws Exception { 79 Spatializer spat = mAudioManager.getSpatializer(); 80 if (spat.getImmersiveAudioLevel() == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE) { 81 Log.i(TAG, "skipping testSupportedDevices, functionality unsupported"); 82 return; 83 } 84 85 final AudioDeviceAttributes device = new AudioDeviceAttributes( 86 AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, "bla"); 87 // try to add/remove compatible device without permission, expect failure 88 assertThrows("Able to call addCompatibleAudioDevice without permission", 89 SecurityException.class, 90 () -> spat.addCompatibleAudioDevice(device)); 91 assertThrows("Able to call removeCompatibleAudioDevice without permission", 92 SecurityException.class, 93 () -> spat.removeCompatibleAudioDevice(device)); 94 assertThrows("Able to call getCompatibleAudioDevice without permission", 95 SecurityException.class, 96 () -> spat.getCompatibleAudioDevices()); 97 assertThrows("Able to call isAvailableForDevice without permission", 98 SecurityException.class, 99 () -> spat.isAvailableForDevice(device)); 100 assertThrows("Able to call hasHeadTracker without permission", 101 SecurityException.class, 102 () -> spat.hasHeadTracker(device)); 103 assertThrows("Able to call setHeadTrackerEnabled without permission", 104 SecurityException.class, 105 () -> spat.setHeadTrackerEnabled(true, device)); 106 assertThrows("Able to call isHeadTrackerEnabled without permission", 107 SecurityException.class, 108 () -> spat.isHeadTrackerEnabled(device)); 109 110 // try again with permission, then add a device and remove it 111 getInstrumentation().getUiAutomation() 112 .adoptShellPermissionIdentity("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS"); 113 spat.addCompatibleAudioDevice(device); 114 List<AudioDeviceAttributes> compatDevices = spat.getCompatibleAudioDevices(); 115 assertTrue("added device not in list of compatible devices", 116 compatDevices.contains(device)); 117 assertTrue("compatible device should be available", spat.isAvailableForDevice(device)); 118 if (spat.hasHeadTracker(device)) { 119 spat.setHeadTrackerEnabled(true, device); 120 assertTrue("head tracker not found enabled", spat.isHeadTrackerEnabled(device)); 121 spat.setHeadTrackerEnabled(false, device); 122 assertFalse("head tracker not found disabled", spat.isHeadTrackerEnabled(device)); 123 } 124 spat.removeCompatibleAudioDevice(device); 125 compatDevices = spat.getCompatibleAudioDevices(); 126 assertFalse("removed device still in list of compatible devices", 127 compatDevices.contains(device)); 128 129 getInstrumentation().getUiAutomation().dropShellPermissionIdentity(); 130 } 131 132 @RequiresFlagsEnabled(FLAG_FEATURE_SPATIAL_AUDIO_HEADTRACKING_LOW_LATENCY) testLowLatencyHeadtrackingFeature()133 public void testLowLatencyHeadtrackingFeature() throws Exception { 134 Spatializer spat = mAudioManager.getSpatializer(); 135 if (spat.getImmersiveAudioLevel() != Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE) { 136 return; 137 } 138 assertFalse("Cannot have SPATIALIZER_IMMERSIVE_LEVEL_NONE with feature " 139 + "FEATURE_AUDIO_SPATIAL_HEADTRACKING_LOW_LATENCY declared", 140 getContext().getPackageManager().hasSystemFeature( 141 PackageManager.FEATURE_AUDIO_SPATIAL_HEADTRACKING_LOW_LATENCY)); 142 } 143 testHeadTrackingListener()144 public void testHeadTrackingListener() throws Exception { 145 Spatializer spat = mAudioManager.getSpatializer(); 146 if (spat.getImmersiveAudioLevel() == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE) { 147 Log.i(TAG, "skipping testHeadTrackingListener, functionality unsupported"); 148 return; 149 } 150 151 // try to call any head tracking method without permission 152 assertThrows("Able to call getHeadTrackingMode without permission", 153 SecurityException.class, 154 () -> spat.getHeadTrackingMode()); 155 assertThrows("Able to call getDesiredHeadTrackingMode without permission", 156 SecurityException.class, 157 () -> spat.getDesiredHeadTrackingMode()); 158 assertThrows("Able to call getSupportedHeadTrackingModes without permission", 159 SecurityException.class, 160 () -> spat.getSupportedHeadTrackingModes()); 161 assertThrows("Able to call setDesiredHeadTrackingMode without permission", 162 SecurityException.class, 163 () -> spat.setDesiredHeadTrackingMode( 164 Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE)); 165 final MyHeadTrackingModeListener listener = new MyHeadTrackingModeListener(); 166 assertThrows("Able to call addOnHeadTrackingModeChangedListener without permission", 167 SecurityException.class, 168 () -> spat.addOnHeadTrackingModeChangedListener(Executors.newSingleThreadExecutor(), 169 listener)); 170 getInstrumentation().getUiAutomation() 171 .adoptShellPermissionIdentity("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS"); 172 173 // argument validation 174 assertThrows("Able to call addOnHeadTrackingModeChangedListener with null Executor", 175 NullPointerException.class, 176 () -> spat.addOnHeadTrackingModeChangedListener(null, listener)); 177 assertThrows("Able to call addOnHeadTrackingModeChangedListener with null listener", 178 NullPointerException.class, 179 () -> spat.addOnHeadTrackingModeChangedListener(Executors.newSingleThreadExecutor(), 180 null)); 181 assertThrows("Able to call removeOnHeadTrackingModeChangedListener with null listener", 182 NullPointerException.class, 183 () -> spat.removeOnHeadTrackingModeChangedListener(null)); 184 185 // test of functionality 186 spat.setEnabled(true); 187 List<Integer> supportedModes = spat.getSupportedHeadTrackingModes(); 188 Assert.assertNotNull("Invalid null list of tracking modes", supportedModes); 189 Log.i(TAG, "Reported supported head tracking modes:" + supportedModes); 190 if (!(supportedModes.contains(Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE) 191 || supportedModes.contains(Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD) 192 || supportedModes.contains(Spatializer.HEAD_TRACKING_MODE_OTHER))) { 193 // no head tracking is supported, verify it is correctly reported by the API 194 Log.i(TAG, "no headtracking modes supported"); 195 assertEquals("When no head tracking mode supported, list of modes must be empty", 196 0, supportedModes.size()); 197 assertEquals("Invalid mode when no head tracking mode supported", 198 Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED, spat.getHeadTrackingMode()); 199 // verify you can't enable head tracking on a device 200 final AudioDeviceAttributes device = new AudioDeviceAttributes( 201 AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, "bli"); 202 spat.addCompatibleAudioDevice(device); 203 spat.setHeadTrackerEnabled(true, device); 204 assertFalse(spat.isHeadTrackerEnabled(device)); 205 return; 206 } 207 int trackingModeToUse; 208 if (supportedModes.contains(Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE)) { 209 trackingModeToUse = Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE; 210 } else { 211 trackingModeToUse = Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD; 212 } 213 spat.setDesiredHeadTrackingMode(Spatializer.HEAD_TRACKING_MODE_DISABLED); 214 spat.addOnHeadTrackingModeChangedListener(Executors.newSingleThreadExecutor(), listener); 215 spat.setDesiredHeadTrackingMode(trackingModeToUse); 216 Integer observedDesired = listener.getDesired(); 217 assertNotNull("No desired head tracking mode change reported", observedDesired); 218 assertEquals("Wrong reported desired tracking mode", trackingModeToUse, 219 observedDesired.intValue()); 220 assertEquals("Set desired mode not returned by getter", spat.getDesiredHeadTrackingMode(), 221 trackingModeToUse); 222 final int actualMode = spat.getHeadTrackingMode(); 223 // not failing test if modes differ, just logging 224 if (trackingModeToUse != actualMode) { 225 Log.i(TAG, "head tracking mode desired:" + trackingModeToUse + " actual mode:" 226 + actualMode); 227 } 228 spat.removeOnHeadTrackingModeChangedListener(listener); 229 } 230 testSpatializerOutput()231 public void testSpatializerOutput() throws Exception { 232 Spatializer spat = mAudioManager.getSpatializer(); 233 if (spat.getImmersiveAudioLevel() == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE) { 234 Log.i(TAG, "skipping testSpatializerOutput, functionality unsupported"); 235 return; 236 } 237 238 // try to call any output method without permission 239 assertThrows("Able to call getOutput without permission", 240 SecurityException.class, 241 () -> spat.getOutput()); 242 final MyOutputChangedListener listener = new MyOutputChangedListener(); 243 assertThrows("Able to call setOnSpatializerOutputChangedListener without permission", 244 SecurityException.class, 245 () -> spat.setOnSpatializerOutputChangedListener( 246 Executors.newSingleThreadExecutor(), listener)); 247 assertThrows("Able to call clearOnSpatializerOutputChangedListener with no listener", 248 SecurityException.class, 249 () -> spat.clearOnSpatializerOutputChangedListener()); 250 251 getInstrumentation().getUiAutomation() 252 .adoptShellPermissionIdentity("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS"); 253 254 // argument validation 255 assertThrows("Able to call setOnSpatializerOutputChangedListener with null Executor", 256 NullPointerException.class, 257 () -> spat.setOnSpatializerOutputChangedListener(null, listener)); 258 assertThrows("Able to call setOnSpatializerOutputChangedListener with null listener", 259 NullPointerException.class, 260 () -> spat.setOnSpatializerOutputChangedListener( 261 Executors.newSingleThreadExecutor(), null)); 262 263 spat.getOutput(); 264 // output doesn't change upon playback, so at this point only exercising 265 // registering / clearing of output listener under permission 266 spat.clearOnSpatializerOutputChangedListener(); // this is to clear the client listener ref 267 spat.setOnSpatializerOutputChangedListener(Executors.newSingleThreadExecutor(), listener); 268 spat.clearOnSpatializerOutputChangedListener(); 269 assertThrows("Able to call clearOnSpatializerOutputChangedListener with no listener", 270 IllegalStateException.class, 271 () -> spat.clearOnSpatializerOutputChangedListener()); 272 } 273 testExercisePose()274 public void testExercisePose() throws Exception { 275 Spatializer spat = mAudioManager.getSpatializer(); 276 if (spat.getImmersiveAudioLevel() == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE) { 277 Log.i(TAG, "skipping testExercisePose, functionality unsupported"); 278 return; 279 } 280 281 // argument validation 282 assertThrows("Able to call setGlobalTransform without a 6-float array", 283 IllegalArgumentException.class, 284 () -> spat.setGlobalTransform(new float[5])); 285 assertThrows("Able to call setGlobalTransform without a null array", 286 NullPointerException.class, 287 () -> spat.setGlobalTransform(null)); 288 final MyPoseUpdatedListener listener = new MyPoseUpdatedListener(); 289 assertThrows("Able to call setOnHeadToSoundstagePoseUpdatedListener with null Executor", 290 NullPointerException.class, 291 () -> spat.setOnHeadToSoundstagePoseUpdatedListener(null, listener)); 292 assertThrows("Able to call setOnHeadToSoundstagePoseUpdatedListener with null listener", 293 NullPointerException.class, 294 () -> spat.setOnHeadToSoundstagePoseUpdatedListener( 295 Executors.newSingleThreadExecutor(), null)); 296 assertThrows("Able to call clearOnHeadToSoundstagePoseUpdatedListener with no listener", 297 IllegalStateException.class, 298 () -> spat.clearOnHeadToSoundstagePoseUpdatedListener()); 299 300 getInstrumentation().getUiAutomation() 301 .adoptShellPermissionIdentity("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS"); 302 // TODO once headtracking is properly reported: check pose changes on recenter and transform 303 spat.setOnHeadToSoundstagePoseUpdatedListener( 304 Executors.newSingleThreadExecutor(), listener); 305 // oneway call from client to AudioService, can't check for exception earlier 306 spat.recenterHeadTracker(); 307 // oneway call from client to AudioService, can't check for exception earler 308 spat.setGlobalTransform(new float[6]); 309 spat.clearOnHeadToSoundstagePoseUpdatedListener(); 310 } 311 testEffectParameters()312 public void testEffectParameters() throws Exception { 313 Spatializer spat = mAudioManager.getSpatializer(); 314 if (spat.getImmersiveAudioLevel() == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE) { 315 Log.i(TAG, "skipping testEffectParameters, functionality unsupported"); 316 return; 317 } 318 319 // argument validation 320 assertThrows("Able to call setEffectParameter with null value", 321 NullPointerException.class, 322 () -> spat.setEffectParameter(0, null)); 323 assertThrows("Able to call getEffectParameter with null value", 324 NullPointerException.class, 325 () -> spat.getEffectParameter(0, null)); 326 327 // permission check 328 byte[] val = new byte[4]; 329 assertThrows("Able to call setEffectParameter without permission", 330 SecurityException.class, 331 () -> spat.setEffectParameter(0, val)); 332 assertThrows("Able to call getEffectParameter without permission", 333 SecurityException.class, 334 () -> spat.getEffectParameter(0, val)); 335 } 336 testSpatializerStateListenerManagement()337 public void testSpatializerStateListenerManagement() throws Exception { 338 final Spatializer spat = mAudioManager.getSpatializer(); 339 final MySpatStateListener stateListener = new MySpatStateListener(); 340 341 // add listener: 342 // verify null arg checks 343 assertThrows("null Executor allowed in addOnSpatializerStateChangedListener", 344 NullPointerException.class, 345 () -> spat.addOnSpatializerStateChangedListener(null, stateListener)); 346 assertThrows("null listener allowed in addOnSpatializerStateChangedListener", 347 NullPointerException.class, 348 () -> spat.addOnSpatializerStateChangedListener( 349 Executors.newSingleThreadExecutor(), null)); 350 351 spat.addOnSpatializerStateChangedListener(Executors.newSingleThreadExecutor(), 352 stateListener); 353 // verify double add 354 assertThrows("duplicate listener allowed in addOnSpatializerStateChangedListener", 355 IllegalArgumentException.class, 356 () -> spat.addOnSpatializerStateChangedListener(Executors.newSingleThreadExecutor(), 357 stateListener)); 358 359 // remove listener: 360 // verify null arg check 361 assertThrows("null listener allowed in removeOnSpatializerStateChangedListener", 362 NullPointerException.class, 363 () -> spat.removeOnSpatializerStateChangedListener(null)); 364 365 // verify unregistered listener 366 assertThrows("unregistered listener allowed in removeOnSpatializerStateChangedListener", 367 IllegalArgumentException.class, 368 () -> spat.removeOnSpatializerStateChangedListener(new MySpatStateListener())); 369 370 spat.removeOnSpatializerStateChangedListener(stateListener); 371 // verify double remove 372 assertThrows("double listener removal allowed in removeOnSpatializerStateChangedListener", 373 IllegalArgumentException.class, 374 () -> spat.removeOnSpatializerStateChangedListener(stateListener)); 375 } 376 testMinSpatializationCapabilities()377 public void testMinSpatializationCapabilities() throws Exception { 378 Spatializer spat = mAudioManager.getSpatializer(); 379 if (spat.getImmersiveAudioLevel() == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE) { 380 Log.i(TAG, "skipping testMinSpatializationCapabilities, no Spatializer"); 381 return; 382 } 383 if (!spat.isAvailable()) { 384 Log.i(TAG, "skipping testMinSpatializationCapabilities, Spatializer not available"); 385 return; 386 } 387 for (int sampleRate : new int[] { 44100, 4800 }) { 388 AudioFormat minFormat = new AudioFormat.Builder() 389 .setSampleRate(sampleRate) 390 .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1) 391 .setEncoding(AudioFormat.ENCODING_PCM_16BIT) 392 .build(); 393 for (int usage : new int[] { AudioAttributes.USAGE_MEDIA, 394 AudioAttributes.USAGE_GAME}) { 395 AudioAttributes defAttr = new AudioAttributes.Builder() 396 .setUsage(usage) 397 .build(); 398 assertTrue("AudioAttributes usage:" + usage + " at " + sampleRate 399 + " should be virtualizeable", spat.canBeSpatialized(defAttr, minFormat)); 400 } 401 } 402 } 403 testSpatializerDisabling()404 public void testSpatializerDisabling() throws Exception { 405 Spatializer spat = mAudioManager.getSpatializer(); 406 if (spat.getImmersiveAudioLevel() == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE) { 407 Log.i(TAG, "skipping testSpatializerDisabling, no Spatializer"); 408 return; 409 } 410 if (!spat.isAvailable()) { 411 Log.i(TAG, "skipping testSpatializerDisabling, Spatializer not available"); 412 return; 413 } 414 if (!spat.isEnabled()) { 415 // this test can only test disabling the feature, and thus requires 416 // to start with an "enabled" state, as a "disabled" state can reflect 417 // a number of internal states that can't always be reset (e.g. an uninitialized 418 // effect or a disabled feature) 419 Log.i(TAG, "skipping testSpatializerDisabling, Spatializer not enabled"); 420 return; 421 } 422 final MySpatStateListener stateListener = new MySpatStateListener(); 423 424 spat.addOnSpatializerStateChangedListener(Executors.newSingleThreadExecutor(), 425 stateListener); 426 getInstrumentation().getUiAutomation() 427 .adoptShellPermissionIdentity("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS"); 428 try { 429 spat.setEnabled(false); 430 assertEquals("Spatializer not reported as disabled", false, spat.isEnabled()); 431 Boolean enabled = stateListener.getEnabled(); 432 assertNotNull("Spatializer state listener wasn't called", enabled); 433 assertEquals("Spatializer state listener didn't get expected value", 434 false, enabled.booleanValue()); 435 } finally { 436 // restore state 437 spat.setEnabled(true); 438 getInstrumentation().getUiAutomation().dropShellPermissionIdentity(); 439 spat.removeOnSpatializerStateChangedListener(stateListener); 440 assertEquals("Spatializer state cannot be restored", true, spat.isEnabled()); 441 } 442 } 443 testHeadTrackerAvailable()444 public void testHeadTrackerAvailable() throws Exception { 445 Spatializer spat = mAudioManager.getSpatializer(); 446 if (spat.getImmersiveAudioLevel() == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE) { 447 Log.i(TAG, "skipping testHeadTrackerAvailable, no Spatializer"); 448 return; 449 } 450 final MyHeadTrackerAvailable htAvailableListener = new MyHeadTrackerAvailable(); 451 452 assertThrows("null Executor allowed in addOnHeadTrackerAvailableListener", 453 NullPointerException.class, 454 () -> spat.addOnHeadTrackerAvailableListener(null, htAvailableListener)); 455 assertThrows("null listener allowed in addOnHeadTrackerAvailableListener", 456 NullPointerException.class, 457 () -> spat.addOnHeadTrackerAvailableListener(Executors.newSingleThreadExecutor(), 458 null)); 459 spat.addOnHeadTrackerAvailableListener( 460 Executors.newSingleThreadExecutor(), htAvailableListener); 461 462 final boolean enabled = spat.isEnabled(); 463 // verify that with spatializer disabled, the head tracker is not available 464 if (!enabled) { 465 // spatializer not enabled 466 assertFalse("head tracker available despite spatializer disabled", 467 spat.isHeadTrackerAvailable()); 468 } else { 469 final MySpatStateListener stateListener = new MySpatStateListener(); 470 spat.addOnSpatializerStateChangedListener(Executors.newSingleThreadExecutor(), 471 stateListener); 472 // now disable the effect and check head tracker availability 473 getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( 474 "android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS"); 475 spat.setEnabled(false); 476 getInstrumentation().getUiAutomation().dropShellPermissionIdentity(); 477 assertFalse("spatializer state listener not notified after disabling", 478 stateListener.getEnabled()); 479 assertFalse("head tracker available despite spatializer disabled", 480 spat.isHeadTrackerAvailable()); 481 // reset state and wait until done 482 getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( 483 "android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS"); 484 spat.setEnabled(true); 485 getInstrumentation().getUiAutomation().dropShellPermissionIdentity(); 486 assertTrue("spatializer state listener not notified after enabling", 487 stateListener.getEnabled()); 488 } 489 assertThrows("null listener allowed in removeOnHeadTrackerAvailableListener", 490 NullPointerException.class, 491 () -> spat.removeOnHeadTrackerAvailableListener(null)); 492 spat.removeOnHeadTrackerAvailableListener(htAvailableListener); 493 assertThrows("able to remove listener twice in removeOnHeadTrackerAvailableListener", 494 IllegalArgumentException.class, 495 () -> spat.removeOnHeadTrackerAvailableListener(htAvailableListener)); 496 } 497 498 static class MySpatStateListener 499 implements Spatializer.OnSpatializerStateChangedListener { 500 501 private final LinkedBlockingQueue<Boolean> mEnabledQueue = 502 new LinkedBlockingQueue<Boolean>(1); 503 reset()504 void reset() { 505 mEnabledQueue.clear(); 506 } 507 getEnabled()508 Boolean getEnabled() throws Exception { 509 return mEnabledQueue.poll(LISTENER_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS); 510 } 511 MySpatStateListener()512 MySpatStateListener() { 513 reset(); 514 } 515 516 @Override onSpatializerEnabledChanged(Spatializer spat, boolean enabled)517 public void onSpatializerEnabledChanged(Spatializer spat, boolean enabled) { 518 Log.i(TAG, "onSpatializerEnabledChanged:" + enabled); 519 mEnabledQueue.offer(enabled); 520 } 521 522 @Override onSpatializerAvailableChanged(@onNull Spatializer spat, boolean available)523 public void onSpatializerAvailableChanged(@NonNull Spatializer spat, boolean available) { 524 Log.i(TAG, "onSpatializerAvailableChanged:" + available); 525 } 526 } 527 528 static class MyHeadTrackingModeListener 529 implements Spatializer.OnHeadTrackingModeChangedListener { 530 private final LinkedBlockingQueue<Integer> mDesiredQueue = 531 new LinkedBlockingQueue<Integer>(1); 532 private final LinkedBlockingQueue<Integer> mRealQueue = 533 new LinkedBlockingQueue<Integer>(1); 534 535 @Override onHeadTrackingModeChanged(Spatializer spatializer, int mode)536 public void onHeadTrackingModeChanged(Spatializer spatializer, int mode) { 537 Log.i(TAG, "onHeadTrackingModeChanged:" + mode); 538 mRealQueue.offer(mode); 539 } 540 541 @Override onDesiredHeadTrackingModeChanged(Spatializer spatializer, int mode)542 public void onDesiredHeadTrackingModeChanged(Spatializer spatializer, int mode) { 543 Log.i(TAG, "onDesiredHeadTrackingModeChanged:" + mode); 544 mDesiredQueue.offer(mode); 545 } 546 getDesired()547 public Integer getDesired() throws Exception { 548 return mDesiredQueue.poll(LISTENER_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS); 549 } 550 } 551 552 static class MyOutputChangedListener 553 implements Spatializer.OnSpatializerOutputChangedListener { 554 @Override onSpatializerOutputChanged(Spatializer spatializer, int output)555 public void onSpatializerOutputChanged(Spatializer spatializer, int output) { 556 Log.i(TAG, "onSpatializerOutputChanged:" + output); 557 } 558 } 559 560 static class MyPoseUpdatedListener 561 implements Spatializer.OnHeadToSoundstagePoseUpdatedListener { 562 @Override onHeadToSoundstagePoseUpdated(Spatializer spatializer, float[] pose)563 public void onHeadToSoundstagePoseUpdated(Spatializer spatializer, float[] pose) { 564 Log.i(TAG, "onHeadToSoundstagePoseUpdated:" + Arrays.toString(pose)); 565 } 566 } 567 568 static class MyHeadTrackerAvailable implements Spatializer.OnHeadTrackerAvailableListener { 569 @Override onHeadTrackerAvailableChanged(Spatializer spatializer, boolean available)570 public void onHeadTrackerAvailableChanged(Spatializer spatializer, boolean available) { 571 Log.i(TAG, "onHeadTrackerAvailable(" + available + ")"); 572 } 573 } 574 } 575