From 89f87edfd2f0407c5101a9bc9f2929f3c183b4e4 Mon Sep 17 00:00:00 2001 From: jiabin Date: Thu, 1 Dec 2022 22:55:05 +0000 Subject: [PATCH] Add APIs for setting preferred mixer attributes. This is part of improving USB audio improvment. With setting preferred mixer attributes, the apps will be able to pick the best configuration for their playback. That can help avoid data conversion so that the apps can provide best audio experience and improve latency. Bug: 239435816 Test: atest AudioManagerTest Change-Id: I6c930a53c4071e5426be1c6dd3ccb6a999de214d --- core/api/current.txt | 25 ++ core/jni/android_media_AudioMixerAttributes.h | 46 ++++ core/jni/android_media_AudioSystem.cpp | 253 +++++++++++++++++- media/java/android/media/AudioManager.java | 215 +++++++++++++++ .../android/media/AudioMixerAttributes.aidl | 18 ++ .../android/media/AudioMixerAttributes.java | 183 +++++++++++++ media/java/android/media/AudioSystem.java | 36 +++ media/java/android/media/IAudioService.aidl | 12 + .../IPreferredMixerAttributesDispatcher.aidl | 34 +++ .../server/audio/AudioDeviceBroker.java | 9 + .../server/audio/AudioDeviceInventory.java | 18 ++ .../android/server/audio/AudioService.java | 140 ++++++++++ .../server/audio/AudioSystemAdapter.java | 31 +++ 13 files changed, 1019 insertions(+), 1 deletion(-) create mode 100644 core/jni/android_media_AudioMixerAttributes.h create mode 100644 media/java/android/media/AudioMixerAttributes.aidl create mode 100644 media/java/android/media/AudioMixerAttributes.java create mode 100644 media/java/android/media/IPreferredMixerAttributesDispatcher.aidl diff --git a/core/api/current.txt b/core/api/current.txt index 2a958933713c1..c98b58ee1c5d1 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -20546,10 +20546,12 @@ package android.media { method public int abandonAudioFocusRequest(@NonNull android.media.AudioFocusRequest); method public void addOnCommunicationDeviceChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnCommunicationDeviceChangedListener); method public void addOnModeChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnModeChangedListener); + method public void addOnPreferredMixerAttributesChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredMixerAttributesChangedListener); method public void adjustStreamVolume(int, int, int); method public void adjustSuggestedStreamVolume(int, int, int); method public void adjustVolume(int, int); method public void clearCommunicationDevice(); + method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS) public boolean clearPreferredMixerAttributes(@NonNull android.media.AudioAttributes, @NonNull android.media.AudioDeviceInfo); method public void dispatchMediaKeyEvent(android.view.KeyEvent); method public int generateAudioSessionId(); method @NonNull public java.util.List getActivePlaybackConfigurations(); @@ -20567,6 +20569,7 @@ package android.media { method public int getMode(); method public String getParameters(String); method @Deprecated public static int getPlaybackOffloadSupport(@NonNull android.media.AudioFormat, @NonNull android.media.AudioAttributes); + method @Nullable public android.media.AudioMixerAttributes getPreferredMixerAttributes(@NonNull android.media.AudioAttributes, @NonNull android.media.AudioDeviceInfo); method public String getProperty(String); method public int getRingerMode(); method @Deprecated public int getRouting(int); @@ -20575,6 +20578,7 @@ package android.media { method public int getStreamMinVolume(int); method public int getStreamVolume(int); method public float getStreamVolumeDb(int, int, int); + method @NonNull public java.util.List getSupportedMixerAttributes(@NonNull android.media.AudioDeviceInfo); method @Deprecated public int getVibrateSetting(int); method @Deprecated public boolean isBluetoothA2dpOn(); method public boolean isBluetoothScoAvailableOffCall(); @@ -20602,6 +20606,7 @@ package android.media { method @Deprecated public boolean registerRemoteController(android.media.RemoteController); method public void removeOnCommunicationDeviceChangedListener(@NonNull android.media.AudioManager.OnCommunicationDeviceChangedListener); method public void removeOnModeChangedListener(@NonNull android.media.AudioManager.OnModeChangedListener); + method public void removeOnPreferredMixerAttributesChangedListener(@NonNull android.media.AudioManager.OnPreferredMixerAttributesChangedListener); method @Deprecated public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, int, int); method public int requestAudioFocus(@NonNull android.media.AudioFocusRequest); method public void setAllowedCapturePolicy(int); @@ -20612,6 +20617,7 @@ package android.media { method public void setMicrophoneMute(boolean); method public void setMode(int); method public void setParameters(String); + method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS) public boolean setPreferredMixerAttributes(@NonNull android.media.AudioAttributes, @NonNull android.media.AudioDeviceInfo, @NonNull android.media.AudioMixerAttributes); method public void setRingerMode(int); method @Deprecated public void setRouting(int, int, int); method @Deprecated public void setSpeakerphoneOn(boolean); @@ -20767,6 +20773,10 @@ package android.media { method public void onModeChanged(int); } + public static interface AudioManager.OnPreferredMixerAttributesChangedListener { + method public void onPreferredMixerAttributesChanged(@NonNull android.media.AudioAttributes, @NonNull android.media.AudioDeviceInfo, @Nullable android.media.AudioMixerAttributes); + } + public final class AudioMetadata { method @NonNull public static android.media.AudioMetadataMap createMap(); } @@ -20802,6 +20812,21 @@ package android.media { method @IntRange(from=0) public int size(); } + public final class AudioMixerAttributes implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public android.media.AudioFormat getFormat(); + method public int getMixerBehavior(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field public static final int MIXER_BEHAVIOR_DEFAULT = 0; // 0x0 + } + + public static final class AudioMixerAttributes.Builder { + ctor public AudioMixerAttributes.Builder(@NonNull android.media.AudioFormat); + method @NonNull public android.media.AudioMixerAttributes build(); + method @NonNull public android.media.AudioMixerAttributes.Builder setMixerBehavior(int); + } + public final class AudioPlaybackCaptureConfiguration { method @NonNull public int[] getExcludeUids(); method @NonNull public int[] getExcludeUsages(); diff --git a/core/jni/android_media_AudioMixerAttributes.h b/core/jni/android_media_AudioMixerAttributes.h new file mode 100644 index 0000000000000..61adb27bdc7f0 --- /dev/null +++ b/core/jni/android_media_AudioMixerAttributes.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 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. + */ + +#ifndef ANDROID_MEDIA_AUDIOMIXERATTRIBUTES_H +#define ANDROID_MEDIA_AUDIOMIXERATTRIBUTES_H + +#include + +// Keep sync with AudioMixerAttributes.java +#define MIXER_BEHAVIOR_DEFAULT 0 +// Invalid value is not added in JAVA API, but keep sync with native value +#define MIXER_BEHAVIOR_INVALID -1 + +static inline audio_mixer_behavior_t audioMixerBehaviorToNative(int mixerBehavior) { + switch (mixerBehavior) { + case MIXER_BEHAVIOR_DEFAULT: + return AUDIO_MIXER_BEHAVIOR_DEFAULT; + default: + return AUDIO_MIXER_BEHAVIOR_INVALID; + } +} + +static inline jint audioMixerBehaviorFromNative(audio_mixer_behavior_t mixerBehavior) { + switch (mixerBehavior) { + case AUDIO_MIXER_BEHAVIOR_DEFAULT: + return MIXER_BEHAVIOR_DEFAULT; + case AUDIO_MIXER_BEHAVIOR_INVALID: + default: + return MIXER_BEHAVIOR_INVALID; + } +} + +#endif diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 334a0e0fd0a14..a0e1bcafedb7b 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -34,6 +35,7 @@ #include #include +#include #include #include @@ -43,6 +45,7 @@ #include "android_media_AudioEffectDescriptor.h" #include "android_media_AudioErrors.h" #include "android_media_AudioFormat.h" +#include "android_media_AudioMixerAttributes.h" #include "android_media_AudioProfile.h" #include "android_media_MicrophoneInfo.h" #include "android_util_Binder.h" @@ -51,6 +54,7 @@ // ---------------------------------------------------------------------------- using namespace android; +using media::audio::common::AudioConfigBase; static const char* const kClassPathName = "android/media/AudioSystem"; @@ -145,10 +149,12 @@ static struct { } gAudioMixFields; static jclass gAudioFormatClass; +static jmethodID gAudioFormatCstor; static struct { jfieldID mEncoding; jfieldID mSampleRate; jfieldID mChannelMask; + jfieldID mChannelIndexMask; // other fields unused by JNI } gAudioFormatFields; @@ -211,6 +217,7 @@ static struct { jfieldID mChannelMasks; jfieldID mChannelIndexMasks; jfieldID mEncapsulationType; + jfieldID mMixerBehaviors; } gAudioProfileFields; jclass gVibratorClass; @@ -221,6 +228,13 @@ static struct { jmethodID getMaxAmplitude; } gVibratorMethods; +jclass gAudioMixerAttributesClass; +jmethodID gAudioMixerAttributesCstor; +static struct { + jfieldID mFormat; + jfieldID mMixerBehavior; +} gAudioMixerAttributesField; + static Mutex gLock; enum AudioError { @@ -237,6 +251,12 @@ enum { #define MAX_PORT_GENERATION_SYNC_ATTEMPTS 5 +// Keep sync with AudioFormat.java +#define AUDIO_FORMAT_HAS_PROPERTY_ENCODING 0x1 +#define AUDIO_FORMAT_HAS_PROPERTY_SAMPLE_RATE 0x2 +#define AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK 0x4 +#define AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_INDEX_MASK 0x8 + // ---------------------------------------------------------------------------- // ref-counted object for audio port callbacks class JNIAudioPortCallback: public AudioSystem::AudioPortCallback @@ -2105,6 +2125,80 @@ void javaAudioFormatToNativeAudioConfig(JNIEnv *env, audio_config_t *nConfig, } } +void javaAudioFormatToNativeAudioConfigBase(JNIEnv *env, const jobject jFormat, + audio_config_base_t *nConfigBase, bool isInput) { + *nConfigBase = AUDIO_CONFIG_BASE_INITIALIZER; + nConfigBase->format = + audioFormatToNative(env->GetIntField(jFormat, gAudioFormatFields.mEncoding)); + nConfigBase->sample_rate = env->GetIntField(jFormat, gAudioFormatFields.mSampleRate); + jint jChannelMask = env->GetIntField(jFormat, gAudioFormatFields.mChannelMask); + jint jChannelIndexMask = env->GetIntField(jFormat, gAudioFormatFields.mChannelIndexMask); + nConfigBase->channel_mask = jChannelIndexMask != 0 + ? audio_channel_mask_from_representation_and_bits(AUDIO_CHANNEL_REPRESENTATION_INDEX, + jChannelIndexMask) + : isInput ? inChannelMaskToNative(jChannelMask) + : outChannelMaskToNative(jChannelMask); +} + +jobject nativeAudioConfigBaseToJavaAudioFormat(JNIEnv *env, const audio_config_base_t *nConfigBase, + bool isInput) { + if (nConfigBase == nullptr) { + return nullptr; + } + int propertyMask = AUDIO_FORMAT_HAS_PROPERTY_ENCODING | AUDIO_FORMAT_HAS_PROPERTY_SAMPLE_RATE; + int channelMask = 0; + int channelIndexMask = 0; + switch (audio_channel_mask_get_representation(nConfigBase->channel_mask)) { + case AUDIO_CHANNEL_REPRESENTATION_POSITION: + channelMask = isInput ? inChannelMaskFromNative(nConfigBase->channel_mask) + : outChannelMaskFromNative(nConfigBase->channel_mask); + propertyMask |= AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK; + break; + case AUDIO_CHANNEL_REPRESENTATION_INDEX: + channelIndexMask = audio_channel_mask_get_bits(nConfigBase->channel_mask); + propertyMask |= AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_INDEX_MASK; + break; + default: + // This must not happen + break; + } + return env->NewObject(gAudioFormatClass, gAudioFormatCstor, propertyMask, + audioFormatFromNative(nConfigBase->format), nConfigBase->sample_rate, + channelMask, channelIndexMask); +} + +jint convertAudioMixerAttributesToNative(JNIEnv *env, const jobject jAudioMixerAttributes, + audio_mixer_attributes_t *nMixerAttributes) { + ScopedLocalRef jFormat(env, + env->GetObjectField(jAudioMixerAttributes, + gAudioMixerAttributesField.mFormat)); + javaAudioFormatToNativeAudioConfigBase(env, jFormat.get(), &nMixerAttributes->config, + false /*isInput*/); + nMixerAttributes->mixer_behavior = audioMixerBehaviorToNative( + env->GetIntField(jAudioMixerAttributes, gAudioMixerAttributesField.mMixerBehavior)); + if (nMixerAttributes->mixer_behavior == AUDIO_MIXER_BEHAVIOR_INVALID) { + return (jint)AUDIO_JAVA_BAD_VALUE; + } + return (jint)AUDIO_JAVA_SUCCESS; +} + +jobject convertAudioMixerAttributesFromNative(JNIEnv *env, + const audio_mixer_attributes_t *nMixerAttributes) { + if (nMixerAttributes == nullptr) { + return nullptr; + } + jint mixerBehavior = audioMixerBehaviorFromNative(nMixerAttributes->mixer_behavior); + if (mixerBehavior == MIXER_BEHAVIOR_INVALID) { + return nullptr; + } + ScopedLocalRef + jFormat(env, + nativeAudioConfigBaseToJavaAudioFormat(env, &nMixerAttributes->config, + false /*isInput*/)); + return env->NewObject(gAudioMixerAttributesClass, gAudioMixerAttributesCstor, jFormat.get(), + mixerBehavior); +} + static jint convertAudioMixToNative(JNIEnv *env, AudioMix *nAudioMix, const jobject jAudioMix) @@ -2966,6 +3060,142 @@ static jint android_media_AudioSystem_getDirectProfilesForAttributes(JNIEnv *env return jStatus; } +static jint android_media_AudioSystem_getSupportedMixerAttributes(JNIEnv *env, jobject thiz, + jint jDeviceId, + jobject jAudioMixerAttributes) { + ALOGV("%s", __func__); + if (jAudioMixerAttributes == NULL) { + ALOGE("getSupportedMixerAttributes NULL AudioMixerAttributes list"); + return (jint)AUDIO_JAVA_BAD_VALUE; + } + if (!env->IsInstanceOf(jAudioMixerAttributes, gListClass)) { + ALOGE("getSupportedMixerAttributes not a list"); + return (jint)AUDIO_JAVA_BAD_VALUE; + } + + std::vector nMixerAttributes; + status_t status = AudioSystem::getSupportedMixerAttributes((audio_port_handle_t)jDeviceId, + &nMixerAttributes); + if (status != NO_ERROR) { + return nativeToJavaStatus(status); + } + for (const auto &mixerAttr : nMixerAttributes) { + ScopedLocalRef jMixerAttributes(env, + convertAudioMixerAttributesFromNative(env, + &mixerAttr)); + if (jMixerAttributes.get() == nullptr) { + return (jint)AUDIO_JAVA_ERROR; + } + + env->CallBooleanMethod(jAudioMixerAttributes, gListMethods.add, jMixerAttributes.get()); + } + + return (jint)AUDIO_JAVA_SUCCESS; +} + +static jint android_media_AudioSystem_setPreferredMixerAttributes(JNIEnv *env, jobject thiz, + jobject jAudioAttributes, + jint portId, jint uid, + jobject jAudioMixerAttributes) { + ALOGV("%s", __func__); + + if (jAudioAttributes == nullptr) { + ALOGE("jAudioAttributes is NULL"); + return (jint)AUDIO_JAVA_BAD_VALUE; + } + if (jAudioMixerAttributes == nullptr) { + ALOGE("jAudioMixerAttributes is NULL"); + return (jint)AUDIO_JAVA_BAD_VALUE; + } + + JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique(); + jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jAudioAttributes, paa.get()); + if (jStatus != (jint)AUDIO_JAVA_SUCCESS) { + return jStatus; + } + + audio_mixer_attributes_t mixerAttributes = AUDIO_MIXER_ATTRIBUTES_INITIALIZER; + jStatus = convertAudioMixerAttributesToNative(env, jAudioMixerAttributes, &mixerAttributes); + if (jStatus != (jint)AUDIO_JAVA_SUCCESS) { + return jStatus; + } + + status_t status = + AudioSystem::setPreferredMixerAttributes(paa.get(), (audio_port_handle_t)portId, + (uid_t)uid, &mixerAttributes); + return nativeToJavaStatus(status); +} + +static jint android_media_AudioSystem_getPreferredMixerAttributes(JNIEnv *env, jobject thiz, + jobject jAudioAttributes, + jint portId, + jobject jAudioMixerAttributes) { + ALOGV("%s", __func__); + + if (jAudioAttributes == nullptr) { + ALOGE("getPreferredMixerAttributes jAudioAttributes is NULL"); + return (jint)AUDIO_JAVA_BAD_VALUE; + } + if (jAudioMixerAttributes == NULL) { + ALOGE("getPreferredMixerAttributes NULL AudioMixerAttributes list"); + return (jint)AUDIO_JAVA_BAD_VALUE; + } + if (!env->IsInstanceOf(jAudioMixerAttributes, gListClass)) { + ALOGE("getPreferredMixerAttributes not a list"); + return (jint)AUDIO_JAVA_BAD_VALUE; + } + + JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique(); + jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jAudioAttributes, paa.get()); + if (jStatus != (jint)AUDIO_JAVA_SUCCESS) { + return jStatus; + } + + std::optional nMixerAttributes; + status_t status = + AudioSystem::getPreferredMixerAttributes(paa.get(), (audio_port_handle_t)portId, + &nMixerAttributes); + if (status != NO_ERROR) { + return nativeToJavaStatus(status); + } + + ScopedLocalRef + jMixerAttributes(env, + convertAudioMixerAttributesFromNative(env, + nMixerAttributes.has_value() + ? &nMixerAttributes + .value() + : nullptr)); + if (jMixerAttributes.get() == nullptr) { + return (jint)AUDIO_JAVA_ERROR; + } + + env->CallBooleanMethod(jAudioMixerAttributes, gListMethods.add, jMixerAttributes.get()); + return AUDIO_JAVA_SUCCESS; +} + +static jint android_media_AudioSystem_clearPreferredMixerAttributes(JNIEnv *env, jobject thiz, + jobject jAudioAttributes, + jint portId, jint uid) { + ALOGV("%s", __func__); + + if (jAudioAttributes == nullptr) { + ALOGE("jAudioAttributes is NULL"); + return (jint)AUDIO_JAVA_BAD_VALUE; + } + + JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique(); + jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jAudioAttributes, paa.get()); + if (jStatus != (jint)AUDIO_JAVA_SUCCESS) { + return jStatus; + } + + status_t status = + AudioSystem::clearPreferredMixerAttributes(paa.get(), (audio_port_handle_t)portId, + (uid_t)uid); + return nativeToJavaStatus(status); +} + // ---------------------------------------------------------------------------- static const JNINativeMethod gMethods[] = @@ -3120,7 +3350,16 @@ static const JNINativeMethod gMethods[] = (void *)android_media_AudioSystem_getDirectPlaybackSupport}, {"getDirectProfilesForAttributes", "(Landroid/media/AudioAttributes;Ljava/util/ArrayList;)I", - (void *)android_media_AudioSystem_getDirectProfilesForAttributes}}; + (void *)android_media_AudioSystem_getDirectProfilesForAttributes}, + {"getSupportedMixerAttributes", "(ILjava/util/List;)I", + (void *)android_media_AudioSystem_getSupportedMixerAttributes}, + {"setPreferredMixerAttributes", + "(Landroid/media/AudioAttributes;IILandroid/media/AudioMixerAttributes;)I", + (void *)android_media_AudioSystem_setPreferredMixerAttributes}, + {"getPreferredMixerAttributes", "(Landroid/media/AudioAttributes;ILjava/util/List;)I", + (void *)android_media_AudioSystem_getPreferredMixerAttributes}, + {"clearPreferredMixerAttributes", "(Landroid/media/AudioAttributes;II)I", + (void *)android_media_AudioSystem_clearPreferredMixerAttributes}}; static const JNINativeMethod gEventHandlerMethods[] = { {"native_setup", @@ -3283,9 +3522,12 @@ int register_android_media_AudioSystem(JNIEnv *env) jclass audioFormatClass = FindClassOrDie(env, "android/media/AudioFormat"); gAudioFormatClass = MakeGlobalRefOrDie(env, audioFormatClass); + gAudioFormatCstor = GetMethodIDOrDie(env, audioFormatClass, "", "(IIIII)V"); gAudioFormatFields.mEncoding = GetFieldIDOrDie(env, audioFormatClass, "mEncoding", "I"); gAudioFormatFields.mSampleRate = GetFieldIDOrDie(env, audioFormatClass, "mSampleRate", "I"); gAudioFormatFields.mChannelMask = GetFieldIDOrDie(env, audioFormatClass, "mChannelMask", "I"); + gAudioFormatFields.mChannelIndexMask = + GetFieldIDOrDie(env, audioFormatClass, "mChannelIndexMask", "I"); jclass audioMixingRuleClass = FindClassOrDie(env, "android/media/audiopolicy/AudioMixingRule"); gAudioMixingRuleClass = MakeGlobalRefOrDie(env, audioMixingRuleClass); @@ -3359,6 +3601,15 @@ int register_android_media_AudioSystem(JNIEnv *env) gVibratorMethods.getMaxAmplitude = GetMethodIDOrDie(env, vibratorClass, "getHapticChannelMaximumAmplitude", "()F"); + jclass audioMixerAttributesClass = FindClassOrDie(env, "android/media/AudioMixerAttributes"); + gAudioMixerAttributesClass = MakeGlobalRefOrDie(env, audioMixerAttributesClass); + gAudioMixerAttributesCstor = GetMethodIDOrDie(env, audioMixerAttributesClass, "", + "(Landroid/media/AudioFormat;I)V"); + gAudioMixerAttributesField.mFormat = GetFieldIDOrDie(env, audioMixerAttributesClass, "mFormat", + "Landroid/media/AudioFormat;"); + gAudioMixerAttributesField.mMixerBehavior = + GetFieldIDOrDie(env, audioMixerAttributesClass, "mMixerBehavior", "I"); + AudioSystem::addErrorCallback(android_media_AudioSystem_error_callback); RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods)); diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index ae0d45ffca249..3ed2c4b6ebc7b 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -8518,6 +8518,221 @@ public class AudioManager { } } + //==================================================================== + // Preferred mixer attributes + + /** + * Returns the {@link AudioMixerAttributes} that can be used to set as preferred mixe + * attributes via {@link #setPreferredMixerAttributes( + * AudioAttributes, AudioDeviceInfo, AudioMixerAttributes)}. + *

Note that only USB devices are guaranteed to expose configurable mixer attributes, the + * returned list may be empty when devices do not allow dynamic configuration. + * + * @param device the device to query + * @return a list of {@link AudioMixerAttributes} that can be used as preferred mixer attributes + * for the given device. + * @see #setPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo, AudioMixerAttributes) + */ + @NonNull + public List getSupportedMixerAttributes(@NonNull AudioDeviceInfo device) { + Objects.requireNonNull(device); + List mixerAttrs = new ArrayList<>(); + return (AudioSystem.getSupportedMixerAttributes(device.getId(), mixerAttrs) + == AudioSystem.SUCCESS) ? mixerAttrs : new ArrayList<>(); + } + + /** + * Configures the mixer attributes for a particular {@link AudioAttributes} over a given + * {@link AudioDeviceInfo}. + *

When constructing an {@link AudioMixerAttributes} for setting preferred mixer attributes, + * the mixer format must be constructed from an {@link AudioProfile} that can be used to set + * preferred mixer attributes. + *

The ownership of preferred mixer attributes is recognized by uid. When a playback from the + * same uid is routed to the given audio device when calling this API, the output mixer/stream + * will be configured with the values previously set via this API. + *

Use {@link #clearPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo)} + * to cancel setting mixer attributes for this {@link AudioAttributes}. + * + * @param attributes the {@link AudioAttributes} whose mixer attributes should be set. + * Currently, only {@link AudioAttributes#USAGE_MEDIA} is supported. When + * playing audio targeted at the given device, use the same attributes for + * playback. + * @param device the device to be routed. Currently, only USB device will be allowed. + * @param mixerAttributes the preferred mixer attributes. When playing audio targeted at the + * given device, use the same {@link AudioFormat} for both playback + * and the mixer attributes. + * @return true only if the preferred mixer attributes are set successfully. + * @see #getPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo) + * @see #clearPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo) + */ + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS) + public boolean setPreferredMixerAttributes(@NonNull AudioAttributes attributes, + @NonNull AudioDeviceInfo device, + @NonNull AudioMixerAttributes mixerAttributes) { + Objects.requireNonNull(attributes); + Objects.requireNonNull(device); + Objects.requireNonNull(mixerAttributes); + try { + final int status = getService().setPreferredMixerAttributes( + attributes, device.getId(), mixerAttributes); + return status == AudioSystem.SUCCESS; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns current preferred mixer attributes that is set via + * {@link #setPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo, AudioMixerAttributes)} + * + * @param attributes the {@link AudioAttributes} whose mixer attributes should be set. + * @param device the expected routing device + * @return the preferred mixer attributes, which will be null when no preferred mixer attributes + * have been set, or when they have been cleared. + * @see #setPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo, AudioMixerAttributes) + * @see #clearPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo) + */ + @Nullable + public AudioMixerAttributes getPreferredMixerAttributes( + @NonNull AudioAttributes attributes, + @NonNull AudioDeviceInfo device) { + Objects.requireNonNull(attributes); + Objects.requireNonNull(device); + List mixerAttrList = new ArrayList<>(); + int ret = AudioSystem.getPreferredMixerAttributes( + attributes, device.getId(), mixerAttrList); + if (ret == AudioSystem.SUCCESS) { + return mixerAttrList.isEmpty() ? null : mixerAttrList.get(0); + } else { + Log.e(TAG, "Failed calling getPreferredMixerAttributes, ret=" + ret); + return null; + } + } + + /** + * Clears the current preferred mixer attributes that were previously set via + * {@link #setPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo, AudioMixerAttributes)} + * + * @param attributes the {@link AudioAttributes} whose mixer attributes should be cleared. + * @param device the expected routing device + * @return true only if the preferred mixer attributes are removed successfully. + * @see #setPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo, AudioMixerAttributes) + * @see #getPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo) + */ + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS) + public boolean clearPreferredMixerAttributes( + @NonNull AudioAttributes attributes, + @NonNull AudioDeviceInfo device) { + Objects.requireNonNull(attributes); + Objects.requireNonNull(device); + try { + final int status = getService().clearPreferredMixerAttributes( + attributes, device.getId()); + return status == AudioSystem.SUCCESS; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Interface to be notified of changes in the preferred mixer attributes. + *

Note that this listener will only be invoked whenever + * {@link #setPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo, AudioMixerAttributes)} + * or {@link #clearPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo)} or device + * disconnection causes a change in preferred mixer attributes. + * @see #setPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo, AudioMixerAttributes) + * @see #clearPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo) + */ + public interface OnPreferredMixerAttributesChangedListener { + /** + * Called on the listener to indicate that the preferred mixer attributes for the audio + * attributes over the given device has changed. + * + * @param attributes the audio attributes for playback + * @param device the targeted device + * @param mixerAttributes the {@link AudioMixerAttributes} that contains information for + * preferred mixer attributes or null if preferred mixer attributes + * is cleared + */ + void onPreferredMixerAttributesChanged( + @NonNull AudioAttributes attributes, + @NonNull AudioDeviceInfo device, + @Nullable AudioMixerAttributes mixerAttributes); + } + + /** + * Manage the {@link OnPreferredMixerAttributesChangedListener} listeners and the + * {@link PreferredMixerAttributesDispatcherStub}. + */ + private final CallbackUtil.LazyListenerManager + mPrefMixerAttributesListenerMgr = new CallbackUtil.LazyListenerManager(); + + /** + * Adds a listener for being notified of changes to the preferred mixer attributes. + * @param executor the executor to execute the callback + * @param listener the listener to be notified of changes in the preferred mixer attributes. + */ + public void addOnPreferredMixerAttributesChangedListener( + @NonNull @CallbackExecutor Executor executor, + @NonNull OnPreferredMixerAttributesChangedListener listener) { + Objects.requireNonNull(executor); + Objects.requireNonNull(listener); + mPrefMixerAttributesListenerMgr.addListener(executor, listener, + "addOnPreferredMixerAttributesChangedListener", + () -> new PreferredMixerAttributesDispatcherStub()); + } + + /** + * Removes a previously added listener of changes to the preferred mixer attributes. + * @param listener the listener to be notified of changes in the preferred mixer attributes, + * which were added via {@link #addOnPreferredMixerAttributesChangedListener( + * Executor, OnPreferredMixerAttributesChangedListener)}. + */ + public void removeOnPreferredMixerAttributesChangedListener( + @NonNull OnPreferredMixerAttributesChangedListener listener) { + Objects.requireNonNull(listener); + mPrefMixerAttributesListenerMgr.removeListener(listener, + "removeOnPreferredMixerAttributesChangedListener"); + } + + private final class PreferredMixerAttributesDispatcherStub + extends IPreferredMixerAttributesDispatcher.Stub + implements CallbackUtil.DispatcherStub { + + @Override + public void register(boolean register) { + try { + if (register) { + getService().registerPreferredMixerAttributesDispatcher(this); + } else { + getService().unregisterPreferredMixerAttributesDispatcher(this); + } + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + @Override + public void dispatchPrefMixerAttributesChanged(@NonNull AudioAttributes attr, + int deviceId, + @Nullable AudioMixerAttributes mixerAttr) { + // TODO: If the device is disconnected, we may not be able to find the device with + // given device id. We need a better to carry the device information via binder. + AudioDeviceInfo device = getDeviceForPortId(deviceId, GET_DEVICES_OUTPUTS); + if (device == null) { + Log.d(TAG, "Drop preferred mixer attributes changed as the device(" + + deviceId + ") is disconnected"); + return; + } + mPrefMixerAttributesListenerMgr.callListeners( + (listener) -> listener.onPreferredMixerAttributesChanged( + attr, device, mixerAttr)); + } + } + + //==================================================================== + // Mute await connection + private final Object mMuteAwaitConnectionListenerLock = new Object(); @GuardedBy("mMuteAwaitConnectionListenerLock") diff --git a/media/java/android/media/AudioMixerAttributes.aidl b/media/java/android/media/AudioMixerAttributes.aidl new file mode 100644 index 0000000000000..0d9badde8d256 --- /dev/null +++ b/media/java/android/media/AudioMixerAttributes.aidl @@ -0,0 +1,18 @@ +/* Copyright 2022, 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; + +parcelable AudioMixerAttributes; diff --git a/media/java/android/media/AudioMixerAttributes.java b/media/java/android/media/AudioMixerAttributes.java new file mode 100644 index 0000000000000..320d6bdbf227e --- /dev/null +++ b/media/java/android/media/AudioMixerAttributes.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2022 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.IntDef; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * Class to represent the attributes of the audio mixer: its format, which represents by an + * {@link AudioFormat} object and mixer behavior. + */ +public final class AudioMixerAttributes implements Parcelable { + + /** + * Constant indicating the audio mixer behavior will follow the default platform behavior, which + * is mixing all audio sources in the mixer. + */ + public static final int MIXER_BEHAVIOR_DEFAULT = 0; + + /** @hide */ + @IntDef(flag = false, prefix = "MIXER_BEHAVIOR_", value = { + MIXER_BEHAVIOR_DEFAULT } + ) + @Retention(RetentionPolicy.SOURCE) + public @interface MixerBehavior {} + + private final AudioFormat mFormat; + private final @MixerBehavior int mMixerBehavior; + + /** + * Constructor from {@link AudioFormat} and mixer behavior + */ + AudioMixerAttributes(AudioFormat format, @MixerBehavior int mixerBehavior) { + mFormat = format; + mMixerBehavior = mixerBehavior; + } + + /** + * Return the format of the audio mixer. The format is an {@link AudioFormat} object, which + * includes encoding format, sample rate and channel mask or channel index mask. + * @return the format of the audio mixer. + */ + @NonNull + public AudioFormat getFormat() { + return mFormat; + } + + /** + * Returns the mixer behavior for this set of mixer attributes. + * + * @return the mixer behavior + */ + public @MixerBehavior int getMixerBehavior() { + return mMixerBehavior; + } + + /** + * Builder class for {@link AudioMixerAttributes} objects. + */ + public static final class Builder { + private final AudioFormat mFormat; + private int mMixerBehavior = MIXER_BEHAVIOR_DEFAULT; + + /** + * Constructs a new Builder with the defaults. + * + * @param format the {@link AudioFormat} for the audio mixer. + */ + public Builder(@NonNull AudioFormat format) { + Objects.requireNonNull(format); + mFormat = format; + } + + /** + * Combines all attributes that have been set and returns a new {@link AudioMixerAttributes} + * object. + * @return a new {@link AudioMixerAttributes} object + */ + public @NonNull AudioMixerAttributes build() { + AudioMixerAttributes ama = new AudioMixerAttributes(mFormat, mMixerBehavior); + return ama; + } + + /** + * Sets the mixer behavior for the audio mixer + * @param mixerBehavior must be {@link #MIXER_BEHAVIOR_DEFAULT}. + * @return the same Builder instance. + */ + public @NonNull Builder setMixerBehavior(@MixerBehavior int mixerBehavior) { + switch (mixerBehavior) { + case MIXER_BEHAVIOR_DEFAULT: + mMixerBehavior = mixerBehavior; + break; + default: + throw new IllegalArgumentException("Invalid mixer behavior " + mixerBehavior); + } + return this; + } + } + + @Override + public int hashCode() { + return Objects.hash(mFormat, mMixerBehavior); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + AudioMixerAttributes that = (AudioMixerAttributes) o; + return (mFormat.equals(that.mFormat) + && (mMixerBehavior == that.mMixerBehavior)); + } + + private String mixerBehaviorToString(@MixerBehavior int mixerBehavior) { + switch (mixerBehavior) { + case MIXER_BEHAVIOR_DEFAULT: + return "default"; + default: + return "unknown"; + } + } + + @Override + public String toString() { + return new String("AudioMixerAttributes:" + + " format:" + mFormat.toString() + + " mixer behavior:" + mixerBehaviorToString(mMixerBehavior)); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeParcelable(mFormat, flags); + dest.writeInt(mMixerBehavior); + } + + private AudioMixerAttributes(@NonNull Parcel in) { + mFormat = in.readParcelable(AudioFormat.class.getClassLoader(), AudioFormat.class); + mMixerBehavior = in.readInt(); + } + + public static final @NonNull Parcelable.Creator CREATOR = + new Parcelable.Creator() { + /** + * Rebuilds an AudioMixerAttributes previously stored with writeToParcel(). + * @param p Parcel object to read the AudioMixerAttributes from + * @return a new AudioMixerAttributes created from the data in the parcel + */ + public AudioMixerAttributes createFromParcel(Parcel p) { + return new AudioMixerAttributes(p); + } + + public AudioMixerAttributes[] newArray(int size) { + return new AudioMixerAttributes[size]; + } + }; +} diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index fb643507f0373..607225ea18224 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -2434,4 +2434,40 @@ public class AudioSystem * Keep in sync with core/jni/android_media_DeviceCallback.h. */ final static int NATIVE_EVENT_ROUTING_CHANGE = 1000; + + /** + * @hide + * Query the mixer attributes that can be set as preferred mixer attributes for the given + * device. + */ + public static native int getSupportedMixerAttributes( + int deviceId, @NonNull List mixerAttrs); + + /** + * @hide + * Set preferred mixer attributes for a given device when playing particular + * audio attributes. + */ + public static native int setPreferredMixerAttributes( + @NonNull AudioAttributes attributes, + int portId, + int uid, + @NonNull AudioMixerAttributes mixerAttributes); + + /** + * @hide + * Get preferred mixer attributes that is previously set via + * {link #setPreferredMixerAttributes}. + */ + public static native int getPreferredMixerAttributes( + @NonNull AudioAttributes attributes, int portId, + List mixerAttributesList); + + /** + * @hide + * Clear preferred mixer attributes that is previously set via + * {@link #setPreferredMixerAttributes} + */ + public static native int clearPreferredMixerAttributes( + @NonNull AudioAttributes attributes, int portId, int uid); } diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index ee453a4541c26..5502db2da4c68 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -23,6 +23,7 @@ import android.media.AudioDeviceAttributes; import android.media.AudioFormat; import android.media.AudioFocusInfo; import android.media.AudioHalVersionInfo; +import android.media.AudioMixerAttributes; import android.media.AudioPlaybackConfiguration; import android.media.AudioRecordingConfiguration; import android.media.AudioRoutesInfo; @@ -37,6 +38,7 @@ import android.media.ICommunicationDeviceDispatcher; import android.media.IDeviceVolumeBehaviorDispatcher; import android.media.IMuteAwaitConnectionCallback; import android.media.IPlaybackConfigDispatcher; +import android.media.IPreferredMixerAttributesDispatcher; import android.media.IRecordingConfigDispatcher; import android.media.IRingtonePlayer; import android.media.IStrategyPreferredDevicesDispatcher; @@ -573,4 +575,14 @@ interface IAudioService { boolean handlesvolumeAdjustment); AudioHalVersionInfo getHalVersion(); + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS)") + int setPreferredMixerAttributes( + in AudioAttributes aa, int portId, in AudioMixerAttributes mixerAttributes); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS)") + int clearPreferredMixerAttributes(in AudioAttributes aa, int portId); + void registerPreferredMixerAttributesDispatcher( + IPreferredMixerAttributesDispatcher dispatcher); + oneway void unregisterPreferredMixerAttributesDispatcher( + IPreferredMixerAttributesDispatcher dispatcher); } diff --git a/media/java/android/media/IPreferredMixerAttributesDispatcher.aidl b/media/java/android/media/IPreferredMixerAttributesDispatcher.aidl new file mode 100644 index 0000000000000..9138fa74eccc8 --- /dev/null +++ b/media/java/android/media/IPreferredMixerAttributesDispatcher.aidl @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2022 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.media.AudioAttributes; +import android.media.AudioMixerAttributes; + +/** + * AIDL for AudioService to signal preferred mixer attributes update. + * + * {@hide} + */ +oneway interface IPreferredMixerAttributesDispatcher { + + void dispatchPrefMixerAttributesChanged( + in AudioAttributes attributes, + int deviceId, + in @nullable AudioMixerAttributes mixerAttributes); + +} diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 2fe06094bd88f..418027fb37f6c 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -1998,4 +1998,13 @@ import java.util.concurrent.atomic.AtomicBoolean; return mDeviceInventory.getDeviceSensorUuid(device); } } + + void dispatchPreferredMixerAttributesChangedCausedByDeviceRemoved(AudioDeviceInfo info) { + // Currently, only media usage will be allowed to set preferred mixer attributes + mAudioService.dispatchPreferredMixerAttributesChanged( + new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_MEDIA).build(), + info.getId(), + null /*mixerAttributes*/); + } } diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index c8f282fd576e4..34457b09ce17a 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -22,6 +22,7 @@ import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Intent; import android.media.AudioDeviceAttributes; +import android.media.AudioDeviceInfo; import android.media.AudioDevicePort; import android.media.AudioFormat; import android.media.AudioManager; @@ -532,6 +533,18 @@ public class AudioDeviceInventory { .set(MediaMetrics.Property.STATE, wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED ? MediaMetrics.Value.DISCONNECTED : MediaMetrics.Value.CONNECTED); + AudioDeviceInfo info = null; + if (wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED + && AudioSystem.DEVICE_OUT_ALL_USB_SET.contains( + wdcs.mAttributes.getInternalType())) { + for (AudioDeviceInfo deviceInfo : AudioManager.getDevicesStatic( + AudioManager.GET_DEVICES_OUTPUTS)) { + if (deviceInfo.getInternalType() == wdcs.mAttributes.getInternalType()) { + info = deviceInfo; + break; + } + } + } synchronized (mDevicesLock) { if ((wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED) && DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.contains(type)) { @@ -556,6 +569,11 @@ public class AudioDeviceInventory { if (type == AudioSystem.DEVICE_OUT_HDMI) { mDeviceBroker.checkVolumeCecOnHdmiConnection(wdcs.mState, wdcs.mCaller); } + if (wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED + && AudioSystem.DEVICE_OUT_ALL_USB_SET.contains( + wdcs.mAttributes.getInternalType())) { + mDeviceBroker.dispatchPreferredMixerAttributesChangedCausedByDeviceRemoved(info); + } sendDeviceConnectionIntent(type, wdcs.mState, wdcs.mAttributes.getAddress(), wdcs.mAttributes.getName()); updateAudioRoutes(type, wdcs.mState); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 09cd8a6dfc386..66770cb66b23d 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -88,6 +88,7 @@ import android.media.AudioFormat; import android.media.AudioHalVersionInfo; import android.media.AudioManager; import android.media.AudioManagerInternal; +import android.media.AudioMixerAttributes; import android.media.AudioPlaybackConfiguration; import android.media.AudioRecordingConfiguration; import android.media.AudioRoutesInfo; @@ -104,6 +105,7 @@ import android.media.ICommunicationDeviceDispatcher; import android.media.IDeviceVolumeBehaviorDispatcher; import android.media.IMuteAwaitConnectionCallback; import android.media.IPlaybackConfigDispatcher; +import android.media.IPreferredMixerAttributesDispatcher; import android.media.IRecordingConfigDispatcher; import android.media.IRingtonePlayer; import android.media.ISpatializerCallback; @@ -376,6 +378,7 @@ public class AudioService extends IAudioService.Stub private static final int MSG_FOLD_UPDATE = 49; private static final int MSG_RESET_SPATIALIZER = 50; private static final int MSG_NO_LOG_FOR_PLAYER_I = 51; + private static final int MSG_DISPATCH_PREFERRED_MIXER_ATTRIBUTES = 52; /** Messages handled by the {@link SoundDoseHelper}. */ /*package*/ static final int SAFE_MEDIA_VOLUME_MSG_START = 1000; @@ -6566,6 +6569,20 @@ public class AudioService extends IAudioService.Stub handler.sendMessageAtTime(handler.obtainMessage(msg, arg1, arg2, obj), time); } + private static void sendBundleMsg(Handler handler, int msg, + int existingMsgPolicy, int arg1, int arg2, Object obj, Bundle bundle, int delay) { + if (existingMsgPolicy == SENDMSG_REPLACE) { + handler.removeMessages(msg); + } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) { + return; + } + + final long time = SystemClock.uptimeMillis() + delay; + Message message = handler.obtainMessage(msg, arg1, arg2, obj); + message.setData(bundle); + handler.sendMessageAtTime(message, time); + } + boolean checkAudioSettingsPermission(String method) { if (callingOrSelfHasAudioSettingsPermission()) { return true; @@ -8531,6 +8548,10 @@ public class AudioService extends IAudioService.Stub mPlaybackMonitor.ignorePlayerIId(msg.arg1); break; + case MSG_DISPATCH_PREFERRED_MIXER_ATTRIBUTES: + onDispatchPreferredMixerAttributesChanged(msg.getData(), msg.arg1); + break; + default: if (msg.what >= SAFE_MEDIA_VOLUME_MSG_START) { // msg could be for the SoundDoseHelper @@ -10995,6 +11016,125 @@ public class AudioService extends IAudioService.Stub } } + /** + * @see AudioManager#setPreferredMixerAttributes( + * AudioAttributes, AudioDeviceInfo, AudioMixerAttributes) + */ + public int setPreferredMixerAttributes(AudioAttributes attributes, + int portId, AudioMixerAttributes mixerAttributes) { + Objects.requireNonNull(attributes); + Objects.requireNonNull(mixerAttributes); + if (!checkAudioSettingsPermission("setPreferredMixerAttributes()")) { + return AudioSystem.PERMISSION_DENIED; + } + final int uid = Binder.getCallingUid(); + final int pid = Binder.getCallingPid(); + int status = AudioSystem.SUCCESS; + final long token = Binder.clearCallingIdentity(); + try { + final String logString = TextUtils.formatSimple( + "setPreferredMixerAttributes u/pid:%d/%d attr:%s mixerAttributes:%s portId:%d", + uid, pid, attributes.toString(), mixerAttributes.toString(), portId); + sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG)); + + status = mAudioSystem.setPreferredMixerAttributes( + attributes, portId, uid, mixerAttributes); + if (status == AudioSystem.SUCCESS) { + dispatchPreferredMixerAttributesChanged(attributes, portId, mixerAttributes); + } else { + Log.e(TAG, TextUtils.formatSimple("Error %d in %s)", status, logString)); + } + } finally { + Binder.restoreCallingIdentity(token); + } + return status; + } + + /** + * @see AudioManager#clearPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo) + */ + public int clearPreferredMixerAttributes(AudioAttributes attributes, int portId) { + Objects.requireNonNull(attributes); + if (!checkAudioSettingsPermission("clearPreferredMixerAttributes()")) { + return AudioSystem.PERMISSION_DENIED; + } + final int uid = Binder.getCallingUid(); + final int pid = Binder.getCallingPid(); + int status = AudioSystem.SUCCESS; + final long token = Binder.clearCallingIdentity(); + try { + final String logString = TextUtils.formatSimple( + "clearPreferredMixerAttributes u/pid:%d/%d attr:%s", + uid, pid, attributes.toString()); + sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG)); + + status = mAudioSystem.clearPreferredMixerAttributes(attributes, portId, uid); + if (status == AudioSystem.SUCCESS) { + dispatchPreferredMixerAttributesChanged(attributes, portId, null /*mixerAttr*/); + } else { + Log.e(TAG, TextUtils.formatSimple("Error %d in %s)", status, logString)); + } + } finally { + Binder.restoreCallingIdentity(token); + } + return status; + } + + void dispatchPreferredMixerAttributesChanged( + AudioAttributes attr, int deviceId, AudioMixerAttributes mixerAttr) { + Bundle bundle = new Bundle(); + bundle.putParcelable(KEY_AUDIO_ATTRIBUTES, attr); + bundle.putParcelable(KEY_AUDIO_MIXER_ATTRIBUTES, mixerAttr); + sendBundleMsg(mAudioHandler, MSG_DISPATCH_PREFERRED_MIXER_ATTRIBUTES, SENDMSG_QUEUE, + deviceId, 0, null, bundle, 0); + } + + final RemoteCallbackList mPrefMixerAttrDispatcher = + new RemoteCallbackList(); + private static final String KEY_AUDIO_ATTRIBUTES = "audio_attributes"; + private static final String KEY_AUDIO_MIXER_ATTRIBUTES = "audio_mixer_attributes"; + + /** @see AudioManager#addOnPreferredMixerAttributesChangedListener( + * Executor, AudioManager.OnPreferredMixerAttributesChangedListener) + */ + public void registerPreferredMixerAttributesDispatcher( + @Nullable IPreferredMixerAttributesDispatcher dispatcher) { + if (dispatcher == null) { + return; + } + mPrefMixerAttrDispatcher.register(dispatcher); + } + + /** @see AudioManager#removeOnPreferredMixerAttributesChangedListener( + * AudioManager.OnPreferredMixerAttributesChangedListener) + */ + public void unregisterPreferredMixerAttributesDispatcher( + @Nullable IPreferredMixerAttributesDispatcher dispatcher) { + if (dispatcher == null) { + return; + } + mPrefMixerAttrDispatcher.unregister(dispatcher); + } + + protected void onDispatchPreferredMixerAttributesChanged(Bundle data, int deviceId) { + final int nbDispathers = mPrefMixerAttrDispatcher.beginBroadcast(); + final AudioAttributes attr = data.getParcelable( + KEY_AUDIO_ATTRIBUTES, AudioAttributes.class); + final AudioMixerAttributes mixerAttr = data.getParcelable( + KEY_AUDIO_MIXER_ATTRIBUTES, AudioMixerAttributes.class); + for (int i = 0; i < nbDispathers; i++) { + try { + mPrefMixerAttrDispatcher.getBroadcastItem(i) + .dispatchPrefMixerAttributesChanged(attr, deviceId, mixerAttr); + } catch (RemoteException e) { + Log.e(TAG, "Can't call dispatchPrefMixerAttributesChanged() " + + "IPreferredMixerAttributesDispatcher " + + mPrefMixerAttrDispatcher.getBroadcastItem(i).asBinder(), e); + } + } + mPrefMixerAttrDispatcher.finishBroadcast(); + } + private final Object mExtVolumeControllerLock = new Object(); private IAudioPolicyCallback mExtVolumeController; private void setExtVolumeController(IAudioPolicyCallback apc) { diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java index 558daa183afaf..db406a642c0c2 100644 --- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java +++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.media.AudioAttributes; import android.media.AudioDeviceAttributes; +import android.media.AudioMixerAttributes; import android.media.AudioSystem; import android.media.ISoundDoseCallback; import android.media.audiopolicy.AudioMix; @@ -504,6 +505,36 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback, return AudioSystem.registerSoundDoseCallback(callback); } + /** + * Same as + * {@link AudioSystem#setPreferredMixerAttributes( + * AudioAttributes, int, int, AudioMixerAttributes)} + * @param attributes + * @param mixerAttributes + * @param uid + * @param portId + * @return + */ + public int setPreferredMixerAttributes( + @NonNull AudioAttributes attributes, + int portId, + int uid, + @NonNull AudioMixerAttributes mixerAttributes) { + return AudioSystem.setPreferredMixerAttributes(attributes, portId, uid, mixerAttributes); + } + + /** + * Same as {@link AudioSystem#clearPreferredMixerAttributes(AudioAttributes, int, int)} + * @param attributes + * @param uid + * @param portId + * @return + */ + public int clearPreferredMixerAttributes( + @NonNull AudioAttributes attributes, int portId, int uid) { + return AudioSystem.clearPreferredMixerAttributes(attributes, portId, uid); + } + /** * Part of AudioService dump * @param pw