Show paired devices on Bluetooth device slice card

- support tapping to activate for all available media devices including
  Hearing aid and Headset
- support tapping to connect for previously connected devices

Bug: 149667096
Test: robotest
Change-Id: I25f74b1b20fbb1876200a561775aa675ff60ac37
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index dc220f2..ef38240 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -123,7 +123,8 @@
                   android:label="@string/settings_label_launcher"
                   android:theme="@style/Theme.Settings.Home"
                   android:taskAffinity="com.android.settings.root"
-                  android:launchMode="singleTask">
+                  android:launchMode="singleTask"
+                  android:configChanges="keyboard|keyboardHidden">
             <intent-filter android:priority="1">
                 <action android:name="android.settings.SETTINGS" />
                 <category android:name="android.intent.category.DEFAULT" />
diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java
index 3778862..21312a5 100644
--- a/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java
+++ b/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java
@@ -69,18 +69,17 @@
 
     public BluetoothDeviceUpdater(Context context, DashboardFragment fragment,
             DevicePreferenceCallback devicePreferenceCallback) {
-        this(fragment, devicePreferenceCallback, Utils.getLocalBtManager(context));
+        this(context, fragment, devicePreferenceCallback, Utils.getLocalBtManager(context));
     }
 
     @VisibleForTesting
-    BluetoothDeviceUpdater(DashboardFragment fragment,
+    BluetoothDeviceUpdater(Context context, DashboardFragment fragment,
             DevicePreferenceCallback devicePreferenceCallback, LocalBluetoothManager localManager) {
         mFragment = fragment;
         mDevicePreferenceCallback = devicePreferenceCallback;
         mPreferenceMap = new HashMap<>();
         mLocalManager = localManager;
-        mMetricsFeatureProvider = FeatureFactory.getFactory(mFragment.getContext())
-                .getMetricsFeatureProvider();
+        mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
     }
 
     /**
diff --git a/src/com/android/settings/homepage/contextualcards/slices/BluetoothDevicesSlice.java b/src/com/android/settings/homepage/contextualcards/slices/BluetoothDevicesSlice.java
index 53782e5..14a93b8 100644
--- a/src/com/android/settings/homepage/contextualcards/slices/BluetoothDevicesSlice.java
+++ b/src/com/android/settings/homepage/contextualcards/slices/BluetoothDevicesSlice.java
@@ -39,8 +39,10 @@
 import com.android.settings.R;
 import com.android.settings.SubSettings;
 import com.android.settings.Utils;
+import com.android.settings.bluetooth.AvailableMediaBluetoothDeviceUpdater;
 import com.android.settings.bluetooth.BluetoothDeviceDetailsFragment;
 import com.android.settings.bluetooth.BluetoothPairingDetail;
+import com.android.settings.bluetooth.SavedBluetoothDeviceUpdater;
 import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment;
 import com.android.settings.core.SubSettingLauncher;
 import com.android.settings.slices.CustomSliceRegistry;
@@ -78,9 +80,15 @@
     private static final String TAG = "BluetoothDevicesSlice";
 
     private final Context mContext;
+    private final AvailableMediaBluetoothDeviceUpdater mAvailableMediaBtDeviceUpdater;
+    private final SavedBluetoothDeviceUpdater mSavedBtDeviceUpdater;
 
     public BluetoothDevicesSlice(Context context) {
         mContext = context;
+        mAvailableMediaBtDeviceUpdater = new AvailableMediaBluetoothDeviceUpdater(mContext,
+                null /* fragment */, null /* devicePreferenceCallback */);
+        mSavedBtDeviceUpdater = new SavedBluetoothDeviceUpdater(mContext,
+                null /* fragment */, null /* devicePreferenceCallback */);
     }
 
     @Override
@@ -123,10 +131,10 @@
         final List<ListBuilder.RowBuilder> rows = getBluetoothRowBuilder();
 
         // Get displayable device count.
-        final int deviceCount = Math.min(rows.size(), DEFAULT_EXPANDED_ROW_COUNT);
+        final int displayableCount = Math.min(rows.size(), DEFAULT_EXPANDED_ROW_COUNT);
 
         // According to the displayable device count to add bluetooth device rows.
-        for (int i = 0; i < deviceCount; i++) {
+        for (int i = 0; i < displayableCount; i++) {
             listBuilder.addRow(rows.get(i));
         }
 
@@ -148,11 +156,14 @@
 
     @Override
     public void onNotifyChange(Intent intent) {
-        // Activate available media device.
         final int bluetoothDeviceHashCode = intent.getIntExtra(BLUETOOTH_DEVICE_HASH_CODE, -1);
-        for (CachedBluetoothDevice cachedBluetoothDevice : getConnectedBluetoothDevices()) {
-            if (cachedBluetoothDevice.hashCode() == bluetoothDeviceHashCode) {
-                cachedBluetoothDevice.setActive();
+        for (CachedBluetoothDevice device : getPairedBluetoothDevices()) {
+            if (device.hashCode() == bluetoothDeviceHashCode) {
+                if (device.isConnected()) {
+                    device.setActive();
+                } else if (!device.isBusy()) {
+                    device.connect();
+                }
                 return;
             }
         }
@@ -164,7 +175,7 @@
     }
 
     @VisibleForTesting
-    List<CachedBluetoothDevice> getConnectedBluetoothDevices() {
+    List<CachedBluetoothDevice> getPairedBluetoothDevices() {
         final List<CachedBluetoothDevice> bluetoothDeviceList = new ArrayList<>();
 
         // If Bluetooth is disable, skip to get the Bluetooth devices.
@@ -174,19 +185,18 @@
         }
 
         // Get the Bluetooth devices from LocalBluetoothManager.
-        final LocalBluetoothManager bluetoothManager =
+        final LocalBluetoothManager localBtManager =
                 com.android.settings.bluetooth.Utils.getLocalBtManager(mContext);
-        if (bluetoothManager == null) {
+        if (localBtManager == null) {
             Log.i(TAG, "Cannot get Bluetooth devices, Bluetooth is unsupported.");
             return bluetoothDeviceList;
         }
         final Collection<CachedBluetoothDevice> cachedDevices =
-                bluetoothManager.getCachedDeviceManager().getCachedDevicesCopy();
+                localBtManager.getCachedDeviceManager().getCachedDevicesCopy();
 
-        // Get all connected devices and sort them.
+        // Get all paired devices and sort them.
         return cachedDevices.stream()
-                .filter(device -> device.getDevice().getBondState() == BluetoothDevice.BOND_BONDED
-                        && device.getDevice().isConnected())
+                .filter(device -> device.getDevice().getBondState() == BluetoothDevice.BOND_BONDED)
                 .sorted(COMPARATOR).collect(Collectors.toList());
     }
 
@@ -242,21 +252,21 @@
     private List<ListBuilder.RowBuilder> getBluetoothRowBuilder() {
         // According to Bluetooth devices to create row builders.
         final List<ListBuilder.RowBuilder> bluetoothRows = new ArrayList<>();
-        final List<CachedBluetoothDevice> bluetoothDevices = getConnectedBluetoothDevices();
-        for (CachedBluetoothDevice bluetoothDevice : bluetoothDevices) {
+        for (CachedBluetoothDevice device : getPairedBluetoothDevices()) {
             final ListBuilder.RowBuilder rowBuilder = new ListBuilder.RowBuilder()
-                    .setTitleItem(getBluetoothDeviceIcon(bluetoothDevice), ListBuilder.ICON_IMAGE)
-                    .setTitle(bluetoothDevice.getName())
-                    .setSubtitle(bluetoothDevice.getConnectionSummary());
+                    .setTitleItem(getBluetoothDeviceIcon(device), ListBuilder.ICON_IMAGE)
+                    .setTitle(device.getName())
+                    .setSubtitle(device.getConnectionSummary());
 
-            if (bluetoothDevice.isConnectedA2dpDevice()) {
-                // For available media devices, the primary action is to activate audio stream and
-                // add setting icon to the end to link detail page.
-                rowBuilder.setPrimaryAction(buildMediaBluetoothAction(bluetoothDevice));
-                rowBuilder.addEndItem(buildBluetoothDetailDeepLinkAction(bluetoothDevice));
+            if (mAvailableMediaBtDeviceUpdater.isFilterMatched(device)
+                    || mSavedBtDeviceUpdater.isFilterMatched(device)) {
+                // For all available media devices and previously connected devices, the primary
+                // action is to activate or connect, and the end gear icon links to detail page.
+                rowBuilder.setPrimaryAction(buildPrimaryBluetoothAction(device));
+                rowBuilder.addEndItem(buildBluetoothDetailDeepLinkAction(device));
             } else {
-                // For other devices, the primary action is to link detail page.
-                rowBuilder.setPrimaryAction(buildBluetoothDetailDeepLinkAction(bluetoothDevice));
+                // For other devices, the primary action is to link to detail page.
+                rowBuilder.setPrimaryAction(buildBluetoothDetailDeepLinkAction(device));
             }
 
             bluetoothRows.add(rowBuilder);
@@ -266,8 +276,7 @@
     }
 
     @VisibleForTesting
-    SliceAction buildMediaBluetoothAction(CachedBluetoothDevice bluetoothDevice) {
-        // Send broadcast to activate available media device.
+    SliceAction buildPrimaryBluetoothAction(CachedBluetoothDevice bluetoothDevice) {
         final Intent intent = new Intent(getUri().toString())
                 .setClass(mContext, SliceBroadcastReceiver.class)
                 .putExtra(BLUETOOTH_DEVICE_HASH_CODE, bluetoothDevice.hashCode());
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceUpdaterTest.java
index 260fc41..fcc7f51 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceUpdaterTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceUpdaterTest.java
@@ -102,7 +102,7 @@
         mPreference = new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
                 false, BluetoothDevicePreference.SortType.TYPE_DEFAULT);
         mBluetoothDeviceUpdater =
-            new BluetoothDeviceUpdater(mDashboardFragment, mDevicePreferenceCallback,
+            new BluetoothDeviceUpdater(mContext, mDashboardFragment, mDevicePreferenceCallback,
                     mLocalManager) {
                 @Override
                 public boolean isFilterMatched(CachedBluetoothDevice cachedBluetoothDevice) {
diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/BluetoothDevicesSliceTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/BluetoothDevicesSliceTest.java
index cec3bee..a0b2141 100644
--- a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/BluetoothDevicesSliceTest.java
+++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/BluetoothDevicesSliceTest.java
@@ -23,12 +23,15 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.PendingIntent;
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
 import android.content.Context;
 import android.content.Intent;
 
@@ -62,6 +65,7 @@
 import java.util.List;
 
 @RunWith(RobolectricTestRunner.class)
+@Config(shadows = ShadowBluetoothAdapter.class)
 public class BluetoothDevicesSliceTest {
 
     private static final String BLUETOOTH_MOCK_ADDRESS = "00:11:00:11:00:11";
@@ -96,6 +100,13 @@
 
         // Initial Bluetooth device list.
         mBluetoothDeviceList = new ArrayList<>();
+
+        final BluetoothAdapter defaultAdapter = BluetoothAdapter.getDefaultAdapter();
+        if (defaultAdapter != null) {
+            final ShadowBluetoothAdapter shadowBluetoothAdapter = Shadow.extract(defaultAdapter);
+            shadowBluetoothAdapter.setEnabled(true);
+            shadowBluetoothAdapter.setState(BluetoothAdapter.STATE_ON);
+        }
     }
 
     @After
@@ -114,7 +125,6 @@
     }
 
     @Test
-    @Config(shadows = ShadowBluetoothAdapter.class)
     public void getSlice_hasBluetoothHardware_shouldHaveBluetoothDevicesTitleAndPairNewDevice() {
         final Slice slice = mBluetoothDevicesSlice.getSlice();
 
@@ -127,12 +137,9 @@
     }
 
     @Test
-    @Config(shadows = ShadowBluetoothAdapter.class)
     public void getSlice_hasBluetoothDevices_shouldMatchBluetoothMockTitle() {
-        final ShadowBluetoothAdapter adapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
-        adapter.setState(BluetoothAdapter.STATE_ON);
         mockBluetoothDeviceList(1);
-        doReturn(mBluetoothDeviceList).when(mBluetoothDevicesSlice).getConnectedBluetoothDevices();
+        doReturn(mBluetoothDeviceList).when(mBluetoothDevicesSlice).getPairedBluetoothDevices();
 
         final Slice slice = mBluetoothDevicesSlice.getSlice();
 
@@ -141,39 +148,43 @@
     }
 
     @Test
-    @Config(shadows = ShadowBluetoothAdapter.class)
-    public void getSlice_hasMediaBluetoothDevice_shouldBuildMediaBluetoothAction() {
-        final ShadowBluetoothAdapter adapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
-        adapter.setState(BluetoothAdapter.STATE_ON);
-        mockBluetoothDeviceList(1 /* deviceCount */);
-        doReturn(true).when(mBluetoothDeviceList.get(0)).isConnectedA2dpDevice();
-        doReturn(mBluetoothDeviceList).when(mBluetoothDevicesSlice).getConnectedBluetoothDevices();
+    public void getSlice_hasAvailableMediaDevice_shouldBuildPrimaryBluetoothAction() {
+        mockBluetoothDeviceList(1);
+        when(mBluetoothDeviceList.get(0).getDevice().isConnected()).thenReturn(true);
+        doReturn(true).when(mBluetoothDeviceList.get(0)).isConnectedHearingAidDevice();
+        doReturn(mBluetoothDeviceList).when(mBluetoothDevicesSlice).getPairedBluetoothDevices();
 
         mBluetoothDevicesSlice.getSlice();
 
-        verify(mBluetoothDevicesSlice).buildMediaBluetoothAction(any());
+        verify(mBluetoothDevicesSlice).buildPrimaryBluetoothAction(any());
     }
 
     @Test
-    @Config(shadows = ShadowBluetoothAdapter.class)
-    public void getSlice_noMediaBluetoothDevice_shouldNotBuildMediaBluetoothAction() {
-        final ShadowBluetoothAdapter adapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
-        adapter.setState(BluetoothAdapter.STATE_ON);
-        mockBluetoothDeviceList(1 /* deviceCount */);
-        doReturn(mBluetoothDeviceList).when(mBluetoothDevicesSlice).getConnectedBluetoothDevices();
+    public void getSlice_hasPreviouslyConnectedDevice_shouldBuildPrimaryBluetoothAction() {
+        mockBluetoothDeviceList(1);
+        when(mBluetoothDeviceList.get(0).getDevice().isConnected()).thenReturn(false);
+        doReturn(mBluetoothDeviceList).when(mBluetoothDevicesSlice).getPairedBluetoothDevices();
 
         mBluetoothDevicesSlice.getSlice();
 
-        verify(mBluetoothDevicesSlice, never()).buildMediaBluetoothAction(any());
+        verify(mBluetoothDevicesSlice).buildPrimaryBluetoothAction(any());
     }
 
     @Test
-    @Config(shadows = ShadowBluetoothAdapter.class)
+    public void getSlice_hasNonMediaDeviceConnected_shouldNotBuildPrimaryBluetoothAction() {
+        mockBluetoothDeviceList(1);
+        when(mBluetoothDeviceList.get(0).getDevice().isConnected()).thenReturn(true);
+        doReturn(mBluetoothDeviceList).when(mBluetoothDevicesSlice).getPairedBluetoothDevices();
+
+        mBluetoothDevicesSlice.getSlice();
+
+        verify(mBluetoothDevicesSlice, never()).buildPrimaryBluetoothAction(any());
+    }
+
+    @Test
     public void getSlice_exceedDefaultRowCount_shouldOnlyShowDefaultRows() {
-        final ShadowBluetoothAdapter adapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
-        adapter.setState(BluetoothAdapter.STATE_ON);
         mockBluetoothDeviceList(BluetoothDevicesSlice.DEFAULT_EXPANDED_ROW_COUNT + 1);
-        doReturn(mBluetoothDeviceList).when(mBluetoothDevicesSlice).getConnectedBluetoothDevices();
+        doReturn(mBluetoothDeviceList).when(mBluetoothDevicesSlice).getPairedBluetoothDevices();
 
         final Slice slice = mBluetoothDevicesSlice.getSlice();
 
@@ -183,9 +194,10 @@
     }
 
     @Test
-    public void onNotifyChange_mediaDevice_shouldActivateDevice() {
+    public void onNotifyChange_connectedDevice_shouldActivateDevice() {
         mockBluetoothDeviceList(1);
-        doReturn(mBluetoothDeviceList).when(mBluetoothDevicesSlice).getConnectedBluetoothDevices();
+        doReturn(true).when(mBluetoothDeviceList.get(0)).isConnected();
+        doReturn(mBluetoothDeviceList).when(mBluetoothDevicesSlice).getPairedBluetoothDevices();
         final Intent intent = new Intent().putExtra(
                 BluetoothDevicesSlice.BLUETOOTH_DEVICE_HASH_CODE,
                 mCachedBluetoothDevice.hashCode());
@@ -195,7 +207,41 @@
         verify(mCachedBluetoothDevice).setActive();
     }
 
+    @Test
+    public void onNotifyChange_availableDisconnectedDevice_shouldConnectToDevice() {
+        mockBluetoothDeviceList(1);
+        doReturn(false).when(mBluetoothDeviceList.get(0)).isConnected();
+        doReturn(false).when(mBluetoothDeviceList.get(0)).isBusy();
+        doReturn(mBluetoothDeviceList).when(mBluetoothDevicesSlice).getPairedBluetoothDevices();
+        final Intent intent = new Intent().putExtra(
+                BluetoothDevicesSlice.BLUETOOTH_DEVICE_HASH_CODE,
+                mCachedBluetoothDevice.hashCode());
+
+        mBluetoothDevicesSlice.onNotifyChange(intent);
+
+        verify(mCachedBluetoothDevice).connect();
+    }
+
+    @Test
+    public void onNotifyChange_busyDisconnectedDevice_shouldDoNothing() {
+        mockBluetoothDeviceList(1);
+        doReturn(false).when(mBluetoothDeviceList.get(0)).isConnected();
+        doReturn(true).when(mBluetoothDeviceList.get(0)).isBusy();
+        doReturn(mBluetoothDeviceList).when(mBluetoothDevicesSlice).getPairedBluetoothDevices();
+        final Intent intent = new Intent().putExtra(
+                BluetoothDevicesSlice.BLUETOOTH_DEVICE_HASH_CODE,
+                mCachedBluetoothDevice.hashCode());
+
+        mBluetoothDevicesSlice.onNotifyChange(intent);
+
+        verify(mCachedBluetoothDevice, never()).setActive();
+        verify(mCachedBluetoothDevice, never()).connect();
+    }
+
     private void mockBluetoothDeviceList(int deviceCount) {
+        final BluetoothDevice device = mock(BluetoothDevice.class);
+        doReturn(BluetoothDevice.BOND_BONDED).when(device).getBondState();
+        doReturn(device).when(mCachedBluetoothDevice).getDevice();
         doReturn(BLUETOOTH_MOCK_TITLE).when(mCachedBluetoothDevice).getName();
         doReturn(BLUETOOTH_MOCK_SUMMARY).when(mCachedBluetoothDevice).getConnectionSummary();
         doReturn(BLUETOOTH_MOCK_ADDRESS).when(mCachedBluetoothDevice).getAddress();