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