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     })
271     @Retention(RetentionPolicy.SOURCE)
272     public @interface HeadTrackingMode {};
273 
274     /** @hide */
275     @IntDef(flag = false, value = {
276             HEAD_TRACKING_MODE_DISABLED,
277             HEAD_TRACKING_MODE_RELATIVE_WORLD,
278             HEAD_TRACKING_MODE_RELATIVE_DEVICE,
279     })
280     @Retention(RetentionPolicy.SOURCE)
281     public @interface HeadTrackingModeSet {};
282 
283     /** @hide */
284     @IntDef(flag = false, value = {
285             HEAD_TRACKING_MODE_RELATIVE_WORLD,
286             HEAD_TRACKING_MODE_RELATIVE_DEVICE,
287     })
288     @Retention(RetentionPolicy.SOURCE)
289     public @interface HeadTrackingModeSupported {};
290 
291     /**
292      * @hide
293      * Constant indicating head tracking is not supported by this {@code Spatializer}
294      * @see #getHeadTrackingMode()
295      */
296     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
297     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
298     public static final int HEAD_TRACKING_MODE_UNSUPPORTED = -2;
299 
300     /**
301      * @hide
302      * Constant indicating head tracking is disabled on this {@code Spatializer}
303      * @see #getHeadTrackingMode()
304      */
305     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
306     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
307     public static final int HEAD_TRACKING_MODE_DISABLED = -1;
308 
309     /**
310      * @hide
311      * Constant indicating head tracking is in a mode whose behavior is unknown. This is not an
312      * error state but represents a customized behavior not defined by this API.
313      * @see #getHeadTrackingMode()
314      */
315     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
316     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
317     public static final int HEAD_TRACKING_MODE_OTHER = 0;
318 
319     /**
320      * @hide
321      * Constant indicating head tracking is tracking the user's position / orientation relative to
322      * the world around them
323      * @see #getHeadTrackingMode()
324      */
325     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
326     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
327     public static final int HEAD_TRACKING_MODE_RELATIVE_WORLD = 1;
328 
329     /**
330      * @hide
331      * Constant indicating head tracking is tracking the user's position / orientation relative to
332      * the device
333      * @see #getHeadTrackingMode()
334      */
335     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
336     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
337     public static final int HEAD_TRACKING_MODE_RELATIVE_DEVICE = 2;
338 
339     /**
340      * @hide
341      * Head tracking mode to string conversion
342      * @param mode a valid head tracking mode
343      * @return a string containing the matching constant name
344      */
headtrackingModeToString(int mode)345     public static final String headtrackingModeToString(int mode) {
346         switch(mode) {
347             case HEAD_TRACKING_MODE_UNSUPPORTED:
348                 return "HEAD_TRACKING_MODE_UNSUPPORTED";
349             case HEAD_TRACKING_MODE_DISABLED:
350                 return "HEAD_TRACKING_MODE_DISABLED";
351             case HEAD_TRACKING_MODE_OTHER:
352                 return "HEAD_TRACKING_MODE_OTHER";
353             case HEAD_TRACKING_MODE_RELATIVE_WORLD:
354                 return "HEAD_TRACKING_MODE_RELATIVE_WORLD";
355             case HEAD_TRACKING_MODE_RELATIVE_DEVICE:
356                 return "HEAD_TRACKING_MODE_RELATIVE_DEVICE";
357             default:
358                 return "head tracking mode unknown " + mode;
359         }
360     }
361 
362     /**
363      * Return the level of support for the spatialization feature on this device.
364      * This level of support is independent of whether the {@code Spatializer} is currently
365      * enabled or available and will not change over time.
366      * @return the level of spatialization support
367      * @see #isEnabled()
368      * @see #isAvailable()
369      */
getImmersiveAudioLevel()370     public @ImmersiveAudioLevel int getImmersiveAudioLevel() {
371         int level = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
372         try {
373             level = mAm.getService().getSpatializerImmersiveAudioLevel();
374         } catch (Exception e) { /* using NONE */ }
375         return level;
376     }
377 
378     /**
379      * @hide
380      * Enables / disables the spatializer effect.
381      * Changing the enabled state will trigger the public
382      * {@link OnSpatializerStateChangedListener#onSpatializerEnabledChanged(Spatializer, boolean)}
383      * registered listeners.
384      * @param enabled {@code true} for enabling the effect
385      */
386     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
387     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
setEnabled(boolean enabled)388     public void setEnabled(boolean enabled) {
389         try {
390             mAm.getService().setSpatializerEnabled(enabled);
391         } catch (RemoteException e) {
392             Log.e(TAG, "Error calling setSpatializerEnabled", e);
393         }
394     }
395 
396     /**
397      * An interface to be notified of changes to the state of the spatializer effect.
398      */
399     public interface OnSpatializerStateChangedListener {
400         /**
401          * Called when the enabled state of the spatializer effect changes
402          * @param spat the {@code Spatializer} instance whose state changed
403          * @param enabled {@code true} if the spatializer effect is enabled on the device,
404          *                            {@code false} otherwise
405          * @see #isEnabled()
406          */
onSpatializerEnabledChanged(@onNull Spatializer spat, boolean enabled)407         void onSpatializerEnabledChanged(@NonNull Spatializer spat, boolean enabled);
408 
409         /**
410          * Called when the availability of the spatializer effect changes
411          * @param spat the {@code Spatializer} instance whose state changed
412          * @param available {@code true} if the spatializer effect is available and capable
413          *                  of processing the audio for the current configuration of the device,
414          *                  {@code false} otherwise.
415          * @see #isAvailable()
416          */
onSpatializerAvailableChanged(@onNull Spatializer spat, boolean available)417         void onSpatializerAvailableChanged(@NonNull Spatializer spat, boolean available);
418     }
419 
420     /**
421      * @hide
422      * An interface to be notified of changes to the head tracking mode, used by the spatializer
423      * effect.
424      * Changes to the mode may come from explicitly setting a different mode
425      * (see {@link #setDesiredHeadTrackingMode(int)}) or a change in system conditions (see
426      * {@link #getHeadTrackingMode()}
427      * @see #addOnHeadTrackingModeChangedListener(Executor, OnHeadTrackingModeChangedListener)
428      * @see #removeOnHeadTrackingModeChangedListener(OnHeadTrackingModeChangedListener)
429      */
430     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
431     public interface OnHeadTrackingModeChangedListener {
432         /**
433          * Called when the actual head tracking mode of the spatializer changed.
434          * @param spatializer the {@code Spatializer} instance whose head tracking mode is changing
435          * @param mode the new head tracking mode
436          */
onHeadTrackingModeChanged(@onNull Spatializer spatializer, @HeadTrackingMode int mode)437         void onHeadTrackingModeChanged(@NonNull Spatializer spatializer,
438                 @HeadTrackingMode int mode);
439 
440         /**
441          * Called when the desired head tracking mode of the spatializer changed
442          * @param spatializer the {@code Spatializer} instance whose head tracking mode was set
443          * @param mode the newly set head tracking mode
444          */
onDesiredHeadTrackingModeChanged(@onNull Spatializer spatializer, @HeadTrackingModeSet int mode)445         void onDesiredHeadTrackingModeChanged(@NonNull Spatializer spatializer,
446                 @HeadTrackingModeSet int mode);
447     }
448 
449     /**
450      * Interface to be notified of changes to the availability of a head tracker on the audio
451      * device to be used by the spatializer effect.
452      */
453     public interface OnHeadTrackerAvailableListener {
454         /**
455          * Called when the availability of the head tracker changed.
456          * @param spatializer the {@code Spatializer} instance for which the head tracker
457          *                    availability was updated
458          * @param available true if the audio device that would output audio processed by
459          *                  the {@code Spatializer} has a head tracker associated with it, false
460          *                  otherwise.
461          */
onHeadTrackerAvailableChanged(@onNull Spatializer spatializer, boolean available)462         void onHeadTrackerAvailableChanged(@NonNull Spatializer spatializer,
463                 boolean available);
464     }
465 
466     /**
467      * @hide
468      * An interface to be notified of changes to the output stream used by the spatializer
469      * effect.
470      * @see #getOutput()
471      */
472     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
473     public interface OnSpatializerOutputChangedListener {
474         /**
475          * Called when the id of the output stream of the spatializer effect changed.
476          * @param spatializer the {@code Spatializer} instance whose output is updated
477          * @param output the id of the output stream, or 0 when there is no spatializer output
478          */
onSpatializerOutputChanged(@onNull Spatializer spatializer, @IntRange(from = 0) int output)479         void onSpatializerOutputChanged(@NonNull Spatializer spatializer,
480                 @IntRange(from = 0) int output);
481     }
482 
483     /**
484      * @hide
485      * An interface to be notified of updates to the head to soundstage pose, as represented by the
486      * current head tracking mode.
487      * @see #setOnHeadToSoundstagePoseUpdatedListener(Executor, OnHeadToSoundstagePoseUpdatedListener)
488      */
489     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
490     public interface OnHeadToSoundstagePoseUpdatedListener {
491         /**
492          * Called when the head to soundstage transform is updated
493          * @param spatializer the {@code Spatializer} instance affected by the pose update
494          * @param pose the new pose data representing the transform between the frame
495          *                 of reference for the current head tracking mode (see
496          *                 {@link #getHeadTrackingMode()}) and the device being tracked (for
497          *                 instance a pair of headphones with a head tracker).<br>
498          *                 The head pose data is represented as an array of six float values, where
499          *                 the first three values are the translation vector, and the next three
500          *                 are the rotation vector.
501          */
onHeadToSoundstagePoseUpdated(@onNull Spatializer spatializer, @NonNull float[] pose)502         void onHeadToSoundstagePoseUpdated(@NonNull Spatializer spatializer,
503                 @NonNull float[] pose);
504     }
505 
506     /**
507      * Returns whether audio of the given {@link AudioFormat}, played with the given
508      * {@link AudioAttributes} can be spatialized.
509      * Note that the result reflects the capabilities of the device and may change when
510      * audio accessories are connected/disconnected (e.g. wired headphones plugged in or not).
511      * The result is independent from whether spatialization processing is enabled or not.
512      * @param attributes the {@code AudioAttributes} of the content as used for playback
513      * @param format the {@code AudioFormat} of the content as used for playback
514      * @return {@code true} if the device is capable of spatializing the combination of audio format
515      *     and attributes, {@code false} otherwise.
516      */
canBeSpatialized( @onNull AudioAttributes attributes, @NonNull AudioFormat format)517     public boolean canBeSpatialized(
518             @NonNull AudioAttributes attributes, @NonNull AudioFormat format) {
519         try {
520             return mAm.getService().canBeSpatialized(
521                     Objects.requireNonNull(attributes), Objects.requireNonNull(format));
522         } catch (RemoteException e) {
523             Log.e(TAG, "Error querying canBeSpatialized for attr:" + attributes
524                     + " format:" + format + " returning false", e);
525             return false;
526         }
527     }
528 
529     /**
530      * Adds a listener to be notified of changes to the enabled state of the
531      * {@code Spatializer}.
532      * @param executor the {@code Executor} handling the callback
533      * @param listener the listener to receive enabled state updates
534      * @see #isEnabled()
535      */
addOnSpatializerStateChangedListener( @onNull @allbackExecutor Executor executor, @NonNull OnSpatializerStateChangedListener listener)536     public void addOnSpatializerStateChangedListener(
537             @NonNull @CallbackExecutor Executor executor,
538             @NonNull OnSpatializerStateChangedListener listener) {
539         mStateListenerMgr.addListener(executor, listener, "addOnSpatializerStateChangedListener",
540                 () -> new SpatializerInfoDispatcherStub());
541     }
542 
543     /**
544      * Removes a previously added listener for changes to the enabled state of the
545      * {@code Spatializer}.
546      * @param listener the listener to receive enabled state updates
547      * @see #isEnabled()
548      */
removeOnSpatializerStateChangedListener( @onNull OnSpatializerStateChangedListener listener)549     public void removeOnSpatializerStateChangedListener(
550             @NonNull OnSpatializerStateChangedListener listener) {
551         mStateListenerMgr.removeListener(listener, "removeOnSpatializerStateChangedListener");
552     }
553 
554     /**
555      * @hide
556      * Returns the list of playback devices that are compatible with the playback of multichannel
557      * audio through virtualization
558      * @return a list of devices. An empty list indicates virtualization is not supported.
559      */
560     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
561     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
getCompatibleAudioDevices()562     public @NonNull List<AudioDeviceAttributes> getCompatibleAudioDevices() {
563         try {
564             return mAm.getService().getSpatializerCompatibleAudioDevices();
565         } catch (RemoteException e) {
566             Log.e(TAG, "Error querying getSpatializerCompatibleAudioDevices(), "
567                     + " returning empty list", e);
568             return new ArrayList<AudioDeviceAttributes>(0);
569         }
570     }
571 
572     /**
573      * @hide
574      * Adds a playback device to the list of devices compatible with the playback of multichannel
575      * audio through spatialization.
576      * @see #getCompatibleAudioDevices()
577      * @param ada the audio device compatible with spatialization
578      */
579     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
580     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
addCompatibleAudioDevice(@onNull AudioDeviceAttributes ada)581     public void addCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
582         try {
583             mAm.getService().addSpatializerCompatibleAudioDevice(Objects.requireNonNull(ada));
584         } catch (RemoteException e) {
585             Log.e(TAG, "Error calling addSpatializerCompatibleAudioDevice(), ", e);
586         }
587     }
588 
589     /**
590      * @hide
591      * Remove a playback device from the list of devices compatible with the playback of
592      * multichannel audio through spatialization.
593      * @see #getCompatibleAudioDevices()
594      * @param ada the audio device incompatible with spatialization
595      */
596     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
597     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
removeCompatibleAudioDevice(@onNull AudioDeviceAttributes ada)598     public void removeCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
599         try {
600             mAm.getService().removeSpatializerCompatibleAudioDevice(Objects.requireNonNull(ada));
601         } catch (RemoteException e) {
602             Log.e(TAG, "Error calling removeSpatializerCompatibleAudioDevice(), ", e);
603         }
604     }
605 
606     /**
607      * manages the OnSpatializerStateChangedListener listeners and the
608      * SpatializerInfoDispatcherStub
609      */
610     private final CallbackUtil.LazyListenerManager<OnSpatializerStateChangedListener>
611             mStateListenerMgr = new CallbackUtil.LazyListenerManager();
612 
613     private final class SpatializerInfoDispatcherStub extends ISpatializerCallback.Stub
614             implements CallbackUtil.DispatcherStub {
615         @Override
register(boolean register)616         public void register(boolean register) {
617             try {
618                 if (register) {
619                     mAm.getService().registerSpatializerCallback(this);
620                 } else {
621                     mAm.getService().unregisterSpatializerCallback(this);
622                 }
623             } catch (RemoteException e) {
624                 e.rethrowFromSystemServer();
625             }
626         }
627 
628         @Override
629         @SuppressLint("GuardedBy") // lock applied inside callListeners method
dispatchSpatializerEnabledChanged(boolean enabled)630         public void dispatchSpatializerEnabledChanged(boolean enabled) {
631             mStateListenerMgr.callListeners(
632                     (listener) -> listener.onSpatializerEnabledChanged(
633                             Spatializer.this, enabled));
634         }
635 
636         @Override
637         @SuppressLint("GuardedBy") // lock applied inside callListeners method
dispatchSpatializerAvailableChanged(boolean available)638         public void dispatchSpatializerAvailableChanged(boolean available) {
639             mStateListenerMgr.callListeners(
640                     (listener) -> listener.onSpatializerAvailableChanged(
641                             Spatializer.this, available));
642         }
643     }
644 
645 
646     /**
647      * @hide
648      * Return the current head tracking mode as used by the system.
649      * Note this may differ from the desired head tracking mode. Reasons for the two to differ
650      * include: a head tracking device is not available for the current audio output device,
651      * the transmission conditions between the tracker and device have deteriorated and tracking
652      * has been disabled.
653      * @see #getDesiredHeadTrackingMode()
654      * @return the current head tracking mode
655      */
656     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
657     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
getHeadTrackingMode()658     public @HeadTrackingMode int getHeadTrackingMode() {
659         try {
660             return mAm.getService().getActualHeadTrackingMode();
661         } catch (RemoteException e) {
662             Log.e(TAG, "Error calling getActualHeadTrackingMode", e);
663             return HEAD_TRACKING_MODE_UNSUPPORTED;
664         }
665 
666     }
667 
668     /**
669      * @hide
670      * Return the desired head tracking mode.
671      * Note this may differ from the actual head tracking mode, reflected by
672      * {@link #getHeadTrackingMode()}.
673      * @return the desired head tring mode
674      */
675     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
676     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
getDesiredHeadTrackingMode()677     public @HeadTrackingMode int getDesiredHeadTrackingMode() {
678         try {
679             return mAm.getService().getDesiredHeadTrackingMode();
680         } catch (RemoteException e) {
681             Log.e(TAG, "Error calling getDesiredHeadTrackingMode", e);
682             return HEAD_TRACKING_MODE_UNSUPPORTED;
683         }
684     }
685 
686     /**
687      * @hide
688      * Returns the list of supported head tracking modes.
689      * @return the list of modes that can be used in {@link #setDesiredHeadTrackingMode(int)} to
690      *         enable head tracking. The list will be empty if {@link #getHeadTrackingMode()}
691      *         is {@link #HEAD_TRACKING_MODE_UNSUPPORTED}. Values can be
692      *         {@link #HEAD_TRACKING_MODE_OTHER},
693      *         {@link #HEAD_TRACKING_MODE_RELATIVE_WORLD} or
694      *         {@link #HEAD_TRACKING_MODE_RELATIVE_DEVICE}
695      */
696     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
697     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
getSupportedHeadTrackingModes()698     public @NonNull List<Integer> getSupportedHeadTrackingModes() {
699         try {
700             final int[] modes = mAm.getService().getSupportedHeadTrackingModes();
701             final ArrayList<Integer> list = new ArrayList<>(0);
702             for (int mode : modes) {
703                 list.add(mode);
704             }
705             return list;
706         } catch (RemoteException e) {
707             Log.e(TAG, "Error calling getSupportedHeadTrackModes", e);
708             return new ArrayList(0);
709         }
710     }
711 
712     /**
713      * @hide
714      * Sets the desired head tracking mode.
715      * Note a set desired mode may differ from the actual head tracking mode.
716      * @see #getHeadTrackingMode()
717      * @param mode the desired head tracking mode, one of the values returned by
718      *             {@link #getSupportedHeadTrackModes()}, or {@link #HEAD_TRACKING_MODE_DISABLED} to
719      *             disable head tracking.
720      */
721     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
722     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
setDesiredHeadTrackingMode(@eadTrackingModeSet int mode)723     public void setDesiredHeadTrackingMode(@HeadTrackingModeSet int mode) {
724         try {
725             mAm.getService().setDesiredHeadTrackingMode(mode);
726         } catch (RemoteException e) {
727             Log.e(TAG, "Error calling setDesiredHeadTrackingMode to " + mode, e);
728         }
729     }
730 
731     /**
732      * @hide
733      * Recenters the head tracking at the current position / orientation.
734      */
735     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
736     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
recenterHeadTracker()737     public void recenterHeadTracker() {
738         try {
739             mAm.getService().recenterHeadTracker();
740         } catch (RemoteException e) {
741             Log.e(TAG, "Error calling recenterHeadTracker", e);
742         }
743     }
744 
745     /**
746      * @hide
747      * Adds a listener to be notified of changes to the head tracking mode of the
748      * {@code Spatializer}
749      * @param executor the {@code Executor} handling the callbacks
750      * @param listener the listener to register
751      */
752     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
753     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
addOnHeadTrackingModeChangedListener( @onNull @allbackExecutor Executor executor, @NonNull OnHeadTrackingModeChangedListener listener)754     public void addOnHeadTrackingModeChangedListener(
755             @NonNull @CallbackExecutor Executor executor,
756             @NonNull OnHeadTrackingModeChangedListener listener) {
757         mHeadTrackingListenerMgr.addListener(executor, listener,
758                 "addOnHeadTrackingModeChangedListener",
759                  () -> new SpatializerHeadTrackingDispatcherStub());
760     }
761 
762     /**
763      * @hide
764      * Removes a previously added listener for changes to the head tracking mode of the
765      * {@code Spatializer}.
766      * @param listener the listener to unregister
767      */
768     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
769     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
removeOnHeadTrackingModeChangedListener( @onNull OnHeadTrackingModeChangedListener listener)770     public void removeOnHeadTrackingModeChangedListener(
771             @NonNull OnHeadTrackingModeChangedListener listener) {
772         mHeadTrackingListenerMgr.removeListener(listener,
773                 "removeOnHeadTrackingModeChangedListener");
774     }
775 
776     /**
777      * @hide
778      * Set the listener to receive head to soundstage pose updates.
779      * @param executor the {@code Executor} handling the callbacks
780      * @param listener the listener to register
781      * @see #clearOnHeadToSoundstagePoseUpdatedListener()
782      */
783     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
784     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
setOnHeadToSoundstagePoseUpdatedListener( @onNull @allbackExecutor Executor executor, @NonNull OnHeadToSoundstagePoseUpdatedListener listener)785     public void setOnHeadToSoundstagePoseUpdatedListener(
786             @NonNull @CallbackExecutor Executor executor,
787             @NonNull OnHeadToSoundstagePoseUpdatedListener listener) {
788         Objects.requireNonNull(executor);
789         Objects.requireNonNull(listener);
790         synchronized (mPoseListenerLock) {
791             if (mPoseListener != null) {
792                 throw new IllegalStateException("Trying to overwrite existing listener");
793             }
794             mPoseListener =
795                     new ListenerInfo<OnHeadToSoundstagePoseUpdatedListener>(listener, executor);
796             mPoseDispatcher = new SpatializerPoseDispatcherStub();
797             try {
798                 mAm.getService().registerHeadToSoundstagePoseCallback(mPoseDispatcher);
799             } catch (RemoteException e) {
800                 mPoseListener = null;
801                 mPoseDispatcher = null;
802             }
803         }
804     }
805 
806     /**
807      * @hide
808      * Clears the listener for head to soundstage pose updates
809      * @see #setOnHeadToSoundstagePoseUpdatedListener(Executor, OnHeadToSoundstagePoseUpdatedListener)
810      */
811     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
812     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
clearOnHeadToSoundstagePoseUpdatedListener()813     public void clearOnHeadToSoundstagePoseUpdatedListener() {
814         synchronized (mPoseListenerLock) {
815             if (mPoseDispatcher == null) {
816                 throw (new IllegalStateException("No listener to clear"));
817             }
818             try {
819                 mAm.getService().unregisterHeadToSoundstagePoseCallback(mPoseDispatcher);
820             } catch (RemoteException e) { }
821             mPoseListener = null;
822             mPoseDispatcher = null;
823         }
824     }
825 
826     /**
827      * @hide
828      * Sets an additional transform over the soundstage.
829      * The transform represents the pose of the soundstage, relative
830      * to either the device (in {@link #HEAD_TRACKING_MODE_RELATIVE_DEVICE} mode), the world (in
831      * {@link #HEAD_TRACKING_MODE_RELATIVE_WORLD}) or the listener’s head (in
832      * {@link #HEAD_TRACKING_MODE_DISABLED} mode).
833      * @param transform an array of 6 float values, the first 3 are the translation vector, the
834      *                  other 3 are the rotation vector.
835      */
836     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
837     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
setGlobalTransform(@onNull float[] transform)838     public void setGlobalTransform(@NonNull float[] transform) {
839         if (Objects.requireNonNull(transform).length != 6) {
840             throw new IllegalArgumentException("transform array must be of size 6, was "
841                     + transform.length);
842         }
843         try {
844             mAm.getService().setSpatializerGlobalTransform(transform);
845         } catch (RemoteException e) {
846             Log.e(TAG, "Error calling setGlobalTransform", e);
847         }
848     }
849 
850     /**
851      * @hide
852      * Sets a parameter on the platform spatializer effect implementation.
853      * This is to be used for vendor-specific configurations of their effect, keys and values are
854      * not reuseable across implementations.
855      * @param key the parameter to change
856      * @param value an array for the value of the parameter to change
857      */
858     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
859     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
setEffectParameter(int key, @NonNull byte[] value)860     public void setEffectParameter(int key, @NonNull byte[] value) {
861         Objects.requireNonNull(value);
862         try {
863             mAm.getService().setSpatializerParameter(key, value);
864         } catch (RemoteException e) {
865             Log.e(TAG, "Error calling setEffectParameter", e);
866         }
867     }
868 
869     /**
870      * @hide
871      * Retrieves a parameter value from the platform spatializer effect implementation.
872      * This is to be used for vendor-specific configurations of their effect, keys and values are
873      * not reuseable across implementations.
874      * @param key the parameter for which the value is queried
875      * @param value a non-empty array to contain the return value. The caller is responsible for
876      *              passing an array of size matching the parameter.
877      */
878     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
879     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
getEffectParameter(int key, @NonNull byte[] value)880     public void getEffectParameter(int key, @NonNull byte[] value) {
881         Objects.requireNonNull(value);
882         try {
883             mAm.getService().getSpatializerParameter(key, value);
884         } catch (RemoteException e) {
885             Log.e(TAG, "Error calling getEffectParameter", e);
886         }
887     }
888 
889     /**
890      * @hide
891      * Returns the id of the output stream used for the spatializer effect playback.
892      * This getter or associated listener {@link OnSpatializerOutputChangedListener} can be used for
893      * handling spatializer output-specific configurations (e.g. disabling speaker post-processing
894      * to avoid double-processing of the spatialized path).
895      * @return id of the output stream, or 0 if no spatializer playback is active
896      * @see #setOnSpatializerOutputChangedListener(Executor, OnSpatializerOutputChangedListener)
897      */
898     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
899     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
getOutput()900     public @IntRange(from = 0) int getOutput() {
901         try {
902             return mAm.getService().getSpatializerOutput();
903         } catch (RemoteException e) {
904             Log.e(TAG, "Error calling getSpatializerOutput", e);
905             return 0;
906         }
907     }
908 
909     /**
910      * @hide
911      * Sets the listener to receive spatializer effect output updates
912      * @param executor the {@code Executor} handling the callbacks
913      * @param listener the listener to register
914      * @see #clearOnSpatializerOutputChangedListener()
915      * @see #getOutput()
916      */
917     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
918     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
setOnSpatializerOutputChangedListener( @onNull @allbackExecutor Executor executor, @NonNull OnSpatializerOutputChangedListener listener)919     public void setOnSpatializerOutputChangedListener(
920             @NonNull @CallbackExecutor Executor executor,
921             @NonNull OnSpatializerOutputChangedListener listener) {
922         Objects.requireNonNull(executor);
923         Objects.requireNonNull(listener);
924         synchronized (mOutputListenerLock) {
925             if (mOutputListener != null) {
926                 throw new IllegalStateException("Trying to overwrite existing listener");
927             }
928             mOutputListener =
929                     new ListenerInfo<OnSpatializerOutputChangedListener>(listener, executor);
930             mOutputDispatcher = new SpatializerOutputDispatcherStub();
931             try {
932                 mAm.getService().registerSpatializerOutputCallback(mOutputDispatcher);
933                 // immediately report the current output
934                 mOutputDispatcher.dispatchSpatializerOutputChanged(getOutput());
935             } catch (RemoteException e) {
936                 mOutputListener = null;
937                 mOutputDispatcher = null;
938             }
939         }
940     }
941 
942     /**
943      * @hide
944      * Clears the listener for spatializer effect output updates
945      * @see #setOnSpatializerOutputChangedListener(Executor, OnSpatializerOutputChangedListener)
946      */
947     @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
948     @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
clearOnSpatializerOutputChangedListener()949     public void clearOnSpatializerOutputChangedListener() {
950         synchronized (mOutputListenerLock) {
951             if (mOutputDispatcher == null) {
952                 throw (new IllegalStateException("No listener to clear"));
953             }
954             try {
955                 mAm.getService().unregisterSpatializerOutputCallback(mOutputDispatcher);
956             } catch (RemoteException e) { }
957             mOutputListener = null;
958             mOutputDispatcher = null;
959         }
960     }
961 
962     //-----------------------------------------------------------------------------
963     // head tracking callback management and stub
964 
965     /**
966      * manages the OnHeadTrackingModeChangedListener listeners and the
967      * SpatializerHeadTrackingDispatcherStub
968      */
969     private final CallbackUtil.LazyListenerManager<OnHeadTrackingModeChangedListener>
970             mHeadTrackingListenerMgr = new CallbackUtil.LazyListenerManager();
971 
972     private final class SpatializerHeadTrackingDispatcherStub
973             extends ISpatializerHeadTrackingModeCallback.Stub
974             implements CallbackUtil.DispatcherStub {
975         @Override
register(boolean register)976         public void register(boolean register) {
977             try {
978                 if (register) {
979                     mAm.getService().registerSpatializerHeadTrackingCallback(this);
980                 } else {
981                     mAm.getService().unregisterSpatializerHeadTrackingCallback(this);
982                 }
983             } catch (RemoteException e) {
984                 e.rethrowFromSystemServer();
985             }
986         }
987 
988         @Override
989         @SuppressLint("GuardedBy") // lock applied inside callListeners method
dispatchSpatializerActualHeadTrackingModeChanged(int mode)990         public void dispatchSpatializerActualHeadTrackingModeChanged(int mode) {
991             mHeadTrackingListenerMgr.callListeners(
992                     (listener) -> listener.onHeadTrackingModeChanged(Spatializer.this, mode));
993         }
994 
995         @Override
996         @SuppressLint("GuardedBy") // lock applied inside callListeners method
dispatchSpatializerDesiredHeadTrackingModeChanged(int mode)997         public void dispatchSpatializerDesiredHeadTrackingModeChanged(int mode) {
998             mHeadTrackingListenerMgr.callListeners(
999                     (listener) -> listener.onDesiredHeadTrackingModeChanged(
1000                             Spatializer.this, mode));
1001         }
1002     }
1003 
1004     //-----------------------------------------------------------------------------
1005     // head tracker availability callback management and stub
1006     /**
1007      * manages the OnHeadTrackerAvailableListener listeners and the
1008      * SpatializerHeadTrackerAvailableDispatcherStub
1009      */
1010     private final CallbackUtil.LazyListenerManager<OnHeadTrackerAvailableListener>
1011             mHeadTrackerListenerMgr = new CallbackUtil.LazyListenerManager();
1012 
1013     private final class SpatializerHeadTrackerAvailableDispatcherStub
1014             extends ISpatializerHeadTrackerAvailableCallback.Stub
1015             implements CallbackUtil.DispatcherStub {
1016         @Override
register(boolean register)1017         public void register(boolean register) {
1018             try {
1019                 mAm.getService().registerSpatializerHeadTrackerAvailableCallback(this, register);
1020             } catch (RemoteException e) {
1021                 e.rethrowFromSystemServer();
1022             }
1023         }
1024 
1025         @Override
1026         @SuppressLint("GuardedBy") // lock applied inside callListeners method
dispatchSpatializerHeadTrackerAvailable(boolean available)1027         public void dispatchSpatializerHeadTrackerAvailable(boolean available) {
1028             mHeadTrackerListenerMgr.callListeners(
1029                     (listener) -> listener.onHeadTrackerAvailableChanged(
1030                             Spatializer.this, available));
1031         }
1032     }
1033 
1034     //-----------------------------------------------------------------------------
1035     // head pose callback management and stub
1036     private final Object mPoseListenerLock = new Object();
1037     /**
1038      * Listener for head to soundstage updates
1039      */
1040     @GuardedBy("mPoseListenerLock")
1041     private @Nullable ListenerInfo<OnHeadToSoundstagePoseUpdatedListener> mPoseListener;
1042     @GuardedBy("mPoseListenerLock")
1043     private @Nullable SpatializerPoseDispatcherStub mPoseDispatcher;
1044 
1045     private final class SpatializerPoseDispatcherStub
1046             extends ISpatializerHeadToSoundStagePoseCallback.Stub {
1047 
1048         @Override
dispatchPoseChanged(float[] pose)1049         public void dispatchPoseChanged(float[] pose) {
1050             // make a copy of ref to listener so callback is not executed under lock
1051             final ListenerInfo<OnHeadToSoundstagePoseUpdatedListener> listener;
1052             synchronized (mPoseListenerLock) {
1053                 listener = mPoseListener;
1054             }
1055             if (listener == null) {
1056                 return;
1057             }
1058             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
1059                 listener.mExecutor.execute(() -> listener.mListener
1060                         .onHeadToSoundstagePoseUpdated(Spatializer.this, pose));
1061             }
1062         }
1063     }
1064 
1065     //-----------------------------------------------------------------------------
1066     // output callback management and stub
1067     private final Object mOutputListenerLock = new Object();
1068     /**
1069      * Listener for output updates
1070      */
1071     @GuardedBy("mOutputListenerLock")
1072     private @Nullable ListenerInfo<OnSpatializerOutputChangedListener> mOutputListener;
1073     @GuardedBy("mOutputListenerLock")
1074     private @Nullable SpatializerOutputDispatcherStub mOutputDispatcher;
1075 
1076     private final class SpatializerOutputDispatcherStub
1077             extends ISpatializerOutputCallback.Stub {
1078 
1079         @Override
dispatchSpatializerOutputChanged(int output)1080         public void dispatchSpatializerOutputChanged(int output) {
1081             // make a copy of ref to listener so callback is not executed under lock
1082             final ListenerInfo<OnSpatializerOutputChangedListener> listener;
1083             synchronized (mOutputListenerLock) {
1084                 listener = mOutputListener;
1085             }
1086             if (listener == null) {
1087                 return;
1088             }
1089             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
1090                 listener.mExecutor.execute(() -> listener.mListener
1091                         .onSpatializerOutputChanged(Spatializer.this, output));
1092             }
1093         }
1094     }
1095 }
1096