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
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java
index 766cd43..9dd2dbb 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 @@
                 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 874e030..0c50166 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 @@
 
     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 @@
         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 @@
                             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 2501ae6..8a18d07 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.when;
 
 import android.content.Context;
+import android.media.AudioDeviceAttributes;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 
@@ -36,6 +39,10 @@
 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 @@
     }
 
     @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 2cbc7575..f7b7353 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 @@
     @VisibleForTesting
     boolean mNeedRefresh = false;
     private MediaController mMediaController;
-    private InputRouteManager mInputRouteManager;
+    @VisibleForTesting InputRouteManager mInputRouteManager;
     @VisibleForTesting
     Callback mCallback;
     @VisibleForTesting
@@ -927,7 +927,18 @@
     }
 
     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 d3e20c6..53f0800 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.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.MockitoAnnotations;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 @SmallTest
@@ -115,6 +117,10 @@
     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 @@
     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 @@
         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 @@
         // 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 @@
                         AudioDeviceInfo.TYPE_BUILTIN_MIC,
                         MAX_VOLUME,
                         CURRENT_VOLUME,
-                        IS_VOLUME_FIXED);
+                        VOLUME_FIXED_TRUE);
         final MediaDevice mediaDevice4 =
                 InputMediaDevice.create(
                         mContext,
@@ -563,7 +571,7 @@
                         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 @@
 
         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);
+    }
 }