summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/system-current.txt9
-rw-r--r--core/jni/android_media_AudioSystem.cpp83
-rw-r--r--media/java/android/media/AudioDeviceAttributes.java25
-rw-r--r--media/java/android/media/AudioDeviceInfo.java45
-rwxr-xr-xmedia/java/android/media/AudioManager.java343
-rw-r--r--media/java/android/media/AudioSystem.java129
-rwxr-xr-xmedia/java/android/media/IAudioService.aidl13
-rw-r--r--media/java/android/media/ICapturePresetDevicesRoleDispatcher.aidl31
-rw-r--r--media/java/android/media/MediaRecorder.java26
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java45
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java77
-rwxr-xr-xservices/core/java/com/android/server/audio/AudioService.java89
-rw-r--r--services/core/java/com/android/server/audio/AudioSystemAdapter.java34
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java17
14 files changed, 964 insertions, 2 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 02ce813cce4e..b4f139553271 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -4146,8 +4146,10 @@ package android.media {
public class AudioManager {
method @Deprecated public int abandonAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes);
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDeviceForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener) throws java.lang.SecurityException;
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDevicesForCapturePresetChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDevicesForCapturePresetChangedListener) throws java.lang.SecurityException;
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDevicesForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDevicesForStrategyChangedListener) throws java.lang.SecurityException;
method public void clearAudioServerStateCallback();
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean clearPreferredDevicesForCapturePreset(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int dispatchAudioFocusChange(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
method @IntRange(from=0) public long getAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioProductStrategy> getAudioProductStrategies();
@@ -4157,6 +4159,7 @@ package android.media {
method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMaxVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMinVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioDeviceAttributes getPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
+ method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAttributes> getPreferredDevicesForCapturePreset(int);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAttributes> getPreferredDevicesForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int[] getSupportedSystemUsages();
method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
@@ -4165,6 +4168,7 @@ package android.media {
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int registerAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
method public void registerVolumeGroupCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.VolumeGroupCallback);
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDeviceForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDevicesForCapturePresetChangedListener(@NonNull android.media.AudioManager.OnPreferredDevicesForCapturePresetChangedListener);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDevicesForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDevicesForStrategyChangedListener);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean removePreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, @NonNull android.media.AudioAttributes, int, int) throws java.lang.IllegalArgumentException;
@@ -4173,6 +4177,7 @@ package android.media {
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo, @IntRange(from=0) long);
method public void setAudioServerStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.AudioServerStateCallback);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setFocusRequestResult(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDeviceForCapturePreset(int, @NonNull android.media.AudioDeviceAttributes);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull android.media.AudioDeviceAttributes);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDevicesForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull java.util.List<android.media.AudioDeviceAttributes>);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setSupportedSystemUsages(@NonNull int[]);
@@ -4197,6 +4202,10 @@ package android.media {
method @Deprecated public void onPreferredDeviceForStrategyChanged(@NonNull android.media.audiopolicy.AudioProductStrategy, @Nullable android.media.AudioDeviceAttributes);
}
+ public static interface AudioManager.OnPreferredDevicesForCapturePresetChangedListener {
+ method public void onPreferredDevicesForCapturePresetChanged(int, @NonNull java.util.List<android.media.AudioDeviceAttributes>);
+ }
+
public static interface AudioManager.OnPreferredDevicesForStrategyChangedListener {
method public void onPreferredDevicesForStrategyChanged(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull java.util.List<android.media.AudioDeviceAttributes>);
}
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index ae725259d0ca..1ca45fe9f70b 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -2408,6 +2408,79 @@ static jint android_media_AudioSystem_getDevicesForRoleAndStrategy(JNIEnv *env,
return AUDIO_JAVA_SUCCESS;
}
+static jint android_media_AudioSystem_setDevicesRoleForCapturePreset(
+ JNIEnv *env, jobject thiz, jint capturePreset, jint role, jintArray jDeviceTypes,
+ jobjectArray jDeviceAddresses) {
+ AudioDeviceTypeAddrVector nDevices;
+ jint results = getVectorOfAudioDeviceTypeAddr(env, jDeviceTypes, jDeviceAddresses, nDevices);
+ if (results != NO_ERROR) {
+ return results;
+ }
+ int status = check_AudioSystem_Command(
+ AudioSystem::setDevicesRoleForCapturePreset((audio_source_t)capturePreset,
+ (device_role_t)role, nDevices));
+ return (jint)status;
+}
+
+static jint android_media_AudioSystem_addDevicesRoleForCapturePreset(
+ JNIEnv *env, jobject thiz, jint capturePreset, jint role, jintArray jDeviceTypes,
+ jobjectArray jDeviceAddresses) {
+ AudioDeviceTypeAddrVector nDevices;
+ jint results = getVectorOfAudioDeviceTypeAddr(env, jDeviceTypes, jDeviceAddresses, nDevices);
+ if (results != NO_ERROR) {
+ return results;
+ }
+ int status = check_AudioSystem_Command(
+ AudioSystem::addDevicesRoleForCapturePreset((audio_source_t)capturePreset,
+ (device_role_t)role, nDevices));
+ return (jint)status;
+}
+
+static jint android_media_AudioSystem_removeDevicesRoleForCapturePreset(
+ JNIEnv *env, jobject thiz, jint capturePreset, jint role, jintArray jDeviceTypes,
+ jobjectArray jDeviceAddresses) {
+ AudioDeviceTypeAddrVector nDevices;
+ jint results = getVectorOfAudioDeviceTypeAddr(env, jDeviceTypes, jDeviceAddresses, nDevices);
+ if (results != NO_ERROR) {
+ return results;
+ }
+ int status = check_AudioSystem_Command(
+ AudioSystem::removeDevicesRoleForCapturePreset((audio_source_t)capturePreset,
+ (device_role_t)role, nDevices));
+ return (jint)status;
+}
+
+static jint android_media_AudioSystem_clearDevicesRoleForCapturePreset(JNIEnv *env, jobject thiz,
+ jint capturePreset,
+ jint role) {
+ return (jint)check_AudioSystem_Command(
+ AudioSystem::clearDevicesRoleForCapturePreset((audio_source_t)capturePreset,
+ (device_role_t)role));
+}
+
+static jint android_media_AudioSystem_getDevicesForRoleAndCapturePreset(JNIEnv *env, jobject thiz,
+ jint capturePreset,
+ jint role,
+ jobject jDevices) {
+ AudioDeviceTypeAddrVector nDevices;
+ status_t status = check_AudioSystem_Command(
+ AudioSystem::getDevicesForRoleAndCapturePreset((audio_source_t)capturePreset,
+ (device_role_t)role, nDevices));
+ if (status != NO_ERROR) {
+ return (jint)status;
+ }
+ for (const auto &device : nDevices) {
+ jobject jAudioDeviceAttributes = NULL;
+ jint jStatus = createAudioDeviceAttributesFromNative(env, &jAudioDeviceAttributes, &device);
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
+ return jStatus;
+ }
+ env->CallBooleanMethod(jDevices, gListMethods.add, jAudioDeviceAttributes);
+ env->DeleteLocalRef(jAudioDeviceAttributes);
+ }
+ return AUDIO_JAVA_SUCCESS;
+}
+
static jint
android_media_AudioSystem_getDevicesForAttributes(JNIEnv *env, jobject thiz,
jobject jaa, jobjectArray jDeviceArray)
@@ -2559,6 +2632,16 @@ static const JNINativeMethod gMethods[] =
(void *)android_media_AudioSystem_removeDevicesRoleForStrategy},
{"getDevicesForRoleAndStrategy", "(IILjava/util/List;)I",
(void *)android_media_AudioSystem_getDevicesForRoleAndStrategy},
+ {"setDevicesRoleForCapturePreset", "(II[I[Ljava/lang/String;)I",
+ (void *)android_media_AudioSystem_setDevicesRoleForCapturePreset},
+ {"addDevicesRoleForCapturePreset", "(II[I[Ljava/lang/String;)I",
+ (void *)android_media_AudioSystem_addDevicesRoleForCapturePreset},
+ {"removeDevicesRoleForCapturePreset", "(II[I[Ljava/lang/String;)I",
+ (void *)android_media_AudioSystem_removeDevicesRoleForCapturePreset},
+ {"clearDevicesRoleForCapturePreset", "(II)I",
+ (void *)android_media_AudioSystem_clearDevicesRoleForCapturePreset},
+ {"getDevicesForRoleAndCapturePreset", "(IILjava/util/List;)I",
+ (void *)android_media_AudioSystem_getDevicesForRoleAndCapturePreset},
{"getDevicesForAttributes",
"(Landroid/media/AudioAttributes;[Landroid/media/AudioDeviceAttributes;)I",
(void *)android_media_AudioSystem_getDevicesForAttributes},
diff --git a/media/java/android/media/AudioDeviceAttributes.java b/media/java/android/media/AudioDeviceAttributes.java
index 0ab62c14ab9f..6c8b50037d3d 100644
--- a/media/java/android/media/AudioDeviceAttributes.java
+++ b/media/java/android/media/AudioDeviceAttributes.java
@@ -72,6 +72,11 @@ public final class AudioDeviceAttributes implements Parcelable {
private final @Role int mRole;
/**
+ * The internal audio device type
+ */
+ private final int mNativeType;
+
+ /**
* @hide
* Constructor from a valid {@link AudioDeviceInfo}
* @param deviceInfo the connected audio device from which to obtain the device-identifying
@@ -83,6 +88,7 @@ public final class AudioDeviceAttributes implements Parcelable {
mRole = deviceInfo.isSink() ? ROLE_OUTPUT : ROLE_INPUT;
mType = deviceInfo.getType();
mAddress = deviceInfo.getAddress();
+ mNativeType = deviceInfo.getInternalType();
}
/**
@@ -101,9 +107,12 @@ public final class AudioDeviceAttributes implements Parcelable {
}
if (role == ROLE_OUTPUT) {
AudioDeviceInfo.enforceValidAudioDeviceTypeOut(type);
- }
- if (role == ROLE_INPUT) {
+ mNativeType = AudioDeviceInfo.convertDeviceTypeToInternalDevice(type);
+ } else if (role == ROLE_INPUT) {
AudioDeviceInfo.enforceValidAudioDeviceTypeIn(type);
+ mNativeType = AudioDeviceInfo.convertDeviceTypeToInternalInputDevice(type);
+ } else {
+ mNativeType = AudioSystem.DEVICE_NONE;
}
mRole = role;
@@ -115,6 +124,7 @@ public final class AudioDeviceAttributes implements Parcelable {
mRole = (nativeType & AudioSystem.DEVICE_BIT_IN) != 0 ? ROLE_INPUT : ROLE_OUTPUT;
mType = AudioDeviceInfo.convertInternalDeviceToDeviceType(nativeType);
mAddress = address;
+ mNativeType = nativeType;
}
/**
@@ -147,6 +157,15 @@ public final class AudioDeviceAttributes implements Parcelable {
return mAddress;
}
+ /**
+ * @hide
+ * Returns the internal device type of a device
+ * @return the internal device type
+ */
+ public int getInternalType() {
+ return mNativeType;
+ }
+
@Override
public int hashCode() {
return Objects.hash(mRole, mType, mAddress);
@@ -189,12 +208,14 @@ public final class AudioDeviceAttributes implements Parcelable {
dest.writeInt(mRole);
dest.writeInt(mType);
dest.writeString(mAddress);
+ dest.writeInt(mNativeType);
}
private AudioDeviceAttributes(@NonNull Parcel in) {
mRole = in.readInt();
mType = in.readInt();
mAddress = in.readString();
+ mNativeType = in.readInt();
}
public static final @NonNull Parcelable.Creator<AudioDeviceAttributes> CREATOR =
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index ff4a25622bca..f79fc92477f7 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -350,6 +350,14 @@ public final class AudioDeviceInfo {
}
/**
+ * @hide
+ * @return the internal device tyoe
+ */
+ public int getInternalType() {
+ return mPort.type();
+ }
+
+ /**
* @return The internal device ID.
*/
public int getId() {
@@ -512,10 +520,21 @@ public final class AudioDeviceInfo {
return INT_TO_EXT_DEVICE_MAPPING.get(intDevice, TYPE_UNKNOWN);
}
+ /** @hide */
+ public static int convertDeviceTypeToInternalInputDevice(int deviceType) {
+ return EXT_TO_INT_INPUT_DEVICE_MAPPING.get(deviceType, AudioSystem.DEVICE_NONE);
+ }
+
private static final SparseIntArray INT_TO_EXT_DEVICE_MAPPING;
private static final SparseIntArray EXT_TO_INT_DEVICE_MAPPING;
+ /**
+ * EXT_TO_INT_INPUT_DEVICE_MAPPING aims at mapping external device type to internal input device
+ * type.
+ */
+ private static final SparseIntArray EXT_TO_INT_INPUT_DEVICE_MAPPING;
+
static {
INT_TO_EXT_DEVICE_MAPPING = new SparseIntArray();
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_EARPIECE, TYPE_BUILTIN_EARPIECE);
@@ -600,6 +619,32 @@ public final class AudioDeviceInfo {
EXT_TO_INT_DEVICE_MAPPING.put(TYPE_REMOTE_SUBMIX, AudioSystem.DEVICE_OUT_REMOTE_SUBMIX);
EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BLE_HEADSET, AudioSystem.DEVICE_OUT_BLE_HEADSET);
EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BLE_SPEAKER, AudioSystem.DEVICE_OUT_BLE_SPEAKER);
+
+ // privileges mapping to input device
+ EXT_TO_INT_INPUT_DEVICE_MAPPING = new SparseIntArray();
+ EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_BUILTIN_MIC, AudioSystem.DEVICE_IN_BUILTIN_MIC);
+ EXT_TO_INT_INPUT_DEVICE_MAPPING.put(
+ TYPE_BLUETOOTH_SCO, AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET);
+ EXT_TO_INT_INPUT_DEVICE_MAPPING.put(
+ TYPE_WIRED_HEADSET, AudioSystem.DEVICE_IN_WIRED_HEADSET);
+ EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_HDMI, AudioSystem.DEVICE_IN_HDMI);
+ EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_TELEPHONY, AudioSystem.DEVICE_IN_TELEPHONY_RX);
+ EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_DOCK, AudioSystem.DEVICE_IN_ANLG_DOCK_HEADSET);
+ EXT_TO_INT_INPUT_DEVICE_MAPPING.put(
+ TYPE_USB_ACCESSORY, AudioSystem.DEVICE_IN_USB_ACCESSORY);
+ EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_USB_DEVICE, AudioSystem.DEVICE_IN_USB_DEVICE);
+ EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_USB_HEADSET, AudioSystem.DEVICE_IN_USB_HEADSET);
+ EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_FM_TUNER, AudioSystem.DEVICE_IN_FM_TUNER);
+ EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_TV_TUNER, AudioSystem.DEVICE_IN_TV_TUNER);
+ EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_LINE_ANALOG, AudioSystem.DEVICE_IN_LINE);
+ EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_LINE_DIGITAL, AudioSystem.DEVICE_IN_SPDIF);
+ EXT_TO_INT_INPUT_DEVICE_MAPPING.put(
+ TYPE_BLUETOOTH_A2DP, AudioSystem.DEVICE_IN_BLUETOOTH_A2DP);
+ EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_IP, AudioSystem.DEVICE_IN_IP);
+ EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_BUS, AudioSystem.DEVICE_IN_BUS);
+ EXT_TO_INT_INPUT_DEVICE_MAPPING.put(
+ TYPE_REMOTE_SUBMIX, AudioSystem.DEVICE_IN_REMOTE_SUBMIX);
+ EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_BLE_HEADSET, AudioSystem.DEVICE_IN_BLE_HEADSET);
}
}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index b33003737ccd..ed9e5175fb78 100755
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1948,6 +1948,349 @@ public class AudioManager {
}
//====================================================================
+ // Audio Capture Preset routing
+
+ /**
+ * @hide
+ * Set the preferred device for a given capture preset, i.e. the audio routing to be used by
+ * this capture preset. Note that the device may not be available at the time the preferred
+ * device is set, but it will be used once made available.
+ * <p>Use {@link #clearPreferredDevicesForCapturePreset(int)} to cancel setting this preference
+ * for this capture preset.</p>
+ * @param capturePreset the audio capture preset whose routing will be affected
+ * @param device the audio device to route to when available
+ * @return true if the operation was successful, false otherwise
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public boolean setPreferredDeviceForCapturePreset(int capturePreset,
+ @NonNull AudioDeviceAttributes device) {
+ return setPreferredDevicesForCapturePreset(capturePreset, Arrays.asList(device));
+ }
+
+ /**
+ * @hide
+ * Remove all the preferred audio devices previously set
+ * @param capturePreset the audio capture preset whose routing will be affected
+ * @return true if the operation was successful, false otherwise (invalid capture preset, or no
+ * device set for example)
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public boolean clearPreferredDevicesForCapturePreset(int capturePreset) {
+ if (!MediaRecorder.isValidAudioSource(capturePreset)) {
+ return false;
+ }
+ try {
+ final int status = getService().clearPreferredDevicesForCapturePreset(capturePreset);
+ return status == AudioSystem.SUCCESS;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ * Return the preferred devices for an audio capture preset, previously set with
+ * {@link #setPreferredDeviceForCapturePreset(int, AudioDeviceAttributes)}
+ * @param capturePreset the capture preset to query
+ * @return a list that contains preferred devices for that capture preset.
+ */
+ @NonNull
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public List<AudioDeviceAttributes> getPreferredDevicesForCapturePreset(int capturePreset) {
+ if (!MediaRecorder.isValidAudioSource(capturePreset)) {
+ return new ArrayList<AudioDeviceAttributes>();
+ }
+ try {
+ return getService().getPreferredDevicesForCapturePreset(capturePreset);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private boolean setPreferredDevicesForCapturePreset(
+ int capturePreset, @NonNull List<AudioDeviceAttributes> devices) {
+ Objects.requireNonNull(devices);
+ if (!MediaRecorder.isValidAudioSource(capturePreset)) {
+ return false;
+ }
+ if (devices.size() != 1) {
+ throw new IllegalArgumentException(
+ "Only support setting one preferred devices for capture preset");
+ }
+ for (AudioDeviceAttributes device : devices) {
+ Objects.requireNonNull(device);
+ }
+ try {
+ final int status =
+ getService().setPreferredDevicesForCapturePreset(capturePreset, devices);
+ return status == AudioSystem.SUCCESS;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ * Interface to be notified of changes in the preferred audio devices set for a given capture
+ * preset.
+ * <p>Note that this listener will only be invoked whenever
+ * {@link #setPreferredDeviceForCapturePreset(int, AudioDeviceAttributes)} or
+ * {@link #clearPreferredDevicesForCapturePreset(int)} causes a change in
+ * preferred device. It will not be invoked directly after registration with
+ * {@link #addOnPreferredDevicesForCapturePresetChangedListener(
+ * Executor, OnPreferredDevicesForCapturePresetChangedListener)}
+ * to indicate which strategies had preferred devices at the time of registration.</p>
+ * @see #setPreferredDeviceForCapturePreset(int, AudioDeviceAttributes)
+ * @see #clearPreferredDevicesForCapturePreset(int)
+ * @see #getPreferredDevicesForCapturePreset(int)
+ */
+ @SystemApi
+ public interface OnPreferredDevicesForCapturePresetChangedListener {
+ /**
+ * Called on the listener to indicate that the preferred audio devices for the given
+ * capture preset has changed.
+ * @param capturePreset the capture preset whose preferred device changed
+ * @param devices a list of newly set preferred audio devices
+ */
+ void onPreferredDevicesForCapturePresetChanged(
+ int capturePreset, @NonNull List<AudioDeviceAttributes> devices);
+ }
+
+ /**
+ * @hide
+ * Adds a listener for being notified of changes to the capture-preset-preferred audio device.
+ * @param executor
+ * @param listener
+ * @throws SecurityException if the caller doesn't hold the required permission
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public void addOnPreferredDevicesForCapturePresetChangedListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnPreferredDevicesForCapturePresetChangedListener listener)
+ throws SecurityException {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(listener);
+ int status = addOnDevRoleForCapturePresetChangedListener(
+ executor, listener, AudioSystem.DEVICE_ROLE_PREFERRED);
+ if (status == AudioSystem.ERROR) {
+ // This must not happen
+ throw new RuntimeException("Unknown error happened");
+ }
+ if (status == AudioSystem.BAD_VALUE) {
+ throw new IllegalArgumentException(
+ "attempt to call addOnPreferredDevicesForCapturePresetChangedListener() "
+ + "on a previously registered listener");
+ }
+ }
+
+ /**
+ * @hide
+ * Removes a previously added listener of changes to the capture-preset-preferred audio device.
+ * @param listener
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public void removeOnPreferredDevicesForCapturePresetChangedListener(
+ @NonNull OnPreferredDevicesForCapturePresetChangedListener listener) {
+ Objects.requireNonNull(listener);
+ int status = removeOnDevRoleForCapturePresetChangedListener(
+ listener, AudioSystem.DEVICE_ROLE_PREFERRED);
+ if (status == AudioSystem.ERROR) {
+ // This must not happen
+ throw new RuntimeException("Unknown error happened");
+ }
+ if (status == AudioSystem.BAD_VALUE) {
+ throw new IllegalArgumentException(
+ "attempt to call removeOnPreferredDevicesForCapturePresetChangedListener() "
+ + "on an unregistered listener");
+ }
+ }
+
+ private <T> int addOnDevRoleForCapturePresetChangedListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull T listener, int deviceRole) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(listener);
+ DevRoleListeners<T> devRoleListeners =
+ (DevRoleListeners<T>) mDevRoleForCapturePresetListeners.get(deviceRole);
+ if (devRoleListeners == null) {
+ return AudioSystem.ERROR;
+ }
+ synchronized (devRoleListeners.mDevRoleListenersLock) {
+ if (devRoleListeners.hasDevRoleListener(listener)) {
+ return AudioSystem.BAD_VALUE;
+ }
+ // lazy initialization of the list of device role listener
+ if (devRoleListeners.mListenerInfos == null) {
+ devRoleListeners.mListenerInfos = new ArrayList<>();
+ }
+ final int oldCbCount = devRoleListeners.mListenerInfos.size();
+ devRoleListeners.mListenerInfos.add(new DevRoleListenerInfo<T>(executor, listener));
+ if (oldCbCount == 0 && devRoleListeners.mListenerInfos.size() > 0) {
+ // register binder for callbacks
+ synchronized (mDevRoleForCapturePresetListenersLock) {
+ int deviceRoleListenerStatus = mDeviceRoleListenersStatus;
+ mDeviceRoleListenersStatus |= (1 << deviceRole);
+ if (deviceRoleListenerStatus != 0) {
+ // There are already device role changed listeners active.
+ return AudioSystem.SUCCESS;
+ }
+ if (mDevicesRoleForCapturePresetDispatcherStub == null) {
+ mDevicesRoleForCapturePresetDispatcherStub =
+ new CapturePresetDevicesRoleDispatcherStub();
+ }
+ try {
+ getService().registerCapturePresetDevicesRoleDispatcher(
+ mDevicesRoleForCapturePresetDispatcherStub);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+ }
+ return AudioSystem.SUCCESS;
+ }
+
+ private <T> int removeOnDevRoleForCapturePresetChangedListener(
+ @NonNull T listener, int deviceRole) {
+ Objects.requireNonNull(listener);
+ DevRoleListeners<T> devRoleListeners =
+ (DevRoleListeners<T>) mDevRoleForCapturePresetListeners.get(deviceRole);
+ if (devRoleListeners == null) {
+ return AudioSystem.ERROR;
+ }
+ synchronized (devRoleListeners.mDevRoleListenersLock) {
+ if (!devRoleListeners.removeDevRoleListener(listener)) {
+ return AudioSystem.BAD_VALUE;
+ }
+ if (devRoleListeners.mListenerInfos.size() == 0) {
+ // unregister binder for callbacks
+ synchronized (mDevRoleForCapturePresetListenersLock) {
+ mDeviceRoleListenersStatus ^= (1 << deviceRole);
+ if (mDeviceRoleListenersStatus != 0) {
+ // There are some other device role changed listeners active.
+ return AudioSystem.SUCCESS;
+ }
+ try {
+ getService().unregisterCapturePresetDevicesRoleDispatcher(
+ mDevicesRoleForCapturePresetDispatcherStub);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+ }
+ return AudioSystem.SUCCESS;
+ }
+
+ private final Map<Integer, Object> mDevRoleForCapturePresetListeners = new HashMap<>(){{
+ put(AudioSystem.DEVICE_ROLE_PREFERRED,
+ new DevRoleListeners<OnPreferredDevicesForCapturePresetChangedListener>());
+ }};
+
+ private class DevRoleListenerInfo<T> {
+ final @NonNull Executor mExecutor;
+ final @NonNull T mListener;
+ DevRoleListenerInfo(Executor executor, T listener) {
+ mExecutor = executor;
+ mListener = listener;
+ }
+ }
+
+ private class DevRoleListeners<T> {
+ private final Object mDevRoleListenersLock = new Object();
+ @GuardedBy("mDevRoleListenersLock")
+ private @Nullable ArrayList<DevRoleListenerInfo<T>> mListenerInfos;
+
+ @GuardedBy("mDevRoleListenersLock")
+ private @Nullable DevRoleListenerInfo<T> getDevRoleListenerInfo(T listener) {
+ if (mListenerInfos == null) {
+ return null;
+ }
+ for (DevRoleListenerInfo<T> listenerInfo : mListenerInfos) {
+ if (listenerInfo.mListener == listener) {
+ return listenerInfo;
+ }
+ }
+ return null;
+ }
+
+ @GuardedBy("mDevRoleListenersLock")
+ private boolean hasDevRoleListener(T listener) {
+ return getDevRoleListenerInfo(listener) != null;
+ }
+
+ @GuardedBy("mDevRoleListenersLock")
+ private boolean removeDevRoleListener(T listener) {
+ final DevRoleListenerInfo<T> infoToRemove = getDevRoleListenerInfo(listener);
+ if (infoToRemove != null) {
+ mListenerInfos.remove(infoToRemove);
+ return true;
+ }
+ return false;
+ }
+ }
+
+ private final Object mDevRoleForCapturePresetListenersLock = new Object();
+ /**
+ * Record if there is a listener added for device role change. If there is a listener added for
+ * a specified device role change, the bit at position `1 << device_role` is set.
+ */
+ @GuardedBy("mDevRoleForCapturePresetListenersLock")
+ private int mDeviceRoleListenersStatus = 0;
+ @GuardedBy("mDevRoleForCapturePresetListenersLock")
+ private CapturePresetDevicesRoleDispatcherStub mDevicesRoleForCapturePresetDispatcherStub;
+
+ private final class CapturePresetDevicesRoleDispatcherStub
+ extends ICapturePresetDevicesRoleDispatcher.Stub {
+
+ @Override
+ public void dispatchDevicesRoleChanged(
+ int capturePreset, int role, List<AudioDeviceAttributes> devices) {
+ final Object listenersObj = mDevRoleForCapturePresetListeners.get(role);
+ if (listenersObj == null) {
+ return;
+ }
+ switch (role) {
+ case AudioSystem.DEVICE_ROLE_PREFERRED: {
+ final DevRoleListeners<OnPreferredDevicesForCapturePresetChangedListener>
+ listeners =
+ (DevRoleListeners<OnPreferredDevicesForCapturePresetChangedListener>)
+ listenersObj;
+ final ArrayList<DevRoleListenerInfo<
+ OnPreferredDevicesForCapturePresetChangedListener>> prefDevListeners;
+ synchronized (listeners.mDevRoleListenersLock) {
+ if (listeners.mListenerInfos.isEmpty()) {
+ return;
+ }
+ prefDevListeners = (ArrayList<DevRoleListenerInfo<
+ OnPreferredDevicesForCapturePresetChangedListener>>)
+ listeners.mListenerInfos.clone();
+ }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ for (DevRoleListenerInfo<
+ OnPreferredDevicesForCapturePresetChangedListener> info :
+ prefDevListeners) {
+ info.mExecutor.execute(() ->
+ info.mListener.onPreferredDevicesForCapturePresetChanged(
+ capturePreset, devices));
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ } break;
+ default:
+ break;
+ }
+ }
+ }
+
+ //====================================================================
// Offload query
/**
* Returns whether offloaded playback of an audio format is supported on the device.
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 159048548ce8..ef6ba065f414 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -28,6 +28,7 @@ import android.media.audiopolicy.AudioMix;
import android.os.Build;
import android.telephony.TelephonyManager;
import android.util.Log;
+import android.util.Pair;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -1750,6 +1751,134 @@ public class AudioSystem
public static native int getDevicesForRoleAndStrategy(
int strategy, int role, @NonNull List<AudioDeviceAttributes> devices);
+ // use case routing by capture preset
+
+ private static Pair<int[], String[]> populateInputDevicesTypeAndAddress(
+ @NonNull List<AudioDeviceAttributes> devices) {
+ int[] types = new int[devices.size()];
+ String[] addresses = new String[devices.size()];
+ for (int i = 0; i < devices.size(); ++i) {
+ types[i] = devices.get(i).getInternalType();
+ if (types[i] == AudioSystem.DEVICE_NONE) {
+ types[i] = AudioDeviceInfo.convertDeviceTypeToInternalInputDevice(
+ devices.get(i).getType());
+ }
+ addresses[i] = devices.get(i).getAddress();
+ }
+ return new Pair<int[], String[]>(types, addresses);
+ }
+
+ /**
+ * @hide
+ * Set devices as role for capture preset.
+ * @param capturePreset the capture preset to configure
+ * @param role the role of the devices
+ * @param devices the list of devices to be set as role for the given capture preset
+ * @return {@link #SUCCESS} if successfully set
+ */
+ public static int setDevicesRoleForCapturePreset(
+ int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devices) {
+ if (devices.isEmpty()) {
+ return BAD_VALUE;
+ }
+ Pair<int[], String[]> typeAddresses = populateInputDevicesTypeAndAddress(devices);
+ return setDevicesRoleForCapturePreset(
+ capturePreset, role, typeAddresses.first, typeAddresses.second);
+ }
+
+ /**
+ * @hide
+ * Set devices as role for capture preset.
+ * @param capturePreset the capture preset to configure
+ * @param role the role of the devices
+ * @param types all device types
+ * @param addresses all device addresses
+ * @return {@link #SUCCESS} if successfully set
+ */
+ private static native int setDevicesRoleForCapturePreset(
+ int capturePreset, int role, @NonNull int[] types, @NonNull String[] addresses);
+
+ /**
+ * @hide
+ * Add devices as role for capture preset.
+ * @param capturePreset the capture preset to configure
+ * @param role the role of the devices
+ * @param devices the list of devices to be added as role for the given capture preset
+ * @return {@link #SUCCESS} if successfully add
+ */
+ public static int addDevicesRoleForCapturePreset(
+ int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devices) {
+ if (devices.isEmpty()) {
+ return BAD_VALUE;
+ }
+ Pair<int[], String[]> typeAddresses = populateInputDevicesTypeAndAddress(devices);
+ return addDevicesRoleForCapturePreset(
+ capturePreset, role, typeAddresses.first, typeAddresses.second);
+ }
+
+ /**
+ * @hide
+ * Add devices as role for capture preset.
+ * @param capturePreset the capture preset to configure
+ * @param role the role of the devices
+ * @param types all device types
+ * @param addresses all device addresses
+ * @return {@link #SUCCESS} if successfully set
+ */
+ private static native int addDevicesRoleForCapturePreset(
+ int capturePreset, int role, @NonNull int[] types, @NonNull String[] addresses);
+
+ /**
+ * @hide
+ * Remove devices as role for the capture preset
+ * @param capturePreset the capture preset to configure
+ * @param role the role of the devices
+ * @param devices the devices to be removed
+ * @return {@link #SUCCESS} if successfully removed
+ */
+ public static int removeDevicesRoleForCapturePreset(
+ int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devices) {
+ if (devices.isEmpty()) {
+ return BAD_VALUE;
+ }
+ Pair<int[], String[]> typeAddresses = populateInputDevicesTypeAndAddress(devices);
+ return removeDevicesRoleForCapturePreset(
+ capturePreset, role, typeAddresses.first, typeAddresses.second);
+ }
+
+ /**
+ * @hide
+ * Remove devices as role for capture preset.
+ * @param capturePreset the capture preset to configure
+ * @param role the role of the devices
+ * @param types all device types
+ * @param addresses all device addresses
+ * @return {@link #SUCCESS} if successfully set
+ */
+ private static native int removeDevicesRoleForCapturePreset(
+ int capturePreset, int role, @NonNull int[] types, @NonNull String[] addresses);
+
+ /**
+ * @hide
+ * Remove all devices as role for the capture preset
+ * @param capturePreset the capture preset to configure
+ * @param role the role of the devices
+ * @return {@link #SUCCESS} if successfully removed
+ */
+ public static native int clearDevicesRoleForCapturePreset(int capturePreset, int role);
+
+ /**
+ * @hide
+ * Query previously set devices as role for a capture preset
+ * @param capturePreset the capture preset to query for
+ * @param role the role of the devices
+ * @param devices a list that will contain the devices of role
+ * @return {@link #SUCCESS} if there is a preferred device and it was successfully retrieved
+ * and written to the array
+ */
+ public static native int getDevicesForRoleAndCapturePreset(
+ int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devices);
+
// Items shared with audio service
/**
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index bc2839ea214d..d9b44cdd20e7 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -26,6 +26,7 @@ import android.media.AudioRoutesInfo;
import android.media.IAudioFocusDispatcher;
import android.media.IAudioRoutesObserver;
import android.media.IAudioServerStateDispatcher;
+import android.media.ICapturePresetDevicesRoleDispatcher;
import android.media.IPlaybackConfigDispatcher;
import android.media.IRecordingConfigDispatcher;
import android.media.IRingtonePlayer;
@@ -307,4 +308,16 @@ interface IAudioService {
// code via IAudioManager.h need to be added to the top section.
oneway void setMultiAudioFocusEnabled(in boolean enabled);
+
+ int setPreferredDevicesForCapturePreset(
+ in int capturePreset, in List<AudioDeviceAttributes> devices);
+
+ int clearPreferredDevicesForCapturePreset(in int capturePreset);
+
+ List<AudioDeviceAttributes> getPreferredDevicesForCapturePreset(in int capturePreset);
+
+ void registerCapturePresetDevicesRoleDispatcher(ICapturePresetDevicesRoleDispatcher dispatcher);
+
+ oneway void unregisterCapturePresetDevicesRoleDispatcher(
+ ICapturePresetDevicesRoleDispatcher dispatcher);
}
diff --git a/media/java/android/media/ICapturePresetDevicesRoleDispatcher.aidl b/media/java/android/media/ICapturePresetDevicesRoleDispatcher.aidl
new file mode 100644
index 000000000000..5e03e632c4ff
--- /dev/null
+++ b/media/java/android/media/ICapturePresetDevicesRoleDispatcher.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2020 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.AudioDeviceAttributes;
+
+/**
+ * AIDL for AudioService to signal devices role for capture preset updates.
+ *
+ * {@hide}
+ */
+oneway interface ICapturePresetDevicesRoleDispatcher {
+
+ void dispatchDevicesRoleChanged(
+ int capturePreset, int role, in List<AudioDeviceAttributes> devices);
+
+}
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 73ef31504b74..c61a2eb02921 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -404,6 +404,32 @@ public class MediaRecorder implements AudioRouting,
}
}
+ /**
+ * @hide
+ * @param source An audio source to test
+ * @return true if the source is a valid one
+ */
+ public static boolean isValidAudioSource(int source) {
+ switch(source) {
+ case AudioSource.MIC:
+ case AudioSource.VOICE_UPLINK:
+ case AudioSource.VOICE_DOWNLINK:
+ case AudioSource.VOICE_CALL:
+ case AudioSource.CAMCORDER:
+ case AudioSource.VOICE_RECOGNITION:
+ case AudioSource.VOICE_COMMUNICATION:
+ case AudioSource.REMOTE_SUBMIX:
+ case AudioSource.UNPROCESSED:
+ case AudioSource.VOICE_PERFORMANCE:
+ case AudioSource.ECHO_REFERENCE:
+ case AudioSource.RADIO_TUNER:
+ case AudioSource.HOTWORD:
+ return true;
+ default:
+ return false;
+ }
+ }
+
/** @hide */
public static final String toLogFriendlyAudioSource(int source) {
switch(source) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 5447605a36d1..1615998f7787 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -28,6 +28,7 @@ import android.media.AudioDeviceAttributes;
import android.media.AudioRoutesInfo;
import android.media.AudioSystem;
import android.media.IAudioRoutesObserver;
+import android.media.ICapturePresetDevicesRoleDispatcher;
import android.media.IStrategyPreferredDevicesDispatcher;
import android.media.MediaMetrics;
import android.os.Binder;
@@ -546,6 +547,25 @@ import java.util.concurrent.atomic.AtomicBoolean;
mDeviceInventory.unregisterStrategyPreferredDevicesDispatcher(dispatcher);
}
+ /*package*/ int setPreferredDevicesForCapturePresetSync(int capturePreset,
+ @NonNull List<AudioDeviceAttributes> devices) {
+ return mDeviceInventory.setPreferredDevicesForCapturePresetSync(capturePreset, devices);
+ }
+
+ /*package*/ int clearPreferredDevicesForCapturePresetSync(int capturePreset) {
+ return mDeviceInventory.clearPreferredDevicesForCapturePresetSync(capturePreset);
+ }
+
+ /*package*/ void registerCapturePresetDevicesRoleDispatcher(
+ @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) {
+ mDeviceInventory.registerCapturePresetDevicesRoleDispatcher(dispatcher);
+ }
+
+ /*package*/ void unregisterCapturePresetDevicesRoleDispatcher(
+ @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) {
+ mDeviceInventory.unregisterCapturePresetDevicesRoleDispatcher(dispatcher);
+ }
+
//---------------------------------------------------------------------
// Communication with (to) AudioService
//TODO check whether the AudioService methods are candidates to move here
@@ -694,6 +714,17 @@ import java.util.concurrent.atomic.AtomicBoolean;
sendIMsgNoDelay(MSG_I_SAVE_REMOVE_PREF_DEVICES_FOR_STRATEGY, SENDMSG_QUEUE, strategy);
}
+ /*package*/ void postSaveSetPreferredDevicesForCapturePreset(
+ int capturePreset, List<AudioDeviceAttributes> devices) {
+ sendILMsgNoDelay(
+ MSG_IL_SAVE_PREF_DEVICES_FOR_CAPTURE_PRESET, SENDMSG_QUEUE, capturePreset, devices);
+ }
+
+ /*package*/ void postSaveClearPreferredDevicesForCapturePreset(int capturePreset) {
+ sendIMsgNoDelay(
+ MSG_I_SAVE_CLEAR_PREF_DEVICES_FOR_CAPTURE_PRESET, SENDMSG_QUEUE, capturePreset);
+ }
+
//---------------------------------------------------------------------
// Method forwarding between the helper classes (BtHelper, AudioDeviceInventory)
// only call from a "handle"* method or "on"* method
@@ -1098,6 +1129,17 @@ import java.util.concurrent.atomic.AtomicBoolean;
case MSG_CHECK_MUTE_MUSIC:
checkMessagesMuteMusic(0);
break;
+ case MSG_IL_SAVE_PREF_DEVICES_FOR_CAPTURE_PRESET: {
+ final int capturePreset = msg.arg1;
+ final List<AudioDeviceAttributes> devices =
+ (List<AudioDeviceAttributes>) msg.obj;
+ mDeviceInventory.onSaveSetPreferredDevicesForCapturePreset(
+ capturePreset, devices);
+ } break;
+ case MSG_I_SAVE_CLEAR_PREF_DEVICES_FOR_CAPTURE_PRESET: {
+ final int capturePreset = msg.arg1;
+ mDeviceInventory.onSaveClearPreferredDevicesForCapturePreset(capturePreset);
+ } break;
default:
Log.wtf(TAG, "Invalid message " + msg.what);
}
@@ -1174,6 +1216,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
private static final int MSG_CHECK_MUTE_MUSIC = 36;
private static final int MSG_REPORT_NEW_ROUTES_A2DP = 37;
+ private static final int MSG_IL_SAVE_PREF_DEVICES_FOR_CAPTURE_PRESET = 38;
+ private static final int MSG_I_SAVE_CLEAR_PREF_DEVICES_FOR_CAPTURE_PRESET = 39;
+
private static boolean isMessageHandledUnderWakelock(int msgId) {
switch(msgId) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index fbf07cc591ff..33a8a30243de 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -31,6 +31,7 @@ import android.media.AudioPort;
import android.media.AudioRoutesInfo;
import android.media.AudioSystem;
import android.media.IAudioRoutesObserver;
+import android.media.ICapturePresetDevicesRoleDispatcher;
import android.media.IStrategyPreferredDevicesDispatcher;
import android.media.MediaMetrics;
import android.os.Binder;
@@ -140,6 +141,10 @@ public class AudioDeviceInventory {
private final ArrayMap<Integer, List<AudioDeviceAttributes>> mPreferredDevices =
new ArrayMap<>();
+ // List of preferred devices of capture preset
+ private final ArrayMap<Integer, List<AudioDeviceAttributes>> mPreferredDevicesForCapturePreset =
+ new ArrayMap<>();
+
// the wrapper for AudioSystem static methods, allows us to spy AudioSystem
private final @NonNull AudioSystemAdapter mAudioSystem;
@@ -154,6 +159,10 @@ public class AudioDeviceInventory {
final RemoteCallbackList<IStrategyPreferredDevicesDispatcher> mPrefDevDispatchers =
new RemoteCallbackList<IStrategyPreferredDevicesDispatcher>();
+ // Monitoring of devices for role and capture preset
+ final RemoteCallbackList<ICapturePresetDevicesRoleDispatcher> mDevRoleCapturePresetDispatchers =
+ new RemoteCallbackList<ICapturePresetDevicesRoleDispatcher>();
+
/*package*/ AudioDeviceInventory(@NonNull AudioDeviceBroker broker) {
mDeviceBroker = broker;
mAudioSystem = AudioSystemAdapter.getDefaultAdapter();
@@ -243,6 +252,9 @@ public class AudioDeviceInventory {
pw.println(" " + prefix + " type:0x" + Integer.toHexString(keyType)
+ " (" + AudioSystem.getDeviceName(keyType)
+ ") addr:" + valueAddress); });
+ mPreferredDevicesForCapturePreset.forEach((capturePreset, devices) -> {
+ pw.println(" " + prefix + "capturePreset:" + capturePreset
+ + " devices:" + devices); });
}
//------------------------------------------------------------
@@ -270,6 +282,9 @@ public class AudioDeviceInventory {
mAudioSystem.setDevicesRoleForStrategy(
strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices); });
}
+ synchronized (mPreferredDevicesForCapturePreset) {
+ // TODO: call audiosystem to restore
+ }
}
// only public for mocking/spying
@@ -613,6 +628,20 @@ public class AudioDeviceInventory {
dispatchPreferredDevice(strategy, new ArrayList<AudioDeviceAttributes>());
}
+ /*package*/ void onSaveSetPreferredDevicesForCapturePreset(
+ int capturePreset, @NonNull List<AudioDeviceAttributes> devices) {
+ mPreferredDevicesForCapturePreset.put(capturePreset, devices);
+ dispatchDevicesRoleForCapturePreset(
+ capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
+ }
+
+ /*package*/ void onSaveClearPreferredDevicesForCapturePreset(int capturePreset) {
+ mPreferredDevicesForCapturePreset.remove(capturePreset);
+ dispatchDevicesRoleForCapturePreset(
+ capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED,
+ new ArrayList<AudioDeviceAttributes>());
+ }
+
//------------------------------------------------------------
//
@@ -651,6 +680,41 @@ public class AudioDeviceInventory {
mPrefDevDispatchers.unregister(dispatcher);
}
+ /*package*/ int setPreferredDevicesForCapturePresetSync(
+ int capturePreset, @NonNull List<AudioDeviceAttributes> devices) {
+ final long identity = Binder.clearCallingIdentity();
+ final int status = mAudioSystem.setDevicesRoleForCapturePreset(
+ capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
+ Binder.restoreCallingIdentity(identity);
+
+ if (status == AudioSystem.SUCCESS) {
+ mDeviceBroker.postSaveSetPreferredDevicesForCapturePreset(capturePreset, devices);
+ }
+ return status;
+ }
+
+ /*package*/ int clearPreferredDevicesForCapturePresetSync(int capturePreset) {
+ final long identity = Binder.clearCallingIdentity();
+ final int status = mAudioSystem.clearDevicesRoleForCapturePreset(
+ capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED);
+ Binder.restoreCallingIdentity(identity);
+
+ if (status == AudioSystem.SUCCESS) {
+ mDeviceBroker.postSaveClearPreferredDevicesForCapturePreset(capturePreset);
+ }
+ return status;
+ }
+
+ /*package*/ void registerCapturePresetDevicesRoleDispatcher(
+ @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) {
+ mDevRoleCapturePresetDispatchers.register(dispatcher);
+ }
+
+ /*package*/ void unregisterCapturePresetDevicesRoleDispatcher(
+ @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) {
+ mDevRoleCapturePresetDispatchers.unregister(dispatcher);
+ }
+
/**
* Implements the communication with AudioSystem to (dis)connect a device in the native layers
* @param connect true if connection
@@ -1306,6 +1370,19 @@ public class AudioDeviceInventory {
mPrefDevDispatchers.finishBroadcast();
}
+ private void dispatchDevicesRoleForCapturePreset(
+ int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devices) {
+ final int nbDispatchers = mDevRoleCapturePresetDispatchers.beginBroadcast();
+ for (int i = 0; i < nbDispatchers; ++i) {
+ try {
+ mDevRoleCapturePresetDispatchers.getBroadcastItem(i).dispatchDevicesRoleChanged(
+ capturePreset, role, devices);
+ } catch (RemoteException e) {
+ }
+ }
+ mDevRoleCapturePresetDispatchers.finishBroadcast();
+ }
+
//----------------------------------------------------------
// For tests only
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index f74a803973df..83350ed492eb 100755
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -84,6 +84,7 @@ import android.media.IAudioFocusDispatcher;
import android.media.IAudioRoutesObserver;
import android.media.IAudioServerStateDispatcher;
import android.media.IAudioService;
+import android.media.ICapturePresetDevicesRoleDispatcher;
import android.media.IPlaybackConfigDispatcher;
import android.media.IRecordingConfigDispatcher;
import android.media.IRingtonePlayer;
@@ -1938,6 +1939,94 @@ public class AudioService extends IAudioService.Stub
mDeviceBroker.unregisterStrategyPreferredDevicesDispatcher(dispatcher);
}
+ /**
+ * @see AudioManager#setPreferredDeviceForCapturePreset(int, AudioDeviceAttributes)
+ */
+ public int setPreferredDevicesForCapturePreset(
+ int capturePreset, List<AudioDeviceAttributes> devices) {
+ if (devices == null) {
+ return AudioSystem.ERROR;
+ }
+ enforceModifyAudioRoutingPermission();
+ final String logString = String.format(
+ "setPreferredDevicesForCapturePreset u/pid:%d/%d source:%d dev:%s",
+ Binder.getCallingUid(), Binder.getCallingPid(), capturePreset,
+ devices.stream().map(e -> e.toString()).collect(Collectors.joining(",")));
+ sDeviceLogger.log(new AudioEventLogger.StringEvent(logString).printLog(TAG));
+ if (devices.stream().anyMatch(device ->
+ device.getRole() == AudioDeviceAttributes.ROLE_OUTPUT)) {
+ Log.e(TAG, "Unsupported output routing in " + logString);
+ return AudioSystem.ERROR;
+ }
+
+ final int status = mDeviceBroker.setPreferredDevicesForCapturePresetSync(
+ capturePreset, devices);
+ if (status != AudioSystem.SUCCESS) {
+ Log.e(TAG, String.format("Error %d in %s)", status, logString));
+ }
+
+ return status;
+ }
+
+ /** @see AudioManager#clearPreferredDevicesForCapturePreset(int) */
+ public int clearPreferredDevicesForCapturePreset(int capturePreset) {
+ enforceModifyAudioRoutingPermission();
+ final String logString = String.format(
+ "removePreferredDeviceForCapturePreset source:%d", capturePreset);
+ sDeviceLogger.log(new AudioEventLogger.StringEvent(logString).printLog(TAG));
+
+ final int status = mDeviceBroker.clearPreferredDevicesForCapturePresetSync(capturePreset);
+ if (status != AudioSystem.SUCCESS) {
+ Log.e(TAG, String.format("Error %d in %s", status, logString));
+ }
+ return status;
+ }
+
+ /**
+ * @see AudioManager#getPreferredDevicesForCapturePreset(int)
+ */
+ public List<AudioDeviceAttributes> getPreferredDevicesForCapturePreset(int capturePreset) {
+ enforceModifyAudioRoutingPermission();
+ List<AudioDeviceAttributes> devices = new ArrayList<>();
+ final long identity = Binder.clearCallingIdentity();
+ final int status = AudioSystem.getDevicesForRoleAndCapturePreset(
+ capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
+ Binder.restoreCallingIdentity(identity);
+ if (status != AudioSystem.SUCCESS) {
+ Log.e(TAG, String.format("Error %d in getPreferredDeviceForCapturePreset(%d)",
+ status, capturePreset));
+ return new ArrayList<AudioDeviceAttributes>();
+ } else {
+ return devices;
+ }
+ }
+
+ /**
+ * @see AudioManager#addOnPreferredDevicesForCapturePresetChangedListener(
+ * Executor, OnPreferredDevicesForCapturePresetChangedListener)
+ */
+ public void registerCapturePresetDevicesRoleDispatcher(
+ @Nullable ICapturePresetDevicesRoleDispatcher dispatcher) {
+ if (dispatcher == null) {
+ return;
+ }
+ enforceModifyAudioRoutingPermission();
+ mDeviceBroker.registerCapturePresetDevicesRoleDispatcher(dispatcher);
+ }
+
+ /**
+ * @see AudioManager#removeOnPreferredDevicesForCapturePresetChangedListener(
+ * AudioManager.OnPreferredDevicesForCapturePresetChangedListener)
+ */
+ public void unregisterCapturePresetDevicesRoleDispatcher(
+ @Nullable ICapturePresetDevicesRoleDispatcher dispatcher) {
+ if (dispatcher == null) {
+ return;
+ }
+ enforceModifyAudioRoutingPermission();
+ mDeviceBroker.unregisterCapturePresetDevicesRoleDispatcher(dispatcher);
+ }
+
/** @see AudioManager#getDevicesForAttributes(AudioAttributes) */
public @NonNull ArrayList<AudioDeviceAttributes> getDevicesForAttributes(
@NonNull AudioAttributes attributes) {
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index a0e1ca78a5e7..ae64990fd8d0 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -101,6 +101,40 @@ public class AudioSystemAdapter {
}
/**
+ * Same as (@link AudioSystem#setDevicesRoleForCapturePreset(int, List))
+ * @param capturePreset
+ * @param role
+ * @param devices
+ * @return
+ */
+ public int setDevicesRoleForCapturePreset(int capturePreset, int role,
+ @NonNull List<AudioDeviceAttributes> devices) {
+ return AudioSystem.setDevicesRoleForCapturePreset(capturePreset, role, devices);
+ }
+
+ /**
+ * Same as {@link AudioSystem#removeDevicesRoleForCapturePreset(int, int)}
+ * @param capturePreset
+ * @param role
+ * @param devicesToRemove
+ * @return
+ */
+ public int removeDevicesRoleForCapturePreset(
+ int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devicesToRemove) {
+ return AudioSystem.removeDevicesRoleForCapturePreset(capturePreset, role, devicesToRemove);
+ }
+
+ /**
+ * Same as {@link AudioSystem#}
+ * @param capturePreset
+ * @param role
+ * @return
+ */
+ public int clearDevicesRoleForCapturePreset(int capturePreset, int role) {
+ return AudioSystem.clearDevicesRoleForCapturePreset(capturePreset, role);
+ }
+
+ /**
* Same as {@link AudioSystem#setParameters(String)}
* @param keyValuePairs
* @return
diff --git a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
index 609af8d5bf4d..8d706cb960e9 100644
--- a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
+++ b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
@@ -79,6 +79,23 @@ public class NoOpAudioSystemAdapter extends AudioSystemAdapter {
}
@Override
+ public int setDevicesRoleForCapturePreset(int capturePreset, int role,
+ @NonNull List<AudioDeviceAttributes> devices) {
+ return AudioSystem.AUDIO_STATUS_OK;
+ }
+
+ @Override
+ public int removeDevicesRoleForCapturePreset(
+ int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devicesToRemove) {
+ return AudioSystem.AUDIO_STATUS_OK;
+ }
+
+ @Override
+ public int clearDevicesRoleForCapturePreset(int capturePreset, int role) {
+ return AudioSystem.AUDIO_STATUS_OK;
+ }
+
+ @Override
public int setParameters(String keyValuePairs) {
return AudioSystem.AUDIO_STATUS_OK;
}