Loading packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java +77 −6 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 = Loading @@ -76,18 +88,34 @@ public final class InputRouteManager { Handler handler = new Handler(context.getMainLooper()); mAudioManager.registerAudioDeviceCallback(mAudioDeviceCallback, handler); mAudioManager.addOnPreferredDevicesForCapturePresetChangedListener( new HandlerExecutor(handler), this::onPreferredDevicesForCapturePresetChangedListener); } 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) { synchronized (mCallbackLock) { mCallbacks.remove(callback); } } public @Nullable MediaDevice getSelectedInputDevice() { return mSelectedInputDevice; Loading Loading @@ -134,10 +162,53 @@ public final class InputRouteManager { } final List<MediaDevice> inputMediaDevices = new ArrayList<>(mInputMediaDevices); 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); } } public int getMaxInputGain() { // TODO (b/357123335): use real input gain implementation. Loading packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java +32 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -221,6 +228,31 @@ public class InputRouteManagerTest { .isEqualTo(AudioDeviceInfo.TYPE_BUILTIN_MIC); } @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); Loading packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java +12 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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(() -> { Loading packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java +40 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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) Loading @@ -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; Loading Loading @@ -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); } } Loading
packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java +77 −6 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 = Loading @@ -76,18 +88,34 @@ public final class InputRouteManager { Handler handler = new Handler(context.getMainLooper()); mAudioManager.registerAudioDeviceCallback(mAudioDeviceCallback, handler); mAudioManager.addOnPreferredDevicesForCapturePresetChangedListener( new HandlerExecutor(handler), this::onPreferredDevicesForCapturePresetChangedListener); } 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) { synchronized (mCallbackLock) { mCallbacks.remove(callback); } } public @Nullable MediaDevice getSelectedInputDevice() { return mSelectedInputDevice; Loading Loading @@ -134,10 +162,53 @@ public final class InputRouteManager { } final List<MediaDevice> inputMediaDevices = new ArrayList<>(mInputMediaDevices); 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); } } public int getMaxInputGain() { // TODO (b/357123335): use real input gain implementation. Loading
packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java +32 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -221,6 +228,31 @@ public class InputRouteManagerTest { .isEqualTo(AudioDeviceInfo.TYPE_BUILTIN_MIC); } @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); Loading
packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java +12 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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(() -> { Loading
packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java +40 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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) Loading @@ -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; Loading Loading @@ -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); } }