• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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