Merge "Add APIs for setting preferred mixer attributes."

This commit is contained in:
TreeHugger Robot
2022-12-10 06:22:09 +00:00
committed by Android (Google) Code Review
13 changed files with 1019 additions and 1 deletions

View File

@@ -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<android.media.AudioPlaybackConfiguration> 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<android.media.AudioMixerAttributes> 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<android.media.AudioMixerAttributes> 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();

View File

@@ -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 <system/audio.h>
// 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

View File

@@ -21,6 +21,7 @@
#include <android/media/AudioVibratorInfo.h>
#include <android/media/INativeSpatializerCallback.h>
#include <android/media/ISpatializer.h>
#include <android/media/audio/common/AudioConfigBase.h>
#include <android_os_Parcel.h>
#include <audiomanager/AudioManager.h>
#include <jni.h>
@@ -34,6 +35,7 @@
#include <system/audio_policy.h>
#include <utils/Log.h>
#include <optional>
#include <sstream>
#include <vector>
@@ -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<jobject> 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<jobject>
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<audio_mixer_attributes_t> 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<jobject> 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<audio_mixer_attributes_t> nMixerAttributes;
status_t status =
AudioSystem::getPreferredMixerAttributes(paa.get(), (audio_port_handle_t)portId,
&nMixerAttributes);
if (status != NO_ERROR) {
return nativeToJavaStatus(status);
}
ScopedLocalRef<jobject>
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, "<init>", "(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, "<init>",
"(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));

View File

@@ -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)}.
* <p>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<AudioMixerAttributes> getSupportedMixerAttributes(@NonNull AudioDeviceInfo device) {
Objects.requireNonNull(device);
List<AudioMixerAttributes> 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}.
* <p>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.
* <p>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.
* <p>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<AudioMixerAttributes> 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.
* <p>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<OnPreferredMixerAttributesChangedListener>
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")

View File

@@ -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;

View File

@@ -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<AudioMixerAttributes> CREATOR =
new Parcelable.Creator<AudioMixerAttributes>() {
/**
* 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];
}
};
}

View File

@@ -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<AudioMixerAttributes> 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<AudioMixerAttributes> 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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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*/);
}
}

View File

@@ -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);

View File

@@ -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<IPreferredMixerAttributesDispatcher> mPrefMixerAttrDispatcher =
new RemoteCallbackList<IPreferredMixerAttributesDispatcher>();
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) {

View File

@@ -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