diff options
author | 2024-09-18 23:22:10 +0000 | |
---|---|---|
committer | 2024-09-18 23:22:10 +0000 | |
commit | f2d75fee3ffe5e7990bd6cd521abe0fe8853770c (patch) | |
tree | 457bdf7c7fe1af46095e2c95a8ebaea61f0fca78 | |
parent | 2aa24f454a8481a23f081d1dc3e4bb820751d517 (diff) |
Show selected input device
InputRouteManager calls AudioManager.getDevicesForAttributes to
retrieve selected input device and passes it down to
MediaSwitchingController.
Change-Id: I8e50cb911a3a1950dfbab921174dc68a18fc6b97
Bug: b/355684672, b/357122624
Test atest MediaSwitchingControllerTest,InputRouteManagerTest
Flag: com.android.media.flags.enable_audio_input_device_routing_and_volume_control
5 files changed, 184 insertions, 8 deletions
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java index 766cd438a811..9dd2dbb41295 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java @@ -80,6 +80,10 @@ public class InputMediaDevice extends MediaDevice { context, id, audioDeviceInfoType, maxVolume, currentVolume, isVolumeFixed); } + public @AudioDeviceType int getAudioDeviceInfoType() { + return mAudioDeviceInfoType; + } + public static boolean isSupportedInputDevice(@AudioDeviceType int audioDeviceInfoType) { return switch (audioDeviceInfoType) { case TYPE_BUILTIN_MIC, diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java index 874e03012ae2..0c50166fff8b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java @@ -15,13 +15,20 @@ */ package com.android.settingslib.media; +import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED; + import android.content.Context; +import android.media.AudioAttributes; +import android.media.AudioDeviceAttributes; import android.media.AudioDeviceCallback; import android.media.AudioDeviceInfo; import android.media.AudioManager; +import android.media.MediaRecorder; import android.os.Handler; +import android.util.Slog; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; @@ -35,12 +42,18 @@ public final class InputRouteManager { private static final String TAG = "InputRouteManager"; + @VisibleForTesting + static final AudioAttributes INPUT_ATTRIBUTES = + new AudioAttributes.Builder().setCapturePreset(MediaRecorder.AudioSource.MIC).build(); + private final Context mContext; private final AudioManager mAudioManager; @VisibleForTesting final List<MediaDevice> mInputMediaDevices = new CopyOnWriteArrayList<>(); + private MediaDevice mSelectedInputDevice; + private final Collection<InputDeviceCallback> mCallbacks = new CopyOnWriteArrayList<>(); @VisibleForTesting @@ -76,8 +89,27 @@ public final class InputRouteManager { mCallbacks.remove(callback); } + public @Nullable MediaDevice getSelectedInputDevice() { + return mSelectedInputDevice; + } + private void dispatchInputDeviceListUpdate() { - // TODO (b/360175574): Get selected input device. + // Get selected input device. + List<AudioDeviceAttributes> attributesOfSelectedInputDevices = + mAudioManager.getDevicesForAttributes(INPUT_ATTRIBUTES); + int selectedInputDeviceAttributesType; + if (attributesOfSelectedInputDevices.isEmpty()) { + Slog.e(TAG, "Unexpected empty list of input devices. Using built-in mic."); + selectedInputDeviceAttributesType = AudioDeviceInfo.TYPE_BUILTIN_MIC; + } else { + if (attributesOfSelectedInputDevices.size() > 1) { + Slog.w( + TAG, + "AudioManager.getDevicesForAttributes returned more than one element." + + " Using the first one."); + } + selectedInputDeviceAttributesType = attributesOfSelectedInputDevices.get(0).getType(); + } // Get all input devices. AudioDeviceInfo[] audioDeviceInfos = @@ -93,6 +125,10 @@ public final class InputRouteManager { getCurrentInputGain(), isInputGainFixed()); if (mediaDevice != null) { + if (info.getType() == selectedInputDeviceAttributesType) { + mediaDevice.setState(STATE_SELECTED); + mSelectedInputDevice = mediaDevice; + } mInputMediaDevices.add(mediaDevice); } } 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 2501ae6769b6..8a18d0714e0a 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 @@ -16,6 +16,8 @@ package com.android.settingslib.media; +import static com.android.settingslib.media.InputRouteManager.INPUT_ATTRIBUTES; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; @@ -23,6 +25,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.content.Context; +import android.media.AudioDeviceAttributes; import android.media.AudioDeviceInfo; import android.media.AudioManager; @@ -36,6 +39,10 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + @RunWith(RobolectricTestRunner.class) @Config(shadows = {ShadowRouter2Manager.class}) public class InputRouteManagerTest { @@ -124,6 +131,97 @@ public class InputRouteManagerTest { } @Test + public void getSelectedInputDevice_returnOneFromAudioManager() { + final AudioDeviceInfo info1 = mock(AudioDeviceInfo.class); + when(info1.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET); + when(info1.getId()).thenReturn(INPUT_WIRED_HEADSET_ID); + + final AudioDeviceInfo info2 = mock(AudioDeviceInfo.class); + when(info2.getType()).thenReturn(AudioDeviceInfo.TYPE_BUILTIN_MIC); + when(info2.getId()).thenReturn(BUILTIN_MIC_ID); + + final AudioManager audioManager = mock(AudioManager.class); + AudioDeviceInfo[] devices = {info1, info2}; + when(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(devices); + + // Mock audioManager.getDevicesForAttributes returns exactly one audioDeviceAttributes. + AudioDeviceAttributes audioDeviceAttributes = new AudioDeviceAttributes(info1); + when(audioManager.getDevicesForAttributes(INPUT_ATTRIBUTES)) + .thenReturn(Collections.singletonList(audioDeviceAttributes)); + + InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager); + inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices); + + // The selected input device has the same type as the one returned from AudioManager. + InputMediaDevice selectedInputDevice = + (InputMediaDevice) inputRouteManager.getSelectedInputDevice(); + assertThat(selectedInputDevice.getAudioDeviceInfoType()) + .isEqualTo(AudioDeviceInfo.TYPE_WIRED_HEADSET); + } + + @Test + public void getSelectedInputDevice_returnMoreThanOneFromAudioManager() { + final AudioDeviceInfo info1 = mock(AudioDeviceInfo.class); + when(info1.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET); + when(info1.getId()).thenReturn(INPUT_WIRED_HEADSET_ID); + + final AudioDeviceInfo info2 = mock(AudioDeviceInfo.class); + when(info2.getType()).thenReturn(AudioDeviceInfo.TYPE_BUILTIN_MIC); + when(info2.getId()).thenReturn(BUILTIN_MIC_ID); + + final AudioManager audioManager = mock(AudioManager.class); + AudioDeviceInfo[] devices = {info1, info2}; + when(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(devices); + + // Mock audioManager.getDevicesForAttributes returns more than one audioDeviceAttributes. + AudioDeviceAttributes audioDeviceAttributes1 = new AudioDeviceAttributes(info1); + AudioDeviceAttributes audioDeviceAttributes2 = new AudioDeviceAttributes(info2); + List<AudioDeviceAttributes> attributesOfSelectedInputDevices = new ArrayList<>(); + attributesOfSelectedInputDevices.add(audioDeviceAttributes1); + attributesOfSelectedInputDevices.add(audioDeviceAttributes2); + when(audioManager.getDevicesForAttributes(INPUT_ATTRIBUTES)) + .thenReturn(attributesOfSelectedInputDevices); + + InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager); + inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices); + + // The selected input device has the same type as the first one returned from AudioManager. + InputMediaDevice selectedInputDevice = + (InputMediaDevice) inputRouteManager.getSelectedInputDevice(); + assertThat(selectedInputDevice.getAudioDeviceInfoType()) + .isEqualTo(AudioDeviceInfo.TYPE_WIRED_HEADSET); + } + + @Test + public void getSelectedInputDevice_returnEmptyFromAudioManager() { + final AudioDeviceInfo info1 = mock(AudioDeviceInfo.class); + when(info1.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET); + when(info1.getId()).thenReturn(INPUT_WIRED_HEADSET_ID); + + final AudioDeviceInfo info2 = mock(AudioDeviceInfo.class); + when(info2.getType()).thenReturn(AudioDeviceInfo.TYPE_BUILTIN_MIC); + when(info2.getId()).thenReturn(BUILTIN_MIC_ID); + + final AudioManager audioManager = mock(AudioManager.class); + AudioDeviceInfo[] devices = {info1, info2}; + when(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(devices); + + // Mock audioManager.getDevicesForAttributes returns empty list of audioDeviceAttributes. + List<AudioDeviceAttributes> attributesOfSelectedInputDevices = new ArrayList<>(); + when(audioManager.getDevicesForAttributes(INPUT_ATTRIBUTES)) + .thenReturn(attributesOfSelectedInputDevices); + + InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager); + inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices); + + // The selected input device has default type AudioDeviceInfo.TYPE_BUILTIN_MIC. + InputMediaDevice selectedInputDevice = + (InputMediaDevice) inputRouteManager.getSelectedInputDevice(); + assertThat(selectedInputDevice.getAudioDeviceInfoType()) + .isEqualTo(AudioDeviceInfo.TYPE_BUILTIN_MIC); + } + + @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 2cbc75755cfd..f7b73534d35c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java @@ -157,7 +157,7 @@ public class MediaSwitchingController @VisibleForTesting boolean mNeedRefresh = false; private MediaController mMediaController; - private InputRouteManager mInputRouteManager; + @VisibleForTesting InputRouteManager mInputRouteManager; @VisibleForTesting Callback mCallback; @VisibleForTesting @@ -927,7 +927,18 @@ public class MediaSwitchingController } public List<MediaDevice> getSelectedMediaDevice() { - return mLocalMediaManager.getSelectedMediaDevice(); + if (!enableInputRouting()) { + return mLocalMediaManager.getSelectedMediaDevice(); + } + + // Add selected input device if input routing is supported. + List<MediaDevice> selectedDevices = + new ArrayList<>(mLocalMediaManager.getSelectedMediaDevice()); + MediaDevice selectedInputDevice = mInputRouteManager.getSelectedInputDevice(); + if (selectedInputDevice != null) { + selectedDevices.add(selectedInputDevice); + } + return selectedDevices; } List<MediaDevice> getDeselectableMediaDevice() { 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 d3e20c6e39b7..53f0800a7d13 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 @@ -73,6 +73,7 @@ import com.android.media.flags.Flags; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.LocalBluetoothManager; 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.systemui.SysuiTestCase; @@ -100,6 +101,7 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.ArrayList; +import java.util.Collections; import java.util.List; @SmallTest @@ -115,6 +117,10 @@ public class MediaSwitchingControllerTest extends SysuiTestCase { private static final String TEST_SONG = "test_song"; private static final String TEST_SESSION_ID = "test_session_id"; private static final String TEST_SESSION_NAME = "test_session_name"; + private static final int MAX_VOLUME = 1; + private static final int CURRENT_VOLUME = 0; + private static final boolean VOLUME_FIXED_TRUE = true; + @Mock private DialogTransitionAnimator mDialogTransitionAnimator; @Mock @@ -181,6 +187,7 @@ public class MediaSwitchingControllerTest extends SysuiTestCase { private String mPackageName = null; private MediaSwitchingController mMediaSwitchingController; private LocalMediaManager mLocalMediaManager; + private InputRouteManager mInputRouteManager; private List<MediaController> mMediaControllers = new ArrayList<>(); private List<MediaDevice> mMediaDevices = new ArrayList<>(); private List<NearbyDevice> mNearbyDevices = new ArrayList<>(); @@ -228,6 +235,10 @@ public class MediaSwitchingControllerTest extends SysuiTestCase { mLocalMediaManager = spy(mMediaSwitchingController.mLocalMediaManager); when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(false); mMediaSwitchingController.mLocalMediaManager = mLocalMediaManager; + mMediaSwitchingController.mInputRouteManager = + new InputRouteManager(mContext, mAudioManager); + mInputRouteManager = spy(mMediaSwitchingController.mInputRouteManager); + mMediaSwitchingController.mInputRouteManager = mInputRouteManager; MediaDescription.Builder builder = new MediaDescription.Builder(); builder.setTitle(TEST_SONG); builder.setSubtitle(TEST_ARTIST); @@ -545,9 +556,6 @@ public class MediaSwitchingControllerTest extends SysuiTestCase { // Output devices have changed. mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); - final int MAX_VOLUME = 1; - final int CURRENT_VOLUME = 0; - final boolean IS_VOLUME_FIXED = true; final MediaDevice mediaDevice3 = InputMediaDevice.create( mContext, @@ -555,7 +563,7 @@ public class MediaSwitchingControllerTest extends SysuiTestCase { AudioDeviceInfo.TYPE_BUILTIN_MIC, MAX_VOLUME, CURRENT_VOLUME, - IS_VOLUME_FIXED); + VOLUME_FIXED_TRUE); final MediaDevice mediaDevice4 = InputMediaDevice.create( mContext, @@ -563,7 +571,7 @@ public class MediaSwitchingControllerTest extends SysuiTestCase { AudioDeviceInfo.TYPE_WIRED_HEADSET, MAX_VOLUME, CURRENT_VOLUME, - IS_VOLUME_FIXED); + VOLUME_FIXED_TRUE); final List<MediaDevice> inputDevices = new ArrayList<>(); inputDevices.add(mediaDevice3); inputDevices.add(mediaDevice4); @@ -1312,4 +1320,23 @@ public class MediaSwitchingControllerTest extends SysuiTestCase { verify(mCallback).dismissDialog(); } + + @EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL) + @Test + public void getSelectedMediaDevice() { + // Mock MediaDevice since none of the output media device constructor is publicly available + // outside of SettingsLib package. + final MediaDevice selectedOutputMediaDevice = mock(MediaDevice.class); + doReturn(Collections.singletonList(selectedOutputMediaDevice)) + .when(mLocalMediaManager) + .getSelectedMediaDevice(); + + // Mock selected input media device. + final MediaDevice selectedInputMediaDevice = mock(MediaDevice.class); + doReturn(selectedInputMediaDevice).when(mInputRouteManager).getSelectedInputDevice(); + + List<MediaDevice> selectedMediaDevices = mMediaSwitchingController.getSelectedMediaDevice(); + assertThat(selectedMediaDevices) + .containsExactly(selectedOutputMediaDevice, selectedInputMediaDevice); + } } |