Base on MediaDeviceType to ranking devices list

- This CL base on MediaDeviceType to ranking devices list.
  The order is followed below rule:
  1. USB-C audio device
  2. 3.5 mm audio devce
  3. Bluetooth device
  4. Cast device
  5. Cast group device
  6. Phone
- Add test case

Bug: 152633051
Test: make -j42 RunSettingsLibRoboTests
Change-Id: Ia224f6e3b420c5b9c6ea428bfc737e3624a6ebe4
This commit is contained in:
hughchen
2020-04-22 19:31:28 +08:00
parent e8c79b042e
commit 31901c037b
6 changed files with 152 additions and 52 deletions

View File

@@ -88,6 +88,13 @@ public class BluetoothMediaDevice extends MediaDevice {
return false;
}
@Override
public boolean isFastPairDevice() {
return mCachedDevice != null
&& BluetoothUtils.getBooleanMetaData(
mCachedDevice.getDevice(), BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET);
}
@Override
public boolean isConnected() {
return mCachedDevice.getBondState() == BluetoothDevice.BOND_BONDED

View File

@@ -35,6 +35,7 @@ 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;
@@ -185,7 +186,7 @@ public class LocalMediaManager implements BluetoothCallback {
}
void dispatchDeviceListUpdate() {
//TODO(b/149260820): Use new rule to rank device once device type api is ready.
Collections.sort(mMediaDevices, COMPARATOR);
for (DeviceCallback callback : getCallbacks()) {
callback.onDeviceListUpdate(new ArrayList<>(mMediaDevices));
}

View File

@@ -278,16 +278,22 @@ public abstract class MediaDevice implements Comparable<MediaDevice> {
/**
* Rules:
* 1. If there is one of the connected devices identified as a carkit, this carkit will
* be always on the top of the device list. Rule 2 and Rule 3 cant overrule this rule.
* 1. If there is one of the connected devices identified as a carkit or fast pair device,
* the fast pair device will be always on the first of the device list and carkit will be
* second. Rule 2 and Rule 3 cant overrule this rule.
* 2. For devices without any usage data yet
* WiFi device group sorted by alphabetical order + BT device group sorted by alphabetical
* order + phone speaker
* 3. For devices with usage record.
* The most recent used one + device group with usage info sorted by how many times the
* device has been used.
* 4. Phone device always in the top and the connected Bluetooth devices, cast devices and
* phone device will be always above on the disconnect Bluetooth devices.
* 4. The order is followed below rule:
* 1. USB-C audio device
* 2. 3.5 mm audio device
* 3. Bluetooth device
* 4. Cast device
* 5. Cast group device
* 6. Phone
*
* So the device list will look like 5 slots ranked as below.
* Rule 4 + Rule 1 + the most recently used device + Rule 3 + Rule 2
@@ -307,39 +313,50 @@ public abstract class MediaDevice implements Comparable<MediaDevice> {
}
}
// Phone device always in the top.
if (mType == MediaDeviceType.TYPE_PHONE_DEVICE) {
return -1;
} else if (another.mType == MediaDeviceType.TYPE_PHONE_DEVICE) {
return 1;
}
// Check carkit
if (isCarKitDevice()) {
return -1;
} else if (another.isCarKitDevice()) {
return 1;
}
// Set last used device at the first item
String lastSelectedDevice = ConnectionRecordManager.getInstance().getLastSelectedDevice();
if (TextUtils.equals(lastSelectedDevice, getId())) {
return -1;
} else if (TextUtils.equals(lastSelectedDevice, another.getId())) {
return 1;
}
// Sort by how many times the device has been used if there is usage record
if ((mConnectedRecord != another.mConnectedRecord)
&& (another.mConnectedRecord > 0 || mConnectedRecord > 0)) {
return (another.mConnectedRecord - mConnectedRecord);
}
// Both devices have never been used
// To devices with the same type, sort by alphabetical order
if (mType == another.mType) {
// Check fast pair device
if (isFastPairDevice()) {
return -1;
} else if (another.isFastPairDevice()) {
return 1;
}
// Check carkit
if (isCarKitDevice()) {
return -1;
} else if (another.isCarKitDevice()) {
return 1;
}
// Set last used device at the first item
final String lastSelectedDevice = ConnectionRecordManager.getInstance()
.getLastSelectedDevice();
if (TextUtils.equals(lastSelectedDevice, getId())) {
return -1;
} else if (TextUtils.equals(lastSelectedDevice, another.getId())) {
return 1;
}
// Sort by how many times the device has been used if there is usage record
if ((mConnectedRecord != another.mConnectedRecord)
&& (another.mConnectedRecord > 0 || mConnectedRecord > 0)) {
return (another.mConnectedRecord - mConnectedRecord);
}
// Both devices have never been used
// To devices with the same type, sort by alphabetical order
final String s1 = getName();
final String s2 = another.getName();
return s1.compareToIgnoreCase(s2);
} else {
// Both devices have never been used, the priority is:
// 1. USB-C audio device
// 2. 3.5 mm audio device
// 3. Bluetooth device
// 4. Cast device
// 5. Cast group device
// 6. Phone
return mType < another.mType ? -1 : 1;
}
// Both devices have never been used, the priority is Phone > Cast > Bluetooth
return mType - another.mType;
}
/**
@@ -350,6 +367,14 @@ public abstract class MediaDevice implements Comparable<MediaDevice> {
return false;
}
/**
* Check if it is FastPair device
* @return {@code true} if it is FastPair device, otherwise return {@code false}
*/
protected boolean isFastPairDevice() {
return false;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof MediaDevice)) {

View File

@@ -18,6 +18,7 @@ package com.android.settingslib.media;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothDevice;
@@ -69,4 +70,30 @@ public class BluetoothMediaDeviceTest {
assertThat(mBluetoothMediaDevice.isConnected()).isFalse();
}
@Test
public void isFastPairDevice_isUntetheredHeadset_returnTrue() {
final BluetoothDevice bluetoothDevice = mock(BluetoothDevice.class);
when(mDevice.getDevice()).thenReturn(bluetoothDevice);
final String value = "True";
final byte[] bytes = value.getBytes();
when(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
.thenReturn(bytes);
assertThat(mBluetoothMediaDevice.isFastPairDevice()).isTrue();
}
@Test
public void isFastPairDevice_isNotUntetheredHeadset_returnFalse() {
final BluetoothDevice bluetoothDevice = mock(BluetoothDevice.class);
when(mDevice.getDevice()).thenReturn(bluetoothDevice);
final String value = "asjdaioshfaio";
final byte[] bytes = value.getBytes();
when(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
.thenReturn(bytes);
assertThat(mBluetoothMediaDevice.isFastPairDevice()).isFalse();
}
}

View File

@@ -16,6 +16,7 @@
package com.android.settingslib.media;
import static android.bluetooth.BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES;
import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
import static com.google.common.truth.Truth.assertThat;
@@ -28,6 +29,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.media.MediaRoute2Info;
@@ -659,6 +661,7 @@ public class LocalMediaManagerTest {
final BluetoothDevice bluetoothDevice4 = mock(BluetoothDevice.class);
final BluetoothDevice bluetoothDevice5 = mock(BluetoothDevice.class);
final BluetoothDevice bluetoothDevice6 = mock(BluetoothDevice.class);
final BluetoothClass bluetoothClass = mock(BluetoothClass.class);
final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
final CachedBluetoothDeviceManager cachedManager = mock(CachedBluetoothDeviceManager.class);
bluetoothDevices.add(bluetoothDevice);
@@ -678,6 +681,9 @@ public class LocalMediaManagerTest {
when(cachedManager.findDevice(bluetoothDevice6)).thenReturn(cachedDevice);
when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(cachedDevice.isConnected()).thenReturn(false);
when(cachedDevice.getDevice()).thenReturn(bluetoothDevice);
when(bluetoothDevice.getBluetoothClass()).thenReturn(bluetoothClass);
when(bluetoothClass.getDeviceClass()).thenReturn(AUDIO_VIDEO_HEADPHONES);
when(device1.getId()).thenReturn(TEST_DEVICE_ID_1);
when(device2.getId()).thenReturn(TEST_DEVICE_ID_2);

View File

@@ -18,9 +18,11 @@ package com.android.settingslib.media;
import static android.media.MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -210,14 +212,14 @@ public class MediaDeviceTest {
}
@Test
public void compareTo_carKit_phone_phoneFirst() {
public void compareTo_carKit_phone_carKitFirst() {
when(mDevice1.getBluetoothClass()).thenReturn(mCarkitClass);
mMediaDevices.add(mBluetoothMediaDevice1);
mMediaDevices.add(mPhoneMediaDevice);
mMediaDevices.add(mBluetoothMediaDevice1);
assertThat(mMediaDevices.get(0)).isEqualTo(mBluetoothMediaDevice1);
Collections.sort(mMediaDevices, COMPARATOR);
assertThat(mMediaDevices.get(0)).isEqualTo(mPhoneMediaDevice);
Collections.sort(mMediaDevices, COMPARATOR);
assertThat(mMediaDevices.get(0)).isEqualTo(mBluetoothMediaDevice1);
}
@Test
@@ -281,7 +283,7 @@ public class MediaDeviceTest {
}
@Test
public void compareTo_info_bluetooth_infoFirst() {
public void compareTo_info_bluetooth_bluetoothFirst() {
mMediaDevices.add(mInfoMediaDevice1);
mMediaDevices.add(mBluetoothMediaDevice1);
@@ -291,13 +293,45 @@ public class MediaDeviceTest {
}
@Test
public void compareTo_bluetooth_phone_phoneFirst() {
mMediaDevices.add(mBluetoothMediaDevice1);
public void compareTo_bluetooth_phone_bluetoothFirst() {
mMediaDevices.add(mPhoneMediaDevice);
mMediaDevices.add(mBluetoothMediaDevice1);
assertThat(mMediaDevices.get(0)).isEqualTo(mPhoneMediaDevice);
Collections.sort(mMediaDevices, COMPARATOR);
assertThat(mMediaDevices.get(0)).isEqualTo(mBluetoothMediaDevice1);
}
@Test
public void compareTo_bluetooth_wiredHeadset_wiredHeadsetFirst() {
final MediaRoute2Info phoneRouteInfo = mock(MediaRoute2Info.class);
when(phoneRouteInfo.getType()).thenReturn(TYPE_WIRED_HEADPHONES);
final PhoneMediaDevice phoneMediaDevice = new PhoneMediaDevice(mContext,
mMediaRouter2Manager, phoneRouteInfo, TEST_PACKAGE_NAME);
mMediaDevices.add(mBluetoothMediaDevice1);
mMediaDevices.add(phoneMediaDevice);
assertThat(mMediaDevices.get(0)).isEqualTo(mBluetoothMediaDevice1);
Collections.sort(mMediaDevices, COMPARATOR);
assertThat(mMediaDevices.get(0)).isEqualTo(mPhoneMediaDevice);
assertThat(mMediaDevices.get(0)).isEqualTo(phoneMediaDevice);
}
@Test
public void compareTo_info_wiredHeadset_wiredHeadsetFirst() {
final MediaRoute2Info phoneRouteInfo = mock(MediaRoute2Info.class);
when(phoneRouteInfo.getType()).thenReturn(TYPE_WIRED_HEADPHONES);
final PhoneMediaDevice phoneMediaDevice = new PhoneMediaDevice(mContext,
mMediaRouter2Manager, phoneRouteInfo, TEST_PACKAGE_NAME);
mMediaDevices.add(mInfoMediaDevice1);
mMediaDevices.add(phoneMediaDevice);
assertThat(mMediaDevices.get(0)).isEqualTo(mInfoMediaDevice1);
Collections.sort(mMediaDevices, COMPARATOR);
assertThat(mMediaDevices.get(0)).isEqualTo(phoneMediaDevice);
}
@Test
@@ -338,7 +372,7 @@ public class MediaDeviceTest {
// 5.mBluetoothMediaDevice2: * 2 times usage
// 6.mBluetoothMediaDevice3: * 1 time usage
// 7.mPhoneMediaDevice: * 0 time usage
// Order: 7 -> 2 -> 1 -> 5 -> 3 -> 6 -> 4
// Order: 2 -> 5 -> 6 -> 1 -> 3 -> 4 -> 7
@Test
public void compareTo_mixedDevices_carKitFirst() {
when(mDevice1.getBluetoothClass()).thenReturn(mCarkitClass);
@@ -360,13 +394,13 @@ public class MediaDeviceTest {
mInfoMediaDevice1.connect();
Collections.sort(mMediaDevices, COMPARATOR);
assertThat(mMediaDevices.get(0)).isEqualTo(mPhoneMediaDevice);
assertThat(mMediaDevices.get(1)).isEqualTo(mBluetoothMediaDevice1);
assertThat(mMediaDevices.get(2)).isEqualTo(mInfoMediaDevice1);
assertThat(mMediaDevices.get(3)).isEqualTo(mBluetoothMediaDevice2);
assertThat(mMediaDevices.get(0)).isEqualTo(mBluetoothMediaDevice1);
assertThat(mMediaDevices.get(1)).isEqualTo(mBluetoothMediaDevice2);
assertThat(mMediaDevices.get(2)).isEqualTo(mBluetoothMediaDevice3);
assertThat(mMediaDevices.get(3)).isEqualTo(mInfoMediaDevice1);
assertThat(mMediaDevices.get(4)).isEqualTo(mInfoMediaDevice2);
assertThat(mMediaDevices.get(5)).isEqualTo(mBluetoothMediaDevice3);
assertThat(mMediaDevices.get(6)).isEqualTo(mInfoMediaDevice3);
assertThat(mMediaDevices.get(5)).isEqualTo(mInfoMediaDevice3);
assertThat(mMediaDevices.get(6)).isEqualTo(mPhoneMediaDevice);
}
// 1.mInfoMediaDevice1: Last Selected device
@@ -376,7 +410,7 @@ public class MediaDeviceTest {
// 5.mBluetoothMediaDevice2: * 4 times usage not connected
// 6.mBluetoothMediaDevice3: * 1 time usage
// 7.mPhoneMediaDevice: * 0 time usage
// Order: 7 -> 1 -> 3 -> 6 -> 4 -> 2 -> 5
// Order: 6 -> 1 -> 3 -> 4 -> 7 -> 2 -> 5
@Test
public void compareTo_mixedDevices_connectDeviceFirst() {
when(mDevice1.getBluetoothClass()).thenReturn(mCarkitClass);
@@ -402,11 +436,11 @@ public class MediaDeviceTest {
mInfoMediaDevice1.connect();
Collections.sort(mMediaDevices, COMPARATOR);
assertThat(mMediaDevices.get(0)).isEqualTo(mPhoneMediaDevice);
assertThat(mMediaDevices.get(0)).isEqualTo(mBluetoothMediaDevice3);
assertThat(mMediaDevices.get(1)).isEqualTo(mInfoMediaDevice1);
assertThat(mMediaDevices.get(2)).isEqualTo(mInfoMediaDevice2);
assertThat(mMediaDevices.get(3)).isEqualTo(mBluetoothMediaDevice3);
assertThat(mMediaDevices.get(4)).isEqualTo(mInfoMediaDevice3);
assertThat(mMediaDevices.get(3)).isEqualTo(mInfoMediaDevice3);
assertThat(mMediaDevices.get(4)).isEqualTo(mPhoneMediaDevice);
assertThat(mMediaDevices.get(5)).isEqualTo(mBluetoothMediaDevice1);
assertThat(mMediaDevices.get(6)).isEqualTo(mBluetoothMediaDevice2);
}