diff options
author | Eric Laurent <elaurent@google.com> | 2020-12-23 09:44:49 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2020-12-23 09:44:49 +0000 |
commit | 54e6b9c79b86ab1c8407c13a3c3d9be231d6a028 (patch) | |
tree | 8dc0c717aa36080e706d1996d1d882152a0e6a15 | |
parent | a53b25ad26dd48151d6abf6eba6e90d5f2cac1ef (diff) | |
parent | db9517269607a758192ce5dd573996bac22b6471 (diff) |
Merge "Support set/clear/get preferred device for capture preset." am: 617d14954d am: db95172696
Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1534430
MUST ONLY BE SUBMITTED BY AUTOMERGER
Change-Id: Ic7ff104b7e0aab924ff51950b41ce63ec3a742f3
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; } |