Merge "Remove unused resource from TetherServiceTest" into rvc-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index bd04d89..4ce8f36 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -219,7 +219,7 @@
         <activity
             android:name=".Settings$ConnectedDeviceDashboardActivity"
             android:label="@string/connected_devices_dashboard_title"
-            android:icon="@drawable/ic_devices_other">
+            android:icon="@drawable/ic_homepage_connected_device">
             <intent-filter android:priority="1">
                 <action android:name="android.settings.BLUETOOTH_SETTINGS" />
                 <category android:name="android.intent.category.DEFAULT" />
@@ -254,7 +254,7 @@
         <activity
             android:name="Settings$WifiSettingsActivity"
             android:label="@string/wifi_settings"
-            android:icon="@drawable/ic_settings_wireless"
+            android:icon="@drawable/ic_homepage_network"
             android:configChanges="orientation|keyboardHidden|screenSize">
             <intent-filter android:priority="1">
                 <action android:name="android.settings.WIFI_SETTINGS" />
@@ -273,7 +273,7 @@
         <activity
             android:name="Settings$WifiSettings2Activity"
             android:label="@string/wifi_settings"
-            android:icon="@drawable/ic_settings_wireless"
+            android:icon="@drawable/ic_homepage_network"
             android:configChanges="orientation|keyboardHidden|screenSize">
             <intent-filter android:priority="1">
                 <action android:name="android.settings.WIFI_SETTINGS2" />
@@ -492,7 +492,7 @@
         <activity
             android:name="Settings$TetherSettingsActivity"
             android:label="@string/tether_settings_title_all"
-            android:icon="@drawable/ic_settings_wireless">
+            android:icon="@drawable/ic_homepage_network">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <action android:name="android.settings.TETHER_SETTINGS" />
@@ -738,7 +738,7 @@
         <activity
             android:name="Settings$ZenModeSettingsActivity"
             android:label="@string/zen_mode_settings_title"
-            android:icon="@drawable/ic_notifications"
+            android:icon="@drawable/ic_homepage_notification"
             android:exported="true">
             <intent-filter android:priority="1">
                 <action android:name="android.settings.ZEN_MODE_SETTINGS" />
@@ -909,7 +909,7 @@
             android:name="Settings$NightDisplaySettingsActivity"
             android:label="@string/night_display_title"
             android:enabled="@*android:bool/config_nightDisplayAvailable"
-            android:icon="@drawable/ic_settings_night_display">
+            android:icon="@drawable/ic_homepage_night_display">
             <intent-filter android:priority="32">
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="com.android.settings.SHORTCUT" />
@@ -1225,7 +1225,7 @@
         <activity
             android:name="Settings$LocationSettingsActivity"
             android:label="@string/location_settings_title"
-            android:icon="@drawable/ic_settings_location"
+            android:icon="@drawable/ic_homepage_location"
             android:configChanges="orientation|keyboardHidden|screenSize">
             <intent-filter android:priority="1">
                 <action android:name="android.settings.LOCATION_SOURCE_SETTINGS" />
@@ -2179,7 +2179,7 @@
         <activity
             android:name="Settings$PowerUsageSummaryActivity"
             android:label="@string/power_usage_summary_title"
-            android:icon="@drawable/ic_settings_battery">
+            android:icon="@drawable/ic_homepage_battery">
             <intent-filter android:priority="1">
                 <action android:name="android.intent.action.POWER_USAGE_SUMMARY" />
                 <category android:name="android.intent.category.DEFAULT" />
diff --git a/color-check-baseline.xml b/color-check-baseline.xml
index 75f46e5..f959f91 100644
--- a/color-check-baseline.xml
+++ b/color-check-baseline.xml
@@ -2329,6 +2329,38 @@
         priority="4"
         summary="Using hardcoded color"
         explanation="Hardcoded color values are bad because theme changes cannot be uniformly applied.Instead use the theme specific colors such as `?android:attr/textColorPrimary` in attributes.&#xA;This ensures that a theme change from a light to a dark theme can be uniformlyapplied across the app."
+        errorLine1="            android:color=&quot;@color/homepage_generic_icon_background&quot; />"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="res/drawable/ic_homepage_night_display.xml"
+            line="24"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="HardCodedColor"
+        severity="Error"
+        message="Avoid using hardcoded color"
+        category="Correctness"
+        priority="4"
+        summary="Using hardcoded color"
+        explanation="Hardcoded color values are bad because theme changes cannot be uniformly applied.Instead use the theme specific colors such as `?android:attr/textColorPrimary` in attributes.&#xA;This ensures that a theme change from a light to a dark theme can be uniformlyapplied across the app."
+        errorLine1="            android:color=&quot;@color/homepage_generic_icon_background&quot; />"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="res/drawable/ic_homepage_notification.xml"
+            line="24"
+            column="13"/>
+    </issue>
+
+    <issue
+        id="HardCodedColor"
+        severity="Error"
+        message="Avoid using hardcoded color"
+        category="Correctness"
+        priority="4"
+        summary="Using hardcoded color"
+        explanation="Hardcoded color values are bad because theme changes cannot be uniformly applied.Instead use the theme specific colors such as `?android:attr/textColorPrimary` in attributes.&#xA;This ensures that a theme change from a light to a dark theme can be uniformlyapplied across the app."
         errorLine1="                android:color=&quot;@color/notification_alert_color&quot; />"
         errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
diff --git a/res/drawable/ic_homepage_night_display.xml b/res/drawable/ic_homepage_night_display.xml
new file mode 100644
index 0000000..bfd6269
--- /dev/null
+++ b/res/drawable/ic_homepage_night_display.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2020 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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item>
+        <com.android.settingslib.widget.AdaptiveIconShapeDrawable
+            android:width="@dimen/dashboard_tile_image_size"
+            android:height="@dimen/dashboard_tile_image_size"
+            android:color="@color/homepage_generic_icon_background" />
+    </item>
+
+    <item
+        android:width="@dimen/dashboard_tile_foreground_image_size"
+        android:height="@dimen/dashboard_tile_foreground_image_size"
+        android:start="@dimen/dashboard_tile_foreground_image_inset"
+        android:top="@dimen/dashboard_tile_foreground_image_inset"
+        android:drawable="@drawable/ic_settings_night_display_white" />
+</layer-list>
diff --git a/res/drawable/ic_homepage_notification.xml b/res/drawable/ic_homepage_notification.xml
new file mode 100644
index 0000000..d8c8d6d
--- /dev/null
+++ b/res/drawable/ic_homepage_notification.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2020 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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item>
+        <com.android.settingslib.widget.AdaptiveIconShapeDrawable
+            android:width="@dimen/dashboard_tile_image_size"
+            android:height="@dimen/dashboard_tile_image_size"
+            android:color="@color/homepage_generic_icon_background" />
+    </item>
+
+    <item
+        android:width="@dimen/dashboard_tile_foreground_image_size"
+        android:height="@dimen/dashboard_tile_foreground_image_size"
+        android:start="@dimen/dashboard_tile_foreground_image_inset"
+        android:top="@dimen/dashboard_tile_foreground_image_inset"
+        android:drawable="@drawable/ic_notifications_white" />
+</layer-list>
diff --git a/res/drawable/ic_notifications_white.xml b/res/drawable/ic_notifications_white.xml
new file mode 100644
index 0000000..1a37b56
--- /dev/null
+++ b/res/drawable/ic_notifications_white.xml
@@ -0,0 +1,19 @@
+<!--
+  Copyright (C) 2020 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.
+  -->
+<com.android.settings.widget.TintDrawable
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:drawable="@drawable/ic_notifications"
+    android:tint="?android:attr/colorPrimary" />
diff --git a/res/drawable/ic_settings_night_display_white.xml b/res/drawable/ic_settings_night_display_white.xml
new file mode 100644
index 0000000..8424b31
--- /dev/null
+++ b/res/drawable/ic_settings_night_display_white.xml
@@ -0,0 +1,19 @@
+<!--
+  Copyright (C) 2020 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.
+  -->
+<com.android.settings.widget.TintDrawable
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:drawable="@drawable/ic_settings_night_display"
+    android:tint="?android:attr/colorPrimary" />
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 8473357..1b65359 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -8806,6 +8806,9 @@
     <!-- [CHAR LIMIT=NONE] App notification settings: link to app notification settings-->
     <string name="app_settings_link">Additional settings in the app</string>
 
+    <!-- [CHAR LIMIT=NONE] Apps & notification settings: summary on the link to notification settings-->
+    <string name="notification_screen_summary">Notification history, bubbles, recently sent</string>
+
     <!-- [CHAR LIMIT=45] App notification listing summary, blocked apps -->
     <string name="app_notification_listing_summary_zero">On for all apps</string>
     <!-- [CHAR LIMIT=45] App notification listing summary, blocked apps -->
diff --git a/res/xml/app_and_notification.xml b/res/xml/app_and_notification.xml
index 98b9844..bd2e85c 100644
--- a/res/xml/app_and_notification.xml
+++ b/res/xml/app_and_notification.xml
@@ -57,9 +57,9 @@
     <Preference
         android:key="configure_notification_settings"
         android:title="@string/configure_notification_settings"
+        android:summary="@string/notification_screen_summary"
         android:order="-440"
-        android:fragment="com.android.settings.notification.ConfigureNotificationSettings"
-        settings:controller="com.android.settings.notification.ConfigureNotificationPreferenceController"/>
+        android:fragment="com.android.settings.notification.ConfigureNotificationSettings"/>
 
     <!-- Notifications (appears before manage_perms), default apps (appears after) -->
     <PreferenceCategory
diff --git a/src/com/android/settings/accessibility/BalanceSeekBar.java b/src/com/android/settings/accessibility/BalanceSeekBar.java
index b108e18..ecc226b 100644
--- a/src/com/android/settings/accessibility/BalanceSeekBar.java
+++ b/src/com/android/settings/accessibility/BalanceSeekBar.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.accessibility;
 
+import static com.android.settings.Utils.isNightMode;
+
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
@@ -113,8 +115,7 @@
                 res.getDimensionPixelSize(R.dimen.balance_seekbar_center_marker_width),
                 res.getDimensionPixelSize(R.dimen.balance_seekbar_center_marker_height));
         mCenterMarkerPaint = new Paint();
-        // TODO use a more suitable colour?
-        mCenterMarkerPaint.setColor(Color.BLACK);
+        mCenterMarkerPaint.setColor(isNightMode(context) ? Color.WHITE : Color.BLACK);
         mCenterMarkerPaint.setStyle(Paint.Style.FILL);
         // Remove the progress colour
         setProgressTintList(ColorStateList.valueOf(Color.TRANSPARENT));
diff --git a/src/com/android/settings/core/FeatureFlags.java b/src/com/android/settings/core/FeatureFlags.java
index 331e572..d2ad30d 100644
--- a/src/com/android/settings/core/FeatureFlags.java
+++ b/src/com/android/settings/core/FeatureFlags.java
@@ -29,5 +29,4 @@
     public static final String CONTROLLER_ENHANCEMENT = "settings_controller_loading_enhancement";
     public static final String CONDITIONAL_CARDS = "settings_conditionals";
     public static final String TETHER_ALL_IN_ONE = "settings_tether_all_in_one";
-    public static final String CONTEXTUAL_HOME2 = "settings_contextual_home2";
 }
diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java b/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java
index fc666ec..ac35017 100644
--- a/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java
+++ b/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java
@@ -19,14 +19,11 @@
 import static com.android.settings.homepage.contextualcards.ContextualCardLoader.CARD_CONTENT_LOADER_ID;
 import static com.android.settings.intelligence.ContextualCardProto.ContextualCard.Category.STICKY_VALUE;
 import static com.android.settings.intelligence.ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE;
-import static com.android.settings.slices.CustomSliceRegistry.BLUETOOTH_DEVICES_SLICE_URI;
-import static com.android.settings.slices.CustomSliceRegistry.CONTEXTUAL_WIFI_SLICE_URI;
 
 import static java.util.stream.Collectors.groupingBy;
 
 import android.app.settings.SettingsEnums;
 import android.content.Context;
-import android.net.Uri;
 import android.os.Bundle;
 import android.provider.Settings;
 import android.text.format.DateUtils;
@@ -55,7 +52,6 @@
 import com.android.settingslib.core.lifecycle.events.OnStop;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -85,8 +81,6 @@
     static final String KEY_CONTEXTUAL_CARDS = "key_contextual_cards";
 
     private static final String TAG = "ContextualCardManager";
-    private static final List<Uri> STICKY_CARDS =
-            Arrays.asList(CONTEXTUAL_WIFI_SLICE_URI, BLUETOOTH_DEVICES_SLICE_URI);
 
     private final Context mContext;
     private final Lifecycle mLifecycle;
@@ -364,29 +358,11 @@
 
     private List<ContextualCard> getCardsWithStickyViewType(List<ContextualCard> cards) {
         final List<ContextualCard> result = new ArrayList<>(cards);
-        int replaceCount = 0;
         for (int index = 0; index < result.size(); index++) {
             final ContextualCard card = cards.get(index);
-            if (FeatureFlagUtils.isEnabled(mContext, FeatureFlags.CONTEXTUAL_HOME2)) {
-                if (card.getCategory() == STICKY_VALUE) {
-                    result.set(index, card.mutate().setViewType(
-                            SliceContextualCardRenderer.VIEW_TYPE_STICKY).build());
-                }
-                continue;
-            }
-
-            if (replaceCount > STICKY_CARDS.size() - 1) {
-                break;
-            }
-
-            if (card.getCardType() != ContextualCard.CardType.SLICE) {
-                continue;
-            }
-
-            if (STICKY_CARDS.contains(card.getSliceUri())) {
+            if (card.getCategory() == STICKY_VALUE) {
                 result.set(index, card.mutate().setViewType(
                         SliceContextualCardRenderer.VIEW_TYPE_STICKY).build());
-                replaceCount++;
             }
         }
         return result;
diff --git a/src/com/android/settings/notification/ConfigureNotificationPreferenceController.java b/src/com/android/settings/notification/ConfigureNotificationPreferenceController.java
deleted file mode 100644
index 3aa1008..0000000
--- a/src/com/android/settings/notification/ConfigureNotificationPreferenceController.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2019 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.settings.notification;
-
-import android.content.Context;
-
-import com.android.settings.R;
-import com.android.settings.core.BasePreferenceController;
-
-public class ConfigureNotificationPreferenceController extends BasePreferenceController {
-
-    private NotificationBackend mBackend;
-
-    public ConfigureNotificationPreferenceController(Context context, String key) {
-        super(context, key);
-        mBackend = new NotificationBackend();
-    }
-
-    @Override
-    public int getAvailabilityStatus() {
-        return AVAILABLE;
-    }
-
-    @Override
-    public CharSequence getSummary() {
-        final int blockedAppCount = mBackend.getBlockedAppCount();
-        if (blockedAppCount == 0) {
-            return mContext.getText(R.string.app_notification_listing_summary_zero);
-        }
-        return mContext.getResources().getQuantityString(
-                R.plurals.app_notification_listing_summary_others,
-                blockedAppCount, blockedAppCount);
-    }
-}
\ No newline at end of file
diff --git a/src/com/android/settings/notification/zen/ZenModeAddBypassingAppsPreferenceController.java b/src/com/android/settings/notification/zen/ZenModeAddBypassingAppsPreferenceController.java
index 15a46fe..a861ab2 100644
--- a/src/com/android/settings/notification/zen/ZenModeAddBypassingAppsPreferenceController.java
+++ b/src/com/android/settings/notification/zen/ZenModeAddBypassingAppsPreferenceController.java
@@ -20,6 +20,7 @@
 import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.os.Bundle;
+import android.os.UserHandle;
 
 import androidx.annotation.VisibleForTesting;
 import androidx.core.text.BidiFormatter;
@@ -152,6 +153,7 @@
                                 .setDestination(AppChannelsBypassingDndSettings.class.getName())
                                 .setArguments(args)
                                 .setResultListener(mHostFragment, 0)
+                                .setUserHandle(new UserHandle(UserHandle.getUserId(entry.info.uid)))
                                 .setSourceMetricsCategory(
                                         SettingsEnums.NOTIFICATION_ZEN_MODE_OVERRIDING_APP)
                                 .launch();
diff --git a/src/com/android/settings/slices/SettingsSliceProvider.java b/src/com/android/settings/slices/SettingsSliceProvider.java
index 6c245ce..75061a5 100644
--- a/src/com/android/settings/slices/SettingsSliceProvider.java
+++ b/src/com/android/settings/slices/SettingsSliceProvider.java
@@ -243,7 +243,7 @@
                         .createWifiCallingPreferenceSlice(sliceUri);
             }
 
-            SliceData cachedSliceData = mSliceWeakDataCache.get(sliceUri);
+            final SliceData cachedSliceData = mSliceWeakDataCache.get(sliceUri);
             if (cachedSliceData == null) {
                 loadSliceInBackground(sliceUri);
                 return getSliceStub(sliceUri);
@@ -466,14 +466,14 @@
         final SliceBackgroundWorker worker = SliceBackgroundWorker.getInstance(
                 getContext(), sliceable, uri);
         mPinnedWorkers.put(uri, worker);
-        worker.onSlicePinned();
+        worker.pin();
     }
 
     private void stopBackgroundWorker(Uri uri) {
         final SliceBackgroundWorker worker = mPinnedWorkers.get(uri);
         if (worker != null) {
             Log.d(TAG, "Stopping background worker for: " + uri);
-            worker.onSliceUnpinned();
+            worker.unpin();
             mPinnedWorkers.remove(uri);
         }
     }
diff --git a/src/com/android/settings/slices/SliceBackgroundWorker.java b/src/com/android/settings/slices/SliceBackgroundWorker.java
index 6bafc00..6eb154e 100644
--- a/src/com/android/settings/slices/SliceBackgroundWorker.java
+++ b/src/com/android/settings/slices/SliceBackgroundWorker.java
@@ -20,6 +20,12 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.os.SystemClock;
 import android.util.ArrayMap;
 import android.util.Log;
 
@@ -47,6 +53,8 @@
 
     private static final String TAG = "SliceBackgroundWorker";
 
+    private static final long SLICE_UPDATE_THROTTLE_INTERVAL = 300L;
+
     private static final Map<Uri, SliceBackgroundWorker> LIVE_WORKERS = new ArrayMap<>();
 
     private final Context mContext;
@@ -164,6 +172,75 @@
      * Notify that data was updated and attempt to sync changes to the Slice.
      */
     protected final void notifySliceChange() {
-        mContext.getContentResolver().notifyChange(mUri, null);
+        NotifySliceChangeHandler.getInstance().updateSlice(this);
     }
+
+    void pin() {
+        onSlicePinned();
+    }
+
+    void unpin() {
+        onSliceUnpinned();
+        NotifySliceChangeHandler.getInstance().cancelSliceUpdate(this);
+    }
+
+    private static class NotifySliceChangeHandler extends Handler {
+
+        private static final int MSG_UPDATE_SLICE = 1000;
+
+        private static NotifySliceChangeHandler sHandler;
+
+        private final Map<Uri, Long> mLastUpdateTimeLookup = new ArrayMap<>();
+
+        private static NotifySliceChangeHandler getInstance() {
+            if (sHandler == null) {
+                final HandlerThread workerThread = new HandlerThread("NotifySliceChangeHandler",
+                        Process.THREAD_PRIORITY_BACKGROUND);
+                workerThread.start();
+                sHandler = new NotifySliceChangeHandler(workerThread.getLooper());
+            }
+            return sHandler;
+        }
+
+        private NotifySliceChangeHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what != MSG_UPDATE_SLICE) {
+                return;
+            }
+
+            final SliceBackgroundWorker worker = (SliceBackgroundWorker) msg.obj;
+            final Uri uri = worker.getUri();
+            final Context context = worker.getContext();
+            mLastUpdateTimeLookup.put(uri, SystemClock.uptimeMillis());
+            context.getContentResolver().notifyChange(uri, null);
+        }
+
+        private void updateSlice(SliceBackgroundWorker worker) {
+            if (hasMessages(MSG_UPDATE_SLICE, worker)) {
+                return;
+            }
+
+            final Message message = obtainMessage(MSG_UPDATE_SLICE, worker);
+            final long lastUpdateTime = mLastUpdateTimeLookup.getOrDefault(worker.getUri(), 0L);
+            if (lastUpdateTime == 0L) {
+                // Postpone the first update triggering by onSlicePinned() to avoid being too close
+                // to the first Slice bind.
+                sendMessageDelayed(message, SLICE_UPDATE_THROTTLE_INTERVAL);
+            } else if (SystemClock.uptimeMillis() - lastUpdateTime
+                    > SLICE_UPDATE_THROTTLE_INTERVAL) {
+                sendMessage(message);
+            } else {
+                sendMessageAtTime(message, lastUpdateTime + SLICE_UPDATE_THROTTLE_INTERVAL);
+            }
+        }
+
+        private void cancelSliceUpdate(SliceBackgroundWorker worker) {
+            removeMessages(MSG_UPDATE_SLICE, worker);
+            mLastUpdateTimeLookup.remove(worker.getUri());
+        }
+    };
 }
diff --git a/src/com/android/settings/slices/SliceBuilderUtils.java b/src/com/android/settings/slices/SliceBuilderUtils.java
index 391e9fd..552927e 100644
--- a/src/com/android/settings/slices/SliceBuilderUtils.java
+++ b/src/com/android/settings/slices/SliceBuilderUtils.java
@@ -220,9 +220,11 @@
 
     public static Intent getContentIntent(Context context, SliceData sliceData) {
         final Uri contentUri = new Uri.Builder().appendPath(sliceData.getKey()).build();
+        final String screenTitle = TextUtils.isEmpty(sliceData.getScreenTitle()) ? null
+                : sliceData.getScreenTitle().toString();
         final Intent intent = buildSearchResultPageIntent(context,
                 sliceData.getFragmentClassName(), sliceData.getKey(),
-                sliceData.getScreenTitle().toString(), 0 /* TODO */);
+                screenTitle, 0 /* TODO */);
         intent.setClassName(context.getPackageName(), SubSettings.class.getName());
         intent.setData(contentUri);
         return intent;
@@ -399,7 +401,8 @@
 
         keywords.add(data.getTitle());
 
-        if (!TextUtils.equals(data.getTitle(), data.getScreenTitle())) {
+        if (!TextUtils.isEmpty(data.getScreenTitle())
+                && !TextUtils.equals(data.getTitle(), data.getScreenTitle())) {
             keywords.add(data.getScreenTitle().toString());
         }
 
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 127b9e2..69333d7 100644
--- a/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardManagerTest.java
+++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardManagerTest.java
@@ -610,7 +610,6 @@
 
     @Test
     public void getCardsWithViewType_hasOneStickySlice_shouldHaveOneStickyCard() {
-        FeatureFlagUtils.setEnabled(mContext, FeatureFlags.CONTEXTUAL_HOME2, true);
         final List<ContextualCard> cards = new ArrayList<>();
         cards.add(buildContextualCard(CustomSliceRegistry.CONTEXTUAL_WIFI_SLICE_URI.toString()));
         cards.add(buildContextualCard(CustomSliceRegistry.LOW_STORAGE_SLICE_URI.toString()));
@@ -628,89 +627,6 @@
     }
 
     @Test
-    public void getCardsWithViewType_hasWifiSlice_shouldHaveOneStickyCard() {
-        FeatureFlagUtils.setEnabled(mContext, FeatureFlags.CONTEXTUAL_HOME2, false);
-        final List<ContextualCard> cards = new ArrayList<>();
-        cards.add(buildContextualCard(CustomSliceRegistry.CONTEXTUAL_WIFI_SLICE_URI.toString()));
-        cards.add(buildContextualCard(CustomSliceRegistry.LOW_STORAGE_SLICE_URI.toString()));
-        final List<Integer> categories = Arrays.asList(
-                ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
-                ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE
-        );
-        final List<ContextualCard> cardListWithWifi = buildCategoriedCards(cards, categories);
-
-        final List<ContextualCard> result = mManager.getCardsWithViewType(cardListWithWifi);
-
-        assertThat(result).hasSize(cards.size());
-        assertThat(result.get(0).getViewType()).isEqualTo(VIEW_TYPE_STICKY);
-        assertThat(result.get(1).getViewType()).isEqualTo(VIEW_TYPE_FULL_WIDTH);
-    }
-
-    @Test
-    public void getCardsWithViewType_hasBluetoothDeviceSlice_shouldHaveOneStickyCard() {
-        FeatureFlagUtils.setEnabled(mContext, FeatureFlags.CONTEXTUAL_HOME2, false);
-        final List<ContextualCard> cards = new ArrayList<>();
-        cards.add(buildContextualCard(CustomSliceRegistry.BLUETOOTH_DEVICES_SLICE_URI.toString()));
-        cards.add(buildContextualCard(CustomSliceRegistry.LOW_STORAGE_SLICE_URI.toString()));
-        final List<Integer> categories = Arrays.asList(
-                ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
-                ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE
-        );
-        final List<ContextualCard> cardListWithBT = buildCategoriedCards(cards, categories);
-
-        final List<ContextualCard> result = mManager.getCardsWithViewType(cardListWithBT);
-
-        assertThat(result).hasSize(cards.size());
-        assertThat(result.get(0).getViewType()).isEqualTo(VIEW_TYPE_STICKY);
-        assertThat(result.get(1).getViewType()).isEqualTo(VIEW_TYPE_FULL_WIDTH);
-    }
-
-    @Test
-    public void getCardsWithViewType_hasWifiAndBtDeviceSlice_shouldHaveTwoStickyCards() {
-        FeatureFlagUtils.setEnabled(mContext, FeatureFlags.CONTEXTUAL_HOME2, false);
-        final List<ContextualCard> cards = new ArrayList<>();
-        cards.add(buildContextualCard(CustomSliceRegistry.CONTEXTUAL_WIFI_SLICE_URI.toString()));
-        cards.add(buildContextualCard(CustomSliceRegistry.BLUETOOTH_DEVICES_SLICE_URI.toString()));
-        cards.add(buildContextualCard(CustomSliceRegistry.LOW_STORAGE_SLICE_URI.toString()));
-        final List<Integer> categories = Arrays.asList(
-                ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
-                ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
-                ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE
-        );
-        final List<ContextualCard> cardListWithWifiBT = buildCategoriedCards(cards, categories);
-
-        final List<ContextualCard> result = mManager.getCardsWithViewType(cardListWithWifiBT);
-
-        assertThat(result).hasSize(cards.size());
-        assertThat(result.stream()
-                .filter(card -> card.getViewType() == VIEW_TYPE_STICKY)
-                .count())
-                .isEqualTo(2);
-    }
-
-    @Test
-    public void getCardsWithViewType_noWifiOrBtDeviceSlice_shouldNotHaveStickyCard() {
-        FeatureFlagUtils.setEnabled(mContext, FeatureFlags.CONTEXTUAL_HOME2, false);
-        final List<Integer> categories = Arrays.asList(
-                ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
-                ContextualCardProto.ContextualCard.Category.IMPORTANT_VALUE,
-                ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE,
-                ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE,
-                ContextualCardProto.ContextualCard.Category.SUGGESTION_VALUE
-        );
-        final List<ContextualCard> cardListWithoutWifiBT =
-                buildCategoriedCards(getContextualCardList(), categories);
-
-        final List<ContextualCard> result = mManager.getCardsWithViewType(cardListWithoutWifiBT);
-
-        assertThat(result).hasSize(cardListWithoutWifiBT.size());
-        assertThat(result.stream()
-                .filter(card -> card.getViewType() == VIEW_TYPE_STICKY)
-                .count())
-                .isEqualTo(0);
-    }
-
-    @Test
     public void getCardsToKeep_hasSavedCard_shouldResetSavedCards() {
         final List<String> savedCardNames = new ArrayList<>();
         savedCardNames.add(TEST_SLICE_NAME);
diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/BluetoothUpdateWorkerTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/BluetoothUpdateWorkerTest.java
index 4da5c09..e34737b 100644
--- a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/BluetoothUpdateWorkerTest.java
+++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/BluetoothUpdateWorkerTest.java
@@ -25,6 +25,7 @@
 import android.content.Context;
 import android.net.Uri;
 
+import com.android.settings.slices.ShadowSliceBackgroundWorker;
 import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
 
 import org.junit.Before;
@@ -35,7 +36,7 @@
 import org.robolectric.annotation.Config;
 
 @RunWith(RobolectricTestRunner.class)
-@Config(shadows = {ShadowBluetoothAdapter.class})
+@Config(shadows = {ShadowBluetoothAdapter.class, ShadowSliceBackgroundWorker.class})
 public class BluetoothUpdateWorkerTest {
 
     private static final Uri URI = Uri.parse("content://com.android.settings.slices/test");
diff --git a/tests/robotests/src/com/android/settings/media/MediaDeviceUpdateWorkerTest.java b/tests/robotests/src/com/android/settings/media/MediaDeviceUpdateWorkerTest.java
index 624bbd8..be86b6e 100644
--- a/tests/robotests/src/com/android/settings/media/MediaDeviceUpdateWorkerTest.java
+++ b/tests/robotests/src/com/android/settings/media/MediaDeviceUpdateWorkerTest.java
@@ -34,6 +34,7 @@
 import android.media.RoutingSessionInfo;
 import android.net.Uri;
 
+import com.android.settings.slices.ShadowSliceBackgroundWorker;
 import com.android.settings.testutils.shadow.ShadowAudioManager;
 import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
 import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
@@ -57,7 +58,7 @@
 
 @RunWith(RobolectricTestRunner.class)
 @Config(shadows = {ShadowAudioManager.class, ShadowBluetoothAdapter.class,
-        ShadowBluetoothUtils.class})
+        ShadowBluetoothUtils.class, ShadowSliceBackgroundWorker.class})
 public class MediaDeviceUpdateWorkerTest {
 
     private static final Uri URI = Uri.parse("content://com.android.settings.slices/test");
diff --git a/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorWorkerTest.java b/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorWorkerTest.java
index 0aec952..423c7ac 100644
--- a/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorWorkerTest.java
+++ b/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorWorkerTest.java
@@ -39,6 +39,7 @@
 import android.media.session.PlaybackState;
 import android.net.Uri;
 
+import com.android.settings.slices.ShadowSliceBackgroundWorker;
 import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
 import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
 import com.android.settingslib.bluetooth.BluetoothEventManager;
@@ -59,7 +60,8 @@
 import java.util.List;
 
 @RunWith(RobolectricTestRunner.class)
-@Config(shadows = {ShadowBluetoothAdapter.class, ShadowBluetoothUtils.class})
+@Config(shadows = {ShadowBluetoothAdapter.class, ShadowBluetoothUtils.class,
+        ShadowSliceBackgroundWorker.class})
 public class MediaOutputIndicatorWorkerTest {
     private static final Uri URI = Uri.parse("content://com.android.settings.slices/test");
     private static final String TEST_PACKAGE_NAME = "com.android.test";
diff --git a/tests/robotests/src/com/android/settings/notification/ConfigureNotificationPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/ConfigureNotificationPreferenceControllerTest.java
deleted file mode 100644
index 83fe07a..0000000
--- a/tests/robotests/src/com/android/settings/notification/ConfigureNotificationPreferenceControllerTest.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2019 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.settings.notification;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.Context;
-
-import com.android.settings.testutils.shadow.ShadowNotificationBackend;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Config;
-
-@RunWith(RobolectricTestRunner.class)
-@Config(shadows = ShadowNotificationBackend.class)
-public class ConfigureNotificationPreferenceControllerTest {
-
-    private ConfigureNotificationPreferenceController mController;
-    private Context mContext;
-
-    @Before
-    public void setUp() {
-        mContext = RuntimeEnvironment.application;
-        mController = new ConfigureNotificationPreferenceController(mContext, "key");
-    }
-
-    @Test
-    public void getSummary_noBlockedApps() {
-        ShadowNotificationBackend.setBlockedAppCount(0);
-
-        assertThat(mController.getSummary().toString()).contains("On");
-    }
-
-    @Test
-    public void getSummary_someBlockedApps() {
-        ShadowNotificationBackend.setBlockedAppCount(5);
-
-        assertThat(mController.getSummary().toString()).contains("Off");
-        assertThat(mController.getSummary().toString()).contains("5");
-    }
-}
diff --git a/tests/robotests/src/com/android/settings/slices/ShadowSliceBackgroundWorker.java b/tests/robotests/src/com/android/settings/slices/ShadowSliceBackgroundWorker.java
new file mode 100644
index 0000000..7bf8358
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/slices/ShadowSliceBackgroundWorker.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2020 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.settings.slices;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
+
+@Implements(SliceBackgroundWorker.class)
+public class ShadowSliceBackgroundWorker {
+
+    @RealObject
+    private SliceBackgroundWorker mRealWorker;
+
+    @Implementation
+    protected final void notifySliceChange() {
+        mRealWorker.getContext().getContentResolver().notifyChange(mRealWorker.getUri(), null);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/wifi/slice/WifiScanWorkerTest.java b/tests/robotests/src/com/android/settings/wifi/slice/WifiScanWorkerTest.java
index d40331c..3319543 100644
--- a/tests/robotests/src/com/android/settings/wifi/slice/WifiScanWorkerTest.java
+++ b/tests/robotests/src/com/android/settings/wifi/slice/WifiScanWorkerTest.java
@@ -43,6 +43,7 @@
 import android.os.Bundle;
 import android.os.UserHandle;
 
+import com.android.settings.slices.ShadowSliceBackgroundWorker;
 import com.android.settings.testutils.shadow.ShadowWifiManager;
 import com.android.settingslib.wifi.AccessPoint;
 import com.android.settingslib.wifi.WifiTracker;
@@ -63,10 +64,8 @@
 import java.util.List;
 
 @RunWith(RobolectricTestRunner.class)
-@Config(shadows = {
-        ShadowWifiManager.class,
-        WifiScanWorkerTest.ShadowWifiTracker.class,
-})
+@Config(shadows = {ShadowSliceBackgroundWorker.class, ShadowWifiManager.class,
+        WifiScanWorkerTest.ShadowWifiTracker.class})
 public class WifiScanWorkerTest {
 
     private Context mContext;