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
This commit is contained in:
hughchen
2020-02-11 14:30:14 +08:00
parent 7ab8a28306
commit b3d68f670f
5 changed files with 173 additions and 9 deletions

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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());
}
}

View File

@@ -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());
}
}

View File

@@ -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;
}
}