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