summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Wenyu Zhang <zhangwenyu@google.com> 2024-09-24 22:43:48 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-09-24 22:43:48 +0000
commit5b5465ea63f997518a1bec7f2c386f43cc1230bb (patch)
tree2e74559f5f6f1967d4a046acf9a0dcc1326efbe7
parentafde520ff12a789e16053868ba483a59a3087012 (diff)
parent03a3dbdcaaf462b60a87256ec80409afa43d3131 (diff)
Merge "Support selecting input devices" into main
-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);
+ }
}