summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Wenyu Zhang <zhangwenyu@google.com> 2024-09-24 14:45:34 +0000
committer Wenyu Zhang <zhangwenyu@google.com> 2024-09-24 14:45:34 +0000
commit03a3dbdcaaf462b60a87256ec80409afa43d3131 (patch)
tree0feb6561615e00b62c0b76918c9bed015cac363e
parent33c87751f5b85dc9dde67ce8990c38af3021bf07 (diff)
Support selecting input devices
InputRouteManager calls AudioManager.setPreferredDeviceForCapturePreset to select preferred input device. InputRouteManager adds OnPreferredDevicesForCapturePresetChangedListener to listen to preferred input device changes and then updates UI. Change-Id: I8eefceb62699aca547dec438c00d2aada41cedc2 Bug: b/355684672, b/357123258 Test atest MediaSwitchingControllerTest,InputRouteManagerTest Flag: com.android.media.flags.enable_audio_input_device_routing_and_volume_control
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java83
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java40
4 files changed, 161 insertions, 6 deletions
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
index 0c50166fff8b..9164b64057fd 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
@@ -25,6 +25,7 @@ import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.MediaRecorder;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.util.Slog;
import androidx.annotation.NonNull;
@@ -46,6 +47,16 @@ public final class InputRouteManager {
static final AudioAttributes INPUT_ATTRIBUTES =
new AudioAttributes.Builder().setCapturePreset(MediaRecorder.AudioSource.MIC).build();
+ @VisibleForTesting
+ static final int[] PRESETS = {
+ MediaRecorder.AudioSource.MIC,
+ MediaRecorder.AudioSource.CAMCORDER,
+ MediaRecorder.AudioSource.VOICE_RECOGNITION,
+ MediaRecorder.AudioSource.VOICE_COMMUNICATION,
+ MediaRecorder.AudioSource.UNPROCESSED,
+ MediaRecorder.AudioSource.VOICE_PERFORMANCE
+ };
+
private final Context mContext;
private final AudioManager mAudioManager;
@@ -55,6 +66,7 @@ public final class InputRouteManager {
private MediaDevice mSelectedInputDevice;
private final Collection<InputDeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
+ private final Object mCallbackLock = new Object();
@VisibleForTesting
final AudioDeviceCallback mAudioDeviceCallback =
@@ -76,17 +88,33 @@ public final class InputRouteManager {
Handler handler = new Handler(context.getMainLooper());
mAudioManager.registerAudioDeviceCallback(mAudioDeviceCallback, handler);
+
+ mAudioManager.addOnPreferredDevicesForCapturePresetChangedListener(
+ new HandlerExecutor(handler),
+ this::onPreferredDevicesForCapturePresetChangedListener);
}
- public void registerCallback(@NonNull InputDeviceCallback callback) {
- if (!mCallbacks.contains(callback)) {
- mCallbacks.add(callback);
+ private void onPreferredDevicesForCapturePresetChangedListener(
+ @MediaRecorder.SystemSource int capturePreset,
+ @NonNull List<AudioDeviceAttributes> devices) {
+ if (capturePreset == MediaRecorder.AudioSource.MIC) {
dispatchInputDeviceListUpdate();
}
}
+ public void registerCallback(@NonNull InputDeviceCallback callback) {
+ synchronized (mCallbackLock) {
+ if (!mCallbacks.contains(callback)) {
+ mCallbacks.add(callback);
+ dispatchInputDeviceListUpdate();
+ }
+ }
+ }
+
public void unregisterCallback(@NonNull InputDeviceCallback callback) {
- mCallbacks.remove(callback);
+ synchronized (mCallbackLock) {
+ mCallbacks.remove(callback);
+ }
}
public @Nullable MediaDevice getSelectedInputDevice() {
@@ -134,8 +162,51 @@ public final class InputRouteManager {
}
final List<MediaDevice> inputMediaDevices = new ArrayList<>(mInputMediaDevices);
- for (InputDeviceCallback callback : mCallbacks) {
- callback.onInputDeviceListUpdated(inputMediaDevices);
+ synchronized (mCallbackLock) {
+ for (InputDeviceCallback callback : mCallbacks) {
+ callback.onInputDeviceListUpdated(inputMediaDevices);
+ }
+ }
+ }
+
+ public void selectDevice(@NonNull MediaDevice device) {
+ if (!(device instanceof InputMediaDevice)) {
+ Slog.w(TAG, "This device is not an InputMediaDevice: " + device.getName());
+ return;
+ }
+
+ if (device.equals(mSelectedInputDevice)) {
+ Slog.w(TAG, "This device is already selected: " + device.getName());
+ return;
+ }
+
+ // Handle edge case where the targeting device is not available, e.g. disconnected.
+ if (!mInputMediaDevices.contains(device)) {
+ Slog.w(TAG, "This device is not available: " + device.getName());
+ return;
+ }
+
+ // TODO(b/355684672): apply address for BT devices.
+ AudioDeviceAttributes deviceAttributes =
+ new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_INPUT,
+ ((InputMediaDevice) device).getAudioDeviceInfoType(),
+ /* address= */ "");
+ try {
+ setPreferredDeviceForAllPresets(deviceAttributes);
+ } catch (IllegalArgumentException e) {
+ Slog.e(
+ TAG,
+ "Illegal argument exception while setPreferredDeviceForAllPreset: "
+ + device.getName(),
+ e);
+ }
+ }
+
+ private void setPreferredDeviceForAllPresets(@NonNull AudioDeviceAttributes deviceAttributes) {
+ // The input routing via system setting takes effect on all capture presets.
+ for (@MediaRecorder.Source int preset : PRESETS) {
+ mAudioManager.setPreferredDeviceForCapturePreset(preset, deviceAttributes);
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java
index 8a18d0714e0a..16c0c1c71d79 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java
@@ -17,17 +17,21 @@
package com.android.settingslib.media;
import static com.android.settingslib.media.InputRouteManager.INPUT_ATTRIBUTES;
+import static com.android.settingslib.media.InputRouteManager.PRESETS;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
+import android.media.MediaRecorder;
import com.android.settingslib.testutils.shadow.ShadowRouter2Manager;
@@ -51,6 +55,9 @@ public class InputRouteManagerTest {
private static final int INPUT_USB_DEVICE_ID = 3;
private static final int INPUT_USB_HEADSET_ID = 4;
private static final int INPUT_USB_ACCESSORY_ID = 5;
+ private static final int MAX_VOLUME = 1;
+ private static final int CURRENT_VOLUME = 0;
+ private static final boolean VOLUME_FIXED_TRUE = true;
private final Context mContext = spy(RuntimeEnvironment.application);
private InputRouteManager mInputRouteManager;
@@ -222,6 +229,31 @@ public class InputRouteManagerTest {
}
@Test
+ public void selectDevice() {
+ final AudioManager audioManager = mock(AudioManager.class);
+ InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager);
+ final MediaDevice inputMediaDevice =
+ InputMediaDevice.create(
+ mContext,
+ String.valueOf(BUILTIN_MIC_ID),
+ AudioDeviceInfo.TYPE_BUILTIN_MIC,
+ MAX_VOLUME,
+ CURRENT_VOLUME,
+ VOLUME_FIXED_TRUE);
+ inputRouteManager.selectDevice(inputMediaDevice);
+
+ AudioDeviceAttributes deviceAttributes =
+ new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_INPUT,
+ AudioDeviceInfo.TYPE_BUILTIN_MIC,
+ /* address= */ "");
+ for (@MediaRecorder.Source int preset : PRESETS) {
+ verify(audioManager, atLeastOnce())
+ .setPreferredDeviceForCapturePreset(preset, deviceAttributes);
+ }
+ }
+
+ @Test
public void getMaxInputGain_returnMaxInputGain() {
assertThat(mInputRouteManager.getMaxInputGain()).isEqualTo(15);
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
index f7b73534d35c..b69b25dbddf2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
@@ -77,6 +77,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastMetadata;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.media.InfoMediaManager;
+import com.android.settingslib.media.InputMediaDevice;
import com.android.settingslib.media.InputRouteManager;
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
@@ -875,6 +876,17 @@ public class MediaSwitchingController
}
protected void connectDevice(MediaDevice device) {
+ // If input routing is supported and the device is an input device, call mInputRouteManager
+ // to handle routing.
+ if (enableInputRouting() && device instanceof InputMediaDevice) {
+ var unused =
+ ThreadUtils.postOnBackgroundThread(
+ () -> {
+ mInputRouteManager.selectDevice(device);
+ });
+ return;
+ }
+
mMetricLogger.updateOutputEndPoints(getCurrentConnectedMediaDevice(), device);
ThreadUtils.postOnBackgroundThread(() -> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
index 53f0800a7d13..c4f5d621cb6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
@@ -76,6 +76,7 @@ import com.android.settingslib.media.InputMediaDevice;
import com.android.settingslib.media.InputRouteManager;
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
+import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.SysuiTestCaseExtKt;
import com.android.systemui.animation.ActivityTransitionAnimator;
@@ -103,6 +104,8 @@ import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -120,6 +123,8 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
private static final int MAX_VOLUME = 1;
private static final int CURRENT_VOLUME = 0;
private static final boolean VOLUME_FIXED_TRUE = true;
+ private static final int LATCH_COUNT_DOWN_TIME_IN_SECOND = 5;
+ private static final int LATCH_TIME_OUT_TIME_IN_SECOND = 10;
@Mock
private DialogTransitionAnimator mDialogTransitionAnimator;
@@ -1339,4 +1344,39 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
assertThat(selectedMediaDevices)
.containsExactly(selectedOutputMediaDevice, selectedInputMediaDevice);
}
+
+ @EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
+ @Test
+ public void selectInputDevice() throws InterruptedException {
+ final MediaDevice inputMediaDevice =
+ InputMediaDevice.create(
+ mContext,
+ TEST_DEVICE_1_ID,
+ AudioDeviceInfo.TYPE_BUILTIN_MIC,
+ MAX_VOLUME,
+ CURRENT_VOLUME,
+ VOLUME_FIXED_TRUE);
+ mMediaSwitchingController.connectDevice(inputMediaDevice);
+
+ CountDownLatch latch = new CountDownLatch(LATCH_COUNT_DOWN_TIME_IN_SECOND);
+ var unused = ThreadUtils.postOnBackgroundThread(latch::countDown);
+ latch.await(LATCH_TIME_OUT_TIME_IN_SECOND, TimeUnit.SECONDS);
+
+ verify(mInputRouteManager, atLeastOnce()).selectDevice(inputMediaDevice);
+ verify(mLocalMediaManager, never()).connectDevice(inputMediaDevice);
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
+ @Test
+ public void selectOutputDevice() throws InterruptedException {
+ final MediaDevice outputMediaDevice = mock(MediaDevice.class);
+ mMediaSwitchingController.connectDevice(outputMediaDevice);
+
+ CountDownLatch latch = new CountDownLatch(LATCH_COUNT_DOWN_TIME_IN_SECOND);
+ var unused = ThreadUtils.postOnBackgroundThread(latch::countDown);
+ latch.await(LATCH_TIME_OUT_TIME_IN_SECOND, TimeUnit.SECONDS);
+
+ verify(mInputRouteManager, never()).selectDevice(outputMediaDevice);
+ verify(mLocalMediaManager, atLeastOnce()).connectDevice(outputMediaDevice);
+ }
}