Merge "Implement naive rule of device order on seamless transfer"

This commit is contained in:
tim peng
2018-12-24 03:34:17 +00:00
committed by Android (Google) Code Review
10 changed files with 659 additions and 55 deletions

View File

@@ -15,6 +15,8 @@
*/
package com.android.settingslib.media;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.util.Log;
@@ -33,6 +35,13 @@ public class BluetoothMediaDevice extends MediaDevice {
BluetoothMediaDevice(Context context, CachedBluetoothDevice device) {
super(context, MediaDeviceType.TYPE_BLUETOOTH_DEVICE);
mCachedDevice = device;
initDeviceRecord();
buildConnectedState(device);
}
private void buildConnectedState(CachedBluetoothDevice device) {
mIsConnected = device.isActiveDevice(BluetoothProfile.A2DP)
|| device.isActiveDevice(BluetoothProfile.HEARING_AID);
}
@Override
@@ -51,10 +60,16 @@ public class BluetoothMediaDevice extends MediaDevice {
return MediaDeviceUtils.getId(mCachedDevice);
}
@Override
public void notifyConnectedChanged() {
buildConnectedState(mCachedDevice);
}
@Override
public void connect() {
//TODO(b/117129183): add callback to notify LocalMediaManager connection state.
mIsConnected = mCachedDevice.setActive();
super.connect();
Log.d(TAG, "connect() device : " + getName() + ", is selected : " + mIsConnected);
}
@@ -70,4 +85,18 @@ public class BluetoothMediaDevice extends MediaDevice {
public CachedBluetoothDevice getCachedDevice() {
return mCachedDevice;
}
@Override
protected boolean isCarKitDevice() {
final BluetoothClass bluetoothClass = mCachedDevice.getDevice().getBluetoothClass();
if (bluetoothClass != null) {
switch (bluetoothClass.getDeviceClass()) {
// Both are common CarKit class
case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
return true;
}
}
return false;
}
}

View File

@@ -18,6 +18,7 @@ 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.util.Log;
@@ -30,6 +31,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
@@ -39,8 +41,8 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall
private static final String TAG = "BluetoothMediaManager";
private final DeviceAttributeChangeCallback mCachedDeviceCallback =
new DeviceAttributeChangeCallback();
private final DeviceProfileNotReadyObserverCallback mObserverCallback =
new DeviceProfileNotReadyObserverCallback();
private LocalBluetoothManager mLocalBluetoothManager;
private LocalBluetoothProfileManager mProfileManager;
@@ -48,6 +50,10 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall
private MediaDevice mLastAddedDevice;
private MediaDevice mLastRemovedDevice;
private boolean mIsA2dpProfileReady = false;
private boolean mIsHearingAidProfileReady = false;
private Collection<CachedBluetoothDevice> mCachedDevices;
BluetoothMediaManager(Context context, LocalBluetoothManager localBluetoothManager,
Notification notification) {
super(context, notification);
@@ -62,6 +68,18 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall
mLocalBluetoothManager.getEventManager().registerCallback(this);
buildBluetoothDeviceList();
dispatchDeviceListAdded();
// The profile may not ready when calling startScan().
// Device status are all disconnected since profiles are not ready to connected.
// In this case, we observe all devices in CachedDeviceManager.
// When one of these device is connected to profile, will call buildBluetoothDeviceList()
// again to find the connected devices.
if (!mIsA2dpProfileReady && !mIsHearingAidProfileReady) {
mCachedDevices = mLocalBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy();
for (CachedBluetoothDevice device : mCachedDevices) {
device.registerCallback(mObserverCallback);
}
}
}
private void buildBluetoothDeviceList() {
@@ -75,6 +93,7 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall
Log.w(TAG, "addConnectedA2dpDevices() a2dp profile is null!");
return;
}
final List<BluetoothDevice> devices = a2dpProfile.getConnectedDevices();
final CachedBluetoothDeviceManager cachedBluetoothDeviceManager =
mLocalBluetoothManager.getCachedDeviceManager();
@@ -95,6 +114,8 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall
addMediaDevice(cachedDevice);
}
}
mIsA2dpProfileReady = a2dpProfile.isProfileReady();
}
private void addConnectedHearingAidDevices() {
@@ -103,6 +124,7 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall
Log.w(TAG, "addConnectedA2dpDevices() hap profile is null!");
return;
}
final List<Long> devicesHiSyncIds = new ArrayList<>();
final List<BluetoothDevice> devices = hapProfile.getConnectedDevices();
final CachedBluetoothDeviceManager cachedBluetoothDeviceManager =
@@ -128,13 +150,14 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall
addMediaDevice(cachedDevice);
}
}
mIsHearingAidProfileReady = hapProfile.isProfileReady();
}
private void addMediaDevice(CachedBluetoothDevice cachedDevice) {
MediaDevice mediaDevice = findMediaDevice(MediaDeviceUtils.getId(cachedDevice));
if (mediaDevice == null) {
mediaDevice = new BluetoothMediaDevice(mContext, cachedDevice);
cachedDevice.registerCallback(mCachedDeviceCallback);
mLastAddedDevice = mediaDevice;
mMediaDevices.add(mediaDevice);
}
@@ -143,16 +166,6 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall
@Override
public void stopScan() {
mLocalBluetoothManager.getEventManager().unregisterCallback(this);
unregisterCachedDeviceCallback();
}
private void unregisterCachedDeviceCallback() {
for (MediaDevice device : mMediaDevices) {
if (device instanceof BluetoothMediaDevice) {
((BluetoothMediaDevice) device).getCachedDevice()
.unregisterCallback(mCachedDeviceCallback);
}
}
}
@Override
@@ -164,8 +177,6 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall
final List<MediaDevice> removeDevicesList = new ArrayList<>();
for (MediaDevice device : mMediaDevices) {
if (device instanceof BluetoothMediaDevice) {
((BluetoothMediaDevice) device).getCachedDevice()
.unregisterCallback(mCachedDeviceCallback);
removeDevicesList.add(device);
}
}
@@ -185,7 +196,7 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall
private boolean isCachedDeviceConnected(CachedBluetoothDevice cachedDevice) {
final boolean isConnectedHearingAidDevice = cachedDevice.isConnectedHearingAidDevice();
final boolean isConnectedA2dpDevice = cachedDevice.isConnectedA2dpDevice();
Log.d(TAG, "isCachedDeviceConnected() cachedDevice : " + cachedDevice.getName()
Log.d(TAG, "isCachedDeviceConnected() cachedDevice : " + cachedDevice
+ ", is hearing aid connected : " + isConnectedHearingAidDevice
+ ", is a2dp connected : " + isConnectedA2dpDevice);
@@ -210,7 +221,6 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall
private void removeMediaDevice(CachedBluetoothDevice cachedDevice) {
final MediaDevice mediaDevice = findMediaDevice(MediaDeviceUtils.getId(cachedDevice));
if (mediaDevice != null) {
cachedDevice.unregisterCallback(mCachedDeviceCallback);
mLastRemovedDevice = mediaDevice;
mMediaDevices.remove(mediaDevice);
}
@@ -226,7 +236,7 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall
@Override
public void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state,
int bluetoothProfile) {
Log.d(TAG, "onProfileConnectionStateChanged() device: " + cachedDevice.getName()
Log.d(TAG, "onProfileConnectionStateChanged() device: " + cachedDevice
+ ", state: " + state + ", bluetoothProfile: " + bluetoothProfile);
if (isCachedDeviceConnected(cachedDevice)) {
@@ -240,8 +250,7 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall
@Override
public void onAclConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
Log.d(TAG, "onAclConnectionStateChanged() device: " + cachedDevice.getName()
+ ", state: " + state);
Log.d(TAG, "onAclConnectionStateChanged() device: " + cachedDevice + ", state: " + state);
if (isCachedDeviceConnected(cachedDevice)) {
addMediaDevice(cachedDevice);
@@ -252,10 +261,29 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall
}
}
class DeviceAttributeChangeCallback implements CachedBluetoothDevice.Callback {
@Override
public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) {
Log.d(TAG, "onActiveDeviceChanged : device : "
+ activeDevice + ", profile : " + bluetoothProfile);
if (BluetoothProfile.HEARING_AID == bluetoothProfile
|| BluetoothProfile.A2DP == bluetoothProfile) {
final String id = activeDevice == null
? PhoneMediaDevice.ID : MediaDeviceUtils.getId(activeDevice);
dispatchActiveDeviceChanged(id);
}
}
class DeviceProfileNotReadyObserverCallback implements CachedBluetoothDevice.Callback {
@Override
public void onDeviceAttributesChanged() {
dispatchDeviceAttributesChanged();
if (!mIsA2dpProfileReady && !mIsHearingAidProfileReady) {
for (CachedBluetoothDevice device : mCachedDevices) {
device.unregisterCallback(mObserverCallback);
}
buildBluetoothDeviceList();
dispatchDeviceListAdded();
}
}
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.media;
import android.content.Context;
import android.content.SharedPreferences;
/**
* ConnectionRecordManager represents the sharedPreferences operation on device usage record
*/
public class ConnectionRecordManager {
private static final Object sInstanceSync = new Object();
private static final String KEY_LAST_SELECTED_DEVICE = "last_selected_device";
private static final String SHARED_PREFERENCES_NAME = "seamless_transfer_record";
private static final String TAG = "ConnectionRecordManager";
private static ConnectionRecordManager sInstance;
private String mLastSelectedDevice;
/**
* Get an {@code ConnectionRecordManager} instance (create one if necessary).
*/
public static ConnectionRecordManager getInstance() {
synchronized (sInstanceSync) {
if (sInstance == null) {
sInstance = new ConnectionRecordManager();
}
}
return sInstance;
}
private SharedPreferences getSharedPreferences(Context context) {
return context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
}
/**
* Get connection record from sharedPreferences
*
* @param id a unique device Id
* @return the the usage result
*/
public synchronized int fetchConnectionRecord(Context context, String id) {
return getSharedPreferences(context).getInt(id, 0);
}
/**
* Get the last selected device from sharedPreferences
*/
public synchronized void fetchLastSelectedDevice(Context context) {
mLastSelectedDevice = getSharedPreferences(context).getString(KEY_LAST_SELECTED_DEVICE,
null);
}
/**
* Set device usage time and last selected device in sharedPreference
*
* @param id a unique device Id
* @param record usage times
*/
public synchronized void setConnectionRecord(Context context, String id, int record) {
final SharedPreferences.Editor editor = getSharedPreferences(context).edit();
// Update used times
mLastSelectedDevice = id;
editor.putInt(mLastSelectedDevice, record);
// Update last used device
editor.putString(KEY_LAST_SELECTED_DEVICE, mLastSelectedDevice);
editor.apply();
}
/**
* @return the last selected device
*/
public synchronized String getLastSelectedDevice() {
return mLastSelectedDevice;
}
}

View File

@@ -16,6 +16,7 @@
package com.android.settingslib.media;
import android.content.Context;
import android.widget.Toast;
import androidx.mediarouter.media.MediaRouter;
@@ -33,6 +34,7 @@ public class InfoMediaDevice extends MediaDevice {
InfoMediaDevice(Context context, MediaRouter.RouteInfo info) {
super(context, MediaDeviceType.TYPE_CAST_DEVICE);
mRouteInfo = info;
initDeviceRecord();
}
@Override
@@ -51,15 +53,23 @@ public class InfoMediaDevice extends MediaDevice {
return MediaDeviceUtils.getId(mRouteInfo);
}
@Override
public void notifyConnectedChanged() {
//TODO(b/117129183): check mIsConnected state
}
@Override
public void connect() {
//TODO(b/117129183): use MediaController2 to transfer media
mIsConnected = true;
super.connect();
//mIsConnected = true;
Toast.makeText(mContext, "This is cast device !", Toast.LENGTH_SHORT).show();
}
@Override
public void disconnect() {
//TODO(b/117129183): disconnected last select device
mIsConnected = false;
//mIsConnected = false;
}
}

View File

@@ -28,15 +28,20 @@ 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;
/**
* LocalMediaManager provide interface to get MediaDevice list and transfer media to MediaDevice.
*/
public class LocalMediaManager implements BluetoothCallback {
private static final Comparator<MediaDevice> COMPARATOR = Comparator.naturalOrder();
private static final String TAG = "LocalMediaManager";
public static final String NOTIFICATION_EXTRA = "notification_extra";
public static final String NOTIFICATION_PACKAGE_NAME = "notification_package_name";
@Retention(RetentionPolicy.SOURCE)
@IntDef({MediaDeviceState.STATE_CONNECTED,
MediaDeviceState.STATE_CONNECTING,
@@ -56,7 +61,6 @@ public class LocalMediaManager implements BluetoothCallback {
private InfoMediaManager mInfoMediaManager;
private LocalBluetoothManager mLocalBluetoothManager;
private MediaDevice mLastConnectedDevice;
private MediaDevice mPhoneDevice;
/**
@@ -96,29 +100,43 @@ public class LocalMediaManager implements BluetoothCallback {
* @param connectDevice the MediaDevice
*/
public void connectDevice(MediaDevice connectDevice) {
if (connectDevice == mLastConnectedDevice) {
final MediaDevice currentDevice = getCurrentConnectedDevice();
final MediaDevice device =
MediaDeviceUtils.findMediaDevice(mMediaDevices, connectDevice.getId());
if (device != null && currentDevice != null
&& device.getId().equals(currentDevice.getId())) {
return;
}
if (mLastConnectedDevice != null) {
mLastConnectedDevice.disconnect();
//TODO(b/117129183): For demo, will remove check connectDevice is InfoMediaDevice.
if (currentDevice != null && !(connectDevice instanceof InfoMediaDevice)) {
currentDevice.disconnect();
}
connectDevice.connect();
if (connectDevice.isConnected()) {
mLastConnectedDevice = connectDevice;
}
device.connect();
final int state = connectDevice.isConnected()
final int state = device.isConnected()
? MediaDeviceState.STATE_CONNECTED
: MediaDeviceState.STATE_DISCONNECTED;
dispatchSelectedDeviceStateChanged(connectDevice, state);
dispatchSelectedDeviceStateChanged(mMediaDevices, device, state);
}
void dispatchSelectedDeviceStateChanged(MediaDevice device, @MediaDeviceState int state) {
private MediaDevice getCurrentConnectedDevice() {
for (MediaDevice device : mMediaDevices) {
if (device.isConnected()) {
return device;
}
}
Log.w(TAG, "getCurrentConnectedDevice() cannot find current connected device !");
return null;
}
void dispatchSelectedDeviceStateChanged(List<MediaDevice> mMediaDevices, MediaDevice device,
@MediaDeviceState int state) {
synchronized (mCallbacks) {
for (DeviceCallback callback : mCallbacks) {
callback.onSelectedDeviceStateChanged(device, state);
callback.onSelectedDeviceStateChanged(new ArrayList<>(mMediaDevices), device,
state);
}
}
}
@@ -153,6 +171,7 @@ public class LocalMediaManager implements BluetoothCallback {
void dispatchDeviceListUpdate() {
synchronized (mCallbacks) {
Collections.sort(mMediaDevices, COMPARATOR);
for (DeviceCallback callback : mCallbacks) {
callback.onDeviceListUpdate(new ArrayList<>(mMediaDevices));
}
@@ -206,6 +225,25 @@ public class LocalMediaManager implements BluetoothCallback {
public void onDeviceAttributesChanged() {
dispatchDeviceListUpdate();
}
@Override
public void onActiveDeviceChanged(String id) {
final MediaDevice currentDevice = getCurrentConnectedDevice();
final MediaDevice connectDevice = MediaDeviceUtils.findMediaDevice(mMediaDevices, id);
if (connectDevice != null && currentDevice != null
&& connectDevice.getId().equals(currentDevice.getId())) {
return;
}
if (currentDevice != null) {
currentDevice.notifyConnectedChanged();
}
if (connectDevice != null) {
connectDevice.notifyConnectedChanged();
}
dispatchDeviceListUpdate();
}
}
@@ -229,6 +267,7 @@ public class LocalMediaManager implements BluetoothCallback {
* {@link MediaDeviceState#STATE_CONNECTING},
* {@link MediaDeviceState#STATE_DISCONNECTED}
*/
void onSelectedDeviceStateChanged(MediaDevice device, @MediaDeviceState int state);
void onSelectedDeviceStateChanged(List<MediaDevice> devices, MediaDevice device,
@MediaDeviceState int state);
}
}

View File

@@ -16,6 +16,8 @@
package com.android.settingslib.media;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.IntDef;
@@ -25,20 +27,21 @@ import java.lang.annotation.RetentionPolicy;
/**
* MediaDevice represents a media device(such like Bluetooth device, cast device and phone device).
*/
public abstract class MediaDevice {
public abstract class MediaDevice implements Comparable<MediaDevice> {
private static final String TAG = "MediaDevice";
@Retention(RetentionPolicy.SOURCE)
@IntDef({MediaDeviceType.TYPE_BLUETOOTH_DEVICE,
MediaDeviceType.TYPE_CAST_DEVICE,
@IntDef({MediaDeviceType.TYPE_CAST_DEVICE,
MediaDeviceType.TYPE_BLUETOOTH_DEVICE,
MediaDeviceType.TYPE_PHONE_DEVICE})
public @interface MediaDeviceType {
int TYPE_BLUETOOTH_DEVICE = 1;
int TYPE_CAST_DEVICE = 2;
int TYPE_CAST_DEVICE = 1;
int TYPE_BLUETOOTH_DEVICE = 2;
int TYPE_PHONE_DEVICE = 3;
}
private int mConnectedRecord;
protected boolean mIsConnected = false;
protected Context mContext;
protected int mType;
@@ -48,6 +51,13 @@ public abstract class MediaDevice {
mContext = context;
}
void initDeviceRecord() {
ConnectionRecordManager.getInstance().fetchLastSelectedDevice(mContext);
mConnectedRecord = ConnectionRecordManager.getInstance().fetchConnectionRecord(mContext,
getId());
Log.d("ttttt", getName() + " used: " + mConnectedRecord);
}
/**
* Check the MediaDevice is be connected to transfer.
*
@@ -77,13 +87,79 @@ public abstract class MediaDevice {
*/
public abstract String getId();
/**
* Notify MediaDevice to change their connected state.
*/
public abstract void notifyConnectedChanged();
/**
* Transfer MediaDevice for media
*/
public abstract void connect();
public void connect() {
mConnectedRecord++;
ConnectionRecordManager.getInstance().setConnectionRecord(mContext, getId(),
mConnectedRecord);
}
/**
* Stop transfer MediaDevice
*/
public abstract void disconnect();
/**
* 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.
* 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.
*
* So the device list will look like 4 slots ranked as below.
* Rule 1 + the most recently used device + Rule 3 + Rule 2
* Any slot could be empty. And available device will belong to one of the slots.
*
* @return a negative integer, zero, or a positive integer
* as this object is less than, equal to, or greater than the specified object.
*/
@Override
public int compareTo(MediaDevice another) {
// 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) {
final String s1 = getName();
final String s2 = another.getName();
return s1.compareToIgnoreCase(s2);
}
// Both devices have never been used, the priority is Cast > Bluetooth > Phone
return mType - another.mType;
}
/**
* Check if it is CarKit device
* @return true if it is CarKit device
*/
protected boolean isCarKitDevice() {
return false;
}
}

View File

@@ -15,15 +15,21 @@
*/
package com.android.settingslib.media;
import android.util.Log;
import androidx.mediarouter.media.MediaRouter;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import java.util.List;
/**
* MediaDeviceUtils provides utility function for MediaDevice
*/
public class MediaDeviceUtils {
private static final String TAG = "MediaDeviceUtils";
/**
* Use CachedBluetoothDevice address to represent unique id
*
@@ -43,4 +49,21 @@ public class MediaDeviceUtils {
public static String getId(MediaRouter.RouteInfo route) {
return route.getId();
}
/**
* Find the MediaDevice through id.
*
* @param devices the list of MediaDevice
* @param id the unique id of MediaDevice
* @return MediaDevice
*/
public static MediaDevice findMediaDevice(List<MediaDevice> devices, String id) {
for (MediaDevice mediaDevice : devices) {
if (mediaDevice.getId().equals(id)) {
return mediaDevice;
}
}
Log.e(TAG, "findMediaDevice() can't found device");
return null;
}
}

View File

@@ -17,7 +17,6 @@ package com.android.settingslib.media;
import android.app.Notification;
import android.content.Context;
import android.util.Log;
import java.util.ArrayList;
import java.util.Collection;
@@ -68,13 +67,7 @@ public abstract class MediaManager {
public abstract void stopScan();
protected MediaDevice findMediaDevice(String id) {
for (MediaDevice mediaDevice : mMediaDevices) {
if (mediaDevice.getId().equals(id)) {
return mediaDevice;
}
}
Log.e(TAG, "findMediaDevice() can't found device");
return null;
return MediaDeviceUtils.findMediaDevice(mMediaDevices, id);
}
protected void dispatchDeviceAdded(MediaDevice mediaDevice) {
@@ -96,7 +89,7 @@ public abstract class MediaManager {
protected void dispatchDeviceListAdded() {
synchronized (mCallbacks) {
for (MediaDeviceCallback callback : mCallbacks) {
callback.onDeviceListAdded(mMediaDevices);
callback.onDeviceListAdded(new ArrayList<>(mMediaDevices));
}
}
}
@@ -117,6 +110,14 @@ public abstract class MediaManager {
}
}
protected void dispatchActiveDeviceChanged(String id) {
synchronized (mCallbacks) {
for (MediaDeviceCallback callback : mCallbacks) {
callback.onActiveDeviceChanged(id);
}
}
}
/**
* Callback for notifying device is added, removed and attributes changed.
*/
@@ -153,5 +154,12 @@ public abstract class MediaManager {
* Callback for notifying MediaDevice attributes is changed.
*/
void onDeviceAttributesChanged();
/**
* Callback for notifying active MediaDevice is changed.
*
* @param id the id of MediaDevice
*/
void onActiveDeviceChanged(String id);
}
}

View File

@@ -15,6 +15,7 @@
*/
package com.android.settingslib.media;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.util.Log;
@@ -24,6 +25,8 @@ import com.android.settingslib.bluetooth.HearingAidProfile;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import java.util.List;
/**
* PhoneMediaDevice extends MediaDevice to represents Phone device.
*/
@@ -41,6 +44,30 @@ public class PhoneMediaDevice extends MediaDevice {
mLocalBluetoothManager = localBluetoothManager;
mProfileManager = mLocalBluetoothManager.getProfileManager();
initDeviceRecord();
mIsConnected = isPhoneActive();
}
private boolean isPhoneActive() {
boolean isActive = true;
final HearingAidProfile hapProfile = mProfileManager.getHearingAidProfile();
final A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile();
if (a2dpProfile.getActiveDevice() == null) {
final List<BluetoothDevice> activeDevices = hapProfile.getActiveDevices();
for (BluetoothDevice btDevice : activeDevices) {
if (btDevice != null) {
isActive = false;
break;
}
}
} else {
isActive = false;
}
return isActive;
}
@Override
@@ -60,14 +87,19 @@ public class PhoneMediaDevice extends MediaDevice {
return ID;
}
@Override
public void notifyConnectedChanged() {
mIsConnected = isPhoneActive();
}
@Override
public void connect() {
final HearingAidProfile hapProfile = mProfileManager.getHearingAidProfile();
final A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile();
if (hapProfile != null && a2dpProfile != null) {
mIsConnected =
hapProfile.setActiveDevice(null) && a2dpProfile.setActiveDevice(null);
mIsConnected = hapProfile.setActiveDevice(null) && a2dpProfile.setActiveDevice(null);
super.connect();
}
Log.d(TAG, "connect() device : " + getName() + ", is selected : " + mIsConnected);
}

View File

@@ -0,0 +1,270 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.media;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import androidx.mediarouter.media.MediaRouter;
import com.android.settingslib.bluetooth.A2dpProfile;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
public class MediaDeviceTest {
private static final Comparator<MediaDevice> COMPARATOR = Comparator.naturalOrder();
private static final String DEVICE_ADDRESS_1 = "AA:BB:CC:DD:EE:11";
private static final String DEVICE_ADDRESS_2 = "AA:BB:CC:DD:EE:22";
private static final String DEVICE_ADDRESS_3 = "AA:BB:CC:DD:EE:33";
private static final String DEVICE_NAME_1 = "TestName_1";
private static final String DEVICE_NAME_2 = "TestName_2";
private static final String DEVICE_NAME_3 = "TestName_3";
private static final String ROUTER_ID_1 = "RouterId_1";
private static final String ROUTER_ID_2 = "RouterId_2";
private static final String ROUTER_ID_3 = "RouterId_3";
private final BluetoothClass mHeadreeClass =
new BluetoothClass(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
private final BluetoothClass mCarkitClass =
new BluetoothClass(BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO);
@Mock
private A2dpProfile mA2dpProfile;
@Mock
private BluetoothDevice mDevice1;
@Mock
private BluetoothDevice mDevice2;
@Mock
private BluetoothDevice mDevice3;
@Mock
private CachedBluetoothDevice mCachedDevice1;
@Mock
private CachedBluetoothDevice mCachedDevice2;
@Mock
private CachedBluetoothDevice mCachedDevice3;
@Mock
private LocalBluetoothManager mLocalBluetoothManager;
@Mock
private LocalBluetoothProfileManager mProfileManager;
@Mock
private MediaRouter.RouteInfo mRouteInfo1;
@Mock
private MediaRouter.RouteInfo mRouteInfo2;
@Mock
private MediaRouter.RouteInfo mRouteInfo3;
private BluetoothMediaDevice mBluetoothMediaDevice1;
private BluetoothMediaDevice mBluetoothMediaDevice2;
private BluetoothMediaDevice mBluetoothMediaDevice3;
private Context mContext;
private InfoMediaDevice mInfoMediaDevice1;
private InfoMediaDevice mInfoMediaDevice2;
private InfoMediaDevice mInfoMediaDevice3;
private List<MediaDevice> mMediaDevices = new ArrayList<>();
private PhoneMediaDevice mPhoneMediaDevice;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
when(mCachedDevice1.getAddress()).thenReturn(DEVICE_ADDRESS_1);
when(mCachedDevice2.getAddress()).thenReturn(DEVICE_ADDRESS_2);
when(mCachedDevice3.getAddress()).thenReturn(DEVICE_ADDRESS_3);
when(mCachedDevice1.getName()).thenReturn(DEVICE_NAME_1);
when(mCachedDevice2.getName()).thenReturn(DEVICE_NAME_2);
when(mCachedDevice3.getName()).thenReturn(DEVICE_NAME_3);
when(mCachedDevice1.getDevice()).thenReturn(mDevice1);
when(mCachedDevice2.getDevice()).thenReturn(mDevice2);
when(mCachedDevice3.getDevice()).thenReturn(mDevice3);
when(mRouteInfo1.getId()).thenReturn(ROUTER_ID_1);
when(mRouteInfo2.getId()).thenReturn(ROUTER_ID_2);
when(mRouteInfo3.getId()).thenReturn(ROUTER_ID_3);
when(mRouteInfo1.getName()).thenReturn(DEVICE_NAME_1);
when(mRouteInfo2.getName()).thenReturn(DEVICE_NAME_2);
when(mRouteInfo3.getName()).thenReturn(DEVICE_NAME_3);
when(mLocalBluetoothManager.getProfileManager()).thenReturn(mProfileManager);
when(mProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
when(mA2dpProfile.getActiveDevice()).thenReturn(mDevice1);
mBluetoothMediaDevice1 = new BluetoothMediaDevice(mContext, mCachedDevice1);
mBluetoothMediaDevice2 = new BluetoothMediaDevice(mContext, mCachedDevice2);
mBluetoothMediaDevice3 = new BluetoothMediaDevice(mContext, mCachedDevice3);
mInfoMediaDevice1 = new InfoMediaDevice(mContext, mRouteInfo1);
mInfoMediaDevice2 = new InfoMediaDevice(mContext, mRouteInfo2);
mInfoMediaDevice3 = new InfoMediaDevice(mContext, mRouteInfo3);
mPhoneMediaDevice = new PhoneMediaDevice(mContext, mLocalBluetoothManager);
}
@Test
public void compareTo_carKit_nonCarKitBluetooth_carKitFirst() {
when(mDevice1.getBluetoothClass()).thenReturn(mHeadreeClass);
when(mDevice2.getBluetoothClass()).thenReturn(mCarkitClass);
mMediaDevices.add(mBluetoothMediaDevice1);
mMediaDevices.add(mBluetoothMediaDevice2);
assertThat(mMediaDevices.get(0)).isEqualTo(mBluetoothMediaDevice1);
Collections.sort(mMediaDevices, COMPARATOR);
assertThat(mMediaDevices.get(0)).isEqualTo(mBluetoothMediaDevice2);
}
@Test
public void compareTo_carKit_info_carKitFirst() {
when(mDevice1.getBluetoothClass()).thenReturn(mCarkitClass);
mMediaDevices.add(mInfoMediaDevice1);
mMediaDevices.add(mBluetoothMediaDevice1);
assertThat(mMediaDevices.get(0)).isEqualTo(mInfoMediaDevice1);
Collections.sort(mMediaDevices, COMPARATOR);
assertThat(mMediaDevices.get(0)).isEqualTo(mBluetoothMediaDevice1);
}
@Test
public void compareTo_carKit_phone_carKitFirst() {
when(mDevice1.getBluetoothClass()).thenReturn(mCarkitClass);
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_lastSelected_others_lastSelectedFirst() {
mMediaDevices.add(mBluetoothMediaDevice1);
mMediaDevices.add(mBluetoothMediaDevice2);
mBluetoothMediaDevice2.connect();
assertThat(mMediaDevices.get(0)).isEqualTo(mBluetoothMediaDevice1);
Collections.sort(mMediaDevices, COMPARATOR);
assertThat(mMediaDevices.get(0)).isEqualTo(mBluetoothMediaDevice2);
}
@Test
public void compareTo_connectionRecord_sortByRecord() {
mMediaDevices.add(mBluetoothMediaDevice1);
mMediaDevices.add(mBluetoothMediaDevice2);
mBluetoothMediaDevice1.connect();
mBluetoothMediaDevice2.connect();
mBluetoothMediaDevice2.connect();
// Reset last selected record
ConnectionRecordManager.getInstance().setConnectionRecord(mContext, null, 0);
assertThat(mMediaDevices.get(0)).isEqualTo(mBluetoothMediaDevice1);
assertThat(mMediaDevices.get(1)).isEqualTo(mBluetoothMediaDevice2);
Collections.sort(mMediaDevices, COMPARATOR);
assertThat(mMediaDevices.get(0)).isEqualTo(mBluetoothMediaDevice2);
assertThat(mMediaDevices.get(1)).isEqualTo(mBluetoothMediaDevice1);
}
@Test
public void compareTo_info_bluetooth_infoFirst() {
mMediaDevices.add(mBluetoothMediaDevice1);
mMediaDevices.add(mInfoMediaDevice1);
assertThat(mMediaDevices.get(0)).isEqualTo(mBluetoothMediaDevice1);
Collections.sort(mMediaDevices, COMPARATOR);
assertThat(mMediaDevices.get(0)).isEqualTo(mInfoMediaDevice1);
}
@Test
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_twoInfo_sortByAlphabet() {
mMediaDevices.add(mInfoMediaDevice2);
mMediaDevices.add(mInfoMediaDevice1);
assertThat(mMediaDevices.get(0)).isEqualTo(mInfoMediaDevice2);
Collections.sort(mMediaDevices, COMPARATOR);
assertThat(mMediaDevices.get(0)).isEqualTo(mInfoMediaDevice1);
}
@Test
public void compareTo_twoBluetooth_sortByAlphabet() {
mMediaDevices.add(mBluetoothMediaDevice2);
mMediaDevices.add(mBluetoothMediaDevice1);
assertThat(mMediaDevices.get(0)).isEqualTo(mBluetoothMediaDevice2);
Collections.sort(mMediaDevices, COMPARATOR);
assertThat(mMediaDevices.get(0)).isEqualTo(mBluetoothMediaDevice1);
}
// 1.mInfoMediaDevice1: Last Selected device
// 2.mBluetoothMediaDevice1: CarKit device
// 3.mInfoMediaDevice2: * 2 times usage
// 4.mInfoMediaDevice3: * 1 time usage
// 5.mBluetoothMediaDevice2: * 2 times usage
// 6.mBluetoothMediaDevice3: * 1 time usage
// 7.mPhoneMediaDevice: * 0 time usage
// Order: 2 -> 1 -> 3 -> 5 -> 4 -> 6 -> 7
@Test
public void compareTo_mixedDevices_carKitFirst() {
when(mDevice1.getBluetoothClass()).thenReturn(mCarkitClass);
when(mDevice2.getBluetoothClass()).thenReturn(mHeadreeClass);
when(mDevice3.getBluetoothClass()).thenReturn(mHeadreeClass);
mMediaDevices.add(mBluetoothMediaDevice1);
mMediaDevices.add(mBluetoothMediaDevice2);
mMediaDevices.add(mBluetoothMediaDevice3);
mMediaDevices.add(mInfoMediaDevice1);
mMediaDevices.add(mInfoMediaDevice2);
mMediaDevices.add(mInfoMediaDevice3);
mMediaDevices.add(mPhoneMediaDevice);
mBluetoothMediaDevice3.connect();
mBluetoothMediaDevice2.connect();
mBluetoothMediaDevice2.connect();
mInfoMediaDevice3.connect();
mInfoMediaDevice2.connect();
mInfoMediaDevice2.connect();
mInfoMediaDevice1.connect();
Collections.sort(mMediaDevices, COMPARATOR);
assertThat(mMediaDevices.get(0)).isEqualTo(mBluetoothMediaDevice1);
assertThat(mMediaDevices.get(1)).isEqualTo(mInfoMediaDevice1);
assertThat(mMediaDevices.get(2)).isEqualTo(mInfoMediaDevice2);
assertThat(mMediaDevices.get(3)).isEqualTo(mBluetoothMediaDevice2);
assertThat(mMediaDevices.get(4)).isEqualTo(mInfoMediaDevice3);
assertThat(mMediaDevices.get(5)).isEqualTo(mBluetoothMediaDevice3);
assertThat(mMediaDevices.get(6)).isEqualTo(mPhoneMediaDevice);
}
}