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

Commit a1210d2a authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Support dynamic call audio route updates" into main

parents 61cdf8f5 85660ca1
Loading
Loading
Loading
Loading
+131 −27
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceCallback;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.IAudioService;
@@ -126,6 +127,62 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
    private final TelecomSystem.SyncRoot mTelecomLock;
    private CountDownLatch mAudioOperationsCompleteLatch;
    private CountDownLatch mAudioActiveCompleteLatch;

    /** Receiver for added/removed device outputs that are reported by the audio fwk */
    public class AudioRoutesCallback extends AudioDeviceCallback {
        @Override
        public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
            Log.startSession("ARC.oADA");
            try {
                updateAudioRoutes(addedDevices, true);
            } finally {
                Log.endSession();
            }
        }

        @Override
        public void onAudioDevicesRemoved(AudioDeviceInfo[] devices) {
            Log.startSession("ARC.oADR");
            try {
                updateAudioRoutes(devices, false);
            } finally {
                Log.endSession();
            }
        }

        private void updateAudioRoutes(AudioDeviceInfo[] devices, boolean addDevices) {
            Log.i(this, "updateAudioRoutes: add devices? %b", addDevices);
            for (AudioDeviceInfo deviceInfo: devices) {
                int audioRouteType = getAudioType(deviceInfo);
                Log.i(this, "updateAudioRoutes: audioDeviceInfo: %s, audioRouteType: %d",
                        deviceInfo, audioRouteType);
                // We should really only worry about handling earpiece and speaker. Bluetooth and
                // wired headset routes are already dynamically updated. This logic can be updated
                // once we support call audio route centralization.
                if (audioRouteType == TYPE_INVALID || audioRouteType == AudioRoute.TYPE_WIRED
                        || BT_AUDIO_ROUTE_TYPES.contains(audioRouteType)) {
                    Log.i(this, "updateAudioRoutes: skipping route.");
                    continue;
                }
                if (addDevices) {
                    switch(audioRouteType) {
                        case AudioRoute.TYPE_SPEAKER:
                            createSpeakerRoute();
                            break;
                        case AudioRoute.TYPE_EARPIECE:
                            createEarpieceRoute();
                            break;
                        default:
                            break;
                    }
                } else {
                    AudioRoute route = mTypeRoutes.remove(audioRouteType);
                    updateAvailableRoutes(route, false);
                }
            }
        }
    }

    private final BroadcastReceiver mSpeakerPhoneChangeReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
@@ -187,6 +244,7 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
    private boolean mIsPending;
    private boolean mIsActive;
    private boolean mWasOnSpeaker;
    private AudioRoutesCallback mAudioRoutesCallback;
    private final TelecomMetricsController mMetricsController;

    public CallAudioRouteController(
@@ -404,23 +462,7 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
        int supportMask = calculateSupportedRouteMaskInit();
        if ((supportMask & CallAudioState.ROUTE_SPEAKER) != 0) {
            int audioRouteType = AudioRoute.TYPE_SPEAKER;
            // Create speaker routes
            mSpeakerDockRoute = mAudioRouteFactory.create(AudioRoute.TYPE_SPEAKER, null,
                    mAudioManager);
            if (mSpeakerDockRoute == null){
                Log.i(this, "Can't find available audio device info for route TYPE_SPEAKER, trying"
                        + " for TYPE_BUS");
                mSpeakerDockRoute = mAudioRouteFactory.create(AudioRoute.TYPE_BUS, null,
                        mAudioManager);
                audioRouteType = AudioRoute.TYPE_BUS;
            }
            if (mSpeakerDockRoute != null) {
                mTypeRoutes.put(audioRouteType, mSpeakerDockRoute);
                updateAvailableRoutes(mSpeakerDockRoute, true);
            } else {
                Log.w(this, "Can't find available audio device info for route TYPE_SPEAKER "
                        + "or TYPE_BUS.");
            }
            createSpeakerRoute();
        }

        if ((supportMask & CallAudioState.ROUTE_WIRED_HEADSET) != 0) {
@@ -434,15 +476,7 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
                updateAvailableRoutes(mEarpieceWiredRoute, true);
            }
        } else if ((supportMask & CallAudioState.ROUTE_EARPIECE) != 0) {
            // Create earpiece routes
            mEarpieceWiredRoute = mAudioRouteFactory.create(AudioRoute.TYPE_EARPIECE, null,
                    mAudioManager);
            if (mEarpieceWiredRoute == null) {
                Log.w(this, "Can't find available audio device info for route TYPE_EARPIECE");
            } else {
                mTypeRoutes.put(AudioRoute.TYPE_EARPIECE, mEarpieceWiredRoute);
                updateAvailableRoutes(mEarpieceWiredRoute, true);
            }
            createEarpieceRoute();
        }

        // set current route
@@ -464,6 +498,8 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
                supportMask, null, new HashSet<>());
        mAudioManager.addOnCommunicationDeviceChangedListener(
                mCommunicationDeviceChangedExecutor, mCommunicationDeviceListener);
        mAudioRoutesCallback = new AudioRoutesCallback();
        mAudioManager.registerAudioDeviceCallback(mAudioRoutesCallback, null);
    }

    @Override
@@ -1427,8 +1463,15 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
        } else {
            AudioDeviceInfo[] deviceList = mAudioManager.getDevices(
                    AudioManager.GET_DEVICES_OUTPUTS);
            // For debugging purposes in cases where the device list returned by the API fwk is
            // empty and we don't end up adding the earpiece route upon init.
            Log.i(this, "calculateSupportedRouteMaskInit: is device list size: %d",
                    deviceList.length);
            for (AudioDeviceInfo device: deviceList) {
                if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE) {
                Log.i(this, "calculateSupportedRouteMaskInit: audio route type from audio "
                        + "device info: %d", device != null ? DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE
                        .getOrDefault(device.getType(), TYPE_INVALID) : TYPE_INVALID);
                if (device != null && device.getType() == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE) {
                    routeMask |= CallAudioState.ROUTE_EARPIECE;
                    break;
                }
@@ -1692,6 +1735,9 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
    }

    private void updateAvailableRoutes(AudioRoute route, boolean includeRoute) {
        if (route == null) {
            return;
        }
        if (includeRoute) {
            mAvailableRoutes.add(route);
        } else {
@@ -1781,4 +1827,62 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
        }
        return isDestRouteActive;
    }

    private void createSpeakerRoute() {
        int audioRouteType = TYPE_SPEAKER;
        if (mSpeakerDockRoute == null) {
            //create type speaker
            mSpeakerDockRoute = mAudioRouteFactory.create(audioRouteType, null,
                    mAudioManager);
            // If speaker route couldn't be instantiated, try for TYPE_BUS
            if (mSpeakerDockRoute == null) {
                Log.i(this, "createSpeakerRoute: Can't find available audio device info for "
                        + "route TYPE_SPEAKER, trying for TYPE_BUS");
                mSpeakerDockRoute = mAudioRouteFactory.create(AudioRoute.TYPE_BUS, null,
                        mAudioManager);
                audioRouteType = AudioRoute.TYPE_BUS;
            }
            if (mSpeakerDockRoute == null) {
                Log.w(this, "createSpeakerRoute: Can't find available audio device info "
                        + "for route TYPE_SPEAKER or TYPE_BUS.");
            } else {
                // Update available routes
                mTypeRoutes.put(audioRouteType, mSpeakerDockRoute);
                updateAvailableRoutes(mSpeakerDockRoute, true);
            }
        } else {
            Log.i(this, "createSpeakerRoute: route already created. Skipping.");
        }
    }

    private void createEarpieceRoute() {
        // Create earpiece route
        if (mEarpieceWiredRoute != null) {
            Log.i(this, "createEarpieceRoute: route already created. Skipping.");
            return;
        }
        mEarpieceWiredRoute = mAudioRouteFactory.create(AudioRoute.TYPE_EARPIECE, null,
                mAudioManager);
        if (mEarpieceWiredRoute == null) {
            Log.w(this, "createEarpieceRoute: Can't find available audio device info for "
                    + "route TYPE_EARPIECE");
        } else {
            mTypeRoutes.put(AudioRoute.TYPE_EARPIECE, mEarpieceWiredRoute);
            updateAvailableRoutes(mEarpieceWiredRoute, true);
        }
    }

    @VisibleForTesting
    public AudioRoute getAudioRouteForTesting(int audioRouteType) {
        return switch (audioRouteType) {
            case AudioRoute.TYPE_EARPIECE, AudioRoute.TYPE_WIRED -> mEarpieceWiredRoute;
            case AudioRoute.TYPE_SPEAKER -> mSpeakerDockRoute;
            default -> DUMMY_ROUTE;
        };
    }

    @VisibleForTesting
    public AudioRoutesCallback getAudioRoutesCallback() {
        return mAudioRoutesCallback;
    }
}
+41 −0
Original line number Diff line number Diff line
@@ -46,6 +46,8 @@ import static com.android.server.telecom.CallAudioRouteAdapter.USER_SWITCH_SPEAK
import static com.android.server.telecom.CallAudioRouteController.INCLUDE_BLUETOOTH_IN_BASELINE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -1535,6 +1537,45 @@ public class CallAudioRouteControllerTest extends TelecomTestCase {
        BLUETOOTH_DEVICES.remove(scoDevice);
    }

    @Test
    @SmallTest
    public void testAddAudioRoutesDynamic() {
        AudioRoute.Factory audioRouteFactory = new AudioRoute.Factory() {
            @Override
            public AudioRoute create(@AudioRoute.AudioRouteType int type, String bluetoothAddress,
                    AudioManager audioManager) {
                if (mOverrideSpeakerToBus && type == AudioRoute.TYPE_SPEAKER) {
                    type = AudioRoute.TYPE_BUS;
                }
                // Purposely return null to mimic audio routes not being created upon
                // initialization.
                return null;
            }
        };
        mController.setAudioRouteFactory(audioRouteFactory);
        mController.initialize();
        // Verify that the earpiece/speaker routes aren't created upon initialization of the
        // controller.
        assertNull(mController.getAudioRouteForTesting(AudioRoute.TYPE_SPEAKER));
        assertNull(mController.getAudioRouteForTesting(AudioRoute.TYPE_EARPIECE));

        // Set up the AudioDeviceCallback to signal to the controller of the newly added devices
        // (earpiece + speaker).
        CallAudioRouteController.AudioRoutesCallback callback = mController
                .getAudioRoutesCallback();
        AudioDeviceInfo earpieceDeviceInfo = mock(AudioDeviceInfo.class);
        when(earpieceDeviceInfo.getType()).thenReturn(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE);
        AudioDeviceInfo speakerDeviceInfo = mock(AudioDeviceInfo.class);
        when(speakerDeviceInfo.getType()).thenReturn(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);

        // Reset the audio route factory so that the route creation can be successful now.
        mController.setAudioRouteFactory(mAudioRouteFactory);
        callback.onAudioDevicesAdded(new AudioDeviceInfo[] {earpieceDeviceInfo, speakerDeviceInfo});
        // Verify that the earpiece/speaker routes are created this time around.
        assertNotNull(mController.getAudioRouteForTesting(AudioRoute.TYPE_SPEAKER));
        assertNotNull(mController.getAudioRouteForTesting(AudioRoute.TYPE_EARPIECE));
    }

    private void verifyConnectBluetoothDevice(int audioType) {
        mController.initialize();
        mController.setActive(true);