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

Commit e17349ae authored by Chung Tang's avatar Chung Tang Committed by Android (Google) Code Review
Browse files

Merge "[OutputSwitcher] Refactor to allow AudioManagerRouteController to...

Merge "[OutputSwitcher] Refactor to allow AudioManagerRouteController to notify the MR2Provider for route request failure." into main
parents 18746e28 bc91048a
Loading
Loading
Loading
Loading
+24 −10
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import android.media.AudioDeviceCallback;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderService;
import android.media.RoutingSessionInfo;
import android.media.audio.Flags;
import android.media.audiopolicy.AudioProductStrategy;
@@ -97,8 +98,8 @@ import java.util.concurrent.CopyOnWriteArrayList;
    @NonNull private final Handler mHandler;

    @NonNull
    private final CopyOnWriteArrayList<OnDeviceRouteChangedListener>
            mOnDeviceRouteChangedListeners = new CopyOnWriteArrayList<>();
    private final CopyOnWriteArrayList<EventListener> mEventListeners =
            new CopyOnWriteArrayList<>();

    @NonNull private final BluetoothDeviceRoutesManager mBluetoothRouteController;

@@ -187,14 +188,12 @@ import java.util.concurrent.CopyOnWriteArrayList;
        rebuildAvailableRoutes();
    }

    public void registerRouteChangeListener(
            @NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener) {
        mOnDeviceRouteChangedListeners.add(onDeviceRouteChangedListener);
    public void registerRouteChangeListener(@NonNull EventListener eventListener) {
        mEventListeners.add(eventListener);
    }

    public void unregisterRouteChangeListener(
            @NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener) {
        mOnDeviceRouteChangedListeners.remove(onDeviceRouteChangedListener);
    public void unregisterRouteChangeListener(@NonNull EventListener eventListener) {
        mEventListeners.remove(eventListener);
    }

    @RequiresPermission(
@@ -273,13 +272,14 @@ import java.util.concurrent.CopyOnWriteArrayList;

    @RequiresPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)
    @Override
    public void transferTo(@Nullable String routeId) {
    public void transferTo(long requestId, @Nullable String routeId) {
        if (routeId == null) {
            // This should never happen: This branch should only execute when the matching bluetooth
            // route controller is not the no-op one.
            // TODO: b/305199571 - Make routeId non-null and remove this branch once we remove the
            // legacy route controller implementations.
            Slog.e(TAG, "Unexpected call to AudioPoliciesDeviceRouteController#transferTo(null)");
            notifyRequestFailed(requestId, MediaRoute2ProviderService.REASON_ROUTE_NOT_AVAILABLE);
            return;
        }
        MediaRoute2InfoHolder mediaRoute2InfoHolder;
@@ -302,6 +302,8 @@ import java.util.concurrent.CopyOnWriteArrayList;
                                TAG,
                                "Unexpected exception while transferring to route id: " + routeId,
                                throwable);
                        notifyRequestFailed(
                                requestId, MediaRoute2ProviderService.REASON_UNKNOWN_ERROR);
                        mHandler.post(this::rebuildAvailableRoutesAndNotify);
                    }
                };
@@ -369,7 +371,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
            })
    private void rebuildAvailableRoutesAndNotify() {
        rebuildAvailableRoutes();
        for (OnDeviceRouteChangedListener listener : mOnDeviceRouteChangedListeners) {
        for (EventListener listener : mEventListeners) {
            listener.onDeviceRouteChanged();
        }
    }
@@ -608,6 +610,18 @@ import java.util.concurrent.CopyOnWriteArrayList;
        return builder.build();
    }

    /**
     * Notifies the MediaRouter for failed requests.
     *
     * @param requestId Identifies the request that failed.
     * @param reason Value from {@link MediaRoute2ProviderService.Reason}.
     */
    private void notifyRequestFailed(long requestId, int reason) {
        for (EventListener listener : mEventListeners) {
            listener.onDeviceRouteRequestFailed(requestId, reason);
        }
    }

    /**
     * Holds a {@link MediaRoute2Info} and associated information that we don't want to put in the
     * {@link MediaRoute2Info} class because it's solely necessary for the implementation of this
+11 −9
Original line number Diff line number Diff line
@@ -51,7 +51,7 @@ import java.util.List;
    /* package */ static DeviceRouteController createInstance(
            @NonNull Context context,
            @NonNull Looper looper,
            @NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener) {
            @NonNull EventListener eventListener) {
        AudioManager audioManager = context.getSystemService(AudioManager.class);
        AudioProductStrategy strategyForMedia = AudioRoutingUtils.getMediaAudioProductStrategy();

@@ -67,14 +67,14 @@ import java.util.List;
            AudioManagerRouteController controller =
                    AudioManagerRouteController.getInstance(
                            context, audioManager, looper, strategyForMedia, btAdapter);
            controller.registerRouteChangeListener(onDeviceRouteChangedListener);
            controller.registerRouteChangeListener(eventListener);
            return controller;
        } else {
            IAudioService audioService =
                    IAudioService.Stub.asInterface(
                            ServiceManager.getService(Context.AUDIO_SERVICE));
            return new LegacyDeviceRouteController(
                    context, audioManager, audioService, onDeviceRouteChangedListener);
                    context, audioManager, audioService, eventListener);
        }
    }

@@ -122,9 +122,10 @@ import java.util.List;
     *
     * <p>If the route is {@code null} then active route will be deactivated.
     *
     * @param routeId to switch to or {@code null} to unset the active device.
     * @param requestId Identifies the request.
     * @param routeId To switch to or {@code null} to unset the active device.
     */
    void transferTo(@Nullable String routeId);
    void transferTo(long requestId, @Nullable String routeId);

    /**
     * Updates device route volume.
@@ -153,13 +154,14 @@ import java.util.List;
    /** Releases the routing session. */
    void releaseRoutingSession();

    /**
     * Interface for receiving events when device route has changed.
     */
    interface OnDeviceRouteChangedListener {
    /** Interface for receiving route events. */
    interface EventListener {

        /** Called when device route has changed. */
        void onDeviceRouteChanged();

        /** Called when device route request is failed. */
        void onDeviceRouteRequestFailed(long requestId, int reason);
    }

}
+8 −8
Original line number Diff line number Diff line
@@ -67,8 +67,7 @@ import java.util.Objects;
    @NonNull
    private final IAudioService mAudioService;

    @NonNull
    private final OnDeviceRouteChangedListener mOnDeviceRouteChangedListener;
    @NonNull private final EventListener mEventListener;
    @NonNull
    private final AudioRoutesObserver mAudioRoutesObserver = new AudioRoutesObserver();

@@ -77,17 +76,18 @@ import java.util.Objects;
    private int mDeviceVolume;
    private MediaRoute2Info mDeviceRoute;

    /* package */ LegacyDeviceRouteController(@NonNull Context context,
    /* package */ LegacyDeviceRouteController(
            @NonNull Context context,
            @NonNull AudioManager audioManager,
            @NonNull IAudioService audioService,
            @NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener) {
            @NonNull EventListener eventListener) {
        Objects.requireNonNull(context);
        Objects.requireNonNull(audioManager);
        Objects.requireNonNull(audioService);
        Objects.requireNonNull(onDeviceRouteChangedListener);
        Objects.requireNonNull(eventListener);

        mContext = context;
        mOnDeviceRouteChangedListener = onDeviceRouteChangedListener;
        mEventListener = eventListener;

        mAudioManager = audioManager;
        mAudioService = audioService;
@@ -137,7 +137,7 @@ import java.util.Objects;
    }

    @Override
    public synchronized void transferTo(@Nullable String routeId) {
    public synchronized void transferTo(long requestId, @Nullable String routeId) {
        // Unsupported. This implementation doesn't support transferable routes (always exposes a
        // single non-bluetooth route).
    }
@@ -206,7 +206,7 @@ import java.util.Objects;
    }

    private void notifyDeviceRouteUpdate() {
        mOnDeviceRouteChangedListener.onDeviceRouteChanged();
        mEventListener.onDeviceRouteChanged();
    }

    private class AudioRoutesObserver extends IAudioRoutesObserver.Stub {
+23 −12
Original line number Diff line number Diff line
@@ -100,6 +100,8 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
    @Nullable
    private volatile SessionCreationOrTransferRequest mPendingTransferRequest;

    private final EventListener mOnDeviceRouteEventListener = new EventListener();

    public static SystemMediaRoute2Provider create(
            Context context, UserHandle user, Looper looper) {
        var instance = new SystemMediaRoute2Provider(context, COMPONENT_NAME, user, looper);
@@ -117,17 +119,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {

        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
        mDeviceRouteController =
                DeviceRouteController.createInstance(
                        context,
                        looper,
                        () ->
                                mHandler.post(
                                        () -> {
                                            publishProviderState();
                                            if (updateSessionInfosIfNeeded()) {
                                                notifyGlobalSessionInfoUpdated();
                                            }
                                        }));
                DeviceRouteController.createInstance(context, looper, mOnDeviceRouteEventListener);
    }

    public void start() {
@@ -266,7 +258,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
                                transferInitiatorPackageName);
            }
        }
        mDeviceRouteController.transferTo(routeOriginalId);
        mDeviceRouteController.transferTo(requestId, routeOriginalId);

        if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()
                && updateSessionInfosIfNeeded()) {
@@ -649,6 +641,25 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
        publishProviderState();
    }

    private class EventListener implements DeviceRouteController.EventListener {

        @Override
        public void onDeviceRouteChanged() {
            mHandler.post(
                    () -> {
                        publishProviderState();
                        if (updateSessionInfosIfNeeded()) {
                            notifyGlobalSessionInfoUpdated();
                        }
                    });
        }

        @Override
        public void onDeviceRouteRequestFailed(long requestId, int reason) {
            notifyRequestFailed(requestId, reason);
        }
    }

    private class AudioManagerBroadcastReceiver extends BroadcastReceiver {
        // This will be called in the main thread.
        @Override
+9 −9
Original line number Diff line number Diff line
@@ -105,7 +105,7 @@ public class AudioManagerRouteControllerTest {
    private AudioDeviceInfo mSelectedAudioDeviceInfo;
    private Set<AudioDeviceInfo> mAvailableAudioDeviceInfos;
    @Mock private AudioManager mMockAudioManager;
    @Mock private DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener;
    @Mock private DeviceRouteController.EventListener mEventListener;
    private AudioManagerRouteController mControllerUnderTest;
    private AudioDeviceCallback mAudioDeviceCallback;
    private AudioProductStrategy mMediaAudioProductStrategy;
@@ -142,7 +142,7 @@ public class AudioManagerRouteControllerTest {
                        Looper.getMainLooper(),
                        mMediaAudioProductStrategy,
                        btAdapter);
        mControllerUnderTest.registerRouteChangeListener(mOnDeviceRouteChangedListener);
        mControllerUnderTest.registerRouteChangeListener(mEventListener);
        mControllerUnderTest.start(UserHandle.CURRENT_OR_SELF);

        ArgumentCaptor<AudioDeviceCallback> deviceCallbackCaptor =
@@ -152,7 +152,7 @@ public class AudioManagerRouteControllerTest {
        mAudioDeviceCallback = deviceCallbackCaptor.getValue();

        // We clear any invocations during setup.
        clearInvocations(mOnDeviceRouteChangedListener);
        clearInvocations(mEventListener);
    }

    @After
@@ -168,7 +168,7 @@ public class AudioManagerRouteControllerTest {
        addAvailableAudioDeviceInfo(
                /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP,
                /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP);
        verify(mOnDeviceRouteChangedListener).onDeviceRouteChanged();
        verify(mEventListener).onDeviceRouteChanged();
        assertThat(mControllerUnderTest.getSelectedRoutes().getFirst().getType())
                .isEqualTo(MediaRoute2Info.TYPE_BLUETOOTH_A2DP);

@@ -185,12 +185,12 @@ public class AudioManagerRouteControllerTest {
                /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET,
                /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP,
                FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET);
        verify(mOnDeviceRouteChangedListener).onDeviceRouteChanged();
        verify(mEventListener).onDeviceRouteChanged();

        addAvailableAudioDeviceInfo(
                /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP,
                /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP);
        verify(mOnDeviceRouteChangedListener, times(2)).onDeviceRouteChanged();
        verify(mEventListener, times(2)).onDeviceRouteChanged();
        assertThat(mControllerUnderTest.getSelectedRoutes().getFirst().getType())
                .isEqualTo(MediaRoute2Info.TYPE_BLUETOOTH_A2DP);

@@ -226,7 +226,7 @@ public class AudioManagerRouteControllerTest {
        addAvailableAudioDeviceInfo(
                /* newSelectedDevice= */ null, // Selected device doesn't change.
                /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_BUILTIN_EARPIECE);
        verifyNoMoreInteractions(mOnDeviceRouteChangedListener);
        verifyNoMoreInteractions(mEventListener);
        assertThat(
                        mControllerUnderTest.getAvailableRoutes().stream()
                                .map(MediaRoute2Info::getType)
@@ -244,7 +244,7 @@ public class AudioManagerRouteControllerTest {
                FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET);
        MediaRoute2Info builtInSpeakerRoute =
                getAvailableRouteWithType(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
        mControllerUnderTest.transferTo(builtInSpeakerRoute.getId());
        mControllerUnderTest.transferTo(/* requestId= */ 0L, builtInSpeakerRoute.getId());
        verify(mMockAudioManager, Mockito.timeout(ASYNC_CALL_TIMEOUTS_MS))
                .setPreferredDeviceForStrategy(
                        mMediaAudioProductStrategy,
@@ -253,7 +253,7 @@ public class AudioManagerRouteControllerTest {

        MediaRoute2Info wiredHeadsetRoute =
                getAvailableRouteWithType(MediaRoute2Info.TYPE_WIRED_HEADSET);
        mControllerUnderTest.transferTo(wiredHeadsetRoute.getId());
        mControllerUnderTest.transferTo(/* requestId= */ 0L, wiredHeadsetRoute.getId());
        verify(mMockAudioManager, Mockito.timeout(ASYNC_CALL_TIMEOUTS_MS))
                .setPreferredDeviceForStrategy(
                        mMediaAudioProductStrategy,
Loading