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

Commit 14315e9d authored by Santiago Seifert's avatar Santiago Seifert
Browse files

Run audio and bluetooth calls on handler

This prevents calling these components on a binder thread,
which can cause deadlocks because they may need system_server
binder threads.

Bug: b/329929065
Test: atest CtsMediaHostTestCases CtsMediaBetterTogetherTestCases AudioManagerRouteControllerTest
Test: Also manual using wired and bluetooth outputs.
Change-Id: I2d247ea840ee071ed61d4dea41d520f21384c284
parent 60b866ea
Loading
Loading
Loading
Loading
+40 −17
Original line number Diff line number Diff line
@@ -204,23 +204,24 @@ import java.util.Objects;
            Slog.w(TAG, "transferTo: Ignoring transfer request to unknown route id : " + routeId);
            return;
        }
        // TODO: b/329929065 - Push audio manager and bluetooth operations to the handler, so that
        // they don't run on a binder thread, so as to prevent possible deadlocks (these operations
        // may need system_server binder threads to complete).
        if (mediaRoute2InfoHolder.mCorrespondsToInactiveBluetoothRoute) {
            // By default, the last connected device is the active route so we don't need to apply a
            // routing audio policy.
            mBluetoothRouteController.activateBluetoothDeviceWithAddress(
                    mediaRoute2InfoHolder.mMediaRoute2Info.getAddress());
            mAudioManager.removePreferredDeviceForStrategy(mStrategyForMedia);
        } else {
            AudioDeviceAttributes attr =
                    new AudioDeviceAttributes(
                            AudioDeviceAttributes.ROLE_OUTPUT,
                            mediaRoute2InfoHolder.mAudioDeviceInfoType,
                            /* address= */ ""); // This is not a BT device, hence no address needed.
            mAudioManager.setPreferredDeviceForStrategy(mStrategyForMedia, attr);
        Runnable transferAction = getTransferActionForRoute(mediaRoute2InfoHolder);
        Runnable guardedTransferAction =
                () -> {
                    try {
                        transferAction.run();
                    } catch (Throwable throwable) {
                        // We swallow the exception to avoid crashing system_server, since this
                        // doesn't run on a binder thread.
                        Slog.e(
                                TAG,
                                "Unexpected exception while transferring to route id: " + routeId,
                                throwable);
                        mHandler.post(this::rebuildAvailableRoutesAndNotify);
                    }
                };
        // We post the transfer operation to the handler to avoid making these calls on a binder
        // thread. See class javadoc for details.
        mHandler.post(guardedTransferAction);
    }

    @RequiresPermission(
@@ -236,6 +237,28 @@ import java.util.Objects;
        return true;
    }

    private Runnable getTransferActionForRoute(MediaRoute2InfoHolder mediaRoute2InfoHolder) {
        if (mediaRoute2InfoHolder.mCorrespondsToInactiveBluetoothRoute) {
            String deviceAddress = mediaRoute2InfoHolder.mMediaRoute2Info.getAddress();
            return () -> {
                // By default, the last connected device is the active route so we don't
                // need to apply a routing audio policy.
                mBluetoothRouteController.activateBluetoothDeviceWithAddress(deviceAddress);
                mAudioManager.removePreferredDeviceForStrategy(mStrategyForMedia);
            };

        } else {
            AudioDeviceAttributes deviceAttributes =
                    new AudioDeviceAttributes(
                            AudioDeviceAttributes.ROLE_OUTPUT,
                            mediaRoute2InfoHolder.mAudioDeviceInfoType,
                            /* address= */ ""); // This is not a BT device, hence no address needed.
            return () ->
                    mAudioManager.setPreferredDeviceForStrategy(
                            mStrategyForMedia, deviceAttributes);
        }
    }

    @RequiresPermission(
            anyOf = {
                Manifest.permission.MODIFY_AUDIO_ROUTING,
+9 −2
Original line number Diff line number Diff line
@@ -69,6 +69,13 @@ import java.util.Set;
public class AudioManagerRouteControllerTest {

    private static final String FAKE_ROUTE_NAME = "fake name";

    /**
     * The number of milliseconds to wait for an asynchronous operation before failing an associated
     * assertion.
     */
    private static final int ASYNC_CALL_TIMEOUTS_MS = 1000;

    private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_INFO_BUILTIN_SPEAKER =
            createAudioDeviceInfo(
                    AudioSystem.DEVICE_OUT_SPEAKER, "name_builtin", /* address= */ null);
@@ -231,7 +238,7 @@ public class AudioManagerRouteControllerTest {
        MediaRoute2Info builtInSpeakerRoute =
                getAvailableRouteWithType(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
        mControllerUnderTest.transferTo(builtInSpeakerRoute.getId());
        verify(mMockAudioManager)
        verify(mMockAudioManager, Mockito.timeout(ASYNC_CALL_TIMEOUTS_MS))
                .setPreferredDeviceForStrategy(
                        mMediaAudioProductStrategy,
                        createAudioDeviceAttribute(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER));
@@ -239,7 +246,7 @@ public class AudioManagerRouteControllerTest {
        MediaRoute2Info wiredHeadsetRoute =
                getAvailableRouteWithType(MediaRoute2Info.TYPE_WIRED_HEADSET);
        mControllerUnderTest.transferTo(wiredHeadsetRoute.getId());
        verify(mMockAudioManager)
        verify(mMockAudioManager, Mockito.timeout(ASYNC_CALL_TIMEOUTS_MS))
                .setPreferredDeviceForStrategy(
                        mMediaAudioProductStrategy,
                        createAudioDeviceAttribute(AudioDeviceInfo.TYPE_WIRED_HEADSET));