Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3d404328 authored by Wenyu Zhang's avatar Wenyu Zhang Committed by Android (Google) Code Review
Browse files

Merge "Show selected input device" into main

parents 634d93a1 f2d75fee
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -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,
+37 −1
Original line number Diff line number Diff line
@@ -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);
            }
        }
+98 −0
Original line number Diff line number Diff line
@@ -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 {
@@ -123,6 +130,97 @@ public class InputRouteManagerTest {
        assertThat(inputRouteManager.mInputMediaDevices).isEmpty();
    }

    @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);
+13 −2
Original line number Diff line number Diff line
@@ -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,9 +927,20 @@ public class MediaSwitchingController
    }

    public List<MediaDevice> 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() {
        return mLocalMediaManager.getDeselectableMediaDevice();
    }
+32 −5
Original line number Diff line number Diff line
@@ -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);
    }
}