summaryrefslogtreecommitdiff
path: root/media
diff options
context:
space:
mode:
authorHaamed Gheibi <haamed@google.com>2022-03-09 12:05:14 -0800
committerWeijie Wang <quic_weijiew@quicinc.com>2022-03-15 15:38:25 +0800
commit12bb6d3cbf05cea529a165917c7430af607056f2 (patch)
treeff322630f9716306236ca70ecae1f265ae2aa2c6 /media
parenta42412b7fc93a0eb852d8bf1a4d001f7df7f43b3 (diff)
Merge SP2A.220305.013
Bug: 220074017 Change-Id: Idfdd94e902f656ac65a2a75dfdd199f6f85ba472
Diffstat (limited to 'media')
-rw-r--r--media/java/android/media/AudioAttributes.java99
-rw-r--r--media/java/android/media/AudioFormat.java89
-rw-r--r--media/java/android/media/AudioManager.java15
-rw-r--r--media/java/android/media/AudioSystem.java43
-rw-r--r--media/java/android/media/AudioTrack.java18
-rwxr-xr-xmedia/java/android/media/IAudioService.aidl55
-rw-r--r--media/java/android/media/IMediaRouterClient.aidl1
-rw-r--r--media/java/android/media/IMediaRouterService.aidl1
-rw-r--r--media/java/android/media/ISpatializerCallback.aidl29
-rw-r--r--media/java/android/media/ISpatializerHeadToSoundStagePoseCallback.aidl31
-rw-r--r--media/java/android/media/ISpatializerHeadTrackingModeCallback.aidl29
-rw-r--r--media/java/android/media/ISpatializerOutputCallback.aidl27
-rw-r--r--media/java/android/media/MediaFormat.java13
-rw-r--r--media/java/android/media/MediaMetrics.java10
-rw-r--r--media/java/android/media/MediaRouter.java38
-rw-r--r--media/java/android/media/Spatializer.java1089
-rw-r--r--media/java/android/media/projection/MediaProjection.java60
-rw-r--r--media/packages/BluetoothMidiService/AndroidManifest.xml2
-rw-r--r--media/packages/BluetoothMidiService/AndroidManifestBase.xml2
19 files changed, 1598 insertions, 53 deletions
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index a031b4cfc911..c22ab9463736 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -447,13 +447,26 @@ public final class AudioAttributes implements Parcelable {
*/
public static final int FLAG_CAPTURE_PRIVATE = 0x1 << 13;
+ /**
+ * @hide
+ * Flag indicating the audio content has been processed to provide a virtual multichannel
+ * audio experience
+ */
+ public static final int FLAG_CONTENT_SPATIALIZED = 0x1 << 14;
+
+ /**
+ * @hide
+ * Flag indicating the audio content is to never be spatialized
+ */
+ public static final int FLAG_NEVER_SPATIALIZE = 0x1 << 15;
// Note that even though FLAG_MUTE_HAPTIC is stored as a flag bit, it is not here since
// it is known as a boolean value outside of AudioAttributes.
private static final int FLAG_ALL = FLAG_AUDIBILITY_ENFORCED | FLAG_SECURE | FLAG_SCO
| FLAG_BEACON | FLAG_HW_AV_SYNC | FLAG_HW_HOTWORD | FLAG_BYPASS_INTERRUPTION_POLICY
| FLAG_BYPASS_MUTE | FLAG_LOW_LATENCY | FLAG_DEEP_BUFFER | FLAG_NO_MEDIA_PROJECTION
- | FLAG_NO_SYSTEM_CAPTURE | FLAG_CAPTURE_PRIVATE;
+ | FLAG_NO_SYSTEM_CAPTURE | FLAG_CAPTURE_PRIVATE | FLAG_CONTENT_SPATIALIZED
+ | FLAG_NEVER_SPATIALIZE;
private final static int FLAG_ALL_PUBLIC = FLAG_AUDIBILITY_ENFORCED |
FLAG_HW_AV_SYNC | FLAG_LOW_LATENCY;
/* mask of flags that can be set by SDK and System APIs through the Builder */
@@ -615,6 +628,49 @@ public final class AudioAttributes implements Parcelable {
}
/**
+ * Return true if the audio content associated with these attributes has already been
+ * spatialized, that is it has already been processed to offer a binaural or transaural
+ * immersive audio experience.
+ * @return {@code true} if the content has been processed
+ */
+ public boolean isContentSpatialized() {
+ return (mFlags & FLAG_CONTENT_SPATIALIZED) != 0;
+ }
+
+ /** @hide */
+ @IntDef(flag = false, value = {
+ SPATIALIZATION_BEHAVIOR_AUTO,
+ SPATIALIZATION_BEHAVIOR_NEVER,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SpatializationBehavior {};
+
+ /**
+ * Constant indicating the audio content associated with these attributes will follow the
+ * default platform behavior with regards to which content will be spatialized or not.
+ * @see #getSpatializationBehavior()
+ * @see Spatializer
+ */
+ public static final int SPATIALIZATION_BEHAVIOR_AUTO = 0;
+
+ /**
+ * Constant indicating the audio content associated with these attributes should never
+ * be virtualized.
+ * @see #getSpatializationBehavior()
+ * @see Spatializer
+ */
+ public static final int SPATIALIZATION_BEHAVIOR_NEVER = 1;
+
+ /**
+ * Return the behavior affecting whether spatialization will be used.
+ * @return the spatialization behavior
+ */
+ public @SpatializationBehavior int getSpatializationBehavior() {
+ return ((mFlags & FLAG_NEVER_SPATIALIZE) != 0)
+ ? SPATIALIZATION_BEHAVIOR_NEVER : SPATIALIZATION_BEHAVIOR_AUTO;
+ }
+
+ /**
* Return the capture policy.
* @return the capture policy set by {@link Builder#setAllowedCapturePolicy(int)} or
* the default if it was not called.
@@ -657,6 +713,8 @@ public final class AudioAttributes implements Parcelable {
private int mSource = MediaRecorder.AudioSource.AUDIO_SOURCE_INVALID;
private int mFlags = 0x0;
private boolean mMuteHapticChannels = true;
+ private boolean mIsContentSpatialized = false;
+ private int mSpatializationBehavior = SPATIALIZATION_BEHAVIOR_AUTO;
private HashSet<String> mTags = new HashSet<String>();
private Bundle mBundle;
private int mPrivacySensitive = PRIVACY_SENSITIVE_DEFAULT;
@@ -687,6 +745,8 @@ public final class AudioAttributes implements Parcelable {
mFlags = aa.getAllFlags();
mTags = (HashSet<String>) aa.mTags.clone();
mMuteHapticChannels = aa.areHapticChannelsMuted();
+ mIsContentSpatialized = aa.isContentSpatialized();
+ mSpatializationBehavior = aa.getSpatializationBehavior();
}
/**
@@ -719,6 +779,12 @@ public final class AudioAttributes implements Parcelable {
if (mMuteHapticChannels) {
aa.mFlags |= FLAG_MUTE_HAPTIC;
}
+ if (mIsContentSpatialized) {
+ aa.mFlags |= FLAG_CONTENT_SPATIALIZED;
+ }
+ if (mSpatializationBehavior == SPATIALIZATION_BEHAVIOR_NEVER) {
+ aa.mFlags |= FLAG_NEVER_SPATIALIZE;
+ }
if (mPrivacySensitive == PRIVACY_SENSITIVE_DEFAULT) {
// capturing for camcorder or communication is private by default to
@@ -906,6 +972,35 @@ public final class AudioAttributes implements Parcelable {
}
/**
+ * Specifies whether the content has already been processed for spatialization.
+ * If it has, setting this to true will prevent issues such as double-processing.
+ * @param isSpatialized
+ * @return the same Builder instance
+ */
+ public @NonNull Builder setIsContentSpatialized(boolean isSpatialized) {
+ mIsContentSpatialized = isSpatialized;
+ return this;
+ }
+
+ /**
+ * Sets the behavior affecting whether spatialization will be used.
+ * @param sb the spatialization behavior
+ * @return the same Builder instance
+ *
+ */
+ public @NonNull Builder setSpatializationBehavior(@SpatializationBehavior int sb) {
+ switch (sb) {
+ case SPATIALIZATION_BEHAVIOR_NEVER:
+ case SPATIALIZATION_BEHAVIOR_AUTO:
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid spatialization behavior " + sb);
+ }
+ mSpatializationBehavior = sb;
+ return this;
+ }
+
+ /**
* @hide
* Replaces flags.
* @param flags any combination of {@link AudioAttributes#FLAG_ALL}.
@@ -990,6 +1085,8 @@ public final class AudioAttributes implements Parcelable {
mContentType = attributes.mContentType;
mFlags = attributes.getAllFlags();
mMuteHapticChannels = attributes.areHapticChannelsMuted();
+ mIsContentSpatialized = attributes.isContentSpatialized();
+ mSpatializationBehavior = attributes.getSpatializationBehavior();
mTags = attributes.mTags;
mBundle = attributes.mBundle;
mSource = attributes.mSource;
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index b07dc9b581b2..2988ca8c715c 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -175,6 +175,28 @@ import java.util.Objects;
* <br>These masks are an ORed composite of individual channel masks. For example
* {@link #CHANNEL_OUT_STEREO} is composed of {@link #CHANNEL_OUT_FRONT_LEFT} and
* {@link #CHANNEL_OUT_FRONT_RIGHT}.
+ * <p>
+ * The following diagram represents the layout of the output channels, as seen from above
+ * the listener (in the center at the "lis" position, facing the front-center channel).
+ * <pre>
+ * TFL ----- TFC ----- TFR T is Top
+ * | \ | / |
+ * | FL --- FC --- FR | F is Front
+ * | |\ | /| |
+ * | | BFL-BFC-BFR | | BF is Bottom Front
+ * | | | |
+ * | FWL lis FWR | W is Wide
+ * | | | |
+ * TSL SL TC SR TSR S is Side
+ * | | | |
+ * | BL --- BC -- BR | B is Back
+ * | / \ |
+ * TBL ----- TBC ----- TBR C is Center, L/R is Left/Right
+ * </pre>
+ * All "T" (top) channels are above the listener, all "BF" (bottom-front) channels are below the
+ * listener, all others are in the listener's horizontal plane. When used in conjunction, LFE1 and
+ * LFE2 are below the listener, when used alone, LFE plane is undefined.
+ * See the channel definitions for the abbreviations
*
* <h5 id="channelIndexMask">Channel index masks</h5>
* Channel index masks are introduced in API {@link android.os.Build.VERSION_CODES#M}. They allow
@@ -442,43 +464,62 @@ public final class AudioFormat implements Parcelable {
// Output channel mask definitions below are translated to the native values defined in
// in /system/media/audio/include/system/audio.h in the JNI code of AudioTrack
+ /** Front left output channel (see FL in channel diagram) */
public static final int CHANNEL_OUT_FRONT_LEFT = 0x4;
+ /** Front right output channel (see FR in channel diagram) */
public static final int CHANNEL_OUT_FRONT_RIGHT = 0x8;
+ /** Front center output channel (see FC in channel diagram) */
public static final int CHANNEL_OUT_FRONT_CENTER = 0x10;
+ /** LFE "low frequency effect" channel
+ * When used in conjunction with {@link #CHANNEL_OUT_LOW_FREQUENCY_2}, it is intended
+ * to contain the left low-frequency effect signal, also referred to as "LFE1"
+ * in ITU-R BS.2159-8 */
public static final int CHANNEL_OUT_LOW_FREQUENCY = 0x20;
+ /** Back left output channel (see BL in channel diagram) */
public static final int CHANNEL_OUT_BACK_LEFT = 0x40;
+ /** Back right output channel (see BR in channel diagram) */
public static final int CHANNEL_OUT_BACK_RIGHT = 0x80;
public static final int CHANNEL_OUT_FRONT_LEFT_OF_CENTER = 0x100;
public static final int CHANNEL_OUT_FRONT_RIGHT_OF_CENTER = 0x200;
+ /** Back center output channel (see BC in channel diagram) */
public static final int CHANNEL_OUT_BACK_CENTER = 0x400;
+ /** Side left output channel (see SL in channel diagram) */
public static final int CHANNEL_OUT_SIDE_LEFT = 0x800;
+ /** Side right output channel (see SR in channel diagram) */
public static final int CHANNEL_OUT_SIDE_RIGHT = 0x1000;
- /** @hide */
+ /** Top center (above listener) output channel (see TC in channel diagram) */
public static final int CHANNEL_OUT_TOP_CENTER = 0x2000;
- /** @hide */
+ /** Top front left output channel (see TFL in channel diagram above FL) */
public static final int CHANNEL_OUT_TOP_FRONT_LEFT = 0x4000;
- /** @hide */
+ /** Top front center output channel (see TFC in channel diagram above FC) */
public static final int CHANNEL_OUT_TOP_FRONT_CENTER = 0x8000;
- /** @hide */
+ /** Top front right output channel (see TFR in channel diagram above FR) */
public static final int CHANNEL_OUT_TOP_FRONT_RIGHT = 0x10000;
- /** @hide */
+ /** Top back left output channel (see TBL in channel diagram above BL) */
public static final int CHANNEL_OUT_TOP_BACK_LEFT = 0x20000;
- /** @hide */
+ /** Top back center output channel (see TBC in channel diagram above BC) */
public static final int CHANNEL_OUT_TOP_BACK_CENTER = 0x40000;
- /** @hide */
+ /** Top back right output channel (see TBR in channel diagram above BR) */
public static final int CHANNEL_OUT_TOP_BACK_RIGHT = 0x80000;
- /** @hide */
+ /** Top side left output channel (see TSL in channel diagram above SL) */
public static final int CHANNEL_OUT_TOP_SIDE_LEFT = 0x100000;
- /** @hide */
+ /** Top side right output channel (see TSR in channel diagram above SR) */
public static final int CHANNEL_OUT_TOP_SIDE_RIGHT = 0x200000;
- /** @hide */
+ /** Bottom front left output channel (see BFL in channel diagram below FL) */
public static final int CHANNEL_OUT_BOTTOM_FRONT_LEFT = 0x400000;
- /** @hide */
+ /** Bottom front center output channel (see BFC in channel diagram below FC) */
public static final int CHANNEL_OUT_BOTTOM_FRONT_CENTER = 0x800000;
- /** @hide */
+ /** Bottom front right output channel (see BFR in channel diagram below FR) */
public static final int CHANNEL_OUT_BOTTOM_FRONT_RIGHT = 0x1000000;
- /** @hide */
+ /** The second LFE channel
+ * When used in conjunction with {@link #CHANNEL_OUT_LOW_FREQUENCY}, it is intended
+ * to contain the right low-frequency effect signal, also referred to as "LFE2"
+ * in ITU-R BS.2159-8 */
public static final int CHANNEL_OUT_LOW_FREQUENCY_2 = 0x2000000;
+ /** Front wide left output channel (see FWL in channel diagram) */
+ public static final int CHANNEL_OUT_FRONT_WIDE_LEFT = 0x4000000;
+ /** Front wide right output channel (see FWR in channel diagram) */
+ public static final int CHANNEL_OUT_FRONT_WIDE_RIGHT = 0x8000000;
public static final int CHANNEL_OUT_MONO = CHANNEL_OUT_FRONT_LEFT;
public static final int CHANNEL_OUT_STEREO = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT);
@@ -491,6 +532,7 @@ public final class AudioFormat implements Parcelable {
public static final int CHANNEL_OUT_SURROUND = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT |
CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_BACK_CENTER);
// aka 5POINT1_BACK
+ /** Output channel mask for 5.1 */
public static final int CHANNEL_OUT_5POINT1 = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT |
CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_LOW_FREQUENCY | CHANNEL_OUT_BACK_LEFT | CHANNEL_OUT_BACK_RIGHT);
/** @hide */
@@ -502,26 +544,39 @@ public final class AudioFormat implements Parcelable {
@Deprecated public static final int CHANNEL_OUT_7POINT1 = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT |
CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_LOW_FREQUENCY | CHANNEL_OUT_BACK_LEFT | CHANNEL_OUT_BACK_RIGHT |
CHANNEL_OUT_FRONT_LEFT_OF_CENTER | CHANNEL_OUT_FRONT_RIGHT_OF_CENTER);
+ /** Output channel mask for 7.1 */
// matches AUDIO_CHANNEL_OUT_7POINT1
public static final int CHANNEL_OUT_7POINT1_SURROUND = (
CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_FRONT_RIGHT |
CHANNEL_OUT_SIDE_LEFT | CHANNEL_OUT_SIDE_RIGHT |
CHANNEL_OUT_BACK_LEFT | CHANNEL_OUT_BACK_RIGHT |
CHANNEL_OUT_LOW_FREQUENCY);
- /** @hide */
+ /** Output channel mask for 5.1.2
+ * Same as 5.1 with the addition of left and right top channels */
public static final int CHANNEL_OUT_5POINT1POINT2 = (CHANNEL_OUT_5POINT1 |
CHANNEL_OUT_TOP_SIDE_LEFT | CHANNEL_OUT_TOP_SIDE_RIGHT);
- /** @hide */
+ /** Output channel mask for 5.1.4
+ * Same as 5.1 with the addition of four top channels */
public static final int CHANNEL_OUT_5POINT1POINT4 = (CHANNEL_OUT_5POINT1 |
CHANNEL_OUT_TOP_FRONT_LEFT | CHANNEL_OUT_TOP_FRONT_RIGHT |
CHANNEL_OUT_TOP_BACK_LEFT | CHANNEL_OUT_TOP_BACK_RIGHT);
- /** @hide */
+ /** Output channel mask for 7.1.2
+ * Same as 7.1 with the addition of left and right top channels*/
public static final int CHANNEL_OUT_7POINT1POINT2 = (CHANNEL_OUT_7POINT1_SURROUND |
CHANNEL_OUT_TOP_SIDE_LEFT | CHANNEL_OUT_TOP_SIDE_RIGHT);
- /** @hide */
+ /** Output channel mask for 7.1.4
+ * Same as 7.1 with the addition of four top channels */
public static final int CHANNEL_OUT_7POINT1POINT4 = (CHANNEL_OUT_7POINT1_SURROUND |
CHANNEL_OUT_TOP_FRONT_LEFT | CHANNEL_OUT_TOP_FRONT_RIGHT |
CHANNEL_OUT_TOP_BACK_LEFT | CHANNEL_OUT_TOP_BACK_RIGHT);
+ /** Output channel mask for 9.1.4
+ * Same as 7.1.4 with the addition of left and right front wide channels */
+ public static final int CHANNEL_OUT_9POINT1POINT4 = (CHANNEL_OUT_7POINT1POINT4
+ | CHANNEL_OUT_FRONT_WIDE_LEFT | CHANNEL_OUT_FRONT_WIDE_RIGHT);
+ /** Output channel mask for 9.1.6
+ * Same as 9.1.4 with the addition of left and right top side channels */
+ public static final int CHANNEL_OUT_9POINT1POINT6 = (CHANNEL_OUT_9POINT1POINT4
+ | CHANNEL_OUT_TOP_SIDE_LEFT | CHANNEL_OUT_TOP_SIDE_RIGHT);
/** @hide */
public static final int CHANNEL_OUT_13POINT_360RA = (
CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_FRONT_RIGHT |
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 054a7183e67e..bc8ccc333002 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -805,7 +805,7 @@ public class AudioManager {
}
@UnsupportedAppUsage
- private static IAudioService getService()
+ static IAudioService getService()
{
if (sService != null) {
return sService;
@@ -2443,6 +2443,19 @@ public class AudioManager {
}
//====================================================================
+ // Immersive audio
+
+ /**
+ * Return a handle to the optional platform's {@link Spatializer}
+ * @return the {@code Spatializer} instance.
+ * @see Spatializer#getImmersiveAudioLevel() to check for the level of support of the effect
+ * on the platform
+ */
+ public @NonNull Spatializer getSpatializer() {
+ return new Spatializer(this);
+ }
+
+ //====================================================================
// Bluetooth SCO control
/**
* Sticky broadcast intent action indicating that the Bluetooth SCO audio
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 8a4b2d56feb0..f1197edd453d 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -18,6 +18,7 @@ package android.media;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.TestApi;
import android.bluetooth.BluetoothCodecConfig;
@@ -1341,7 +1342,7 @@ public class AudioSystem
return DEVICE_OUT_BLE_SPEAKER_NAME;
case DEVICE_OUT_DEFAULT:
default:
- return Integer.toString(device);
+ return "0x" + Integer.toHexString(device);
}
}
@@ -2025,6 +2026,46 @@ public class AudioSystem
*/
public static native int setVibratorInfos(@NonNull List<Vibrator> vibrators);
+ /**
+ * @hide
+ * If a spatializer effect is present on the platform, this will return an
+ * ISpatializer interface to control this feature.
+ * If no spatializer is present, a null interface is returned.
+ * The INativeSpatializerCallback passed must not be null.
+ * Only one ISpatializer interface can exist at a given time. The native audio policy
+ * service will reject the request if an interface was already acquired and previous owner
+ * did not die or call ISpatializer.release().
+ * @param callback the callback to receive state updates if the ISpatializer
+ * interface is acquired.
+ * @return the ISpatializer interface made available to control the
+ * platform spatializer
+ */
+ @Nullable
+ public static ISpatializer getSpatializer(INativeSpatializerCallback callback) {
+ return ISpatializer.Stub.asInterface(nativeGetSpatializer(callback));
+ }
+ private static native IBinder nativeGetSpatializer(INativeSpatializerCallback callback);
+
+ /**
+ * @hide
+ * Queries if some kind of spatialization will be performed if the audio playback context
+ * described by the provided arguments is present.
+ * The context is made of:
+ * - The audio attributes describing the playback use case.
+ * - The audio configuration describing the audio format, channels, sampling rate ...
+ * - The devices describing the sink audio device selected for playback.
+ * All arguments are optional and only the specified arguments are used to match against
+ * supported criteria. For instance, supplying no argument will tell if spatialization is
+ * supported or not in general.
+ * @param attributes audio attributes describing the playback use case
+ * @param format audio configuration describing the audio format, channels, sampling rate...
+ * @param devices the sink audio device selected for playback
+ * @return true if spatialization is enabled for this context, false otherwise.
+ */
+ public static native boolean canBeSpatialized(AudioAttributes attributes,
+ AudioFormat format,
+ AudioDeviceAttributes[] devices);
+
// Items shared with audio service
/**
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 23d9532e11a0..476a9a58ef4b 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -1613,7 +1613,9 @@ public class AudioTrack extends PlayerBase
AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_LEFT |
AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_CENTER |
AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_RIGHT |
- AudioFormat.CHANNEL_OUT_LOW_FREQUENCY_2;
+ AudioFormat.CHANNEL_OUT_LOW_FREQUENCY_2 |
+ AudioFormat.CHANNEL_OUT_FRONT_WIDE_LEFT |
+ AudioFormat.CHANNEL_OUT_FRONT_WIDE_RIGHT;
// Returns a boolean whether the attributes, format, bufferSizeInBytes, mode allow
// power saving to be automatically enabled for an AudioTrack. Returns false if
@@ -1787,6 +1789,8 @@ public class AudioTrack extends PlayerBase
| AudioFormat.CHANNEL_OUT_TOP_SIDE_RIGHT);
put("bottom front", AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_LEFT
| AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_RIGHT);
+ put("front wide", AudioFormat.CHANNEL_OUT_FRONT_WIDE_LEFT
+ | AudioFormat.CHANNEL_OUT_FRONT_WIDE_RIGHT);
}};
/**
@@ -1801,9 +1805,15 @@ public class AudioTrack extends PlayerBase
return false;
}
final int channelCount = AudioFormat.channelCountFromOutChannelMask(channelConfig);
- final int channelCountLimit = AudioFormat.isEncodingLinearFrames(encoding)
- ? AudioSystem.OUT_CHANNEL_COUNT_MAX // PCM limited to OUT_CHANNEL_COUNT_MAX
- : AudioSystem.FCC_24; // Compressed limited to 24 channels
+ final int channelCountLimit;
+ try {
+ channelCountLimit = AudioFormat.isEncodingLinearFrames(encoding)
+ ? AudioSystem.OUT_CHANNEL_COUNT_MAX // PCM limited to OUT_CHANNEL_COUNT_MAX
+ : AudioSystem.FCC_24; // Compressed limited to 24 channels
+ } catch (IllegalArgumentException iae) {
+ loge("Unsupported encoding " + iae);
+ return false;
+ }
if (channelCount > channelCountLimit) {
loge("Channel configuration contains too many channels for encoding "
+ encoding + "(" + channelCount + " > " + channelCountLimit + ")");
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 268419f8db8f..d06871b41111 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -20,6 +20,7 @@ import android.bluetooth.BluetoothDevice;
import android.content.ComponentName;
import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
+import android.media.AudioFormat;
import android.media.AudioFocusInfo;
import android.media.AudioPlaybackConfiguration;
import android.media.AudioRecordingConfiguration;
@@ -34,6 +35,10 @@ import android.media.IPlaybackConfigDispatcher;
import android.media.IRecordingConfigDispatcher;
import android.media.IRingtonePlayer;
import android.media.IStrategyPreferredDevicesDispatcher;
+import android.media.ISpatializerCallback;
+import android.media.ISpatializerHeadTrackingModeCallback;
+import android.media.ISpatializerHeadToSoundStagePoseCallback;
+import android.media.ISpatializerOutputCallback;
import android.media.IVolumeController;
import android.media.IVolumeController;
import android.media.PlayerBase;
@@ -397,4 +402,54 @@ interface IAudioService {
void registerModeDispatcher(IAudioModeDispatcher dispatcher);
oneway void unregisterModeDispatcher(IAudioModeDispatcher dispatcher);
+
+ int getSpatializerImmersiveAudioLevel();
+
+ boolean isSpatializerEnabled();
+
+ boolean isSpatializerAvailable();
+
+ void setSpatializerEnabled(boolean enabled);
+
+ boolean canBeSpatialized(in AudioAttributes aa, in AudioFormat af);
+
+ void registerSpatializerCallback(in ISpatializerCallback cb);
+
+ void unregisterSpatializerCallback(in ISpatializerCallback cb);
+
+ void registerSpatializerHeadTrackingCallback(in ISpatializerHeadTrackingModeCallback cb);
+
+ void unregisterSpatializerHeadTrackingCallback(in ISpatializerHeadTrackingModeCallback cb);
+
+ void registerHeadToSoundstagePoseCallback(in ISpatializerHeadToSoundStagePoseCallback cb);
+
+ void unregisterHeadToSoundstagePoseCallback(in ISpatializerHeadToSoundStagePoseCallback cb);
+
+ List<AudioDeviceAttributes> getSpatializerCompatibleAudioDevices();
+
+ void addSpatializerCompatibleAudioDevice(in AudioDeviceAttributes ada);
+
+ void removeSpatializerCompatibleAudioDevice(in AudioDeviceAttributes ada);
+
+ void setDesiredHeadTrackingMode(int mode);
+
+ int getDesiredHeadTrackingMode();
+
+ int[] getSupportedHeadTrackingModes();
+
+ int getActualHeadTrackingMode();
+
+ oneway void setSpatializerGlobalTransform(in float[] transform);
+
+ oneway void recenterHeadTracker();
+
+ void setSpatializerParameter(int key, in byte[] value);
+
+ void getSpatializerParameter(int key, inout byte[] value);
+
+ int getSpatializerOutput();
+
+ void registerSpatializerOutputCallback(in ISpatializerOutputCallback cb);
+
+ void unregisterSpatializerOutputCallback(in ISpatializerOutputCallback cb);
}
diff --git a/media/java/android/media/IMediaRouterClient.aidl b/media/java/android/media/IMediaRouterClient.aidl
index 9b4912373122..6b754e157cfb 100644
--- a/media/java/android/media/IMediaRouterClient.aidl
+++ b/media/java/android/media/IMediaRouterClient.aidl
@@ -23,4 +23,5 @@ oneway interface IMediaRouterClient {
void onStateChanged();
void onRestoreRoute();
void onGroupRouteSelected(String routeId);
+ void onGlobalA2dpChanged(boolean a2dpOn);
}
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index 48289ecde9e0..25b582d2fc8d 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -39,6 +39,7 @@ interface IMediaRouterService {
MediaRouterClientState getState(IMediaRouterClient client);
boolean isPlaybackActive(IMediaRouterClient client);
+ void setBluetoothA2dpOn(IMediaRouterClient client, boolean on);
void setDiscoveryRequest(IMediaRouterClient client, int routeTypes, boolean activeScan);
void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit);
void requestSetVolume(IMediaRouterClient client, String routeId, int volume);
diff --git a/media/java/android/media/ISpatializerCallback.aidl b/media/java/android/media/ISpatializerCallback.aidl
new file mode 100644
index 000000000000..50f91e737fc5
--- /dev/null
+++ b/media/java/android/media/ISpatializerCallback.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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;
+
+/**
+ * AIDL for the AudioService to signal Spatializer state changes.
+ *
+ * {@hide}
+ */
+oneway interface ISpatializerCallback {
+
+ void dispatchSpatializerEnabledChanged(boolean enabled);
+
+ void dispatchSpatializerAvailableChanged(boolean available);
+}
diff --git a/media/java/android/media/ISpatializerHeadToSoundStagePoseCallback.aidl b/media/java/android/media/ISpatializerHeadToSoundStagePoseCallback.aidl
new file mode 100644
index 000000000000..01a146599394
--- /dev/null
+++ b/media/java/android/media/ISpatializerHeadToSoundStagePoseCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+/**
+ * AIDL for the AudioService to signal Spatializer state changes.
+ *
+ * {@hide}
+ */
+oneway interface ISpatializerHeadToSoundStagePoseCallback {
+
+ /**
+ * The pose is sent as an array of 6 float values, the first 3 are the translation vector, the
+ * other 3 are the rotation vector.
+ */
+ void dispatchPoseChanged(in float[] pose);
+}
diff --git a/media/java/android/media/ISpatializerHeadTrackingModeCallback.aidl b/media/java/android/media/ISpatializerHeadTrackingModeCallback.aidl
new file mode 100644
index 000000000000..c61f86e4c60e
--- /dev/null
+++ b/media/java/android/media/ISpatializerHeadTrackingModeCallback.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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;
+
+/**
+ * AIDL for the AudioService to signal Spatializer head tracking mode changes.
+ *
+ * {@hide}
+ */
+oneway interface ISpatializerHeadTrackingModeCallback {
+
+ void dispatchSpatializerActualHeadTrackingModeChanged(int mode);
+
+ void dispatchSpatializerDesiredHeadTrackingModeChanged(int mode);
+}
diff --git a/media/java/android/media/ISpatializerOutputCallback.aidl b/media/java/android/media/ISpatializerOutputCallback.aidl
new file mode 100644
index 000000000000..57572a81a366
--- /dev/null
+++ b/media/java/android/media/ISpatializerOutputCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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;
+
+/**
+ * AIDL for the AudioService to signal Spatializer output changes.
+ *
+ * {@hide}
+ */
+oneway interface ISpatializerOutputCallback {
+
+ void dispatchSpatializerOutputChanged(int output);
+}
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index 9bf0db52f66d..0847be34d90f 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -683,6 +683,19 @@ public final class MediaFormat {
public static final String KEY_CHANNEL_MASK = "channel-mask";
/**
+ * A key describing the maximum number of channels that can be output by an audio decoder.
+ * By default, the decoder will output the same number of channels as present in the encoded
+ * stream, if supported. Set this value to limit the number of output channels, and use
+ * the downmix information in the stream, if available.
+ * <p>Values larger than the number of channels in the content to decode behave like the number
+ * of channels in the content (if applicable), for instance passing 99 for a 5.1 audio stream
+ * behaves like passing 6.
+ * <p>This key is only used during decoding.
+ */
+ public static final String KEY_MAX_OUTPUT_CHANNEL_COUNT =
+ "max-output-channel-count";
+
+ /**
* A key describing the number of frames to trim from the start of the decoded audio stream.
* The associated value is an integer.
*/
diff --git a/media/java/android/media/MediaMetrics.java b/media/java/android/media/MediaMetrics.java
index 3a5216e1c4e7..6eb1af8ac980 100644
--- a/media/java/android/media/MediaMetrics.java
+++ b/media/java/android/media/MediaMetrics.java
@@ -53,6 +53,7 @@ public class MediaMetrics {
public static final String AUDIO_VOLUME = AUDIO + SEPARATOR + "volume";
public static final String AUDIO_VOLUME_EVENT = AUDIO_VOLUME + SEPARATOR + "event";
public static final String AUDIO_MODE = AUDIO + SEPARATOR + "mode";
+ public static final String METRICS_MANAGER = "metrics" + SEPARATOR + "manager";
}
/**
@@ -120,10 +121,11 @@ public class MediaMetrics {
createKey("gainDb", Double.class);
public static final Key<String> GROUP =
createKey("group", String.class);
- // For volume
- public static final Key<Integer> INDEX = createKey("index", Integer.class);
- public static final Key<Integer> MAX_INDEX = createKey("maxIndex", Integer.class);
- public static final Key<Integer> MIN_INDEX = createKey("minIndex", Integer.class);
+
+ public static final Key<Integer> INDEX = createKey("index", Integer.class); // volume
+ public static final Key<String> LOG_SESSION_ID = createKey("logSessionId", String.class);
+ public static final Key<Integer> MAX_INDEX = createKey("maxIndex", Integer.class); // vol
+ public static final Key<Integer> MIN_INDEX = createKey("minIndex", Integer.class); // vol
public static final Key<String> MODE =
createKey("mode", String.class); // audio_mode
public static final Key<String> MUTE =
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index 2986f7c75f4d..748ae52d6c0c 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -654,12 +654,9 @@ public class MediaRouter {
final class Client extends IMediaRouterClient.Stub {
@Override
public void onStateChanged() {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- if (Client.this == mClient) {
- updateClientState();
- }
+ mHandler.post(() -> {
+ if (Client.this == mClient) {
+ updateClientState();
}
});
}
@@ -693,6 +690,26 @@ public class MediaRouter {
}
});
}
+
+ // Called when the selection of a connected device (phone speaker or BT devices)
+ // is changed.
+ @Override
+ public void onGlobalA2dpChanged(boolean a2dpOn) {
+ mHandler.post(() -> {
+ if (mSelectedRoute == null || mBluetoothA2dpRoute == null) {
+ return;
+ }
+ if (mSelectedRoute.isDefault() && a2dpOn) {
+ setSelectedRoute(mBluetoothA2dpRoute, /*explicit=*/ false);
+ dispatchRouteUnselected(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo);
+ dispatchRouteSelected(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute);
+ } else if (mSelectedRoute.isBluetooth() && !a2dpOn) {
+ setSelectedRoute(mDefaultAudioVideo, /*explicit=*/ false);
+ dispatchRouteUnselected(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute);
+ dispatchRouteSelected(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo);
+ }
+ });
+ }
}
}
@@ -1070,7 +1087,8 @@ public class MediaRouter {
&& (types & ROUTE_TYPE_LIVE_AUDIO) != 0
&& (route.isBluetooth() || route.isDefault())) {
try {
- sStatic.mAudioService.setBluetoothA2dpOn(route.isBluetooth());
+ sStatic.mMediaRouterService.setBluetoothA2dpOn(sStatic.mClient,
+ route.isBluetooth());
} catch (RemoteException e) {
Log.e(TAG, "Error changing Bluetooth A2DP state", e);
}
@@ -1350,6 +1368,9 @@ public class MediaRouter {
}
static void dispatchRouteSelected(int type, RouteInfo info) {
+ if (DEBUG) {
+ Log.d(TAG, "Dispatching route selected: " + info);
+ }
for (CallbackInfo cbi : sStatic.mCallbacks) {
if (cbi.filterRouteEvent(info)) {
cbi.cb.onRouteSelected(cbi.router, type, info);
@@ -1358,6 +1379,9 @@ public class MediaRouter {
}
static void dispatchRouteUnselected(int type, RouteInfo info) {
+ if (DEBUG) {
+ Log.d(TAG, "Dispatching route unselected: " + info);
+ }
for (CallbackInfo cbi : sStatic.mCallbacks) {
if (cbi.filterRouteEvent(info)) {
cbi.cb.onRouteUnselected(cbi.router, type, info);
diff --git a/media/java/android/media/Spatializer.java b/media/java/android/media/Spatializer.java
new file mode 100644
index 000000000000..e6fff392c264
--- /dev/null
+++ b/media/java/android/media/Spatializer.java
@@ -0,0 +1,1089 @@
+/*
+ * 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.SystemApi;
+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 */
+ @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,
+ }) public @interface HeadTrackingMode {};
+
+ /** @hide */
+ @IntDef(flag = false, value = {
+ HEAD_TRACKING_MODE_DISABLED,
+ HEAD_TRACKING_MODE_RELATIVE_WORLD,
+ HEAD_TRACKING_MODE_RELATIVE_DEVICE,
+ }) public @interface HeadTrackingModeSet {};
+
+ /** @hide */
+ @IntDef(flag = false, value = {
+ HEAD_TRACKING_MODE_RELATIVE_WORLD,
+ HEAD_TRACKING_MODE_RELATIVE_DEVICE,
+ }) 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;
+
+ /**
+ * 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);
+ }
+
+
+ /**
+ * @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) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(listener);
+ synchronized (mStateListenerLock) {
+ if (hasSpatializerStateListener(listener)) {
+ throw new IllegalArgumentException(
+ "Called addOnSpatializerStateChangedListener() "
+ + "on a previously registered listener");
+ }
+ // lazy initialization of the list of strategy-preferred device listener
+ if (mStateListeners == null) {
+ mStateListeners = new ArrayList<>();
+ }
+ mStateListeners.add(new StateListenerInfo(listener, executor));
+ if (mStateListeners.size() == 1) {
+ // register binder for callbacks
+ if (mInfoDispatcherStub == null) {
+ mInfoDispatcherStub =
+ new SpatializerInfoDispatcherStub();
+ }
+ try {
+ mAm.getService().registerSpatializerCallback(
+ mInfoDispatcherStub);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+ }
+
+ /**
+ * 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) {
+ Objects.requireNonNull(listener);
+ synchronized (mStateListenerLock) {
+ if (!removeStateListener(listener)) {
+ throw new IllegalArgumentException(
+ "Called removeOnSpatializerStateChangedListener() "
+ + "on an unregistered listener");
+ }
+ if (mStateListeners.size() == 0) {
+ // unregister binder for callbacks
+ try {
+ mAm.getService().unregisterSpatializerCallback(mInfoDispatcherStub);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } finally {
+ mInfoDispatcherStub = null;
+ mStateListeners = null;
+ }
+ }
+ }
+ }
+
+ /**
+ * @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);
+ }
+ }
+
+ private final Object mStateListenerLock = new Object();
+ /**
+ * List of listeners for state listener and their associated Executor.
+ * List is lazy-initialized on first registration
+ */
+ @GuardedBy("mStateListenerLock")
+ private @Nullable ArrayList<StateListenerInfo> mStateListeners;
+
+ @GuardedBy("mStateListenerLock")
+ private @Nullable SpatializerInfoDispatcherStub mInfoDispatcherStub;
+
+ private final class SpatializerInfoDispatcherStub extends ISpatializerCallback.Stub {
+ @Override
+ public void dispatchSpatializerEnabledChanged(boolean enabled) {
+ // make a shallow copy of listeners so callback is not executed under lock
+ final ArrayList<StateListenerInfo> stateListeners;
+ synchronized (mStateListenerLock) {
+ if (mStateListeners == null || mStateListeners.size() == 0) {
+ return;
+ }
+ stateListeners = (ArrayList<StateListenerInfo>) mStateListeners.clone();
+ }
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ for (StateListenerInfo info : stateListeners) {
+ info.mExecutor.execute(() ->
+ info.mListener.onSpatializerEnabledChanged(Spatializer.this, enabled));
+ }
+ }
+ }
+
+ @Override
+ public void dispatchSpatializerAvailableChanged(boolean available) {
+ // make a shallow copy of listeners so callback is not executed under lock
+ final ArrayList<StateListenerInfo> stateListeners;
+ synchronized (mStateListenerLock) {
+ if (mStateListeners == null || mStateListeners.size() == 0) {
+ return;
+ }
+ stateListeners = (ArrayList<StateListenerInfo>) mStateListeners.clone();
+ }
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ for (StateListenerInfo info : stateListeners) {
+ info.mExecutor.execute(() ->
+ info.mListener.onSpatializerAvailableChanged(
+ Spatializer.this, available));
+ }
+ }
+ }
+ }
+
+ private static class StateListenerInfo {
+ final @NonNull OnSpatializerStateChangedListener mListener;
+ final @NonNull Executor mExecutor;
+
+ StateListenerInfo(@NonNull OnSpatializerStateChangedListener listener,
+ @NonNull Executor exe) {
+ mListener = listener;
+ mExecutor = exe;
+ }
+ }
+
+ @GuardedBy("mStateListenerLock")
+ private boolean hasSpatializerStateListener(OnSpatializerStateChangedListener listener) {
+ return getStateListenerInfo(listener) != null;
+ }
+
+ @GuardedBy("mStateListenerLock")
+ private @Nullable StateListenerInfo getStateListenerInfo(
+ OnSpatializerStateChangedListener listener) {
+ if (mStateListeners == null) {
+ return null;
+ }
+ for (StateListenerInfo info : mStateListeners) {
+ if (info.mListener == listener) {
+ return info;
+ }
+ }
+ return null;
+ }
+
+ @GuardedBy("mStateListenerLock")
+ /**
+ * @return true if the listener was removed from the list
+ */
+ private boolean removeStateListener(OnSpatializerStateChangedListener listener) {
+ final StateListenerInfo infoToRemove = getStateListenerInfo(listener);
+ if (infoToRemove != null) {
+ mStateListeners.remove(infoToRemove);
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ * @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) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(listener);
+ synchronized (mHeadTrackingListenerLock) {
+ if (hasListener(listener, mHeadTrackingListeners)) {
+ throw new IllegalArgumentException(
+ "Called addOnHeadTrackingModeChangedListener() "
+ + "on a previously registered listener");
+ }
+ // lazy initialization of the list of strategy-preferred device listener
+ if (mHeadTrackingListeners == null) {
+ mHeadTrackingListeners = new ArrayList<>();
+ }
+ mHeadTrackingListeners.add(
+ new ListenerInfo<OnHeadTrackingModeChangedListener>(listener, executor));
+ if (mHeadTrackingListeners.size() == 1) {
+ // register binder for callbacks
+ if (mHeadTrackingDispatcherStub == null) {
+ mHeadTrackingDispatcherStub =
+ new SpatializerHeadTrackingDispatcherStub();
+ }
+ try {
+ mAm.getService().registerSpatializerHeadTrackingCallback(
+ mHeadTrackingDispatcherStub);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+ }
+
+ /**
+ * @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) {
+ Objects.requireNonNull(listener);
+ synchronized (mHeadTrackingListenerLock) {
+ if (!removeListener(listener, mHeadTrackingListeners)) {
+ throw new IllegalArgumentException(
+ "Called removeOnHeadTrackingModeChangedListener() "
+ + "on an unregistered listener");
+ }
+ if (mHeadTrackingListeners.size() == 0) {
+ // unregister binder for callbacks
+ try {
+ mAm.getService().unregisterSpatializerHeadTrackingCallback(
+ mHeadTrackingDispatcherStub);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } finally {
+ mHeadTrackingDispatcherStub = null;
+ mHeadTrackingListeners = null;
+ }
+ }
+ }
+ }
+
+ /**
+ * @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
+ * @return id of the output stream, or 0 if no spatializer playback is active
+ */
+ @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);
+ } 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;
+ }
+ }
+
+ //-----------------------------------------------------------------------------
+ // callback helper definitions
+
+ private static class ListenerInfo<T> {
+ final @NonNull T mListener;
+ final @NonNull Executor mExecutor;
+
+ ListenerInfo(T listener, Executor exe) {
+ mListener = listener;
+ mExecutor = exe;
+ }
+ }
+
+ private static <T> ListenerInfo<T> getListenerInfo(
+ T listener, ArrayList<ListenerInfo<T>> listeners) {
+ if (listeners == null) {
+ return null;
+ }
+ for (ListenerInfo<T> info : listeners) {
+ if (info.mListener == listener) {
+ return info;
+ }
+ }
+ return null;
+ }
+
+ private static <T> boolean hasListener(T listener, ArrayList<ListenerInfo<T>> listeners) {
+ return getListenerInfo(listener, listeners) != null;
+ }
+
+ private static <T> boolean removeListener(T listener, ArrayList<ListenerInfo<T>> listeners) {
+ final ListenerInfo<T> infoToRemove = getListenerInfo(listener, listeners);
+ if (infoToRemove != null) {
+ listeners.remove(infoToRemove);
+ return true;
+ }
+ return false;
+ }
+
+ //-----------------------------------------------------------------------------
+ // head tracking callback management and stub
+
+ private final Object mHeadTrackingListenerLock = new Object();
+ /**
+ * List of listeners for head tracking mode listener and their associated Executor.
+ * List is lazy-initialized on first registration
+ */
+ @GuardedBy("mHeadTrackingListenerLock")
+ private @Nullable ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>>
+ mHeadTrackingListeners;
+
+ @GuardedBy("mHeadTrackingListenerLock")
+ private @Nullable SpatializerHeadTrackingDispatcherStub mHeadTrackingDispatcherStub;
+
+ private final class SpatializerHeadTrackingDispatcherStub
+ extends ISpatializerHeadTrackingModeCallback.Stub {
+ @Override
+ public void dispatchSpatializerActualHeadTrackingModeChanged(int mode) {
+ // make a shallow copy of listeners so callback is not executed under lock
+ final ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>> headTrackingListeners;
+ synchronized (mHeadTrackingListenerLock) {
+ if (mHeadTrackingListeners == null || mHeadTrackingListeners.size() == 0) {
+ return;
+ }
+ headTrackingListeners = (ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>>)
+ mHeadTrackingListeners.clone();
+ }
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ for (ListenerInfo<OnHeadTrackingModeChangedListener> info : headTrackingListeners) {
+ info.mExecutor.execute(() -> info.mListener
+ .onHeadTrackingModeChanged(Spatializer.this, mode));
+ }
+ }
+ }
+
+ @Override
+ public void dispatchSpatializerDesiredHeadTrackingModeChanged(int mode) {
+ // make a shallow copy of listeners so callback is not executed under lock
+ final ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>> headTrackingListeners;
+ synchronized (mHeadTrackingListenerLock) {
+ if (mHeadTrackingListeners == null || mHeadTrackingListeners.size() == 0) {
+ return;
+ }
+ headTrackingListeners = (ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>>)
+ mHeadTrackingListeners.clone();
+ }
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ for (ListenerInfo<OnHeadTrackingModeChangedListener> info : headTrackingListeners) {
+ info.mExecutor.execute(() -> info.mListener
+ .onDesiredHeadTrackingModeChanged(Spatializer.this, mode));
+ }
+ }
+ }
+ }
+
+ //-----------------------------------------------------------------------------
+ // 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));
+ }
+ }
+ }
+}
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index 37e141537c79..5259c4f07639 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -16,15 +16,16 @@
package android.media.projection;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.hardware.display.VirtualDisplayConfig;
-import android.media.projection.IMediaProjection;
-import android.media.projection.IMediaProjectionCallback;
import android.os.Handler;
+import android.os.IBinder;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
@@ -100,19 +101,22 @@ public final class MediaProjection {
public VirtualDisplay createVirtualDisplay(@NonNull String name,
int width, int height, int dpi, boolean isSecure, @Nullable Surface surface,
@Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
- DisplayManager dm = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR
| DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
if (isSecure) {
flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE;
}
- final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,
- height, dpi);
+ Context windowContext = mContext.createWindowContext(mContext.getDisplayNoVerify(),
+ TYPE_APPLICATION, null /* options */);
+ final VirtualDisplayConfig.Builder builder = buildMirroredVirtualDisplay(name, width,
+ height, dpi, windowContext.getWindowContextToken());
builder.setFlags(flags);
if (surface != null) {
builder.setSurface(surface);
}
- return dm.createVirtualDisplay(this, builder.build(), callback, handler);
+ VirtualDisplay virtualDisplay = createVirtualDisplay(builder.build(), callback, handler,
+ windowContext);
+ return virtualDisplay;
}
/**
@@ -141,13 +145,35 @@ public final class MediaProjection {
public VirtualDisplay createVirtualDisplay(@NonNull String name,
int width, int height, int dpi, int flags, @Nullable Surface surface,
@Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
- final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,
- height, dpi);
+ Context windowContext = mContext.createWindowContext(mContext.getDisplayNoVerify(),
+ TYPE_APPLICATION, null /* options */);
+ final VirtualDisplayConfig.Builder builder = buildMirroredVirtualDisplay(name, width,
+ height, dpi, windowContext.getWindowContextToken());
builder.setFlags(flags);
if (surface != null) {
builder.setSurface(surface);
}
- return createVirtualDisplay(builder.build(), callback, handler);
+ VirtualDisplay virtualDisplay = createVirtualDisplay(builder.build(), callback, handler,
+ windowContext);
+ return virtualDisplay;
+ }
+
+ /**
+ * Constructs a {@link VirtualDisplayConfig.Builder}, which will mirror the contents of a
+ * DisplayArea. The DisplayArea to mirror is from the DisplayArea the caller is launched on.
+ *
+ * @param name The name of the virtual display, must be non-empty.
+ * @param width The width of the virtual display in pixels. Must be greater than 0.
+ * @param height The height of the virtual display in pixels. Must be greater than 0.
+ * @param dpi The density of the virtual display in dpi. Must be greater than 0.
+ * @return a config representing a VirtualDisplay
+ */
+ private VirtualDisplayConfig.Builder buildMirroredVirtualDisplay(@NonNull String name,
+ int width, int height, int dpi, IBinder windowContextToken) {
+ final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,
+ height, dpi);
+ builder.setWindowTokenClientToMirror(windowContextToken);
+ return builder;
}
/**
@@ -156,20 +182,22 @@ public final class MediaProjection {
*
* @param virtualDisplayConfig The arguments for the virtual display configuration. See
* {@link VirtualDisplayConfig} for using it.
- * @param callback Callback to call when the virtual display's state
- * changes, or null if none.
- * @param handler The {@link android.os.Handler} on which the callback should be
- * invoked, or null if the callback should be invoked on the calling
- * thread's main {@link android.os.Looper}.
+ * @param callback Callback to call when the virtual display's state changes, or null if none.
+ * @param handler The {@link android.os.Handler} on which the callback should be invoked, or
+ * null if the callback should be invoked on the calling thread's main
+ * {@link android.os.Looper}.
+ * @param windowContext the WindowContext associated with the caller.
*
* @see android.hardware.display.VirtualDisplay
* @hide
*/
@Nullable
public VirtualDisplay createVirtualDisplay(@NonNull VirtualDisplayConfig virtualDisplayConfig,
- @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
+ @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler,
+ Context windowContext) {
DisplayManager dm = mContext.getSystemService(DisplayManager.class);
- return dm.createVirtualDisplay(this, virtualDisplayConfig, callback, handler);
+ return dm.createVirtualDisplay(this, virtualDisplayConfig, callback, handler,
+ windowContext);
}
/**
diff --git a/media/packages/BluetoothMidiService/AndroidManifest.xml b/media/packages/BluetoothMidiService/AndroidManifest.xml
index 03606bac6840..90390116df06 100644
--- a/media/packages/BluetoothMidiService/AndroidManifest.xml
+++ b/media/packages/BluetoothMidiService/AndroidManifest.xml
@@ -20,7 +20,7 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.android.bluetoothmidiservice"
>
- <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
+ <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="30" />
<uses-feature android:name="android.hardware.bluetooth_le"
android:required="true"/>
diff --git a/media/packages/BluetoothMidiService/AndroidManifestBase.xml b/media/packages/BluetoothMidiService/AndroidManifestBase.xml
index bfb05469adb9..5a900c794dd1 100644
--- a/media/packages/BluetoothMidiService/AndroidManifestBase.xml
+++ b/media/packages/BluetoothMidiService/AndroidManifestBase.xml
@@ -19,7 +19,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.bluetoothmidiservice"
>
- <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
+ <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="30" />
<application
android:label="BluetoothMidi"
android:defaultToDeviceProtectedStorage="true"