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