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

Commit b3d68f67 authored by hughchen's avatar hughchen
Browse files

List most recently disconnected devices in the List

- This CL use getMostRecentlyConnectedDevices() to get the
  most recently disconnected devices.
- Register CachedBluetoothDevice.Callback to oberve the device
  connection state used to update UI.
- Update UI when receive onRoutesRemoved().
- Add getMostRecentlyConnectedDevices() and
  setMostRecentlyConnectedDevices() in ShadowBluetoothAdapter.java
- Add test case.

Bug: 149267220
Test: make -j42 RunSettingsLibRoboTests
Change-Id: I43660dc76cf5194cb24d83b5a17d4725fe750e6a
parent 7ab8a283
Loading
Loading
Loading
Loading
+5 −7
Original line number Diff line number Diff line
@@ -74,13 +74,6 @@ public class InfoMediaManager extends MediaManager {
        refreshDevices();
    }

    @VisibleForTesting
    String getControlCategoryByPackageName(String packageName) {
        //TODO(b/117129183): Use package name to get ControlCategory.
        //Since api not ready, return fixed ControlCategory for prototype.
        return "com.google.android.gms.cast.CATEGORY_CAST";
    }

    @Override
    public void stopScan() {
        mRouterManager.unregisterCallback(mMediaRouterCallback);
@@ -192,5 +185,10 @@ public class InfoMediaManager extends MediaManager {
        public void onRoutesChanged(List<MediaRoute2Info> routes) {
            refreshDevices();
        }

        @Override
        public void onRoutesRemoved(List<MediaRoute2Info> routes) {
            refreshDevices();
        }
    }
}
+57 −2
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@
package com.android.settingslib.media;

import android.app.Notification;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.text.TextUtils;
@@ -26,13 +28,13 @@ import androidx.annotation.IntDef;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothManager;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -66,9 +68,16 @@ public class LocalMediaManager implements BluetoothCallback {
    @VisibleForTesting
    List<MediaDevice> mMediaDevices = new ArrayList<>();
    @VisibleForTesting
    List<MediaDevice> mDisconnectedMediaDevices = new ArrayList<>();
    @VisibleForTesting
    MediaDevice mPhoneDevice;
    @VisibleForTesting
    MediaDevice mCurrentConnectedDevice;
    @VisibleForTesting
    DeviceAttributeChangeCallback mDeviceAttributeChangeCallback =
            new DeviceAttributeChangeCallback();
    @VisibleForTesting
    BluetoothAdapter mBluetoothAdapter;

    /**
     * Register to start receiving callbacks for MediaDevice events.
@@ -89,6 +98,7 @@ public class LocalMediaManager implements BluetoothCallback {
        mPackageName = packageName;
        mLocalBluetoothManager =
                LocalBluetoothManager.getInstance(context, /* onInitCallback= */ null);
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        if (mLocalBluetoothManager == null) {
            Log.e(TAG, "Bluetooth is not supported on this device");
            return;
@@ -164,7 +174,7 @@ public class LocalMediaManager implements BluetoothCallback {
    }

    void dispatchDeviceListUpdate() {
        Collections.sort(mMediaDevices, COMPARATOR);
        //TODO(b/149260820): Use new rule to rank device once device type api is ready.
        for (DeviceCallback callback : getCallbacks()) {
            callback.onDeviceListUpdate(new ArrayList<>(mMediaDevices));
        }
@@ -278,12 +288,44 @@ public class LocalMediaManager implements BluetoothCallback {
        public void onDeviceListAdded(List<MediaDevice> devices) {
            mMediaDevices.clear();
            mMediaDevices.addAll(devices);
            mMediaDevices.addAll(buildDisconnectedBluetoothDevice());

            final MediaDevice infoMediaDevice = mInfoMediaManager.getCurrentConnectedDevice();
            mCurrentConnectedDevice = infoMediaDevice != null
                    ? infoMediaDevice : updateCurrentConnectedDevice();
            dispatchDeviceListUpdate();
        }

        private List<MediaDevice> buildDisconnectedBluetoothDevice() {
            for (MediaDevice device : mDisconnectedMediaDevices) {
                ((BluetoothMediaDevice) device).getCachedDevice()
                        .unregisterCallback(mDeviceAttributeChangeCallback);
            }
            mDisconnectedMediaDevices.clear();
            final List<BluetoothDevice> bluetoothDevices =
                    mBluetoothAdapter.getMostRecentlyConnectedDevices();
            final CachedBluetoothDeviceManager cachedDeviceManager =
                    mLocalBluetoothManager.getCachedDeviceManager();

            for (BluetoothDevice device : bluetoothDevices) {
                final CachedBluetoothDevice cachedDevice =
                        cachedDeviceManager.findDevice(device);
                if (cachedDevice != null) {
                    if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED
                            && !cachedDevice.isConnected()) {
                        final MediaDevice mediaDevice = new BluetoothMediaDevice(mContext,
                                cachedDevice,
                                null, null, mPackageName);
                        if (!mMediaDevices.contains(mediaDevice)) {
                            cachedDevice.registerCallback(mDeviceAttributeChangeCallback);
                            mDisconnectedMediaDevices.add(mediaDevice);
                        }
                    }
                }
            }
            return new ArrayList<>(mDisconnectedMediaDevices);
        }

        @Override
        public void onDeviceRemoved(MediaDevice device) {
            if (mMediaDevices.contains(device)) {
@@ -345,4 +387,17 @@ public class LocalMediaManager implements BluetoothCallback {
         */
        default void onDeviceAttributesChanged() {};
    }

    /**
     * This callback is for update {@link BluetoothMediaDevice} summary when
     * {@link CachedBluetoothDevice} connection state is changed.
     */
    @VisibleForTesting
    class DeviceAttributeChangeCallback implements CachedBluetoothDevice.Callback {

        @Override
        public void onDeviceAttributesChanged() {
            dispatchDeviceAttributesChanged();
        }
    }
}
+42 −0
Original line number Diff line number Diff line
@@ -203,4 +203,46 @@ public class InfoMediaManagerTest {

        assertThat(mInfoMediaManager.connectDeviceWithoutPackageName(device)).isFalse();
    }

    @Test
    public void onRoutesRemoved_getAvailableRoutes_shouldAddMediaDevice() {
        final MediaRoute2Info info = mock(MediaRoute2Info.class);
        when(info.getId()).thenReturn(TEST_ID);
        when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);

        final List<MediaRoute2Info> routes = new ArrayList<>();
        routes.add(info);
        when(mRouterManager.getAvailableRoutes(TEST_PACKAGE_NAME)).thenReturn(routes);

        final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
        assertThat(mediaDevice).isNull();

        mInfoMediaManager.mMediaRouterCallback.onRoutesRemoved(routes);

        final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
        assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
        assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(infoDevice);
        assertThat(mInfoMediaManager.mMediaDevices).hasSize(routes.size());
    }

    @Test
    public void onRoutesRemoved_buildAllRoutes_shouldAddMediaDevice() {
        final MediaRoute2Info info = mock(MediaRoute2Info.class);
        when(info.getId()).thenReturn(TEST_ID);
        when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);

        final List<MediaRoute2Info> routes = new ArrayList<>();
        routes.add(info);
        when(mRouterManager.getAllRoutes()).thenReturn(routes);

        final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
        assertThat(mediaDevice).isNull();

        mInfoMediaManager.mPackageName = "";
        mInfoMediaManager.mMediaRouterCallback.onRoutesChanged(routes);

        final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
        assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
        assertThat(mInfoMediaManager.mMediaDevices).hasSize(routes.size());
    }
}
+58 −0
Original line number Diff line number Diff line
@@ -25,13 +25,17 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Context;

import com.android.settingslib.bluetooth.A2dpProfile;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.HearingAidProfile;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;

import org.junit.Before;
import org.junit.Test;
@@ -40,11 +44,14 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;

import java.util.ArrayList;
import java.util.List;

@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowBluetoothAdapter.class})
public class LocalMediaManagerTest {

    private static final String TEST_DEVICE_ID_1 = "device_id_1";
@@ -69,11 +76,15 @@ public class LocalMediaManagerTest {

    private Context mContext;
    private LocalMediaManager mLocalMediaManager;
    private ShadowBluetoothAdapter mShadowBluetoothAdapter;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        mContext = RuntimeEnvironment.application;
        final List<BluetoothDevice> bluetoothDevices = new ArrayList<>();
        mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
        mShadowBluetoothAdapter.setMostRecentlyConnectedDevices(bluetoothDevices);

        when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalProfileManager);
        when(mLocalProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
@@ -81,6 +92,7 @@ public class LocalMediaManagerTest {

        mLocalMediaManager = new LocalMediaManager(mContext, mLocalBluetoothManager,
                mInfoMediaManager, "com.test.packagename");
        mLocalMediaManager.mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    }

    @Test
@@ -419,4 +431,50 @@ public class LocalMediaManagerTest {
                MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE);
        assertThat(activeDevices).containsExactly(device3);
    }

    @Test
    public void onDeviceAttributesChanged_shouldBeCalled() {
        mLocalMediaManager.registerCallback(mCallback);

        mLocalMediaManager.mDeviceAttributeChangeCallback.onDeviceAttributesChanged();

        verify(mCallback).onDeviceAttributesChanged();
    }

    @Test
    public void onDeviceListAdded_haveDisconnectedDevice_addDisconnectedDevice() {
        final List<MediaDevice> devices = new ArrayList<>();
        final MediaDevice device1 = mock(MediaDevice.class);
        final MediaDevice device2 = mock(MediaDevice.class);
        final MediaDevice device3 = mock(MediaDevice.class);
        mLocalMediaManager.mPhoneDevice = mock(PhoneMediaDevice.class);
        devices.add(device1);
        devices.add(device2);
        mLocalMediaManager.mMediaDevices.add(device3);
        mLocalMediaManager.mMediaDevices.add(mLocalMediaManager.mPhoneDevice);

        final List<BluetoothDevice> bluetoothDevices = new ArrayList<>();
        final BluetoothDevice bluetoothDevice = mock(BluetoothDevice.class);
        final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
        final CachedBluetoothDeviceManager cachedManager = mock(CachedBluetoothDeviceManager.class);
        bluetoothDevices.add(bluetoothDevice);
        mShadowBluetoothAdapter.setMostRecentlyConnectedDevices(bluetoothDevices);

        when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(cachedManager);
        when(cachedManager.findDevice(bluetoothDevice)).thenReturn(cachedDevice);
        when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
        when(cachedDevice.isConnected()).thenReturn(false);

        when(device1.getId()).thenReturn(TEST_DEVICE_ID_1);
        when(device2.getId()).thenReturn(TEST_DEVICE_ID_2);
        when(device3.getId()).thenReturn(TEST_DEVICE_ID_3);
        when(mLocalMediaManager.mPhoneDevice.getId()).thenReturn("test_phone_id");

        assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
        mLocalMediaManager.registerCallback(mCallback);
        mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(devices);

        assertThat(mLocalMediaManager.mMediaDevices).hasSize(3);
        verify(mCallback).onDeviceListUpdate(any());
    }
}
+11 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.settingslib.testutils.shadow;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;

@@ -29,6 +30,7 @@ import java.util.List;
public class ShadowBluetoothAdapter extends org.robolectric.shadows.ShadowBluetoothAdapter {

    private List<Integer> mSupportedProfiles;
    private List<BluetoothDevice> mMostRecentlyConnectedDevices;
    private BluetoothProfile.ServiceListener mServiceListener;

    @Implementation
@@ -50,4 +52,13 @@ public class ShadowBluetoothAdapter extends org.robolectric.shadows.ShadowBlueto
    public void setSupportedProfiles(List<Integer> supportedProfiles) {
        mSupportedProfiles = supportedProfiles;
    }

    @Implementation
    protected List<BluetoothDevice> getMostRecentlyConnectedDevices() {
        return mMostRecentlyConnectedDevices;
    }

    public void setMostRecentlyConnectedDevices(List<BluetoothDevice> list) {
        mMostRecentlyConnectedDevices = list;
    }
}