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

Commit 57d0b689 authored by Sungsoo Lim's avatar Sungsoo Lim
Browse files

Move fallback device logic into ActiveDeviceManager

This CL simply moves the logic into ActiveDeviceManager.
Actual refactoring code will be handled in the next CL.

Bug: 293555920
Test: atest BluetoothInstrumentationTests
Change-Id: I7dd5bcdbababe2e6cf309bab9a4a4babf5ded516
parent 3cae7eb7
Loading
Loading
Loading
Loading
+0 −10
Original line number Diff line number Diff line
@@ -1264,16 +1264,6 @@ public class A2dpService extends ProfileService {
                .a2dpConnectionStateChanged(device, fromState, toState);
    }

    /**
     * Retrieves the most recently connected device in the A2DP connected devices list.
     */
    public BluetoothDevice getFallbackDevice() {
        DatabaseManager dbManager = mAdapterService.getDatabase();
        return dbManager != null ? dbManager
            .getMostRecentlyConnectedDevicesInList(getConnectedDevices())
            : null;
    }

    /**
     * Binder object: must be a static class or memory leak may occur.
     */
+46 −2
Original line number Diff line number Diff line
@@ -1052,13 +1052,13 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac
        A2dpService a2dpService = mFactory.getA2dpService();
        BluetoothDevice a2dpFallbackDevice = null;
        if (a2dpService != null) {
            a2dpFallbackDevice = a2dpService.getFallbackDevice();
            a2dpFallbackDevice = getA2dpFallbackDevice();
        }

        HeadsetService headsetService = mFactory.getHeadsetService();
        BluetoothDevice headsetFallbackDevice = null;
        if (headsetService != null) {
            headsetFallbackDevice = headsetService.getFallbackDevice();
            headsetFallbackDevice = getHfpFallbackDevice();
        }

        List<BluetoothDevice> connectedDevices = new ArrayList<>();
@@ -1168,6 +1168,50 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac
        }
    }

    /** Retrieves the most recently connected device in the A2DP connected devices list. */
    public BluetoothDevice getA2dpFallbackDevice() {
        DatabaseManager dbManager = mAdapterService.getDatabase();
        synchronized (mLock) {
            return dbManager != null
                    ? dbManager.getMostRecentlyConnectedDevicesInList(mA2dpConnectedDevices)
                    : null;
        }
    }

    /** Retrieves the most recently connected device in the A2DP connected devices list. */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public BluetoothDevice getHfpFallbackDevice() {
        DatabaseManager dbManager = mAdapterService.getDatabase();
        return dbManager != null
                ? dbManager.getMostRecentlyConnectedDevicesInList(getHfpFallbackCandidates())
                : null;
    }

    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    List<BluetoothDevice> getHfpFallbackCandidates() {
        List<BluetoothDevice> fallbackCandidates;
        synchronized (mLock) {
            fallbackCandidates = new ArrayList<>(mHfpConnectedDevices);
        }
        List<BluetoothDevice> uninterestedCandidates = new ArrayList<>();
        for (BluetoothDevice device : fallbackCandidates) {
            byte[] deviceType =
                    mDbManager.getCustomMeta(device, BluetoothDevice.METADATA_DEVICE_TYPE);
            BluetoothClass deviceClass = device.getBluetoothClass();
            if ((deviceClass != null
                            && deviceClass.getMajorDeviceClass()
                                    == BluetoothClass.Device.WEARABLE_WRIST_WATCH)
                    || (deviceType != null
                            && BluetoothDevice.DEVICE_TYPE_WATCH.equals(new String(deviceType)))) {
                uninterestedCandidates.add(device);
            }
        }
        for (BluetoothDevice device : uninterestedCandidates) {
            fallbackCandidates.remove(device);
        }
        return fallbackCandidates;
    }

    @VisibleForTesting
    BluetoothDevice getA2dpActiveDevice() {
        return mA2dpActiveDevice;
+5 −37
Original line number Diff line number Diff line
@@ -24,7 +24,6 @@ import static com.android.modules.utils.build.SdkLevel.isAtLeastU;

import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
@@ -54,6 +53,7 @@ import com.android.bluetooth.BluetoothMetricsProto;
import com.android.bluetooth.BluetoothStatsLog;
import com.android.bluetooth.Utils;
import com.android.bluetooth.a2dp.A2dpService;
import com.android.bluetooth.btservice.ActiveDeviceManager;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.MetricsLogger;
import com.android.bluetooth.btservice.ProfileService;
@@ -125,6 +125,7 @@ public class HeadsetService extends ProfileService {
    private int mMaxHeadsetConnections = 1;
    private BluetoothDevice mActiveDevice;
    private AdapterService mAdapterService;
    private ActiveDeviceManager mActiveDeviceManager;
    private DatabaseManager mDatabaseManager;
    private HandlerThread mStateMachinesThread;
    private Handler mStateMachinesThreadHandler;
@@ -183,6 +184,7 @@ public class HeadsetService extends ProfileService {
                "AdapterService cannot be null when HeadsetService starts");
        mDatabaseManager = Objects.requireNonNull(mAdapterService.getDatabase(),
                "DatabaseManager cannot be null when HeadsetService starts");
        mActiveDeviceManager = Objects.requireNonNull(mAdapterService.getActiveDeviceManager());
        // Step 2: Start handler thread for state machines
        mStateMachinesThread = new HandlerThread("HeadsetService.StateMachines");
        mStateMachinesThread.start();
@@ -1351,7 +1353,7 @@ public class HeadsetService extends ProfileService {
            // we need to check if another device is connected and set it active instead.
            // Calling this before any other active related calls has the same effect as
            // a classic active device switch.
            BluetoothDevice fallbackDevice = getFallbackDevice();
            BluetoothDevice fallbackDevice = mActiveDeviceManager.getHfpFallbackDevice();
            if (fallbackDevice != null && mActiveDevice != null
                    && getConnectionState(mActiveDevice) != BluetoothProfile.STATE_CONNECTED) {
                setActiveDevice(fallbackDevice);
@@ -2005,9 +2007,7 @@ public class HeadsetService extends ProfileService {
                setActiveDevice(null);
            }
        }
        mAdapterService
                .getActiveDeviceManager()
                .hfpConnectionStateChanged(device, fromState, toState);
        mActiveDeviceManager.hfpConnectionStateChanged(device, fromState, toState);
    }

    /**
@@ -2255,38 +2255,6 @@ public class HeadsetService extends ProfileService {
                == mStateMachinesThread.getId());
    }

    /**
     * Retrieves the most recently connected device in the A2DP connected devices list.
     */
    public BluetoothDevice getFallbackDevice() {
        DatabaseManager dbManager = mAdapterService.getDatabase();
        return dbManager != null ? dbManager
            .getMostRecentlyConnectedDevicesInList(getFallbackCandidates(dbManager))
            : null;
    }

    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    List<BluetoothDevice> getFallbackCandidates(DatabaseManager dbManager) {
        List<BluetoothDevice> fallbackCandidates = getConnectedDevices();
        List<BluetoothDevice> uninterestedCandidates = new ArrayList<>();
        for (BluetoothDevice device : fallbackCandidates) {
            byte[] deviceType = dbManager.getCustomMeta(device,
                    BluetoothDevice.METADATA_DEVICE_TYPE);
            BluetoothClass deviceClass = device.getBluetoothClass();
            if ((deviceClass != null
                    && deviceClass.getMajorDeviceClass()
                    == BluetoothClass.Device.WEARABLE_WRIST_WATCH)
                    || (deviceType != null
                    && BluetoothDevice.DEVICE_TYPE_WATCH.equals(new String(deviceType)))) {
                uninterestedCandidates.add(device);
            }
        }
        for (BluetoothDevice device : uninterestedCandidates) {
            fallbackCandidates.remove(device);
        }
        return fallbackCandidates;
    }

    @Override
    public void dump(StringBuilder sb) {
        boolean isScoOn = mSystemInterface.getAudioManager().isBluetoothScoOn();
+66 −16
Original line number Diff line number Diff line
@@ -63,8 +63,9 @@ import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.Map;

@MediumTest
@RunWith(AndroidJUnit4.class)
@@ -148,21 +149,6 @@ public class ActiveDeviceManagerTest {
        when(mHearingAidService.getHiSyncId(mHearingAidDevice)).thenReturn(mHearingAidHiSyncId);
        when(mHearingAidService.getConnectedPeerDevices(mHearingAidHiSyncId))
                .thenReturn(connectedHearingAidDevices);

        when(mA2dpService.getFallbackDevice()).thenAnswer(invocation -> {
            if (!mDeviceConnectionStack.isEmpty() && Objects.equals(mA2dpDevice,
                    mDeviceConnectionStack.get(mDeviceConnectionStack.size() - 1))) {
                return mA2dpDevice;
            }
            return null;
        });
        when(mHeadsetService.getFallbackDevice()).thenAnswer(invocation -> {
            if (!mDeviceConnectionStack.isEmpty() && Objects.equals(mHeadsetDevice,
                    mDeviceConnectionStack.get(mDeviceConnectionStack.size() - 1))) {
                return mHeadsetDevice;
            }
            return null;
        });
    }

    @After
@@ -1131,6 +1117,49 @@ public class ActiveDeviceManagerTest {
        verify(mHearingAidService, timeout(TIMEOUT_MS)).removeActiveDevice(false);
    }

    @Test
    public void testGetFallbackCandidates() {
        BluetoothDevice deviceA = TestUtils.getTestDevice(mAdapter, 0);
        BluetoothDevice deviceB = TestUtils.getTestDevice(mAdapter, 1);

        // No connected device
        Assert.assertTrue(mActiveDeviceManager.getHfpFallbackCandidates().isEmpty());

        // One connected device
        headsetConnected(deviceA, true);
        TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
        Assert.assertTrue(mActiveDeviceManager.getHfpFallbackCandidates().contains(deviceA));

        // Two connected devices
        headsetConnected(deviceB, false);
        TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
        Assert.assertTrue(mActiveDeviceManager.getHfpFallbackCandidates().contains(deviceA));
        Assert.assertTrue(mActiveDeviceManager.getHfpFallbackCandidates().contains(deviceB));
    }

    @Test
    public void testGetFhpFallbackCandidates_HasWatchDevice() {
        BluetoothDevice deviceWatch = TestUtils.getTestDevice(mAdapter, 0);
        BluetoothDevice deviceRegular = TestUtils.getTestDevice(mAdapter, 1);

        // Make deviceWatch a watch
        mDatabaseManager.setCustomMeta(
                deviceWatch,
                BluetoothDevice.METADATA_DEVICE_TYPE,
                BluetoothDevice.DEVICE_TYPE_WATCH.getBytes());

        // Has a connected watch device
        headsetConnected(deviceWatch, false);
        TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
        Assert.assertTrue(mActiveDeviceManager.getHfpFallbackCandidates().isEmpty());

        // Two connected devices with one watch
        headsetConnected(deviceRegular, true);
        TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
        Assert.assertFalse(mActiveDeviceManager.getHfpFallbackCandidates().contains(deviceWatch));
        Assert.assertTrue(mActiveDeviceManager.getHfpFallbackCandidates().contains(deviceRegular));
    }

    /**
     * Helper to indicate A2dp connected for a device.
     */
@@ -1297,6 +1326,7 @@ public class ActiveDeviceManagerTest {

    private class TestDatabaseManager extends DatabaseManager {
        ArrayMap<BluetoothDevice, SparseIntArray> mProfileConnectionPolicy;
        final Map<String, Map<Integer, byte[]>> mMetadataCache = new HashMap<>();

        TestDatabaseManager(AdapterService service) {
            super(service);
@@ -1345,5 +1375,25 @@ public class ActiveDeviceManagerTest {
            }
            return policy.get(profile, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
        }

        @Override
        public boolean setCustomMeta(BluetoothDevice device, int key, byte[] newValue) {
            Map<Integer, byte[]> metadata = mMetadataCache.get(device.getAddress());
            if (metadata == null) {
                metadata = new ArrayMap<>();
                mMetadataCache.put(device.getAddress(), metadata);
            }
            metadata.put(key, newValue);
            return true;
        }

        @Override
        public byte[] getCustomMeta(BluetoothDevice device, int key) {
            Map<Integer, byte[]> metadata = mMetadataCache.get(device.getAddress());
            if (metadata == null) {
                return null;
            }
            return metadata.get(key);
        }
    }
}
+2 −0
Original line number Diff line number Diff line
@@ -64,6 +64,7 @@ public class ProfileServiceTest {
    @Rule public final ServiceTestRule mServiceTestRule = new ServiceTestRule();
    @Mock private AdapterService mMockAdapterService;
    @Mock private DatabaseManager mDatabaseManager;
    @Mock private ActiveDeviceManager mActiveDeviceManager;
    @Mock private LocationManager mLocationManager;

    private Class[] mProfiles;
@@ -134,6 +135,7 @@ public class ProfileServiceTest {
            }
        });
        doReturn(mDatabaseManager).when(mMockAdapterService).getDatabase();
        doReturn(mActiveDeviceManager).when(mMockAdapterService).getActiveDeviceManager();

        when(mMockAdapterService.getSystemService(Context.LOCATION_SERVICE))
                .thenReturn(mLocationManager);
Loading