From b3d68f670f52778c67ecb9bf88d07158f2d54e84 Mon Sep 17 00:00:00 2001 From: hughchen Date: Tue, 11 Feb 2020 14:30:14 +0800 Subject: [PATCH] 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 --- .../settingslib/media/InfoMediaManager.java | 12 ++-- .../settingslib/media/LocalMediaManager.java | 59 ++++++++++++++++++- .../media/InfoMediaManagerTest.java | 42 +++++++++++++ .../media/LocalMediaManagerTest.java | 58 ++++++++++++++++++ .../shadow/ShadowBluetoothAdapter.java | 11 ++++ 5 files changed, 173 insertions(+), 9 deletions(-) diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java index e91096760180e..8e4a982fde8ca 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java @@ -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 routes) { refreshDevices(); } + + @Override + public void onRoutesRemoved(List routes) { + refreshDevices(); + } } } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java index 984ab11a1a712..f8db70ae0963a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java @@ -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 mMediaDevices = new ArrayList<>(); @VisibleForTesting + List 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 devices) { mMediaDevices.clear(); mMediaDevices.addAll(devices); + mMediaDevices.addAll(buildDisconnectedBluetoothDevice()); + final MediaDevice infoMediaDevice = mInfoMediaManager.getCurrentConnectedDevice(); mCurrentConnectedDevice = infoMediaDevice != null ? infoMediaDevice : updateCurrentConnectedDevice(); dispatchDeviceListUpdate(); } + private List buildDisconnectedBluetoothDevice() { + for (MediaDevice device : mDisconnectedMediaDevices) { + ((BluetoothMediaDevice) device).getCachedDevice() + .unregisterCallback(mDeviceAttributeChangeCallback); + } + mDisconnectedMediaDevices.clear(); + final List 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(); + } + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java index 05f5fa0afc9e0..8b815bfba19dc 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java @@ -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 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 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()); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java index 3d67ba053a45d..3611dfefbb7f9 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java @@ -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 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 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 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()); + } } diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java index 906dba487734d..015ce149a9c4f 100644 --- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java +++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java @@ -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 mSupportedProfiles; + private List mMostRecentlyConnectedDevices; private BluetoothProfile.ServiceListener mServiceListener; @Implementation @@ -50,4 +52,13 @@ public class ShadowBluetoothAdapter extends org.robolectric.shadows.ShadowBlueto public void setSupportedProfiles(List supportedProfiles) { mSupportedProfiles = supportedProfiles; } + + @Implementation + protected List getMostRecentlyConnectedDevices() { + return mMostRecentlyConnectedDevices; + } + + public void setMostRecentlyConnectedDevices(List list) { + mMostRecentlyConnectedDevices = list; + } }