diff --git a/core/api/current.txt b/core/api/current.txt index c1c4c92237dbb..c539c3446c648 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -20549,10 +20549,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(); @@ -20570,6 +20572,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); @@ -20578,6 +20581,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(); @@ -20605,6 +20609,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); @@ -20615,6 +20620,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); @@ -20770,6 +20776,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(); } @@ -20805,6 +20815,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