• 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;
18 
19 import android.annotation.CallbackExecutor;
20 import android.annotation.IntDef;
21 import android.annotation.IntRange;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.annotation.RequiresPermission;
25 import android.annotation.SuppressLint;
26 import android.annotation.SystemApi;
27 import android.media.CallbackUtil.ListenerInfo;
28 import android.media.permission.ClearCallingIdentityContext;
29 import android.media.permission.SafeCloseable;
30 import android.os.RemoteException;
31 import android.util.Log;
32 
33 import com.android.internal.annotations.GuardedBy;
34 
35 import java.lang.annotation.Retention;
36 import java.lang.annotation.RetentionPolicy;
37 import java.util.ArrayList;
38 import java.util.List;
39 import java.util.Objects;
40 import java.util.concurrent.Executor;
41 
42 /**
43  * Spatializer provides access to querying capabilities and behavior of sound spatialization
44  * on the device.
45  * Sound spatialization simulates sounds originating around the listener as if they were coming
46  * from virtual speakers placed around the listener.<br>
47  * Support for spatialization is optional, use {@link AudioManager#getSpatializer()} to obtain an
48  * instance of this class if the feature is supported.
49  *
50  */
51 public class Spatializer {
52 
53     private final @NonNull AudioManager mAm;
54 
55     private static final String TAG = "Spatializer";
56 
57     /**
58      * @hide
59      * Constructor with AudioManager acting as proxy to AudioService
60      * @param am a non-null AudioManager
61      */
Spatializer(@onNull AudioManager am)62     protected Spatializer(@NonNull AudioManager am) {
63         mAm = Objects.requireNonNull(am);
64     }
65 
66     /**
67      * Returns whether spatialization is enabled or not.
68      * A false value can originate for instance from the user electing to
69      * disable the feature, or when the feature is not supported on the device (indicated
70      * by {@link #getImmersiveAudioLevel()} returning {@link #SPATIALIZER_IMMERSIVE_LEVEL_NONE}).
71      * <br>
72      * Note that this state reflects a platform-wide state of the "desire" to use spatialization,
73      * but availability of the audio processing is still dictated by the compatibility between
74      * the effect and the hardware configuration, as indicated by {@link #isAvailable()}.
75      * @return {@code true} if spatialization is enabled
76      * @see #isAvailable()
77      */
isEnabled()78     public boolean isEnabled() {
79         try {
80             return mAm.getService().isSpatializerEnabled();
81         } catch (RemoteException e) {
82             Log.e(TAG, "Error querying isSpatializerEnabled, returning false", e);
83             return false;
84         }
85     }
86 
87     /**
88      * Returns whether spatialization is available.
89      * Reasons for spatialization being unavailable include situations where audio output is
90      * incompatible with sound spatialization, such as playback on a monophonic speaker.<br>
91      * Note that spatialization can be available, but disabled by the user, in which case this
92      * method would still return {@code true}, whereas {@link #isEnabled()}
93      * would return {@code false}.<br>
94      * Also when the feature is not supported on the device (indicated
95      * by {@link #getImmersiveAudioLevel()} returning {@link #SPATIALIZER_IMMERSIVE_LEVEL_NONE}),
96      * the return value will be false.
97      * @return {@code true} if the spatializer effect is available and capable
98      *         of processing the audio for the current configuration of the device,
99      *         {@code false} otherwise.
100      * @see #isEnabled()
101      */
isAvailable()102     public boolean isAvailable()  {
103         try {
104             return mAm.getService().isSpatializerAvailable();
105         } catch (RemoteException e) {
106             Log.e(TAG, "Error querying isSpatializerAvailable, returning false", e);
107             return false;
108         }
109     }
110 
111     /**
112      * @hide
113      * Returns whether spatialization is available for a given audio device
114      * Reasons for spatialization being unavailable include situations where audio output is
115      * incompatible with sound spatialization, such as the device being a monophonic speaker, or
116      * the spatializer effect not supporting transaural processing when querying for speaker.
117      * @param device the audio device for which spatializer availability is queried
118      * @return {@code true} if the spatializer effect is available and capable
119      *         of processing the audio over the given audio device,
120      *         {@code false} otherwise.
121      * @see #isEnabled()
122      */
123     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
124     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
isAvailableForDevice(@onNull AudioDeviceAttributes device)125     public boolean isAvailableForDevice(@NonNull AudioDeviceAttributes device)  {
126         Objects.requireNonNull(device);
127         try {
128             return mAm.getService().isSpatializerAvailableForDevice(device);
129         } catch (RemoteException e) {
130             e.rethrowFromSystemServer();
131         }
132         return false;
133     }
134 
135     /**
136      * @hide
137      * Returns whether the given device has an associated headtracker
138      * @param device the audio device to query
139      * @return true if the device has a head tracker, false otherwise
140      */
141     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
142     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
hasHeadTracker(@onNull AudioDeviceAttributes device)143     public boolean hasHeadTracker(@NonNull AudioDeviceAttributes device) {
144         Objects.requireNonNull(device);
145         try {
146             return mAm.getService().hasHeadTracker(device);
147         } catch (RemoteException e) {
148             e.rethrowFromSystemServer();
149         }
150         return false;
151     }
152 
153     /**
154      * @hide
155      * Enables or disables the head tracker of the given device
156      * @param enabled true to enable, false to disable
157      * @param device the device whose head tracker state is changed
158      */
159     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
160     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
setHeadTrackerEnabled(boolean enabled, @NonNull AudioDeviceAttributes device)161     public void setHeadTrackerEnabled(boolean enabled, @NonNull AudioDeviceAttributes device) {
162         Objects.requireNonNull(device);
163         try {
164             mAm.getService().setHeadTrackerEnabled(enabled, device);
165         } catch (RemoteException e) {
166             e.rethrowFromSystemServer();
167         }
168     }
169 
170     /**
171      * @hide
172      * Returns whether the head tracker of the device is enabled
173      * @param device the device to query
174      * @return true if the head tracker is enabled, false if disabled or if there isn't one
175      */
176     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
177     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
isHeadTrackerEnabled(@onNull AudioDeviceAttributes device)178     public boolean isHeadTrackerEnabled(@NonNull AudioDeviceAttributes device) {
179         Objects.requireNonNull(device);
180         try {
181             return mAm.getService().isHeadTrackerEnabled(device);
182         } catch (RemoteException e) {
183             e.rethrowFromSystemServer();
184         }
185         return false;
186     }
187 
188     /**
189      * Returns whether a head tracker is currently available for the audio device used by the
190      * spatializer effect.
191      * @return true if a head tracker is available and the effect is enabled, false otherwise.
192      * @see OnHeadTrackerAvailableListener
193      * @see #addOnHeadTrackerAvailableListener(Executor, OnHeadTrackerAvailableListener)
194      */
isHeadTrackerAvailable()195     public boolean isHeadTrackerAvailable() {
196         try {
197             return mAm.getService().isHeadTrackerAvailable();
198         } catch (RemoteException e) {
199             e.rethrowFromSystemServer();
200         }
201         return false;
202     }
203 
204     /**
205      * Adds a listener to be notified of changes to the availability of a head tracker.
206      * @param executor the {@code Executor} handling the callback
207      * @param listener the listener to receive availability updates
208      * @see #removeOnHeadTrackerAvailableListener(OnHeadTrackerAvailableListener)
209      */
addOnHeadTrackerAvailableListener(@onNull @allbackExecutor Executor executor, @NonNull OnHeadTrackerAvailableListener listener)210     public void addOnHeadTrackerAvailableListener(@NonNull @CallbackExecutor Executor executor,
211             @NonNull OnHeadTrackerAvailableListener listener) {
212         mHeadTrackerListenerMgr.addListener(executor, listener,
213                 "addOnHeadTrackerAvailableListener",
214                 () -> new SpatializerHeadTrackerAvailableDispatcherStub());
215     }
216 
217     /**
218      * Removes a previously registered listener for the availability of a head tracker.
219      * @param listener the listener previously registered with
220      *      {@link #addOnHeadTrackerAvailableListener(Executor, OnHeadTrackerAvailableListener)}
221      */
removeOnHeadTrackerAvailableListener( @onNull OnHeadTrackerAvailableListener listener)222     public void removeOnHeadTrackerAvailableListener(
223             @NonNull OnHeadTrackerAvailableListener listener) {
224         mHeadTrackerListenerMgr.removeListener(listener, "removeOnHeadTrackerAvailableListener");
225     }
226 
227     /** @hide */
228     @IntDef(flag = false, value = {
229             SPATIALIZER_IMMERSIVE_LEVEL_OTHER,
230             SPATIALIZER_IMMERSIVE_LEVEL_NONE,
231             SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL,
232     })
233     @Retention(RetentionPolicy.SOURCE)
234     public @interface ImmersiveAudioLevel {};
235 
236     /**
237      * Constant indicating the {@code Spatializer} on this device supports a spatialization
238      * mode that differs from the ones available at this SDK level.
239      * @see #getImmersiveAudioLevel()
240      */
241     public static final int SPATIALIZER_IMMERSIVE_LEVEL_OTHER = -1;
242 
243     /**
244      * Constant indicating there are no spatialization capabilities supported on this device.
245      * @see #getImmersiveAudioLevel()
246      */
247     public static final int SPATIALIZER_IMMERSIVE_LEVEL_NONE = 0;
248 
249     /**
250      * Constant indicating the {@code Spatializer} on this device supports multichannel
251      * spatialization.
252      * @see #getImmersiveAudioLevel()
253      */
254     public static final int SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL = 1;
255 
256     /**
257      * @hide
258      * Constant indicating the {@code Spatializer} on this device supports the spatialization of
259      * multichannel bed plus objects.
260      * @see #getImmersiveAudioLevel()
261      */
262     public static final int SPATIALIZER_IMMERSIVE_LEVEL_MCHAN_BED_PLUS_OBJECTS = 2;
263 
264     /** @hide */
265     @IntDef(flag = false, value = {
266             HEAD_TRACKING_MODE_UNSUPPORTED,
267             HEAD_TRACKING_MODE_DISABLED,
268             HEAD_TRACKING_MODE_RELATIVE_WORLD,
269             HEAD_TRACKING_MODE_RELATIVE_DEVICE,
270     }) public @interface HeadTrackingMode {};
271 
272     /** @hide */
273     @IntDef(flag = false, value = {
274             HEAD_TRACKING_MODE_DISABLED,
275             HEAD_TRACKING_MODE_RELATIVE_WORLD,
276             HEAD_TRACKING_MODE_RELATIVE_DEVICE,
277     }) public @interface HeadTrackingModeSet {};
278 
279     /** @hide */
280     @IntDef(flag = false, value = {
281             HEAD_TRACKING_MODE_RELATIVE_WORLD,
282             HEAD_TRACKING_MODE_RELATIVE_DEVICE,
283     }) public @interface HeadTrackingModeSupported {};
284 
285     /**
286      * @hide
287      * Constant indicating head tracking is not supported by this {@code Spatializer}
288      * @see #getHeadTrackingMode()
289      */
290     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
291     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
292     public static final int HEAD_TRACKING_MODE_UNSUPPORTED = -2;
293 
294     /**
295      * @hide
296      * Constant indicating head tracking is disabled on this {@code Spatializer}
297      * @see #getHeadTrackingMode()
298      */
299     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
300     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
301     public static final int HEAD_TRACKING_MODE_DISABLED = -1;
302 
303     /**
304      * @hide
305      * Constant indicating head tracking is in a mode whose behavior is unknown. This is not an
306      * error state but represents a customized behavior not defined by this API.
307      * @see #getHeadTrackingMode()
308      */
309     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
310     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
311     public static final int HEAD_TRACKING_MODE_OTHER = 0;
312 
313     /**
314      * @hide
315      * Constant indicating head tracking is tracking the user's position / orientation relative to
316      * the world around them
317      * @see #getHeadTrackingMode()
318      */
319     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
320     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
321     public static final int HEAD_TRACKING_MODE_RELATIVE_WORLD = 1;
322 
323     /**
324      * @hide
325      * Constant indicating head tracking is tracking the user's position / orientation relative to
326      * the device
327      * @see #getHeadTrackingMode()
328      */
329     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
330     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
331     public static final int HEAD_TRACKING_MODE_RELATIVE_DEVICE = 2;
332 
333     /**
334      * @hide
335      * Head tracking mode to string conversion
336      * @param mode a valid head tracking mode
337      * @return a string containing the matching constant name
338      */
headtrackingModeToString(int mode)339     public static final String headtrackingModeToString(int mode) {
340         switch(mode) {
341             case HEAD_TRACKING_MODE_UNSUPPORTED:
342                 return "HEAD_TRACKING_MODE_UNSUPPORTED";
343             case HEAD_TRACKING_MODE_DISABLED:
344                 return "HEAD_TRACKING_MODE_DISABLED";
345             case HEAD_TRACKING_MODE_OTHER:
346                 return "HEAD_TRACKING_MODE_OTHER";
347             case HEAD_TRACKING_MODE_RELATIVE_WORLD:
348                 return "HEAD_TRACKING_MODE_RELATIVE_WORLD";
349             case HEAD_TRACKING_MODE_RELATIVE_DEVICE:
350                 return "HEAD_TRACKING_MODE_RELATIVE_DEVICE";
351             default:
352                 return "head tracking mode unknown " + mode;
353         }
354     }
355 
356     /**
357      * Return the level of support for the spatialization feature on this device.
358      * This level of support is independent of whether the {@code Spatializer} is currently
359      * enabled or available and will not change over time.
360      * @return the level of spatialization support
361      * @see #isEnabled()
362      * @see #isAvailable()
363      */
getImmersiveAudioLevel()364     public @ImmersiveAudioLevel int getImmersiveAudioLevel() {
365         int level = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
366         try {
367             level = mAm.getService().getSpatializerImmersiveAudioLevel();
368         } catch (Exception e) { /* using NONE */ }
369         return level;
370     }
371 
372     /**
373      * @hide
374      * Enables / disables the spatializer effect.
375      * Changing the enabled state will trigger the public
376      * {@link OnSpatializerStateChangedListener#onSpatializerEnabledChanged(Spatializer, boolean)}
377      * registered listeners.
378      * @param enabled {@code true} for enabling the effect
379      */
380     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
381     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
setEnabled(boolean enabled)382     public void setEnabled(boolean enabled) {
383         try {
384             mAm.getService().setSpatializerEnabled(enabled);
385         } catch (RemoteException e) {
386             Log.e(TAG, "Error calling setSpatializerEnabled", e);
387         }
388     }
389 
390     /**
391      * An interface to be notified of changes to the state of the spatializer effect.
392      */
393     public interface OnSpatializerStateChangedListener {
394         /**
395          * Called when the enabled state of the spatializer effect changes
396          * @param spat the {@code Spatializer} instance whose state changed
397          * @param enabled {@code true} if the spatializer effect is enabled on the device,
398          *                            {@code false} otherwise
399          * @see #isEnabled()
400          */
onSpatializerEnabledChanged(@onNull Spatializer spat, boolean enabled)401         void onSpatializerEnabledChanged(@NonNull Spatializer spat, boolean enabled);
402 
403         /**
404          * Called when the availability of the spatializer effect changes
405          * @param spat the {@code Spatializer} instance whose state changed
406          * @param available {@code true} if the spatializer effect is available and capable
407          *                  of processing the audio for the current configuration of the device,
408          *                  {@code false} otherwise.
409          * @see #isAvailable()
410          */
onSpatializerAvailableChanged(@onNull Spatializer spat, boolean available)411         void onSpatializerAvailableChanged(@NonNull Spatializer spat, boolean available);
412     }
413 
414     /**
415      * @hide
416      * An interface to be notified of changes to the head tracking mode, used by the spatializer
417      * effect.
418      * Changes to the mode may come from explicitly setting a different mode
419      * (see {@link #setDesiredHeadTrackingMode(int)}) or a change in system conditions (see
420      * {@link #getHeadTrackingMode()}
421      * @see #addOnHeadTrackingModeChangedListener(Executor, OnHeadTrackingModeChangedListener)
422      * @see #removeOnHeadTrackingModeChangedListener(OnHeadTrackingModeChangedListener)
423      */
424     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
425     public interface OnHeadTrackingModeChangedListener {
426         /**
427          * Called when the actual head tracking mode of the spatializer changed.
428          * @param spatializer the {@code Spatializer} instance whose head tracking mode is changing
429          * @param mode the new head tracking mode
430          */
onHeadTrackingModeChanged(@onNull Spatializer spatializer, @HeadTrackingMode int mode)431         void onHeadTrackingModeChanged(@NonNull Spatializer spatializer,
432                 @HeadTrackingMode int mode);
433 
434         /**
435          * Called when the desired head tracking mode of the spatializer changed
436          * @param spatializer the {@code Spatializer} instance whose head tracking mode was set
437          * @param mode the newly set head tracking mode
438          */
onDesiredHeadTrackingModeChanged(@onNull Spatializer spatializer, @HeadTrackingModeSet int mode)439         void onDesiredHeadTrackingModeChanged(@NonNull Spatializer spatializer,
440                 @HeadTrackingModeSet int mode);
441     }
442 
443     /**
444      * Interface to be notified of changes to the availability of a head tracker on the audio
445      * device to be used by the spatializer effect.
446      */
447     public interface OnHeadTrackerAvailableListener {
448         /**
449          * Called when the availability of the head tracker changed.
450          * @param spatializer the {@code Spatializer} instance for which the head tracker
451          *                    availability was updated
452          * @param available true if the audio device that would output audio processed by
453          *                  the {@code Spatializer} has a head tracker associated with it, false
454          *                  otherwise.
455          */
onHeadTrackerAvailableChanged(@onNull Spatializer spatializer, boolean available)456         void onHeadTrackerAvailableChanged(@NonNull Spatializer spatializer,
457                 boolean available);
458     }
459 
460     /**
461      * @hide
462      * An interface to be notified of changes to the output stream used by the spatializer
463      * effect.
464      * @see #getOutput()
465      */
466     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
467     public interface OnSpatializerOutputChangedListener {
468         /**
469          * Called when the id of the output stream of the spatializer effect changed.
470          * @param spatializer the {@code Spatializer} instance whose output is updated
471          * @param output the id of the output stream, or 0 when there is no spatializer output
472          */
onSpatializerOutputChanged(@onNull Spatializer spatializer, @IntRange(from = 0) int output)473         void onSpatializerOutputChanged(@NonNull Spatializer spatializer,
474                 @IntRange(from = 0) int output);
475     }
476 
477     /**
478      * @hide
479      * An interface to be notified of updates to the head to soundstage pose, as represented by the
480      * current head tracking mode.
481      * @see #setOnHeadToSoundstagePoseUpdatedListener(Executor, OnHeadToSoundstagePoseUpdatedListener)
482      */
483     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
484     public interface OnHeadToSoundstagePoseUpdatedListener {
485         /**
486          * Called when the head to soundstage transform is updated
487          * @param spatializer the {@code Spatializer} instance affected by the pose update
488          * @param pose the new pose data representing the transform between the frame
489          *                 of reference for the current head tracking mode (see
490          *                 {@link #getHeadTrackingMode()}) and the device being tracked (for
491          *                 instance a pair of headphones with a head tracker).<br>
492          *                 The head pose data is represented as an array of six float values, where
493          *                 the first three values are the translation vector, and the next three
494          *                 are the rotation vector.
495          */
onHeadToSoundstagePoseUpdated(@onNull Spatializer spatializer, @NonNull float[] pose)496         void onHeadToSoundstagePoseUpdated(@NonNull Spatializer spatializer,
497                 @NonNull float[] pose);
498     }
499 
500     /**
501      * Returns whether audio of the given {@link AudioFormat}, played with the given
502      * {@link AudioAttributes} can be spatialized.
503      * Note that the result reflects the capabilities of the device and may change when
504      * audio accessories are connected/disconnected (e.g. wired headphones plugged in or not).
505      * The result is independent from whether spatialization processing is enabled or not.
506      * @param attributes the {@code AudioAttributes} of the content as used for playback
507      * @param format the {@code AudioFormat} of the content as used for playback
508      * @return {@code true} if the device is capable of spatializing the combination of audio format
509      *     and attributes, {@code false} otherwise.
510      */
canBeSpatialized( @onNull AudioAttributes attributes, @NonNull AudioFormat format)511     public boolean canBeSpatialized(
512             @NonNull AudioAttributes attributes, @NonNull AudioFormat format) {
513         try {
514             return mAm.getService().canBeSpatialized(
515                     Objects.requireNonNull(attributes), Objects.requireNonNull(format));
516         } catch (RemoteException e) {
517             Log.e(TAG, "Error querying canBeSpatialized for attr:" + attributes
518                     + " format:" + format + " returning false", e);
519             return false;
520         }
521     }
522 
523     /**
524      * Adds a listener to be notified of changes to the enabled state of the
525      * {@code Spatializer}.
526      * @param executor the {@code Executor} handling the callback
527      * @param listener the listener to receive enabled state updates
528      * @see #isEnabled()
529      */
addOnSpatializerStateChangedListener( @onNull @allbackExecutor Executor executor, @NonNull OnSpatializerStateChangedListener listener)530     public void addOnSpatializerStateChangedListener(
531             @NonNull @CallbackExecutor Executor executor,
532             @NonNull OnSpatializerStateChangedListener listener) {
533         mStateListenerMgr.addListener(executor, listener, "addOnSpatializerStateChangedListener",
534                 () -> new SpatializerInfoDispatcherStub());
535     }
536 
537     /**
538      * Removes a previously added listener for changes to the enabled state of the
539      * {@code Spatializer}.
540      * @param listener the listener to receive enabled state updates
541      * @see #isEnabled()
542      */
removeOnSpatializerStateChangedListener( @onNull OnSpatializerStateChangedListener listener)543     public void removeOnSpatializerStateChangedListener(
544             @NonNull OnSpatializerStateChangedListener listener) {
545         mStateListenerMgr.removeListener(listener, "removeOnSpatializerStateChangedListener");
546     }
547 
548     /**
549      * @hide
550      * Returns the list of playback devices that are compatible with the playback of multichannel
551      * audio through virtualization
552      * @return a list of devices. An empty list indicates virtualization is not supported.
553      */
554     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
555     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
getCompatibleAudioDevices()556     public @NonNull List<AudioDeviceAttributes> getCompatibleAudioDevices() {
557         try {
558             return mAm.getService().getSpatializerCompatibleAudioDevices();
559         } catch (RemoteException e) {
560             Log.e(TAG, "Error querying getSpatializerCompatibleAudioDevices(), "
561                     + " returning empty list", e);
562             return new ArrayList<AudioDeviceAttributes>(0);
563         }
564     }
565 
566     /**
567      * @hide
568      * Adds a playback device to the list of devices compatible with the playback of multichannel
569      * audio through spatialization.
570      * @see #getCompatibleAudioDevices()
571      * @param ada the audio device compatible with spatialization
572      */
573     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
574     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
addCompatibleAudioDevice(@onNull AudioDeviceAttributes ada)575     public void addCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
576         try {
577             mAm.getService().addSpatializerCompatibleAudioDevice(Objects.requireNonNull(ada));
578         } catch (RemoteException e) {
579             Log.e(TAG, "Error calling addSpatializerCompatibleAudioDevice(), ", e);
580         }
581     }
582 
583     /**
584      * @hide
585      * Remove a playback device from the list of devices compatible with the playback of
586      * multichannel audio through spatialization.
587      * @see #getCompatibleAudioDevices()
588      * @param ada the audio device incompatible with spatialization
589      */
590     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
591     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
removeCompatibleAudioDevice(@onNull AudioDeviceAttributes ada)592     public void removeCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
593         try {
594             mAm.getService().removeSpatializerCompatibleAudioDevice(Objects.requireNonNull(ada));
595         } catch (RemoteException e) {
596             Log.e(TAG, "Error calling removeSpatializerCompatibleAudioDevice(), ", e);
597         }
598     }
599 
600     /**
601      * manages the OnSpatializerStateChangedListener listeners and the
602      * SpatializerInfoDispatcherStub
603      */
604     private final CallbackUtil.LazyListenerManager<OnSpatializerStateChangedListener>
605             mStateListenerMgr = new CallbackUtil.LazyListenerManager();
606 
607     private final class SpatializerInfoDispatcherStub extends ISpatializerCallback.Stub
608             implements CallbackUtil.DispatcherStub {
609         @Override
register(boolean register)610         public void register(boolean register) {
611             try {
612                 if (register) {
613                     mAm.getService().registerSpatializerCallback(this);
614                 } else {
615                     mAm.getService().unregisterSpatializerCallback(this);
616                 }
617             } catch (RemoteException e) {
618                 e.rethrowFromSystemServer();
619             }
620         }
621 
622         @Override
623         @SuppressLint("GuardedBy") // lock applied inside callListeners method
dispatchSpatializerEnabledChanged(boolean enabled)624         public void dispatchSpatializerEnabledChanged(boolean enabled) {
625             mStateListenerMgr.callListeners(
626                     (listener) -> listener.onSpatializerEnabledChanged(
627                             Spatializer.this, enabled));
628         }
629 
630         @Override
631         @SuppressLint("GuardedBy") // lock applied inside callListeners method
dispatchSpatializerAvailableChanged(boolean available)632         public void dispatchSpatializerAvailableChanged(boolean available) {
633             mStateListenerMgr.callListeners(
634                     (listener) -> listener.onSpatializerAvailableChanged(
635                             Spatializer.this, available));
636         }
637     }
638 
639 
640     /**
641      * @hide
642      * Return the current head tracking mode as used by the system.
643      * Note this may differ from the desired head tracking mode. Reasons for the two to differ
644      * include: a head tracking device is not available for the current audio output device,
645      * the transmission conditions between the tracker and device have deteriorated and tracking
646      * has been disabled.
647      * @see #getDesiredHeadTrackingMode()
648      * @return the current head tracking mode
649      */
650     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
651     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
getHeadTrackingMode()652     public @HeadTrackingMode int getHeadTrackingMode() {
653         try {
654             return mAm.getService().getActualHeadTrackingMode();
655         } catch (RemoteException e) {
656             Log.e(TAG, "Error calling getActualHeadTrackingMode", e);
657             return HEAD_TRACKING_MODE_UNSUPPORTED;
658         }
659 
660     }
661 
662     /**
663      * @hide
664      * Return the desired head tracking mode.
665      * Note this may differ from the actual head tracking mode, reflected by
666      * {@link #getHeadTrackingMode()}.
667      * @return the desired head tring mode
668      */
669     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
670     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
getDesiredHeadTrackingMode()671     public @HeadTrackingMode int getDesiredHeadTrackingMode() {
672         try {
673             return mAm.getService().getDesiredHeadTrackingMode();
674         } catch (RemoteException e) {
675             Log.e(TAG, "Error calling getDesiredHeadTrackingMode", e);
676             return HEAD_TRACKING_MODE_UNSUPPORTED;
677         }
678     }
679 
680     /**
681      * @hide
682      * Returns the list of supported head tracking modes.
683      * @return the list of modes that can be used in {@link #setDesiredHeadTrackingMode(int)} to
684      *         enable head tracking. The list will be empty if {@link #getHeadTrackingMode()}
685      *         is {@link #HEAD_TRACKING_MODE_UNSUPPORTED}. Values can be
686      *         {@link #HEAD_TRACKING_MODE_OTHER},
687      *         {@link #HEAD_TRACKING_MODE_RELATIVE_WORLD} or
688      *         {@link #HEAD_TRACKING_MODE_RELATIVE_DEVICE}
689      */
690     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
691     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
getSupportedHeadTrackingModes()692     public @NonNull List<Integer> getSupportedHeadTrackingModes() {
693         try {
694             final int[] modes = mAm.getService().getSupportedHeadTrackingModes();
695             final ArrayList<Integer> list = new ArrayList<>(0);
696             for (int mode : modes) {
697                 list.add(mode);
698             }
699             return list;
700         } catch (RemoteException e) {
701             Log.e(TAG, "Error calling getSupportedHeadTrackModes", e);
702             return new ArrayList(0);
703         }
704     }
705 
706     /**
707      * @hide
708      * Sets the desired head tracking mode.
709      * Note a set desired mode may differ from the actual head tracking mode.
710      * @see #getHeadTrackingMode()
711      * @param mode the desired head tracking mode, one of the values returned by
712      *             {@link #getSupportedHeadTrackModes()}, or {@link #HEAD_TRACKING_MODE_DISABLED} to
713      *             disable head tracking.
714      */
715     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
716     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
setDesiredHeadTrackingMode(@eadTrackingModeSet int mode)717     public void setDesiredHeadTrackingMode(@HeadTrackingModeSet int mode) {
718         try {
719             mAm.getService().setDesiredHeadTrackingMode(mode);
720         } catch (RemoteException e) {
721             Log.e(TAG, "Error calling setDesiredHeadTrackingMode to " + mode, e);
722         }
723     }
724 
725     /**
726      * @hide
727      * Recenters the head tracking at the current position / orientation.
728      */
729     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
730     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
recenterHeadTracker()731     public void recenterHeadTracker() {
732         try {
733             mAm.getService().recenterHeadTracker();
734         } catch (RemoteException e) {
735             Log.e(TAG, "Error calling recenterHeadTracker", e);
736         }
737     }
738 
739     /**
740      * @hide
741      * Adds a listener to be notified of changes to the head tracking mode of the
742      * {@code Spatializer}
743      * @param executor the {@code Executor} handling the callbacks
744      * @param listener the listener to register
745      */
746     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
747     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
addOnHeadTrackingModeChangedListener( @onNull @allbackExecutor Executor executor, @NonNull OnHeadTrackingModeChangedListener listener)748     public void addOnHeadTrackingModeChangedListener(
749             @NonNull @CallbackExecutor Executor executor,
750             @NonNull OnHeadTrackingModeChangedListener listener) {
751         mHeadTrackingListenerMgr.addListener(executor, listener,
752                 "addOnHeadTrackingModeChangedListener",
753                  () -> new SpatializerHeadTrackingDispatcherStub());
754     }
755 
756     /**
757      * @hide
758      * Removes a previously added listener for changes to the head tracking mode of the
759      * {@code Spatializer}.
760      * @param listener the listener to unregister
761      */
762     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
763     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
removeOnHeadTrackingModeChangedListener( @onNull OnHeadTrackingModeChangedListener listener)764     public void removeOnHeadTrackingModeChangedListener(
765             @NonNull OnHeadTrackingModeChangedListener listener) {
766         mHeadTrackingListenerMgr.removeListener(listener,
767                 "removeOnHeadTrackingModeChangedListener");
768     }
769 
770     /**
771      * @hide
772      * Set the listener to receive head to soundstage pose updates.
773      * @param executor the {@code Executor} handling the callbacks
774      * @param listener the listener to register
775      * @see #clearOnHeadToSoundstagePoseUpdatedListener()
776      */
777     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
778     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
setOnHeadToSoundstagePoseUpdatedListener( @onNull @allbackExecutor Executor executor, @NonNull OnHeadToSoundstagePoseUpdatedListener listener)779     public void setOnHeadToSoundstagePoseUpdatedListener(
780             @NonNull @CallbackExecutor Executor executor,
781             @NonNull OnHeadToSoundstagePoseUpdatedListener listener) {
782         Objects.requireNonNull(executor);
783         Objects.requireNonNull(listener);
784         synchronized (mPoseListenerLock) {
785             if (mPoseListener != null) {
786                 throw new IllegalStateException("Trying to overwrite existing listener");
787             }
788             mPoseListener =
789                     new ListenerInfo<OnHeadToSoundstagePoseUpdatedListener>(listener, executor);
790             mPoseDispatcher = new SpatializerPoseDispatcherStub();
791             try {
792                 mAm.getService().registerHeadToSoundstagePoseCallback(mPoseDispatcher);
793             } catch (RemoteException e) {
794                 mPoseListener = null;
795                 mPoseDispatcher = null;
796             }
797         }
798     }
799 
800     /**
801      * @hide
802      * Clears the listener for head to soundstage pose updates
803      * @see #setOnHeadToSoundstagePoseUpdatedListener(Executor, OnHeadToSoundstagePoseUpdatedListener)
804      */
805     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
806     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
clearOnHeadToSoundstagePoseUpdatedListener()807     public void clearOnHeadToSoundstagePoseUpdatedListener() {
808         synchronized (mPoseListenerLock) {
809             if (mPoseDispatcher == null) {
810                 throw (new IllegalStateException("No listener to clear"));
811             }
812             try {
813                 mAm.getService().unregisterHeadToSoundstagePoseCallback(mPoseDispatcher);
814             } catch (RemoteException e) { }
815             mPoseListener = null;
816             mPoseDispatcher = null;
817         }
818     }
819 
820     /**
821      * @hide
822      * Sets an additional transform over the soundstage.
823      * The transform represents the pose of the soundstage, relative
824      * to either the device (in {@link #HEAD_TRACKING_MODE_RELATIVE_DEVICE} mode), the world (in
825      * {@link #HEAD_TRACKING_MODE_RELATIVE_WORLD}) or the listener’s head (in
826      * {@link #HEAD_TRACKING_MODE_DISABLED} mode).
827      * @param transform an array of 6 float values, the first 3 are the translation vector, the
828      *                  other 3 are the rotation vector.
829      */
830     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
831     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
setGlobalTransform(@onNull float[] transform)832     public void setGlobalTransform(@NonNull float[] transform) {
833         if (Objects.requireNonNull(transform).length != 6) {
834             throw new IllegalArgumentException("transform array must be of size 6, was "
835                     + transform.length);
836         }
837         try {
838             mAm.getService().setSpatializerGlobalTransform(transform);
839         } catch (RemoteException e) {
840             Log.e(TAG, "Error calling setGlobalTransform", e);
841         }
842     }
843 
844     /**
845      * @hide
846      * Sets a parameter on the platform spatializer effect implementation.
847      * This is to be used for vendor-specific configurations of their effect, keys and values are
848      * not reuseable across implementations.
849      * @param key the parameter to change
850      * @param value an array for the value of the parameter to change
851      */
852     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
853     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
setEffectParameter(int key, @NonNull byte[] value)854     public void setEffectParameter(int key, @NonNull byte[] value) {
855         Objects.requireNonNull(value);
856         try {
857             mAm.getService().setSpatializerParameter(key, value);
858         } catch (RemoteException e) {
859             Log.e(TAG, "Error calling setEffectParameter", e);
860         }
861     }
862 
863     /**
864      * @hide
865      * Retrieves a parameter value from the platform spatializer effect implementation.
866      * This is to be used for vendor-specific configurations of their effect, keys and values are
867      * not reuseable across implementations.
868      * @param key the parameter for which the value is queried
869      * @param value a non-empty array to contain the return value. The caller is responsible for
870      *              passing an array of size matching the parameter.
871      */
872     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
873     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
getEffectParameter(int key, @NonNull byte[] value)874     public void getEffectParameter(int key, @NonNull byte[] value) {
875         Objects.requireNonNull(value);
876         try {
877             mAm.getService().getSpatializerParameter(key, value);
878         } catch (RemoteException e) {
879             Log.e(TAG, "Error calling getEffectParameter", e);
880         }
881     }
882 
883     /**
884      * @hide
885      * Returns the id of the output stream used for the spatializer effect playback.
886      * This getter or associated listener {@link OnSpatializerOutputChangedListener} can be used for
887      * handling spatializer output-specific configurations (e.g. disabling speaker post-processing
888      * to avoid double-processing of the spatialized path).
889      * @return id of the output stream, or 0 if no spatializer playback is active
890      * @see #setOnSpatializerOutputChangedListener(Executor, OnSpatializerOutputChangedListener)
891      */
892     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
893     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
getOutput()894     public @IntRange(from = 0) int getOutput() {
895         try {
896             return mAm.getService().getSpatializerOutput();
897         } catch (RemoteException e) {
898             Log.e(TAG, "Error calling getSpatializerOutput", e);
899             return 0;
900         }
901     }
902 
903     /**
904      * @hide
905      * Sets the listener to receive spatializer effect output updates
906      * @param executor the {@code Executor} handling the callbacks
907      * @param listener the listener to register
908      * @see #clearOnSpatializerOutputChangedListener()
909      * @see #getOutput()
910      */
911     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
912     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
setOnSpatializerOutputChangedListener( @onNull @allbackExecutor Executor executor, @NonNull OnSpatializerOutputChangedListener listener)913     public void setOnSpatializerOutputChangedListener(
914             @NonNull @CallbackExecutor Executor executor,
915             @NonNull OnSpatializerOutputChangedListener listener) {
916         Objects.requireNonNull(executor);
917         Objects.requireNonNull(listener);
918         synchronized (mOutputListenerLock) {
919             if (mOutputListener != null) {
920                 throw new IllegalStateException("Trying to overwrite existing listener");
921             }
922             mOutputListener =
923                     new ListenerInfo<OnSpatializerOutputChangedListener>(listener, executor);
924             mOutputDispatcher = new SpatializerOutputDispatcherStub();
925             try {
926                 mAm.getService().registerSpatializerOutputCallback(mOutputDispatcher);
927                 // immediately report the current output
928                 mOutputDispatcher.dispatchSpatializerOutputChanged(getOutput());
929             } catch (RemoteException e) {
930                 mOutputListener = null;
931                 mOutputDispatcher = null;
932             }
933         }
934     }
935 
936     /**
937      * @hide
938      * Clears the listener for spatializer effect output updates
939      * @see #setOnSpatializerOutputChangedListener(Executor, OnSpatializerOutputChangedListener)
940      */
941     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
942     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
clearOnSpatializerOutputChangedListener()943     public void clearOnSpatializerOutputChangedListener() {
944         synchronized (mOutputListenerLock) {
945             if (mOutputDispatcher == null) {
946                 throw (new IllegalStateException("No listener to clear"));
947             }
948             try {
949                 mAm.getService().unregisterSpatializerOutputCallback(mOutputDispatcher);
950             } catch (RemoteException e) { }
951             mOutputListener = null;
952             mOutputDispatcher = null;
953         }
954     }
955 
956     //-----------------------------------------------------------------------------
957     // head tracking callback management and stub
958 
959     /**
960      * manages the OnHeadTrackingModeChangedListener listeners and the
961      * SpatializerHeadTrackingDispatcherStub
962      */
963     private final CallbackUtil.LazyListenerManager<OnHeadTrackingModeChangedListener>
964             mHeadTrackingListenerMgr = new CallbackUtil.LazyListenerManager();
965 
966     private final class SpatializerHeadTrackingDispatcherStub
967             extends ISpatializerHeadTrackingModeCallback.Stub
968             implements CallbackUtil.DispatcherStub {
969         @Override
register(boolean register)970         public void register(boolean register) {
971             try {
972                 if (register) {
973                     mAm.getService().registerSpatializerHeadTrackingCallback(this);
974                 } else {
975                     mAm.getService().unregisterSpatializerHeadTrackingCallback(this);
976                 }
977             } catch (RemoteException e) {
978                 e.rethrowFromSystemServer();
979             }
980         }
981 
982         @Override
983         @SuppressLint("GuardedBy") // lock applied inside callListeners method
dispatchSpatializerActualHeadTrackingModeChanged(int mode)984         public void dispatchSpatializerActualHeadTrackingModeChanged(int mode) {
985             mHeadTrackingListenerMgr.callListeners(
986                     (listener) -> listener.onHeadTrackingModeChanged(Spatializer.this, mode));
987         }
988 
989         @Override
990         @SuppressLint("GuardedBy") // lock applied inside callListeners method
dispatchSpatializerDesiredHeadTrackingModeChanged(int mode)991         public void dispatchSpatializerDesiredHeadTrackingModeChanged(int mode) {
992             mHeadTrackingListenerMgr.callListeners(
993                     (listener) -> listener.onDesiredHeadTrackingModeChanged(
994                             Spatializer.this, mode));
995         }
996     }
997 
998     //-----------------------------------------------------------------------------
999     // head tracker availability callback management and stub
1000     /**
1001      * manages the OnHeadTrackerAvailableListener listeners and the
1002      * SpatializerHeadTrackerAvailableDispatcherStub
1003      */
1004     private final CallbackUtil.LazyListenerManager<OnHeadTrackerAvailableListener>
1005             mHeadTrackerListenerMgr = new CallbackUtil.LazyListenerManager();
1006 
1007     private final class SpatializerHeadTrackerAvailableDispatcherStub
1008             extends ISpatializerHeadTrackerAvailableCallback.Stub
1009             implements CallbackUtil.DispatcherStub {
1010         @Override
register(boolean register)1011         public void register(boolean register) {
1012             try {
1013                 mAm.getService().registerSpatializerHeadTrackerAvailableCallback(this, register);
1014             } catch (RemoteException e) {
1015                 e.rethrowFromSystemServer();
1016             }
1017         }
1018 
1019         @Override
1020         @SuppressLint("GuardedBy") // lock applied inside callListeners method
dispatchSpatializerHeadTrackerAvailable(boolean available)1021         public void dispatchSpatializerHeadTrackerAvailable(boolean available) {
1022             mHeadTrackerListenerMgr.callListeners(
1023                     (listener) -> listener.onHeadTrackerAvailableChanged(
1024                             Spatializer.this, available));
1025         }
1026     }
1027 
1028     //-----------------------------------------------------------------------------
1029     // head pose callback management and stub
1030     private final Object mPoseListenerLock = new Object();
1031     /**
1032      * Listener for head to soundstage updates
1033      */
1034     @GuardedBy("mPoseListenerLock")
1035     private @Nullable ListenerInfo<OnHeadToSoundstagePoseUpdatedListener> mPoseListener;
1036     @GuardedBy("mPoseListenerLock")
1037     private @Nullable SpatializerPoseDispatcherStub mPoseDispatcher;
1038 
1039     private final class SpatializerPoseDispatcherStub
1040             extends ISpatializerHeadToSoundStagePoseCallback.Stub {
1041 
1042         @Override
dispatchPoseChanged(float[] pose)1043         public void dispatchPoseChanged(float[] pose) {
1044             // make a copy of ref to listener so callback is not executed under lock
1045             final ListenerInfo<OnHeadToSoundstagePoseUpdatedListener> listener;
1046             synchronized (mPoseListenerLock) {
1047                 listener = mPoseListener;
1048             }
1049             if (listener == null) {
1050                 return;
1051             }
1052             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
1053                 listener.mExecutor.execute(() -> listener.mListener
1054                         .onHeadToSoundstagePoseUpdated(Spatializer.this, pose));
1055             }
1056         }
1057     }
1058 
1059     //-----------------------------------------------------------------------------
1060     // output callback management and stub
1061     private final Object mOutputListenerLock = new Object();
1062     /**
1063      * Listener for output updates
1064      */
1065     @GuardedBy("mOutputListenerLock")
1066     private @Nullable ListenerInfo<OnSpatializerOutputChangedListener> mOutputListener;
1067     @GuardedBy("mOutputListenerLock")
1068     private @Nullable SpatializerOutputDispatcherStub mOutputDispatcher;
1069 
1070     private final class SpatializerOutputDispatcherStub
1071             extends ISpatializerOutputCallback.Stub {
1072 
1073         @Override
dispatchSpatializerOutputChanged(int output)1074         public void dispatchSpatializerOutputChanged(int output) {
1075             // make a copy of ref to listener so callback is not executed under lock
1076             final ListenerInfo<OnSpatializerOutputChangedListener> listener;
1077             synchronized (mOutputListenerLock) {
1078                 listener = mOutputListener;
1079             }
1080             if (listener == null) {
1081                 return;
1082             }
1083             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
1084                 listener.mExecutor.execute(() -> listener.mListener
1085                         .onSpatializerOutputChanged(Spatializer.this, output));
1086             }
1087         }
1088     }
1089 }
1090