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

Commit b6f74029 authored by Sungsoo Lim's avatar Sungsoo Lim Committed by Gerrit Code Review
Browse files

Merge "Refactoring: Audio Routing Handler (Step 2)" into main

parents c7066ef9 df90cbdb
Loading
Loading
Loading
Loading
+71 −107
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.bluetooth.btservice;

import static android.bluetooth.IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -128,16 +130,8 @@ public class AudioRoutingManager extends ActiveDeviceManager {
            switch (profile) {
                case BluetoothProfile.A2DP:
                case BluetoothProfile.HEADSET:
                    mHandler.post(() -> mHandler.handleDeviceConnected(device, profile));
                    break;
                case BluetoothProfile.LE_AUDIO:
                    mHandler.post(
                            () -> {
                                AudioRoutingHandler.AudioRoutingDevice arDevice =
                                        mHandler.getAudioRoutingDevice(device);
                                arDevice.connectedProfiles.add(profile);
                                handleLeAudioConnected(device);
                            });
                    mHandler.post(() -> mHandler.handleDeviceConnected(device, profile));
                    break;
                case BluetoothProfile.HEARING_AID:
                    mHandler.post(
@@ -162,16 +156,8 @@ public class AudioRoutingManager extends ActiveDeviceManager {
            switch (profile) {
                case BluetoothProfile.A2DP:
                case BluetoothProfile.HEADSET:
                    mHandler.post(() -> mHandler.handleDeviceDisconnected(device, profile));
                    break;
                case BluetoothProfile.LE_AUDIO:
                    mHandler.post(
                            () -> {
                                AudioRoutingHandler.AudioRoutingDevice arDevice =
                                        mHandler.getAudioRoutingDevice(device);
                                arDevice.connectedProfiles.remove(profile);
                                handleLeAudioDisconnected(device);
                            });
                    mHandler.post(() -> mHandler.handleDeviceDisconnected(device, profile));
                    break;
                case BluetoothProfile.HEARING_AID:
                    mHandler.post(
@@ -281,45 +267,6 @@ public class AudioRoutingManager extends ActiveDeviceManager {
        }
    }

    private void handleLeAudioConnected(BluetoothDevice device) {
        synchronized (mLock) {
            if (DBG) {
                Log.d(TAG, "handleLeAudioConnected: " + device);
            }

            final LeAudioService leAudioService = mFactory.getLeAudioService();
            if (leAudioService == null || device == null) {
                return;
            }
            leAudioService.deviceConnected(device);

            if (mLeAudioConnectedDevices.contains(device)) {
                if (DBG) {
                    Log.d(TAG, "This device is already connected: " + device);
                }
                return;
            }

            mLeAudioConnectedDevices.add(device);
            if (mHearingAidActiveDevices.isEmpty()
                    && mLeHearingAidActiveDevice == null
                    && mPendingLeHearingAidActiveDevice.isEmpty()) {
                // New connected device: select it as active
                boolean leAudioMadeActive = setLeAudioActiveDevice(device);
                if (leAudioMadeActive && !Utils.isDualModeAudioEnabled()) {
                    setA2dpActiveDevice(null, true);
                    setHfpActiveDevice(null);
                }
            } else if (mPendingLeHearingAidActiveDevice.contains(device)) {
                if (setLeHearingAidActiveDevice(device)) {
                    setHearingAidActiveDevice(null, true);
                    setA2dpActiveDevice(null, true);
                    setHfpActiveDevice(null);
                }
            }
        }
    }

    private void handleHapConnected(BluetoothDevice device) {
        synchronized (mLock) {
            if (DBG) {
@@ -366,36 +313,6 @@ public class AudioRoutingManager extends ActiveDeviceManager {
        }
    }

    private void handleLeAudioDisconnected(BluetoothDevice device) {
        synchronized (mLock) {
            if (DBG) {
                Log.d(
                        TAG,
                        "handleLeAudioDisconnected: "
                                + device
                                + ", mLeAudioActiveDevice="
                                + mLeAudioActiveDevice);
            }

            final LeAudioService leAudioService = mFactory.getLeAudioService();
            if (leAudioService == null || device == null) {
                return;
            }

            mLeAudioConnectedDevices.remove(device);
            mLeHearingAidConnectedDevices.remove(device);

            boolean hasFallbackDevice = false;
            if (Objects.equals(mLeAudioActiveDevice, device)) {
                hasFallbackDevice = setFallbackDeviceActiveLocked();
                if (!hasFallbackDevice) {
                    setLeAudioActiveDevice(null, false);
                }
            }
            leAudioService.deviceDisconnected(device, hasFallbackDevice);
        }
    }

    private void handleHapDisconnected(BluetoothDevice device) {
        synchronized (mLock) {
            if (DBG) {
@@ -1176,6 +1093,7 @@ public class AudioRoutingManager extends ActiveDeviceManager {
                switch (profile) {
                    case BluetoothProfile.HEADSET -> mHfpConnectedDevices.add(device);
                    case BluetoothProfile.A2DP -> mA2dpConnectedDevices.add(device);
                    case BluetoothProfile.LE_AUDIO -> mLeAudioConnectedDevices.add(device);
                }
            }
            if (isWatch(device)) {
@@ -1213,12 +1131,15 @@ public class AudioRoutingManager extends ActiveDeviceManager {
                switch (profile) {
                    case BluetoothProfile.HEADSET -> mHfpConnectedDevices.remove(device);
                    case BluetoothProfile.A2DP -> mA2dpConnectedDevices.remove(device);
                    case BluetoothProfile.LE_AUDIO -> mLeAudioConnectedDevices.remove(device);
                }
            }
            List<BluetoothDevice> activeDevices = mActiveDevices.get(profile);
            if (activeDevices != null && Objects.equals(device, activeDevices.get(0))) {
            if (activeDevices != null && activeDevices.contains(device)) {
                // TODO: move setFallbackDeviceActiveLocked into AudioRoutingHandler
                //  and update mConnectedDevices
                activeDevices.remove(device);
                if (activeDevices.size() == 0) {
                    synchronized (mLock) {
                        if (!setFallbackDeviceActiveLocked()) {
                            arDevice.deactivate(profile, false);
@@ -1226,6 +1147,7 @@ public class AudioRoutingManager extends ActiveDeviceManager {
                    }
                }
            }
        }

        // TODO: make getAudioRoutingDevice private
        public AudioRoutingDevice getAudioRoutingDevice(BluetoothDevice device) {
@@ -1281,11 +1203,11 @@ public class AudioRoutingManager extends ActiveDeviceManager {
                // TODO: Return false if there are another active remote streaming an audio.
                // TODO: consider LE audio and HearingAid, HapClient.
                return switch (profile) {
                    case BluetoothProfile.HEADSET ->
                        !supportedProfiles.contains(BluetoothProfile.A2DP)
                    case BluetoothProfile.HEADSET -> !supportedProfiles.contains(
                                    BluetoothProfile.A2DP)
                            || connectedProfiles.contains(BluetoothProfile.A2DP);
                    case BluetoothProfile.A2DP ->
                        !supportedProfiles.contains(BluetoothProfile.HEADSET)
                    case BluetoothProfile.A2DP -> !supportedProfiles.contains(
                                    BluetoothProfile.HEADSET)
                            || connectedProfiles.contains(BluetoothProfile.HEADSET);
                    default -> true;
                };
@@ -1302,7 +1224,7 @@ public class AudioRoutingManager extends ActiveDeviceManager {
            @SuppressLint("MissingPermission")
            public boolean activate(int profile) {
                List<BluetoothDevice> activeDevices = mActiveDevices.get(profile);
                if (activeDevices != null && Objects.equals(device, activeDevices.get(0))) {
                if (activeDevices != null && activeDevices.contains(device)) {
                    return true;
                }
                HashSet<Integer> profilesToActivate = new HashSet<>();
@@ -1319,15 +1241,13 @@ public class AudioRoutingManager extends ActiveDeviceManager {
                        profilesToDeactivate.remove(BluetoothProfile.HEADSET);
                        if (connectedProfiles.contains(BluetoothProfile.HEADSET)) {
                            activeDevices = mActiveDevices.get(BluetoothProfile.HEADSET);
                            if (activeDevices == null
                                    || !Objects.equals(device, activeDevices.get(0))) {
                            if (activeDevices == null || !activeDevices.contains(device)) {
                                profilesToActivate.add(BluetoothProfile.HEADSET);
                            }
                        }
                        if (Utils.isDualModeAudioEnabled()) {
                            activeDevices = mActiveDevices.get(BluetoothProfile.LE_AUDIO);
                            if (activeDevices != null
                                    && Objects.equals(device, activeDevices.get(0))) {
                            if (activeDevices != null && activeDevices.contains(device)) {
                                profilesToDeactivate.remove(BluetoothProfile.LE_AUDIO);
                            }
                        }
@@ -1336,19 +1256,28 @@ public class AudioRoutingManager extends ActiveDeviceManager {
                        profilesToDeactivate.remove(BluetoothProfile.A2DP);
                        if (connectedProfiles.contains(BluetoothProfile.A2DP)) {
                            activeDevices = mActiveDevices.get(BluetoothProfile.A2DP);
                            if (activeDevices == null
                                    || !Objects.equals(device, activeDevices.get(0))) {
                            if (activeDevices == null || !activeDevices.contains(device)) {
                                profilesToActivate.add(BluetoothProfile.A2DP);
                            }
                        }
                        if (Utils.isDualModeAudioEnabled()) {
                            activeDevices = mActiveDevices.get(BluetoothProfile.LE_AUDIO);
                            if (activeDevices != null
                                    && Objects.equals(device, activeDevices.get(0))) {
                            if (activeDevices != null && activeDevices.contains(device)) {
                                profilesToDeactivate.remove(BluetoothProfile.LE_AUDIO);
                            }
                        }
                        break;
                    case BluetoothProfile.LE_AUDIO:
                        if (Utils.isDualModeAudioEnabled()) {
                            activeDevices = mActiveDevices.get(BluetoothProfile.HEADSET);
                            if (activeDevices != null && activeDevices.contains(device)) {
                                profilesToDeactivate.remove(BluetoothProfile.HEADSET);
                            }
                            activeDevices = mActiveDevices.get(BluetoothProfile.A2DP);
                            if (activeDevices != null && activeDevices.contains(device)) {
                                profilesToDeactivate.remove(BluetoothProfile.A2DP);
                            }
                        }
                }
                boolean isAnyProfileActivated = false;
                for (Integer p : profilesToActivate) {
@@ -1365,7 +1294,18 @@ public class AudioRoutingManager extends ActiveDeviceManager {
                                device);
                        default -> false;
                    };
                    if (activated) mActiveDevices.put(p, Arrays.asList(device));
                    if (activated) {
                        // TODO: handle this inside of setXxxActiveDevice() method
                        activeDevices = mActiveDevices.get(p);
                        if (activeDevices == null) {
                            activeDevices = new ArrayList<>();
                            mActiveDevices.put(p, activeDevices);
                        }
                        if (!canActivateTogether(p, device, activeDevices)) {
                            activeDevices.clear();
                        }
                        activeDevices.add(device);
                    }
                    isAnyProfileActivated |= activated;
                }
                // Do not deactivate profiles if no profiles were activated.
@@ -1389,9 +1329,33 @@ public class AudioRoutingManager extends ActiveDeviceManager {
                switch (profile) {
                    case BluetoothProfile.A2DP -> setA2dpActiveDevice(null, hasFallbackDevice);
                    case BluetoothProfile.HEADSET -> setHfpActiveDevice(null);
                    case BluetoothProfile.LE_AUDIO -> setLeAudioActiveDevice(null, false);
                    case BluetoothProfile.HEARING_AID -> setHearingAidActiveDevice(null, false);
                    case BluetoothProfile.HAP_CLIENT -> setLeHearingAidActiveDevice(null);
                }
                mActiveDevices.remove(profile);
            }

            private boolean canActivateTogether(
                    int profile, BluetoothDevice device, List<BluetoothDevice> group) {
                if (group == null || group.isEmpty()) {
                    return false;
                }
                switch (profile) {
                    // TODO: handle HAP_CLIENT and HEARING_AID
                    case BluetoothProfile.LE_AUDIO:
                        final LeAudioService leAudioService = mFactory.getLeAudioService();
                        if (leAudioService == null || device == null) {
                            return false;
                        }
                        int groupId = leAudioService.getGroupId(device);
                        if (groupId != LE_AUDIO_GROUP_ID_INVALID
                                && groupId == leAudioService.getGroupId(group.get(0))) {
                            return true;
                        }
                }
                return false;
            }
        }
    }
}
+10 −3
Original line number Diff line number Diff line
@@ -74,6 +74,8 @@ import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.btservice.ServiceFactory;
import com.android.bluetooth.btservice.storage.DatabaseManager;
import com.android.bluetooth.csip.CsipSetCoordinatorService;
import com.android.bluetooth.flags.FeatureFlags;
import com.android.bluetooth.flags.FeatureFlagsImpl;
import com.android.bluetooth.hap.HapClientService;
import com.android.bluetooth.hfp.HeadsetService;
import com.android.bluetooth.mcp.McpService;
@@ -130,6 +132,7 @@ public class LeAudioService extends ProfileService {
    private BluetoothDevice mExposedActiveDevice;
    private LeAudioCodecConfig mLeAudioCodecConfig;
    private final Object mGroupLock = new Object();
    private final FeatureFlags mFeatureFlags = new FeatureFlagsImpl();
    ServiceFactory mServiceFactory = new ServiceFactory();

    LeAudioNativeInterface mLeAudioNativeInterface;
@@ -148,7 +151,6 @@ public class LeAudioService extends ProfileService {
    private final LinkedList<BluetoothLeBroadcastSettings> mCreateBroadcastQueue =
            new LinkedList<>();


    @VisibleForTesting
    TbsService mTbsService;

@@ -2410,8 +2412,13 @@ public class LeAudioService extends ProfileService {
            Log.d(TAG, "Creating a new state machine for " + device);
        }

        sm = LeAudioStateMachine.make(device, this,
                mLeAudioNativeInterface, mStateMachinesThread.getLooper());
        sm =
                LeAudioStateMachine.make(
                        device,
                        this,
                        mLeAudioNativeInterface,
                        mStateMachinesThread.getLooper(),
                        mFeatureFlags);
        descriptor.mStateMachine = sm;
        return sm;
    }
+24 −6
Original line number Diff line number Diff line
@@ -53,6 +53,7 @@ import android.os.Message;
import android.util.Log;

import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.flags.FeatureFlags;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
@@ -87,13 +88,19 @@ final class LeAudioStateMachine extends StateMachine {
    private LeAudioNativeInterface mNativeInterface;

    private final BluetoothDevice mDevice;

    LeAudioStateMachine(BluetoothDevice device, LeAudioService svc,
            LeAudioNativeInterface nativeInterface, Looper looper) {
    private final FeatureFlags mFeatureFlags;

    LeAudioStateMachine(
            BluetoothDevice device,
            LeAudioService svc,
            LeAudioNativeInterface nativeInterface,
            Looper looper,
            FeatureFlags featureFlags) {
        super(TAG, looper);
        mDevice = device;
        mService = svc;
        mNativeInterface = nativeInterface;
        mFeatureFlags = featureFlags;

        mDisconnected = new Disconnected();
        mConnecting = new Connecting();
@@ -108,10 +115,15 @@ final class LeAudioStateMachine extends StateMachine {
        setInitialState(mDisconnected);
    }

    static LeAudioStateMachine make(BluetoothDevice device, LeAudioService svc,
            LeAudioNativeInterface nativeInterface, Looper looper) {
    static LeAudioStateMachine make(
            BluetoothDevice device,
            LeAudioService svc,
            LeAudioNativeInterface nativeInterface,
            Looper looper,
            FeatureFlags featureFlags) {
        Log.i(TAG, "make for device");
        LeAudioStateMachine LeAudioSm = new LeAudioStateMachine(device, svc, nativeInterface, looper);
        LeAudioStateMachine LeAudioSm =
                new LeAudioStateMachine(device, svc, nativeInterface, looper, featureFlags);
        LeAudioSm.start();
        return LeAudioSm;
    }
@@ -139,6 +151,9 @@ final class LeAudioStateMachine extends StateMachine {
                // Don't broadcast during startup
                broadcastConnectionState(BluetoothProfile.STATE_DISCONNECTED,
                        mLastConnectionState);
                if (mFeatureFlags.audioRoutingCentralization()) {
                    mService.deviceDisconnected(mDevice, false);
                }
            }
        }

@@ -426,6 +441,9 @@ final class LeAudioStateMachine extends StateMachine {
                    + messageWhatToString(getCurrentMessage().what));
            mConnectionState = BluetoothProfile.STATE_CONNECTED;
            removeDeferredMessages(CONNECT);
            if (mFeatureFlags.audioRoutingCentralization()) {
                mService.deviceConnected(mDevice);
            }
            broadcastConnectionState(BluetoothProfile.STATE_CONNECTED, mLastConnectionState);
        }

+6 −6
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.bluetooth.btservice;

import static android.bluetooth.IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -150,6 +152,7 @@ public class AudioRoutingManagerTest {
        when(mHearingAidService.setActiveDevice(any())).thenReturn(true);
        when(mLeAudioService.setActiveDevice(any())).thenReturn(true);
        when(mLeAudioService.removeActiveDevice(anyBoolean())).thenReturn(true);
        when(mLeAudioService.getGroupId(any())).thenReturn(LE_AUDIO_GROUP_ID_INVALID);

        List<BluetoothDevice> connectedHearingAidDevices = new ArrayList<>();
        connectedHearingAidDevices.add(mHearingAidDevice);
@@ -782,7 +785,6 @@ public class AudioRoutingManagerTest {

        verify(mLeAudioService, never()).removeActiveDevice(false);
        verify(mLeAudioService, never()).setActiveDevice(mLeAudioDevice2);
        verify(mLeAudioService, timeout(TIMEOUT_MS)).deviceDisconnected(mLeAudioDevice, false);
    }

    /**
@@ -809,7 +811,6 @@ public class AudioRoutingManagerTest {
        leAudioDisconnected(mLeAudioDevice2);

        verify(mLeAudioService, timeout(TIMEOUT_MS)).removeActiveDevice(false);
        verify(mLeAudioService, timeout(TIMEOUT_MS)).deviceDisconnected(mLeAudioDevice2, false);
    }

    /**
@@ -832,7 +833,6 @@ public class AudioRoutingManagerTest {
        leAudioDisconnected(mLeAudioDevice2);

        verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice);
        verify(mLeAudioService, timeout(TIMEOUT_MS)).deviceDisconnected(mLeAudioDevice2, true);
    }

    /**
@@ -890,16 +890,16 @@ public class AudioRoutingManagerTest {
        verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeHearingAidDevice);
    }

    /** LE audio is connected after LE Hearing Aid device. Keep LE hearing Aid active. */
    /** LE audio is connected after LE Hearing Aid device. LE audio active. */
    @Test
    public void leAudioConnectedAfterLeHearingAid_setLeAudioActiveShouldNotBeCalled() {
    public void leAudioConnectedAfterLeHearingAid_callsSetLeAudioActive() {
        leHearingAidConnected(mLeHearingAidDevice);
        leAudioConnected(mLeHearingAidDevice);
        verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeHearingAidDevice);

        leAudioConnected(mLeAudioDevice);
        TestUtils.waitForLooperToFinishScheduledTask(mAudioRoutingManager.getHandlerLooper());
        verify(mLeAudioService, never()).setActiveDevice(mLeAudioDevice);
        verify(mLeAudioService).setActiveDevice(mLeAudioDevice);
    }

    /**
+13 −4
Original line number Diff line number Diff line
@@ -42,6 +42,8 @@ import androidx.test.runner.AndroidJUnit4;

import com.android.bluetooth.TestUtils;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.flags.FakeFeatureFlagsImpl;
import com.android.bluetooth.flags.Flags;

import org.junit.After;
import org.junit.Before;
@@ -58,6 +60,7 @@ public class LeAudioStateMachineTest {
    private HandlerThread mHandlerThread;
    private LeAudioStateMachine mLeAudioStateMachine;
    private BluetoothDevice mTestDevice;
    private FakeFeatureFlagsImpl mFakeFlagsImpl;
    private static final int TIMEOUT_MS = 1000;

    @Mock private AdapterService mAdapterService;
@@ -72,6 +75,8 @@ public class LeAudioStateMachineTest {
        TestUtils.setAdapterService(mAdapterService);

        mAdapter = BluetoothAdapter.getDefaultAdapter();
        mFakeFlagsImpl = new FakeFeatureFlagsImpl();
        mFakeFlagsImpl.setFlag(Flags.FLAG_AUDIO_ROUTING_CENTRALIZATION, false);

        // Get a device for testing
        mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05");
@@ -79,11 +84,15 @@ public class LeAudioStateMachineTest {
        // Set up thread and looper
        mHandlerThread = new HandlerThread("LeAudioStateMachineTestHandlerThread");
        mHandlerThread.start();
        mLeAudioStateMachine = new LeAudioStateMachine(mTestDevice, mLeAudioService,
                mLeAudioNativeInterface, mHandlerThread.getLooper());
        // Override the timeout value to speed up the test
        mLeAudioStateMachine.sConnectTimeoutMs = 1000;     // 1s
        mLeAudioStateMachine.start();
        LeAudioStateMachine.sConnectTimeoutMs = 1000; // 1s
        mLeAudioStateMachine =
                LeAudioStateMachine.make(
                        mTestDevice,
                        mLeAudioService,
                        mLeAudioNativeInterface,
                        mHandlerThread.getLooper(),
                        mFakeFlagsImpl);
    }

    @After