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