Merge "Do not allow draw on top for App notification settings"
diff --git a/res/layout/homepage_slice_tile.xml b/res/layout/homepage_slice_tile.xml
index 807c26b..e95129e 100644
--- a/res/layout/homepage_slice_tile.xml
+++ b/res/layout/homepage_slice_tile.xml
@@ -31,6 +31,7 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_gravity="center_vertical"
+            android:animateLayoutChanges="true"
             style="@style/SliceViewStyle"/>
 
         <!--dismissal view-->
diff --git a/res/xml/language_and_input.xml b/res/xml/language_and_input.xml
index b04bdf8..e3690a9 100644
--- a/res/xml/language_and_input.xml
+++ b/res/xml/language_and_input.xml
@@ -94,7 +94,8 @@
 
     <com.android.settings.widget.WorkOnlyCategory
         android:key="language_and_input_for_work_category"
-        android:title="@string/language_and_input_for_work_category_title">
+        android:title="@string/language_and_input_for_work_category_title"
+        settings:searchable="false">
 
         <Preference
             android:key="virtual_keyboards_for_work_pref"
diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java b/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java
index ea6ac43..13564b5 100644
--- a/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java
+++ b/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java
@@ -26,7 +26,6 @@
 import android.net.Uri;
 import android.os.Handler;
 import android.os.Looper;
-import android.text.format.DateUtils;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
@@ -50,7 +49,6 @@
     @VisibleForTesting
     static final int DEFAULT_CARD_COUNT = 4;
     static final int CARD_CONTENT_LOADER_ID = 1;
-    static final long CARD_CONTENT_LOADER_TIMEOUT_MS = DateUtils.SECOND_IN_MILLIS;
 
     private static final String TAG = "ContextualCardLoader";
     private static final long ELIGIBILITY_CHECKER_TIMEOUT_MS = 250;
diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java b/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java
index 3b8aacd..c829015 100644
--- a/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java
+++ b/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java
@@ -24,6 +24,8 @@
 
 import android.content.Context;
 import android.os.Bundle;
+import android.provider.Settings;
+import android.text.format.DateUtils;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.widget.BaseAdapter;
@@ -62,8 +64,12 @@
 public class ContextualCardManager implements ContextualCardLoader.CardContentLoaderListener,
         ContextualCardUpdateListener, LifecycleObserver, OnSaveInstanceState {
 
-    private static final String KEY_CONTEXTUAL_CARDS = "key_contextual_cards";
+    @VisibleForTesting
+    static final long CARD_CONTENT_LOADER_TIMEOUT_MS = DateUtils.SECOND_IN_MILLIS;
+    @VisibleForTesting
+    static final String KEY_GLOBAL_CARD_LOADER_TIMEOUT = "global_card_loader_timeout_key";
 
+    private static final String KEY_CONTEXTUAL_CARDS = "key_contextual_cards";
     private static final String TAG = "ContextualCardManager";
 
     //The list for Settings Custom Card
@@ -201,7 +207,8 @@
         }
 
         //only log homepage display upon a fresh launch
-        if (loadTime <= ContextualCardLoader.CARD_CONTENT_LOADER_TIMEOUT_MS) {
+        final long timeoutLimit = getCardLoaderTimeout(mContext);
+        if (loadTime <= timeoutLimit) {
             onContextualCardUpdated(cards.stream()
                     .collect(groupingBy(ContextualCard::getCardType)));
         }
@@ -239,6 +246,14 @@
         return getCardsWithSuggestionViewType(result);
     }
 
+    @VisibleForTesting
+    long getCardLoaderTimeout(Context context) {
+        // Return the timeout limit if Settings.Global has the KEY_GLOBAL_CARD_LOADER_TIMEOUT key,
+        // else return default timeout.
+        return Settings.Global.getLong(mContext.getContentResolver(),
+                KEY_GLOBAL_CARD_LOADER_TIMEOUT, CARD_CONTENT_LOADER_TIMEOUT_MS);
+    }
+
     private List<ContextualCard> getCardsWithSuggestionViewType(List<ContextualCard> cards) {
         // Shows as half cards if 2 suggestion type of cards are next to each other.
         // Shows as full card if 1 suggestion type of card lives alone.
diff --git a/src/com/android/settings/homepage/contextualcards/slices/BluetoothDevicesSlice.java b/src/com/android/settings/homepage/contextualcards/slices/BluetoothDevicesSlice.java
index 95412a8..3320be0 100644
--- a/src/com/android/settings/homepage/contextualcards/slices/BluetoothDevicesSlice.java
+++ b/src/com/android/settings/homepage/contextualcards/slices/BluetoothDevicesSlice.java
@@ -179,14 +179,9 @@
         final Collection<CachedBluetoothDevice> cachedDevices =
                 bluetoothManager.getCachedDeviceManager().getCachedDevicesCopy();
 
-        /**
-         * TODO(b/114807655): Contextual Home Page - Connected Device
-         * It's under discussion for including available media devices and currently connected
-         * devices from Bluetooth. Will update the devices list or remove TODO later.
-         */
-        // Get available media device list and sort them.
+        // Get all connected devices and sort them.
         return cachedDevices.stream()
-                .filter(device -> device.isConnected() && device.isConnectedA2dpDevice())
+                .filter(device -> device.getDevice().isConnected())
                 .sorted(COMPARATOR).collect(Collectors.toList());
     }
 
@@ -217,7 +212,7 @@
             return Utils.createIconWithDrawable(pair.first);
         } else {
             return IconCompat.createWithResource(mContext,
-                com.android.internal.R.drawable.ic_settings_bluetooth);
+                    com.android.internal.R.drawable.ic_settings_bluetooth);
         }
     }
 
@@ -226,18 +221,29 @@
         final List<ListBuilder.RowBuilder> bluetoothRows = new ArrayList<>();
         final List<CachedBluetoothDevice> bluetoothDevices = getConnectedBluetoothDevices();
         for (CachedBluetoothDevice bluetoothDevice : bluetoothDevices) {
-            bluetoothRows.add(new ListBuilder.RowBuilder()
+            final ListBuilder.RowBuilder rowBuilder = new ListBuilder.RowBuilder()
                     .setTitleItem(getBluetoothDeviceIcon(bluetoothDevice), ListBuilder.ICON_IMAGE)
                     .setTitle(bluetoothDevice.getName())
-                    .setSubtitle(bluetoothDevice.getConnectionSummary())
-                    .setPrimaryAction(buildBluetoothDeviceAction(bluetoothDevice))
-                    .addEndItem(buildBluetoothDetailDeepLinkAction(bluetoothDevice)));
+                    .setSubtitle(bluetoothDevice.getConnectionSummary());
+
+            if (bluetoothDevice.isConnectedA2dpDevice()) {
+                // For available media devices, the primary action is to active audio stream and
+                // add setting icon to the end to link detail page.
+                rowBuilder.setPrimaryAction(buildMediaBluetoothAction(bluetoothDevice));
+                rowBuilder.addEndItem(buildBluetoothDetailDeepLinkAction(bluetoothDevice));
+            } else {
+                // For other devices, the primary action is to link detail page.
+                rowBuilder.setPrimaryAction(buildBluetoothDetailDeepLinkAction(bluetoothDevice));
+            }
+
+            bluetoothRows.add(rowBuilder);
         }
 
         return bluetoothRows;
     }
 
-    private SliceAction buildBluetoothDeviceAction(CachedBluetoothDevice bluetoothDevice) {
+    @VisibleForTesting
+    SliceAction buildMediaBluetoothAction(CachedBluetoothDevice bluetoothDevice) {
         // Send broadcast to activate available media device.
         final Intent intent = new Intent(getUri().toString())
                 .setClass(mContext, SliceBroadcastReceiver.class)
@@ -250,7 +256,8 @@
                 bluetoothDevice.getName());
     }
 
-    private SliceAction buildBluetoothDetailDeepLinkAction(CachedBluetoothDevice bluetoothDevice) {
+    @VisibleForTesting
+    SliceAction buildBluetoothDetailDeepLinkAction(CachedBluetoothDevice bluetoothDevice) {
         return SliceAction.createDeeplink(
                 getBluetoothDetailIntent(bluetoothDevice),
                 IconCompat.createWithResource(mContext, R.drawable.ic_settings_24dp),
diff --git a/src/com/android/settings/homepage/contextualcards/slices/SliceFullCardRendererHelper.java b/src/com/android/settings/homepage/contextualcards/slices/SliceFullCardRendererHelper.java
index ef0a67d..ee63536 100644
--- a/src/com/android/settings/homepage/contextualcards/slices/SliceFullCardRendererHelper.java
+++ b/src/com/android/settings/homepage/contextualcards/slices/SliceFullCardRendererHelper.java
@@ -16,6 +16,7 @@
 
 package com.android.settings.homepage.contextualcards.slices;
 
+import android.animation.LayoutTransition;
 import android.content.Context;
 import android.view.View;
 
@@ -94,6 +95,7 @@
         public SliceViewHolder(View view) {
             super(view);
             sliceView = view.findViewById(R.id.slice_view);
+            sliceView.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
         }
     }
 }
diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardManagerTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardManagerTest.java
index 1a0539c..eb9a461 100644
--- a/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardManagerTest.java
+++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardManagerTest.java
@@ -31,6 +31,7 @@
 
 import android.content.Context;
 import android.net.Uri;
+import android.provider.Settings;
 import android.util.ArrayMap;
 
 import com.android.settings.homepage.contextualcards.conditional.ConditionFooterContextualCard;
@@ -126,6 +127,24 @@
     }
 
     @Test
+    public void getCardLoaderTimeout_noConfiguredTimeout_shouldReturnDefaultTimeout() {
+        final long timeout = mManager.getCardLoaderTimeout(mContext);
+
+        assertThat(timeout).isEqualTo(ContextualCardManager.CARD_CONTENT_LOADER_TIMEOUT_MS);
+    }
+
+    @Test
+    public void getCardLoaderTimeout_hasConfiguredTimeout_shouldReturnConfiguredTimeout() {
+        final long configuredTimeout = 5000L;
+        Settings.Global.putLong(mContext.getContentResolver(),
+                ContextualCardManager.KEY_GLOBAL_CARD_LOADER_TIMEOUT, configuredTimeout);
+
+        final long timeout = mManager.getCardLoaderTimeout(mContext);
+
+        assertThat(timeout).isEqualTo(configuredTimeout);
+    }
+
+    @Test
     public void onFinishCardLoading_fastLoad_shouldCallOnContextualCardUpdated() {
         mManager.mStartTime = System.currentTimeMillis();
         final ContextualCardManager manager = spy(mManager);
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 77fc5d9..4a23c33 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,6 +23,7 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -83,7 +84,7 @@
 
         // Mock the icon and detail intent of Bluetooth.
         mIcon = IconCompat.createWithResource(mContext,
-            com.android.internal.R.drawable.ic_settings_bluetooth);
+                com.android.internal.R.drawable.ic_settings_bluetooth);
         mDetailIntent = PendingIntent.getActivity(mContext, 0, new Intent("test action"), 0);
         doReturn(mIcon).when(mBluetoothDevicesSlice).getBluetoothDeviceIcon(any());
         doReturn(mDetailIntent).when(mBluetoothDevicesSlice).getBluetoothDetailIntent(any());
@@ -122,6 +123,27 @@
     }
 
     @Test
+    public void getSlice_hasMediaBluetoothDevice_shouldBuildMediaBluetoothAction() {
+        mockBluetoothDeviceList(1 /* deviceCount */);
+        doReturn(true).when(mBluetoothDeviceList.get(0)).isConnectedA2dpDevice();
+        doReturn(mBluetoothDeviceList).when(mBluetoothDevicesSlice).getConnectedBluetoothDevices();
+
+        mBluetoothDevicesSlice.getSlice();
+
+        verify(mBluetoothDevicesSlice).buildMediaBluetoothAction(any());
+    }
+
+    @Test
+    public void getSlice_noMediaBluetoothDevice_shouldNotBuildMediaBluetoothAction() {
+        mockBluetoothDeviceList(1 /* deviceCount */);
+        doReturn(mBluetoothDeviceList).when(mBluetoothDevicesSlice).getConnectedBluetoothDevices();
+
+        mBluetoothDevicesSlice.getSlice();
+
+        verify(mBluetoothDevicesSlice, never()).buildMediaBluetoothAction(any());
+    }
+
+    @Test
     public void getSlice_noBluetoothDevices_shouldHaveNoBluetoothDevicesTitle() {
         doReturn(mBluetoothDeviceList).when(mBluetoothDevicesSlice).getConnectedBluetoothDevices();
 
@@ -175,7 +197,6 @@
         doReturn(BLUETOOTH_MOCK_TITLE).when(mCachedBluetoothDevice).getName();
         doReturn(BLUETOOTH_MOCK_SUMMARY).when(mCachedBluetoothDevice).getConnectionSummary();
         doReturn(BLUETOOTH_MOCK_ADDRESS).when(mCachedBluetoothDevice).getAddress();
-        doReturn(true).when(mCachedBluetoothDevice).isConnectedA2dpDevice();
         for (int i = 0; i < deviceCount; i++) {
             mBluetoothDeviceList.add(mCachedBluetoothDevice);
         }