1096 lines
46 KiB
Java
1096 lines
46 KiB
Java
/*
|
||
* Copyright (C) 2021 The Android Open Source Project
|
||
*
|
||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
* you may not use this file except in compliance with the License.
|
||
* You may obtain a copy of the License at
|
||
*
|
||
* http://www.apache.org/licenses/LICENSE-2.0
|
||
*
|
||
* Unless required by applicable law or agreed to in writing, software
|
||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
* See the License for the specific language governing permissions and
|
||
* limitations under the License.
|
||
*/
|
||
|
||
package android.media;
|
||
|
||
import android.annotation.CallbackExecutor;
|
||
import android.annotation.IntDef;
|
||
import android.annotation.IntRange;
|
||
import android.annotation.NonNull;
|
||
import android.annotation.Nullable;
|
||
import android.annotation.RequiresPermission;
|
||
import android.annotation.SuppressLint;
|
||
import android.annotation.SystemApi;
|
||
import android.media.CallbackUtil.ListenerInfo;
|
||
import android.media.permission.ClearCallingIdentityContext;
|
||
import android.media.permission.SafeCloseable;
|
||
import android.os.RemoteException;
|
||
import android.util.Log;
|
||
|
||
import com.android.internal.annotations.GuardedBy;
|
||
|
||
import java.lang.annotation.Retention;
|
||
import java.lang.annotation.RetentionPolicy;
|
||
import java.util.ArrayList;
|
||
import java.util.List;
|
||
import java.util.Objects;
|
||
import java.util.concurrent.Executor;
|
||
|
||
/**
|
||
* Spatializer provides access to querying capabilities and behavior of sound spatialization
|
||
* on the device.
|
||
* Sound spatialization simulates sounds originating around the listener as if they were coming
|
||
* from virtual speakers placed around the listener.<br>
|
||
* Support for spatialization is optional, use {@link AudioManager#getSpatializer()} to obtain an
|
||
* instance of this class if the feature is supported.
|
||
*
|
||
*/
|
||
public class Spatializer {
|
||
|
||
private final @NonNull AudioManager mAm;
|
||
|
||
private static final String TAG = "Spatializer";
|
||
|
||
/**
|
||
* @hide
|
||
* Constructor with AudioManager acting as proxy to AudioService
|
||
* @param am a non-null AudioManager
|
||
*/
|
||
protected Spatializer(@NonNull AudioManager am) {
|
||
mAm = Objects.requireNonNull(am);
|
||
}
|
||
|
||
/**
|
||
* Returns whether spatialization is enabled or not.
|
||
* A false value can originate for instance from the user electing to
|
||
* disable the feature, or when the feature is not supported on the device (indicated
|
||
* by {@link #getImmersiveAudioLevel()} returning {@link #SPATIALIZER_IMMERSIVE_LEVEL_NONE}).
|
||
* <br>
|
||
* Note that this state reflects a platform-wide state of the "desire" to use spatialization,
|
||
* but availability of the audio processing is still dictated by the compatibility between
|
||
* the effect and the hardware configuration, as indicated by {@link #isAvailable()}.
|
||
* @return {@code true} if spatialization is enabled
|
||
* @see #isAvailable()
|
||
*/
|
||
public boolean isEnabled() {
|
||
try {
|
||
return mAm.getService().isSpatializerEnabled();
|
||
} catch (RemoteException e) {
|
||
Log.e(TAG, "Error querying isSpatializerEnabled, returning false", e);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Returns whether spatialization is available.
|
||
* Reasons for spatialization being unavailable include situations where audio output is
|
||
* incompatible with sound spatialization, such as playback on a monophonic speaker.<br>
|
||
* Note that spatialization can be available, but disabled by the user, in which case this
|
||
* method would still return {@code true}, whereas {@link #isEnabled()}
|
||
* would return {@code false}.<br>
|
||
* Also when the feature is not supported on the device (indicated
|
||
* by {@link #getImmersiveAudioLevel()} returning {@link #SPATIALIZER_IMMERSIVE_LEVEL_NONE}),
|
||
* the return value will be false.
|
||
* @return {@code true} if the spatializer effect is available and capable
|
||
* of processing the audio for the current configuration of the device,
|
||
* {@code false} otherwise.
|
||
* @see #isEnabled()
|
||
*/
|
||
public boolean isAvailable() {
|
||
try {
|
||
return mAm.getService().isSpatializerAvailable();
|
||
} catch (RemoteException e) {
|
||
Log.e(TAG, "Error querying isSpatializerAvailable, returning false", e);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @hide
|
||
* Returns whether spatialization is available for a given audio device
|
||
* Reasons for spatialization being unavailable include situations where audio output is
|
||
* incompatible with sound spatialization, such as the device being a monophonic speaker, or
|
||
* the spatializer effect not supporting transaural processing when querying for speaker.
|
||
* @param device the audio device for which spatializer availability is queried
|
||
* @return {@code true} if the spatializer effect is available and capable
|
||
* of processing the audio over the given audio device,
|
||
* {@code false} otherwise.
|
||
* @see #isEnabled()
|
||
*/
|
||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
||
@RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
|
||
public boolean isAvailableForDevice(@NonNull AudioDeviceAttributes device) {
|
||
Objects.requireNonNull(device);
|
||
try {
|
||
return mAm.getService().isSpatializerAvailableForDevice(device);
|
||
} catch (RemoteException e) {
|
||
e.rethrowFromSystemServer();
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* @hide
|
||
* Returns whether the given device has an associated headtracker
|
||
* @param device the audio device to query
|
||
* @return true if the device has a head tracker, false otherwise
|
||
*/
|
||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
||
@RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
|
||
public boolean hasHeadTracker(@NonNull AudioDeviceAttributes device) {
|
||
Objects.requireNonNull(device);
|
||
try {
|
||
return mAm.getService().hasHeadTracker(device);
|
||
} catch (RemoteException e) {
|
||
e.rethrowFromSystemServer();
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* @hide
|
||
* Enables or disables the head tracker of the given device
|
||
* @param enabled true to enable, false to disable
|
||
* @param device the device whose head tracker state is changed
|
||
*/
|
||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
||
@RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
|
||
public void setHeadTrackerEnabled(boolean enabled, @NonNull AudioDeviceAttributes device) {
|
||
Objects.requireNonNull(device);
|
||
try {
|
||
mAm.getService().setHeadTrackerEnabled(enabled, device);
|
||
} catch (RemoteException e) {
|
||
e.rethrowFromSystemServer();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @hide
|
||
* Returns whether the head tracker of the device is enabled
|
||
* @param device the device to query
|
||
* @return true if the head tracker is enabled, false if disabled or if there isn't one
|
||
*/
|
||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
||
@RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
|
||
public boolean isHeadTrackerEnabled(@NonNull AudioDeviceAttributes device) {
|
||
Objects.requireNonNull(device);
|
||
try {
|
||
return mAm.getService().isHeadTrackerEnabled(device);
|
||
} catch (RemoteException e) {
|
||
e.rethrowFromSystemServer();
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* Returns whether a head tracker is currently available for the audio device used by the
|
||
* spatializer effect.
|
||
* @return true if a head tracker is available and the effect is enabled, false otherwise.
|
||
* @see OnHeadTrackerAvailableListener
|
||
* @see #addOnHeadTrackerAvailableListener(Executor, OnHeadTrackerAvailableListener)
|
||
*/
|
||
public boolean isHeadTrackerAvailable() {
|
||
try {
|
||
return mAm.getService().isHeadTrackerAvailable();
|
||
} catch (RemoteException e) {
|
||
e.rethrowFromSystemServer();
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* Adds a listener to be notified of changes to the availability of a head tracker.
|
||
* @param executor the {@code Executor} handling the callback
|
||
* @param listener the listener to receive availability updates
|
||
* @see #removeOnHeadTrackerAvailableListener(OnHeadTrackerAvailableListener)
|
||
*/
|
||
public void addOnHeadTrackerAvailableListener(@NonNull @CallbackExecutor Executor executor,
|
||
@NonNull OnHeadTrackerAvailableListener listener) {
|
||
mHeadTrackerListenerMgr.addListener(executor, listener,
|
||
"addOnHeadTrackerAvailableListener",
|
||
() -> new SpatializerHeadTrackerAvailableDispatcherStub());
|
||
}
|
||
|
||
/**
|
||
* Removes a previously registered listener for the availability of a head tracker.
|
||
* @param listener the listener previously registered with
|
||
* {@link #addOnHeadTrackerAvailableListener(Executor, OnHeadTrackerAvailableListener)}
|
||
*/
|
||
public void removeOnHeadTrackerAvailableListener(
|
||
@NonNull OnHeadTrackerAvailableListener listener) {
|
||
mHeadTrackerListenerMgr.removeListener(listener, "removeOnHeadTrackerAvailableListener");
|
||
}
|
||
|
||
/** @hide */
|
||
@IntDef(flag = false, value = {
|
||
SPATIALIZER_IMMERSIVE_LEVEL_OTHER,
|
||
SPATIALIZER_IMMERSIVE_LEVEL_NONE,
|
||
SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL,
|
||
})
|
||
@Retention(RetentionPolicy.SOURCE)
|
||
public @interface ImmersiveAudioLevel {};
|
||
|
||
/**
|
||
* Constant indicating the {@code Spatializer} on this device supports a spatialization
|
||
* mode that differs from the ones available at this SDK level.
|
||
* @see #getImmersiveAudioLevel()
|
||
*/
|
||
public static final int SPATIALIZER_IMMERSIVE_LEVEL_OTHER = -1;
|
||
|
||
/**
|
||
* Constant indicating there are no spatialization capabilities supported on this device.
|
||
* @see #getImmersiveAudioLevel()
|
||
*/
|
||
public static final int SPATIALIZER_IMMERSIVE_LEVEL_NONE = 0;
|
||
|
||
/**
|
||
* Constant indicating the {@code Spatializer} on this device supports multichannel
|
||
* spatialization.
|
||
* @see #getImmersiveAudioLevel()
|
||
*/
|
||
public static final int SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL = 1;
|
||
|
||
/**
|
||
* @hide
|
||
* Constant indicating the {@code Spatializer} on this device supports the spatialization of
|
||
* multichannel bed plus objects.
|
||
* @see #getImmersiveAudioLevel()
|
||
*/
|
||
public static final int SPATIALIZER_IMMERSIVE_LEVEL_MCHAN_BED_PLUS_OBJECTS = 2;
|
||
|
||
/** @hide */
|
||
@IntDef(flag = false, value = {
|
||
HEAD_TRACKING_MODE_UNSUPPORTED,
|
||
HEAD_TRACKING_MODE_DISABLED,
|
||
HEAD_TRACKING_MODE_RELATIVE_WORLD,
|
||
HEAD_TRACKING_MODE_RELATIVE_DEVICE,
|
||
})
|
||
@Retention(RetentionPolicy.SOURCE)
|
||
public @interface HeadTrackingMode {};
|
||
|
||
/** @hide */
|
||
@IntDef(flag = false, value = {
|
||
HEAD_TRACKING_MODE_DISABLED,
|
||
HEAD_TRACKING_MODE_RELATIVE_WORLD,
|
||
HEAD_TRACKING_MODE_RELATIVE_DEVICE,
|
||
})
|
||
@Retention(RetentionPolicy.SOURCE)
|
||
public @interface HeadTrackingModeSet {};
|
||
|
||
/** @hide */
|
||
@IntDef(flag = false, value = {
|
||
HEAD_TRACKING_MODE_RELATIVE_WORLD,
|
||
HEAD_TRACKING_MODE_RELATIVE_DEVICE,
|
||
})
|
||
@Retention(RetentionPolicy.SOURCE)
|
||
public @interface HeadTrackingModeSupported {};
|
||
|
||
/**
|
||
* @hide
|
||
* Constant indicating head tracking is not supported by this {@code Spatializer}
|
||
* @see #getHeadTrackingMode()
|
||
*/
|
||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
||
@RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
|
||
public static final int HEAD_TRACKING_MODE_UNSUPPORTED = -2;
|
||
|
||
/**
|
||
* @hide
|
||
* Constant indicating head tracking is disabled on this {@code Spatializer}
|
||
* @see #getHeadTrackingMode()
|
||
*/
|
||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
||
@RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
|
||
public static final int HEAD_TRACKING_MODE_DISABLED = -1;
|
||
|
||
/**
|
||
* @hide
|
||
* Constant indicating head tracking is in a mode whose behavior is unknown. This is not an
|
||
* error state but represents a customized behavior not defined by this API.
|
||
* @see #getHeadTrackingMode()
|
||
*/
|
||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
||
@RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
|
||
public static final int HEAD_TRACKING_MODE_OTHER = 0;
|
||
|
||
/**
|
||
* @hide
|
||
* Constant indicating head tracking is tracking the user's position / orientation relative to
|
||
* the world around them
|
||
* @see #getHeadTrackingMode()
|
||
*/
|
||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
||
@RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
|
||
public static final int HEAD_TRACKING_MODE_RELATIVE_WORLD = 1;
|
||
|
||
/**
|
||
* @hide
|
||
* Constant indicating head tracking is tracking the user's position / orientation relative to
|
||
* the device
|
||
* @see #getHeadTrackingMode()
|
||
*/
|
||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
||
@RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
|
||
public static final int HEAD_TRACKING_MODE_RELATIVE_DEVICE = 2;
|
||
|
||
/**
|
||
* @hide
|
||
* Head tracking mode to string conversion
|
||
* @param mode a valid head tracking mode
|
||
* @return a string containing the matching constant name
|
||
*/
|
||
public static final String headtrackingModeToString(int mode) {
|
||
switch(mode) {
|
||
case HEAD_TRACKING_MODE_UNSUPPORTED:
|
||
return "HEAD_TRACKING_MODE_UNSUPPORTED";
|
||
case HEAD_TRACKING_MODE_DISABLED:
|
||
return "HEAD_TRACKING_MODE_DISABLED";
|
||
case HEAD_TRACKING_MODE_OTHER:
|
||
return "HEAD_TRACKING_MODE_OTHER";
|
||
case HEAD_TRACKING_MODE_RELATIVE_WORLD:
|
||
return "HEAD_TRACKING_MODE_RELATIVE_WORLD";
|
||
case HEAD_TRACKING_MODE_RELATIVE_DEVICE:
|
||
return "HEAD_TRACKING_MODE_RELATIVE_DEVICE";
|
||
default:
|
||
return "head tracking mode unknown " + mode;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Return the level of support for the spatialization feature on this device.
|
||
* This level of support is independent of whether the {@code Spatializer} is currently
|
||
* enabled or available and will not change over time.
|
||
* @return the level of spatialization support
|
||
* @see #isEnabled()
|
||
* @see #isAvailable()
|
||
*/
|
||
public @ImmersiveAudioLevel int getImmersiveAudioLevel() {
|
||
int level = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
|
||
try {
|
||
level = mAm.getService().getSpatializerImmersiveAudioLevel();
|
||
} catch (Exception e) { /* using NONE */ }
|
||
return level;
|
||
}
|
||
|
||
/**
|
||
* @hide
|
||
* Enables / disables the spatializer effect.
|
||
* Changing the enabled state will trigger the public
|
||
* {@link OnSpatializerStateChangedListener#onSpatializerEnabledChanged(Spatializer, boolean)}
|
||
* registered listeners.
|
||
* @param enabled {@code true} for enabling the effect
|
||
*/
|
||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
||
@RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
|
||
public void setEnabled(boolean enabled) {
|
||
try {
|
||
mAm.getService().setSpatializerEnabled(enabled);
|
||
} catch (RemoteException e) {
|
||
Log.e(TAG, "Error calling setSpatializerEnabled", e);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* An interface to be notified of changes to the state of the spatializer effect.
|
||
*/
|
||
public interface OnSpatializerStateChangedListener {
|
||
/**
|
||
* Called when the enabled state of the spatializer effect changes
|
||
* @param spat the {@code Spatializer} instance whose state changed
|
||
* @param enabled {@code true} if the spatializer effect is enabled on the device,
|
||
* {@code false} otherwise
|
||
* @see #isEnabled()
|
||
*/
|
||
void onSpatializerEnabledChanged(@NonNull Spatializer spat, boolean enabled);
|
||
|
||
/**
|
||
* Called when the availability of the spatializer effect changes
|
||
* @param spat the {@code Spatializer} instance whose state changed
|
||
* @param available {@code true} if the spatializer effect is available and capable
|
||
* of processing the audio for the current configuration of the device,
|
||
* {@code false} otherwise.
|
||
* @see #isAvailable()
|
||
*/
|
||
void onSpatializerAvailableChanged(@NonNull Spatializer spat, boolean available);
|
||
}
|
||
|
||
/**
|
||
* @hide
|
||
* An interface to be notified of changes to the head tracking mode, used by the spatializer
|
||
* effect.
|
||
* Changes to the mode may come from explicitly setting a different mode
|
||
* (see {@link #setDesiredHeadTrackingMode(int)}) or a change in system conditions (see
|
||
* {@link #getHeadTrackingMode()}
|
||
* @see #addOnHeadTrackingModeChangedListener(Executor, OnHeadTrackingModeChangedListener)
|
||
* @see #removeOnHeadTrackingModeChangedListener(OnHeadTrackingModeChangedListener)
|
||
*/
|
||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
||
public interface OnHeadTrackingModeChangedListener {
|
||
/**
|
||
* Called when the actual head tracking mode of the spatializer changed.
|
||
* @param spatializer the {@code Spatializer} instance whose head tracking mode is changing
|
||
* @param mode the new head tracking mode
|
||
*/
|
||
void onHeadTrackingModeChanged(@NonNull Spatializer spatializer,
|
||
@HeadTrackingMode int mode);
|
||
|
||
/**
|
||
* Called when the desired head tracking mode of the spatializer changed
|
||
* @param spatializer the {@code Spatializer} instance whose head tracking mode was set
|
||
* @param mode the newly set head tracking mode
|
||
*/
|
||
void onDesiredHeadTrackingModeChanged(@NonNull Spatializer spatializer,
|
||
@HeadTrackingModeSet int mode);
|
||
}
|
||
|
||
/**
|
||
* Interface to be notified of changes to the availability of a head tracker on the audio
|
||
* device to be used by the spatializer effect.
|
||
*/
|
||
public interface OnHeadTrackerAvailableListener {
|
||
/**
|
||
* Called when the availability of the head tracker changed.
|
||
* @param spatializer the {@code Spatializer} instance for which the head tracker
|
||
* availability was updated
|
||
* @param available true if the audio device that would output audio processed by
|
||
* the {@code Spatializer} has a head tracker associated with it, false
|
||
* otherwise.
|
||
*/
|
||
void onHeadTrackerAvailableChanged(@NonNull Spatializer spatializer,
|
||
boolean available);
|
||
}
|
||
|
||
/**
|
||
* @hide
|
||
* An interface to be notified of changes to the output stream used by the spatializer
|
||
* effect.
|
||
* @see #getOutput()
|
||
*/
|
||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
||
public interface OnSpatializerOutputChangedListener {
|
||
/**
|
||
* Called when the id of the output stream of the spatializer effect changed.
|
||
* @param spatializer the {@code Spatializer} instance whose output is updated
|
||
* @param output the id of the output stream, or 0 when there is no spatializer output
|
||
*/
|
||
void onSpatializerOutputChanged(@NonNull Spatializer spatializer,
|
||
@IntRange(from = 0) int output);
|
||
}
|
||
|
||
/**
|
||
* @hide
|
||
* An interface to be notified of updates to the head to soundstage pose, as represented by the
|
||
* current head tracking mode.
|
||
* @see #setOnHeadToSoundstagePoseUpdatedListener(Executor, OnHeadToSoundstagePoseUpdatedListener)
|
||
*/
|
||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
||
public interface OnHeadToSoundstagePoseUpdatedListener {
|
||
/**
|
||
* Called when the head to soundstage transform is updated
|
||
* @param spatializer the {@code Spatializer} instance affected by the pose update
|
||
* @param pose the new pose data representing the transform between the frame
|
||
* of reference for the current head tracking mode (see
|
||
* {@link #getHeadTrackingMode()}) and the device being tracked (for
|
||
* instance a pair of headphones with a head tracker).<br>
|
||
* The head pose data is represented as an array of six float values, where
|
||
* the first three values are the translation vector, and the next three
|
||
* are the rotation vector.
|
||
*/
|
||
void onHeadToSoundstagePoseUpdated(@NonNull Spatializer spatializer,
|
||
@NonNull float[] pose);
|
||
}
|
||
|
||
/**
|
||
* Returns whether audio of the given {@link AudioFormat}, played with the given
|
||
* {@link AudioAttributes} can be spatialized.
|
||
* Note that the result reflects the capabilities of the device and may change when
|
||
* audio accessories are connected/disconnected (e.g. wired headphones plugged in or not).
|
||
* The result is independent from whether spatialization processing is enabled or not.
|
||
* @param attributes the {@code AudioAttributes} of the content as used for playback
|
||
* @param format the {@code AudioFormat} of the content as used for playback
|
||
* @return {@code true} if the device is capable of spatializing the combination of audio format
|
||
* and attributes, {@code false} otherwise.
|
||
*/
|
||
public boolean canBeSpatialized(
|
||
@NonNull AudioAttributes attributes, @NonNull AudioFormat format) {
|
||
try {
|
||
return mAm.getService().canBeSpatialized(
|
||
Objects.requireNonNull(attributes), Objects.requireNonNull(format));
|
||
} catch (RemoteException e) {
|
||
Log.e(TAG, "Error querying canBeSpatialized for attr:" + attributes
|
||
+ " format:" + format + " returning false", e);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Adds a listener to be notified of changes to the enabled state of the
|
||
* {@code Spatializer}.
|
||
* @param executor the {@code Executor} handling the callback
|
||
* @param listener the listener to receive enabled state updates
|
||
* @see #isEnabled()
|
||
*/
|
||
public void addOnSpatializerStateChangedListener(
|
||
@NonNull @CallbackExecutor Executor executor,
|
||
@NonNull OnSpatializerStateChangedListener listener) {
|
||
mStateListenerMgr.addListener(executor, listener, "addOnSpatializerStateChangedListener",
|
||
() -> new SpatializerInfoDispatcherStub());
|
||
}
|
||
|
||
/**
|
||
* Removes a previously added listener for changes to the enabled state of the
|
||
* {@code Spatializer}.
|
||
* @param listener the listener to receive enabled state updates
|
||
* @see #isEnabled()
|
||
*/
|
||
public void removeOnSpatializerStateChangedListener(
|
||
@NonNull OnSpatializerStateChangedListener listener) {
|
||
mStateListenerMgr.removeListener(listener, "removeOnSpatializerStateChangedListener");
|
||
}
|
||
|
||
/**
|
||
* @hide
|
||
* Returns the list of playback devices that are compatible with the playback of multichannel
|
||
* audio through virtualization
|
||
* @return a list of devices. An empty list indicates virtualization is not supported.
|
||
*/
|
||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
||
@RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
|
||
public @NonNull List<AudioDeviceAttributes> getCompatibleAudioDevices() {
|
||
try {
|
||
return mAm.getService().getSpatializerCompatibleAudioDevices();
|
||
} catch (RemoteException e) {
|
||
Log.e(TAG, "Error querying getSpatializerCompatibleAudioDevices(), "
|
||
+ " returning empty list", e);
|
||
return new ArrayList<AudioDeviceAttributes>(0);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @hide
|
||
* Adds a playback device to the list of devices compatible with the playback of multichannel
|
||
* audio through spatialization.
|
||
* @see #getCompatibleAudioDevices()
|
||
* @param ada the audio device compatible with spatialization
|
||
*/
|
||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
||
@RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
|
||
public void addCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
|
||
try {
|
||
mAm.getService().addSpatializerCompatibleAudioDevice(Objects.requireNonNull(ada));
|
||
} catch (RemoteException e) {
|
||
Log.e(TAG, "Error calling addSpatializerCompatibleAudioDevice(), ", e);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @hide
|
||
* Remove a playback device from the list of devices compatible with the playback of
|
||
* multichannel audio through spatialization.
|
||
* @see #getCompatibleAudioDevices()
|
||
* @param ada the audio device incompatible with spatialization
|
||
*/
|
||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
||
@RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
|
||
public void removeCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
|
||
try {
|
||
mAm.getService().removeSpatializerCompatibleAudioDevice(Objects.requireNonNull(ada));
|
||
} catch (RemoteException e) {
|
||
Log.e(TAG, "Error calling removeSpatializerCompatibleAudioDevice(), ", e);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* manages the OnSpatializerStateChangedListener listeners and the
|
||
* SpatializerInfoDispatcherStub
|
||
*/
|
||
private final CallbackUtil.LazyListenerManager<OnSpatializerStateChangedListener>
|
||
mStateListenerMgr = new CallbackUtil.LazyListenerManager();
|
||
|
||
private final class SpatializerInfoDispatcherStub extends ISpatializerCallback.Stub
|
||
implements CallbackUtil.DispatcherStub {
|
||
@Override
|
||
public void register(boolean register) {
|
||
try {
|
||
if (register) {
|
||
mAm.getService().registerSpatializerCallback(this);
|
||
} else {
|
||
mAm.getService().unregisterSpatializerCallback(this);
|
||
}
|
||
} catch (RemoteException e) {
|
||
e.rethrowFromSystemServer();
|
||
}
|
||
}
|
||
|
||
@Override
|
||
@SuppressLint("GuardedBy") // lock applied inside callListeners method
|
||
public void dispatchSpatializerEnabledChanged(boolean enabled) {
|
||
mStateListenerMgr.callListeners(
|
||
(listener) -> listener.onSpatializerEnabledChanged(
|
||
Spatializer.this, enabled));
|
||
}
|
||
|
||
@Override
|
||
@SuppressLint("GuardedBy") // lock applied inside callListeners method
|
||
public void dispatchSpatializerAvailableChanged(boolean available) {
|
||
mStateListenerMgr.callListeners(
|
||
(listener) -> listener.onSpatializerAvailableChanged(
|
||
Spatializer.this, available));
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
* @hide
|
||
* Return the current head tracking mode as used by the system.
|
||
* Note this may differ from the desired head tracking mode. Reasons for the two to differ
|
||
* include: a head tracking device is not available for the current audio output device,
|
||
* the transmission conditions between the tracker and device have deteriorated and tracking
|
||
* has been disabled.
|
||
* @see #getDesiredHeadTrackingMode()
|
||
* @return the current head tracking mode
|
||
*/
|
||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
||
@RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
|
||
public @HeadTrackingMode int getHeadTrackingMode() {
|
||
try {
|
||
return mAm.getService().getActualHeadTrackingMode();
|
||
} catch (RemoteException e) {
|
||
Log.e(TAG, "Error calling getActualHeadTrackingMode", e);
|
||
return HEAD_TRACKING_MODE_UNSUPPORTED;
|
||
}
|
||
|
||
}
|
||
|
||
/**
|
||
* @hide
|
||
* Return the desired head tracking mode.
|
||
* Note this may differ from the actual head tracking mode, reflected by
|
||
* {@link #getHeadTrackingMode()}.
|
||
* @return the desired head tring mode
|
||
*/
|
||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
||
@RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
|
||
public @HeadTrackingMode int getDesiredHeadTrackingMode() {
|
||
try {
|
||
return mAm.getService().getDesiredHeadTrackingMode();
|
||
} catch (RemoteException e) {
|
||
Log.e(TAG, "Error calling getDesiredHeadTrackingMode", e);
|
||
return HEAD_TRACKING_MODE_UNSUPPORTED;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @hide
|
||
* Returns the list of supported head tracking modes.
|
||
* @return the list of modes that can be used in {@link #setDesiredHeadTrackingMode(int)} to
|
||
* enable head tracking. The list will be empty if {@link #getHeadTrackingMode()}
|
||
* is {@link #HEAD_TRACKING_MODE_UNSUPPORTED}. Values can be
|
||
* {@link #HEAD_TRACKING_MODE_OTHER},
|
||
* {@link #HEAD_TRACKING_MODE_RELATIVE_WORLD} or
|
||
* {@link #HEAD_TRACKING_MODE_RELATIVE_DEVICE}
|
||
*/
|
||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
||
@RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
|
||
public @NonNull List<Integer> getSupportedHeadTrackingModes() {
|
||
try {
|
||
final int[] modes = mAm.getService().getSupportedHeadTrackingModes();
|
||
final ArrayList<Integer> list = new ArrayList<>(0);
|
||
for (int mode : modes) {
|
||
list.add(mode);
|
||
}
|
||
return list;
|
||
} catch (RemoteException e) {
|
||
Log.e(TAG, "Error calling getSupportedHeadTrackModes", e);
|
||
return new ArrayList(0);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @hide
|
||
* Sets the desired head tracking mode.
|
||
* Note a set desired mode may differ from the actual head tracking mode.
|
||
* @see #getHeadTrackingMode()
|
||
* @param mode the desired head tracking mode, one of the values returned by
|
||
* {@link #getSupportedHeadTrackModes()}, or {@link #HEAD_TRACKING_MODE_DISABLED} to
|
||
* disable head tracking.
|
||
*/
|
||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
||
@RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
|
||
public void setDesiredHeadTrackingMode(@HeadTrackingModeSet int mode) {
|
||
try {
|
||
mAm.getService().setDesiredHeadTrackingMode(mode);
|
||
} catch (RemoteException e) {
|
||
Log.e(TAG, "Error calling setDesiredHeadTrackingMode to " + mode, e);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @hide
|
||
* Recenters the head tracking at the current position / orientation.
|
||
*/
|
||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
||
@RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
|
||
public void recenterHeadTracker() {
|
||
try {
|
||
mAm.getService().recenterHeadTracker();
|
||
} catch (RemoteException e) {
|
||
Log.e(TAG, "Error calling recenterHeadTracker", e);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @hide
|
||
* Adds a listener to be notified of changes to the head tracking mode of the
|
||
* {@code Spatializer}
|
||
* @param executor the {@code Executor} handling the callbacks
|
||
* @param listener the listener to register
|
||
*/
|
||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
||
@RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
|
||
public void addOnHeadTrackingModeChangedListener(
|
||
@NonNull @CallbackExecutor Executor executor,
|
||
@NonNull OnHeadTrackingModeChangedListener listener) {
|
||
mHeadTrackingListenerMgr.addListener(executor, listener,
|
||
"addOnHeadTrackingModeChangedListener",
|
||
() -> new SpatializerHeadTrackingDispatcherStub());
|
||
}
|
||
|
||
/**
|
||
* @hide
|
||
* Removes a previously added listener for changes to the head tracking mode of the
|
||
* {@code Spatializer}.
|
||
* @param listener the listener to unregister
|
||
*/
|
||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
||
@RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
|
||
public void removeOnHeadTrackingModeChangedListener(
|
||
@NonNull OnHeadTrackingModeChangedListener listener) {
|
||
mHeadTrackingListenerMgr.removeListener(listener,
|
||
"removeOnHeadTrackingModeChangedListener");
|
||
}
|
||
|
||
/**
|
||
* @hide
|
||
* Set the listener to receive head to soundstage pose updates.
|
||
* @param executor the {@code Executor} handling the callbacks
|
||
* @param listener the listener to register
|
||
* @see #clearOnHeadToSoundstagePoseUpdatedListener()
|
||
*/
|
||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
||
@RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
|
||
public void setOnHeadToSoundstagePoseUpdatedListener(
|
||
@NonNull @CallbackExecutor Executor executor,
|
||
@NonNull OnHeadToSoundstagePoseUpdatedListener listener) {
|
||
Objects.requireNonNull(executor);
|
||
Objects.requireNonNull(listener);
|
||
synchronized (mPoseListenerLock) {
|
||
if (mPoseListener != null) {
|
||
throw new IllegalStateException("Trying to overwrite existing listener");
|
||
}
|
||
mPoseListener =
|
||
new ListenerInfo<OnHeadToSoundstagePoseUpdatedListener>(listener, executor);
|
||
mPoseDispatcher = new SpatializerPoseDispatcherStub();
|
||
try {
|
||
mAm.getService().registerHeadToSoundstagePoseCallback(mPoseDispatcher);
|
||
} catch (RemoteException e) {
|
||
mPoseListener = null;
|
||
mPoseDispatcher = null;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @hide
|
||
* Clears the listener for head to soundstage pose updates
|
||
* @see #setOnHeadToSoundstagePoseUpdatedListener(Executor, OnHeadToSoundstagePoseUpdatedListener)
|
||
*/
|
||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
||
@RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
|
||
public void clearOnHeadToSoundstagePoseUpdatedListener() {
|
||
synchronized (mPoseListenerLock) {
|
||
if (mPoseDispatcher == null) {
|
||
throw (new IllegalStateException("No listener to clear"));
|
||
}
|
||
try {
|
||
mAm.getService().unregisterHeadToSoundstagePoseCallback(mPoseDispatcher);
|
||
} catch (RemoteException e) { }
|
||
mPoseListener = null;
|
||
mPoseDispatcher = null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @hide
|
||
* Sets an additional transform over the soundstage.
|
||
* The transform represents the pose of the soundstage, relative
|
||
* to either the device (in {@link #HEAD_TRACKING_MODE_RELATIVE_DEVICE} mode), the world (in
|
||
* {@link #HEAD_TRACKING_MODE_RELATIVE_WORLD}) or the listener’s head (in
|
||
* {@link #HEAD_TRACKING_MODE_DISABLED} mode).
|
||
* @param transform an array of 6 float values, the first 3 are the translation vector, the
|
||
* other 3 are the rotation vector.
|
||
*/
|
||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
||
@RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
|
||
public void setGlobalTransform(@NonNull float[] transform) {
|
||
if (Objects.requireNonNull(transform).length != 6) {
|
||
throw new IllegalArgumentException("transform array must be of size 6, was "
|
||
+ transform.length);
|
||
}
|
||
try {
|
||
mAm.getService().setSpatializerGlobalTransform(transform);
|
||
} catch (RemoteException e) {
|
||
Log.e(TAG, "Error calling setGlobalTransform", e);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @hide
|
||
* Sets a parameter on the platform spatializer effect implementation.
|
||
* This is to be used for vendor-specific configurations of their effect, keys and values are
|
||
* not reuseable across implementations.
|
||
* @param key the parameter to change
|
||
* @param value an array for the value of the parameter to change
|
||
*/
|
||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
||
@RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
|
||
public void setEffectParameter(int key, @NonNull byte[] value) {
|
||
Objects.requireNonNull(value);
|
||
try {
|
||
mAm.getService().setSpatializerParameter(key, value);
|
||
} catch (RemoteException e) {
|
||
Log.e(TAG, "Error calling setEffectParameter", e);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @hide
|
||
* Retrieves a parameter value from the platform spatializer effect implementation.
|
||
* This is to be used for vendor-specific configurations of their effect, keys and values are
|
||
* not reuseable across implementations.
|
||
* @param key the parameter for which the value is queried
|
||
* @param value a non-empty array to contain the return value. The caller is responsible for
|
||
* passing an array of size matching the parameter.
|
||
*/
|
||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
||
@RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
|
||
public void getEffectParameter(int key, @NonNull byte[] value) {
|
||
Objects.requireNonNull(value);
|
||
try {
|
||
mAm.getService().getSpatializerParameter(key, value);
|
||
} catch (RemoteException e) {
|
||
Log.e(TAG, "Error calling getEffectParameter", e);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @hide
|
||
* Returns the id of the output stream used for the spatializer effect playback.
|
||
* This getter or associated listener {@link OnSpatializerOutputChangedListener} can be used for
|
||
* handling spatializer output-specific configurations (e.g. disabling speaker post-processing
|
||
* to avoid double-processing of the spatialized path).
|
||
* @return id of the output stream, or 0 if no spatializer playback is active
|
||
* @see #setOnSpatializerOutputChangedListener(Executor, OnSpatializerOutputChangedListener)
|
||
*/
|
||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
||
@RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
|
||
public @IntRange(from = 0) int getOutput() {
|
||
try {
|
||
return mAm.getService().getSpatializerOutput();
|
||
} catch (RemoteException e) {
|
||
Log.e(TAG, "Error calling getSpatializerOutput", e);
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @hide
|
||
* Sets the listener to receive spatializer effect output updates
|
||
* @param executor the {@code Executor} handling the callbacks
|
||
* @param listener the listener to register
|
||
* @see #clearOnSpatializerOutputChangedListener()
|
||
* @see #getOutput()
|
||
*/
|
||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
||
@RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
|
||
public void setOnSpatializerOutputChangedListener(
|
||
@NonNull @CallbackExecutor Executor executor,
|
||
@NonNull OnSpatializerOutputChangedListener listener) {
|
||
Objects.requireNonNull(executor);
|
||
Objects.requireNonNull(listener);
|
||
synchronized (mOutputListenerLock) {
|
||
if (mOutputListener != null) {
|
||
throw new IllegalStateException("Trying to overwrite existing listener");
|
||
}
|
||
mOutputListener =
|
||
new ListenerInfo<OnSpatializerOutputChangedListener>(listener, executor);
|
||
mOutputDispatcher = new SpatializerOutputDispatcherStub();
|
||
try {
|
||
mAm.getService().registerSpatializerOutputCallback(mOutputDispatcher);
|
||
// immediately report the current output
|
||
mOutputDispatcher.dispatchSpatializerOutputChanged(getOutput());
|
||
} catch (RemoteException e) {
|
||
mOutputListener = null;
|
||
mOutputDispatcher = null;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @hide
|
||
* Clears the listener for spatializer effect output updates
|
||
* @see #setOnSpatializerOutputChangedListener(Executor, OnSpatializerOutputChangedListener)
|
||
*/
|
||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
||
@RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
|
||
public void clearOnSpatializerOutputChangedListener() {
|
||
synchronized (mOutputListenerLock) {
|
||
if (mOutputDispatcher == null) {
|
||
throw (new IllegalStateException("No listener to clear"));
|
||
}
|
||
try {
|
||
mAm.getService().unregisterSpatializerOutputCallback(mOutputDispatcher);
|
||
} catch (RemoteException e) { }
|
||
mOutputListener = null;
|
||
mOutputDispatcher = null;
|
||
}
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
// head tracking callback management and stub
|
||
|
||
/**
|
||
* manages the OnHeadTrackingModeChangedListener listeners and the
|
||
* SpatializerHeadTrackingDispatcherStub
|
||
*/
|
||
private final CallbackUtil.LazyListenerManager<OnHeadTrackingModeChangedListener>
|
||
mHeadTrackingListenerMgr = new CallbackUtil.LazyListenerManager();
|
||
|
||
private final class SpatializerHeadTrackingDispatcherStub
|
||
extends ISpatializerHeadTrackingModeCallback.Stub
|
||
implements CallbackUtil.DispatcherStub {
|
||
@Override
|
||
public void register(boolean register) {
|
||
try {
|
||
if (register) {
|
||
mAm.getService().registerSpatializerHeadTrackingCallback(this);
|
||
} else {
|
||
mAm.getService().unregisterSpatializerHeadTrackingCallback(this);
|
||
}
|
||
} catch (RemoteException e) {
|
||
e.rethrowFromSystemServer();
|
||
}
|
||
}
|
||
|
||
@Override
|
||
@SuppressLint("GuardedBy") // lock applied inside callListeners method
|
||
public void dispatchSpatializerActualHeadTrackingModeChanged(int mode) {
|
||
mHeadTrackingListenerMgr.callListeners(
|
||
(listener) -> listener.onHeadTrackingModeChanged(Spatializer.this, mode));
|
||
}
|
||
|
||
@Override
|
||
@SuppressLint("GuardedBy") // lock applied inside callListeners method
|
||
public void dispatchSpatializerDesiredHeadTrackingModeChanged(int mode) {
|
||
mHeadTrackingListenerMgr.callListeners(
|
||
(listener) -> listener.onDesiredHeadTrackingModeChanged(
|
||
Spatializer.this, mode));
|
||
}
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
// head tracker availability callback management and stub
|
||
/**
|
||
* manages the OnHeadTrackerAvailableListener listeners and the
|
||
* SpatializerHeadTrackerAvailableDispatcherStub
|
||
*/
|
||
private final CallbackUtil.LazyListenerManager<OnHeadTrackerAvailableListener>
|
||
mHeadTrackerListenerMgr = new CallbackUtil.LazyListenerManager();
|
||
|
||
private final class SpatializerHeadTrackerAvailableDispatcherStub
|
||
extends ISpatializerHeadTrackerAvailableCallback.Stub
|
||
implements CallbackUtil.DispatcherStub {
|
||
@Override
|
||
public void register(boolean register) {
|
||
try {
|
||
mAm.getService().registerSpatializerHeadTrackerAvailableCallback(this, register);
|
||
} catch (RemoteException e) {
|
||
e.rethrowFromSystemServer();
|
||
}
|
||
}
|
||
|
||
@Override
|
||
@SuppressLint("GuardedBy") // lock applied inside callListeners method
|
||
public void dispatchSpatializerHeadTrackerAvailable(boolean available) {
|
||
mHeadTrackerListenerMgr.callListeners(
|
||
(listener) -> listener.onHeadTrackerAvailableChanged(
|
||
Spatializer.this, available));
|
||
}
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
// head pose callback management and stub
|
||
private final Object mPoseListenerLock = new Object();
|
||
/**
|
||
* Listener for head to soundstage updates
|
||
*/
|
||
@GuardedBy("mPoseListenerLock")
|
||
private @Nullable ListenerInfo<OnHeadToSoundstagePoseUpdatedListener> mPoseListener;
|
||
@GuardedBy("mPoseListenerLock")
|
||
private @Nullable SpatializerPoseDispatcherStub mPoseDispatcher;
|
||
|
||
private final class SpatializerPoseDispatcherStub
|
||
extends ISpatializerHeadToSoundStagePoseCallback.Stub {
|
||
|
||
@Override
|
||
public void dispatchPoseChanged(float[] pose) {
|
||
// make a copy of ref to listener so callback is not executed under lock
|
||
final ListenerInfo<OnHeadToSoundstagePoseUpdatedListener> listener;
|
||
synchronized (mPoseListenerLock) {
|
||
listener = mPoseListener;
|
||
}
|
||
if (listener == null) {
|
||
return;
|
||
}
|
||
try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
|
||
listener.mExecutor.execute(() -> listener.mListener
|
||
.onHeadToSoundstagePoseUpdated(Spatializer.this, pose));
|
||
}
|
||
}
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
// output callback management and stub
|
||
private final Object mOutputListenerLock = new Object();
|
||
/**
|
||
* Listener for output updates
|
||
*/
|
||
@GuardedBy("mOutputListenerLock")
|
||
private @Nullable ListenerInfo<OnSpatializerOutputChangedListener> mOutputListener;
|
||
@GuardedBy("mOutputListenerLock")
|
||
private @Nullable SpatializerOutputDispatcherStub mOutputDispatcher;
|
||
|
||
private final class SpatializerOutputDispatcherStub
|
||
extends ISpatializerOutputCallback.Stub {
|
||
|
||
@Override
|
||
public void dispatchSpatializerOutputChanged(int output) {
|
||
// make a copy of ref to listener so callback is not executed under lock
|
||
final ListenerInfo<OnSpatializerOutputChangedListener> listener;
|
||
synchronized (mOutputListenerLock) {
|
||
listener = mOutputListener;
|
||
}
|
||
if (listener == null) {
|
||
return;
|
||
}
|
||
try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
|
||
listener.mExecutor.execute(() -> listener.mListener
|
||
.onSpatializerOutputChanged(Spatializer.this, output));
|
||
}
|
||
}
|
||
}
|
||
}
|