Merge "Add Settings icons needed for theme overlaying." into qt-dev
diff --git a/res/drawable/ic_privacy_shield_24dp.xml b/res/drawable/ic_privacy_shield_24dp.xml
new file mode 100644
index 0000000..6bb69c0
--- /dev/null
+++ b/res/drawable/ic_privacy_shield_24dp.xml
@@ -0,0 +1,25 @@
+<!--
+  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
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
+</vector>
diff --git a/res/layout/preference_widget_add.xml b/res/layout/preference_widget_add.xml
index 6e33a4d..9942bb2 100644
--- a/res/layout/preference_widget_add.xml
+++ b/res/layout/preference_widget_add.xml
@@ -21,10 +21,12 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:layout_gravity="center"
+    android:minWidth="@dimen/two_target_min_width"
     android:paddingStart="?android:attr/listPreferredItemPaddingEnd"
     android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
     android:background="?android:attr/selectableItemBackground"
     android:scaleType="center"
     android:src="@drawable/ic_add_24dp"
+    android:tint="?android:attr/colorAccent"
     android:contentDescription="@string/add" />
 
diff --git a/res/layout/preference_widget_master_switch.xml b/res/layout/preference_widget_master_switch.xml
index 7e28be9..26929ab 100644
--- a/res/layout/preference_widget_master_switch.xml
+++ b/res/layout/preference_widget_master_switch.xml
@@ -20,6 +20,7 @@
     android:id="@+id/switchWidget"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:minWidth="@dimen/two_target_min_width"
     android:gravity="center_vertical"
     android:paddingStart="?android:attr/listPreferredItemPaddingEnd"
     android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index f126525..b07865c 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -393,4 +393,6 @@
 
     <!-- Elevation of bluetooth icon -->
     <dimen name="bt_icon_elevation">4dp</dimen>
+
+    <dimen name="two_target_min_width">80dp</dimen>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index b6ff1a8..4e1b32e 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -925,9 +925,9 @@
     <!-- Button text to exit face wizard after everything is done [CHAR LIMIT=15] -->
     <string name="security_settings_face_enroll_done">Done</string>
     <!-- Title for a category shown for the face settings page. [CHAR LIMIT=20] -->
-    <string name="security_settings_face_settings_use_face_category">Use your face to</string>
+    <string name="security_settings_face_settings_use_face_category">Use your face for</string>
     <!-- Text shown on a toggle which allows or disallows the device to use face for unlocking the device. [CHAR LIMIT=20] -->
-    <string name="security_settings_face_settings_use_face_unlock_phone">Unlock your device</string>
+    <string name="security_settings_face_settings_use_face_unlock_phone">Unlocking your device</string>
     <!-- Text shown on a toggle which allows or disallows the device to use face authentication for apps. This will be presented to the user together with the context of security_settings_face_settings_use_face_category. [CHAR LIMIT=30] -->
     <string name="security_settings_face_settings_use_face_for_apps">App sign-in \u0026 payments</string>
     <!-- Text shown on a toggle which disables/enables face authentication, depending if the user's eyes are open. [CHAR LIMIT=30] -->
@@ -2761,13 +2761,15 @@
     <!-- Display settings screen, display white balance settings title [CHAR LIMIT=30] -->
     <string name="display_white_balance_title">Display white balance</string>
     <!-- Display settings screen, setting option name to enable adaptive sleep [CHAR LIMIT=30] -->
-    <string name="adaptive_sleep_title">Adaptive sleep</string>
+    <string name="adaptive_sleep_title">Screen aware</string>
     <!-- Setting option summary when adaptive sleep is on [CHAR LIMIT=NONE] -->
-    <string name="adaptive_sleep_summary_on">On</string>
+    <string name="adaptive_sleep_summary_on">On / Screen won’t turn off if you’re looking at it</string>
     <!-- Setting option summary when adaptive sleep is off [CHAR LIMIT=NONE] -->
     <string name="adaptive_sleep_summary_off">Off</string>
     <!-- Description about the feature adaptive sleep [CHAR LIMIT=NONE]-->
-    <string name="adaptive_sleep_description">Your screen would not dim and go to sleep if the device detects your present attention.</string>
+    <string name="adaptive_sleep_description">Prevents your screen from turning off if you’re looking at it.</string>
+    <!-- Description feature's privacy sensitive details to make sure users understand what feature users, what it saves/sends etc [CHAR LIMIT=NONE]-->
+    <string name="adaptive_sleep_privacy">Screen aware uses the front camera to see if someone is looking at the screen. It works on device, and images are never stored or sent to Google.</string>
 
 
     <!-- Night display screen, setting option name to enable night display (renamed "Night Light" with title caps). [CHAR LIMIT=30] -->
@@ -2829,6 +2831,8 @@
     <string name="screen_timeout_summary">After <xliff:g id="timeout_description">%1$s</xliff:g> of inactivity</string>
     <!-- Wallpaper settings title [CHAR LIMIT=30] -->
     <string name="wallpaper_settings_title">Wallpaper</string>
+    <!-- Styles & Wallpapers settings title [CHAR LIMIT=30] -->
+    <string name="style_and_wallpaper_settings_title">Styles &amp; Wallpapers</string>
     <!-- Wallpaper settings summary when default wallpaper is used [CHAR LIMIT=NONE] -->
     <string name="wallpaper_settings_summary_default">Default</string>
     <!-- Wallpaper settings summary when wallpaper has been updated [CHAR LIMIT=NONE] -->
@@ -3616,7 +3620,7 @@
     <!-- SD card & phone storage settings screen, message on screen after user selects Reset network settings [CHAR LIMIT=NONE] -->
     <string name="reset_network_desc">This will reset all network settings, including:\n\n<li>Wi\u2011Fi</li>\n<li>Mobile data</li>\n<li>Bluetooth</li>"</string>
 
-    <!-- SD card & phone storage settings screen, title for the checkbox to let user decide whether erase eSIM data together [CHAR LIMIT=30] -->
+    <!-- SD card & phone storage settings screen, title for the checkbox to let user decide whether erase eSIM data together [CHAR LIMIT=50] -->
     <string name="reset_esim_title">Erase downloaded SIMs</string>
     <!-- SD card & phone storage settings screen, message for the checkbox to let user decide whether erase eSIM data together [CHAR LIMIT=NONE] -->
     <string name="reset_esim_desc">To download replacement SIMs, contact your carrier. This won\u2019t cancel any mobile service plans.</string>
@@ -7784,9 +7788,9 @@
     <!-- Title of the dialog shown when the user has disabled bubbles at the feature level but tries to enable it for an app. [CHAR LIMIT=NONE] -->
     <string name="bubbles_feature_disabled_dialog_title">Turn on bubbles</string>
     <!-- Description of the dialog shown when the user has disabled bubbles at the feature level but tries to enable it for an app. [CHAR LIMIT=NONE] -->
-    <string name="bubbles_feature_disabled_dialog_text">Before you can turn on bubbles for this app, you need to turn on bubbles for your device</string>
+    <string name="bubbles_feature_disabled_dialog_text">To turn on bubbles for this app, first you need to turn them on for your device. This affects other apps in which you previously turned on bubbles.</string>
     <!-- Button of the dialog shown when the user has disabled bubbles at the feature level but tries to enable it for an app. [CHAR LIMIT=60]-->
-    <string name="bubbles_feature_disabled_button_go_to_bubbles">Go to Bubbles</string>
+    <string name="bubbles_feature_disabled_button_approve">Turn on for device</string>
     <!-- Button to cancel out of the dialog shown when the user has disabled bubbles at the feature level but tries to enable it for an app. [CHAR LIMIT=60] -->
     <string name="bubbles_feature_disabled_button_cancel">Cancel</string>
 
@@ -9304,6 +9308,9 @@
     <string name="display_dashboard_summary">Wallpaper, sleep, font size</string>
 
     <!-- Summary for Display settings, explaining a few important settings under it [CHAR LIMIT=NONE]-->
+    <string name="display_dashboard_summary_with_style">Styles &amp; Wallpapers, sleep, font size</string>
+
+    <!-- Summary for Display settings, explaining a few important settings under it [CHAR LIMIT=NONE]-->
     <string name="display_dashboard_nowallpaper_summary">Sleep, font size</string>
 
     <!-- Example summary of display used in Setup Wizard preview screen [CHAR LIMIT=NONE] -->
@@ -9439,10 +9446,10 @@
     <string name="condition_night_display_summary">Screen tinted amber</string>
 
     <!-- Title of condition that gray scale is on [CHAR LIMIT=NONE] -->
-    <string name="condition_grayscale_title">Greyscale</string>
+    <string name="condition_grayscale_title">Grayscale</string>
 
     <!-- Summary of condition that gray scale is on [CHAR LIMIT=NONE] -->
-    <string name="condition_grayscale_summary">Display only in grey color</string>
+    <string name="condition_grayscale_summary">Display only in gray color</string>
 
     <!-- Summary for the condition section on the dashboard, representing number of conditions. [CHAR LIMIT=10] -->
     <string name="condition_summary" translatable="false"><xliff:g name="count" example="3">%1$d</xliff:g></string>
diff --git a/res/xml/adaptive_sleep_detail.xml b/res/xml/adaptive_sleep_detail.xml
index b7dabbb..e97a758 100644
--- a/res/xml/adaptive_sleep_detail.xml
+++ b/res/xml/adaptive_sleep_detail.xml
@@ -32,6 +32,7 @@
     <com.android.settingslib.RestrictedSwitchPreference
         android:key="adaptive_sleep"
         android:title="@string/adaptive_sleep_title"
+        android:summary="@string/adaptive_sleep_description"
         settings:keywords="@string/keywords_display_adaptive_sleep"
         settings:controller="com.android.settings.display.AdaptiveSleepPreferenceController"
         settings:useAdminDisabledSummary="true"
diff --git a/res/xml/security_dashboard_settings.xml b/res/xml/security_dashboard_settings.xml
index 38bc2d0..9fe3d5b 100644
--- a/res/xml/security_dashboard_settings.xml
+++ b/res/xml/security_dashboard_settings.xml
@@ -87,6 +87,11 @@
             android:title="@string/security_settings_fingerprint_preference_title"
             android:summary="@string/summary_placeholder" />
 
+        <Preference
+            android:key="face_settings_profile"
+            android:title="@string/security_settings_face_preference_title"
+            android:summary="@string/summary_placeholder" />
+
     </PreferenceCategory>
 
     <PreferenceCategory
diff --git a/res/xml/sound_settings.xml b/res/xml/sound_settings.xml
index 90b7528..e610df2 100644
--- a/res/xml/sound_settings.xml
+++ b/res/xml/sound_settings.xml
@@ -103,14 +103,14 @@
         settings:useAdminDisabledSummary="true"
         settings:keywords="@string/keywords_sounds_and_notifications_interruptions"
         settings:allowDividerAbove="true"
-        settings:controller="com.android.settings.notification.ZenModePreferenceController" />
+        settings:controller="com.android.settings.notification.ZenModeSoundSettingsPreferenceController"/>
 
     <Preference
         android:key="gesture_prevent_ringing_sound"
         android:title="@string/gesture_prevent_ringing_sound_title"
         android:order="-110"
         android:fragment="com.android.settings.gestures.PreventRingingGestureSettings"
-        settings:controller="com.android.settings.gestures.PreventRingingParentPreferenceController" />
+        settings:controller="com.android.settings.gestures.PreventRingingParentPreferenceController"/>
 
     <!-- Phone ringtone -->
     <com.android.settings.DefaultRingtonePreference
@@ -150,27 +150,27 @@
         <!-- Dial pad tones -->
         <SwitchPreference
           android:key="dial_pad_tones"
-          android:title="@string/dial_pad_tones_title" />
+          android:title="@string/dial_pad_tones_title"/>
 
         <!-- Screen locking sounds -->
         <SwitchPreference
           android:key="screen_locking_sounds"
-          android:title="@string/screen_locking_sounds_title" />
+          android:title="@string/screen_locking_sounds_title"/>
 
         <!-- Charging sounds -->
         <SwitchPreference
           android:key="charging_sounds"
-          android:title="@string/charging_sounds_title" />
+          android:title="@string/charging_sounds_title"/>
 
         <!-- Docking sounds -->
         <SwitchPreference
           android:key="docking_sounds"
-          android:title="@string/docking_sounds_title" />
+          android:title="@string/docking_sounds_title"/>
 
         <!-- Touch sounds -->
         <SwitchPreference
           android:key="touch_sounds"
-          android:title="@string/touch_sounds_title" />
+          android:title="@string/touch_sounds_title"/>
 
         <!-- Vibrate on touch -->
         <SwitchPreference
@@ -183,18 +183,18 @@
         <DropDownPreference
           android:key="dock_audio_media"
           android:title="@string/dock_audio_media_title"
-          android:summary="%s" />
+          android:summary="%s"/>
 
         <!-- Boot sounds -->
         <SwitchPreference
           android:key="boot_sounds"
-          android:title="@string/boot_sounds_title" />
+          android:title="@string/boot_sounds_title"/>
 
         <!-- Emergency tone -->
         <DropDownPreference
           android:key="emergency_tone"
           android:title="@string/emergency_tone_title"
-          android:summary="%s" />
+          android:summary="%s"/>
     </PreferenceCategory>
 
     <com.android.settings.widget.WorkOnlyCategory
@@ -207,7 +207,7 @@
                     android:key="work_use_personal_sounds"
                     android:title="@string/work_use_personal_sounds_title"
                     android:summary="@string/work_use_personal_sounds_summary"
-                    android:disableDependentsState="true" />
+                    android:disableDependentsState="true"/>
 
                 <!-- Work phone ringtone -->
                 <com.android.settings.DefaultRingtonePreference
@@ -215,7 +215,7 @@
                     android:title="@string/work_ringtone_title"
                     android:dialogTitle="@string/work_alarm_ringtone_title"
                     android:ringtoneType="ringtone"
-                    android:dependency="work_use_personal_sounds" />
+                    android:dependency="work_use_personal_sounds"/>
 
                 <!-- Default work notification ringtone -->
                 <com.android.settings.DefaultRingtonePreference
@@ -223,7 +223,7 @@
                     android:title="@string/work_notification_ringtone_title"
                     android:dialogTitle="@string/work_alarm_ringtone_title"
                     android:ringtoneType="notification"
-                    android:dependency="work_use_personal_sounds" />
+                    android:dependency="work_use_personal_sounds"/>
 
                 <!-- Default work alarm ringtone -->
                 <com.android.settings.DefaultRingtonePreference
@@ -232,7 +232,7 @@
                     android:dialogTitle="@string/work_alarm_ringtone_title"
                     android:persistent="false"
                     android:ringtoneType="alarm"
-                    android:dependency="work_use_personal_sounds" />
+                    android:dependency="work_use_personal_sounds"/>
 
     </com.android.settings.widget.WorkOnlyCategory>
 </PreferenceScreen>
diff --git a/src/com/android/settings/biometrics/face/FaceProfileStatusPreferenceController.java b/src/com/android/settings/biometrics/face/FaceProfileStatusPreferenceController.java
new file mode 100644
index 0000000..196992d
--- /dev/null
+++ b/src/com/android/settings/biometrics/face/FaceProfileStatusPreferenceController.java
@@ -0,0 +1,40 @@
+/*
+ * 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.biometrics.face;
+
+import android.content.Context;
+import android.os.UserHandle;
+
+public class FaceProfileStatusPreferenceController extends FaceStatusPreferenceController {
+
+    public static final String KEY_FACE_SETTINGS = "face_settings_profile";
+
+    public FaceProfileStatusPreferenceController(Context context) {
+        super(context, KEY_FACE_SETTINGS);
+    }
+
+    @Override
+    protected boolean isUserSupported() {
+        return mProfileChallengeUserId != UserHandle.USER_NULL
+                && mLockPatternUtils.isSeparateProfileChallengeAllowed(mProfileChallengeUserId);
+    }
+
+    @Override
+    protected int getUserId() {
+        return mProfileChallengeUserId;
+    }
+}
diff --git a/src/com/android/settings/biometrics/face/FaceSettings.java b/src/com/android/settings/biometrics/face/FaceSettings.java
index 9651eec..b65a31d 100644
--- a/src/com/android/settings/biometrics/face/FaceSettings.java
+++ b/src/com/android/settings/biometrics/face/FaceSettings.java
@@ -27,6 +27,7 @@
 import android.hardware.face.FaceManager;
 import android.os.Bundle;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.SearchIndexableResource;
 import android.util.Log;
 
@@ -53,11 +54,13 @@
     private static final String TAG = "FaceSettings";
     private static final String KEY_TOKEN = "key_token";
 
+    private UserManager mUserManager;
     private FaceManager mFaceManager;
     private int mUserId;
     private byte[] mToken;
     private FaceSettingsAttentionPreferenceController mAttentionController;
     private FaceSettingsRemoveButtonPreferenceController mRemoveController;
+    private List<AbstractPreferenceController> mControllers;
 
     private final FaceSettingsRemoveButtonPreferenceController.Listener mRemovalListener = () -> {
         if (getActivity() != null) {
@@ -95,10 +98,24 @@
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
+        mUserManager = getPrefContext().getSystemService(UserManager.class);
         mFaceManager = getPrefContext().getSystemService(FaceManager.class);
         mUserId = getActivity().getIntent().getIntExtra(
                 Intent.EXTRA_USER_ID, UserHandle.myUserId());
 
+        // There is no better way to do this :/
+        for (AbstractPreferenceController controller : mControllers) {
+            if (controller instanceof  FaceSettingsPreferenceController) {
+                ((FaceSettingsPreferenceController) controller).setUserId(mUserId);
+            }
+        }
+        mRemoveController.setUserId(mUserId);
+
+        // Don't show keyguard controller for work profile settings.
+        if (mUserManager.isManagedProfile(mUserId)) {
+            removePreference(FaceSettingsKeyguardPreferenceController.KEY);
+        }
+
         if (savedInstanceState != null) {
             mToken = savedInstanceState.getByteArray(KEY_TOKEN);
         }
@@ -128,6 +145,7 @@
         super.onActivityResult(requestCode, resultCode, data);
         if (requestCode == CONFIRM_REQUEST) {
             if (resultCode == RESULT_FINISHED || resultCode == RESULT_OK) {
+                mFaceManager.setActiveUser(mUserId);
                 // The pin/pattern/password was set.
                 if (data != null) {
                     mToken = data.getByteArrayExtra(
@@ -161,10 +179,9 @@
         if (!isAvailable(context)) {
             return null;
         }
-        final List<AbstractPreferenceController> controllers =
-                buildPreferenceControllers(context, getSettingsLifecycle());
+        mControllers = buildPreferenceControllers(context, getSettingsLifecycle());
         // There's no great way of doing this right now :/
-        for (AbstractPreferenceController controller : controllers) {
+        for (AbstractPreferenceController controller : mControllers) {
             if (controller instanceof FaceSettingsAttentionPreferenceController) {
                 mAttentionController = (FaceSettingsAttentionPreferenceController) controller;
             } else if (controller instanceof FaceSettingsRemoveButtonPreferenceController) {
@@ -174,7 +191,7 @@
             }
         }
 
-        return controllers;
+        return mControllers;
     }
 
     private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
diff --git a/src/com/android/settings/biometrics/face/FaceSettingsAppPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsAppPreferenceController.java
index 038dbd8..78389b6 100644
--- a/src/com/android/settings/biometrics/face/FaceSettingsAppPreferenceController.java
+++ b/src/com/android/settings/biometrics/face/FaceSettingsAppPreferenceController.java
@@ -21,13 +21,11 @@
 import android.content.Context;
 import android.provider.Settings;
 
-import com.android.settings.core.TogglePreferenceController;
-
 /**
  * Preference controller for Face settings page controlling the ability to use
  * Face authentication in apps (through BiometricPrompt).
  */
-public class FaceSettingsAppPreferenceController extends TogglePreferenceController {
+public class FaceSettingsAppPreferenceController extends FaceSettingsPreferenceController {
 
     private static final String KEY = "security_settings_face_app";
 
@@ -48,14 +46,14 @@
         if (!FaceSettings.isAvailable(mContext)) {
             return false;
         }
-        return Settings.Secure.getInt(
-                mContext.getContentResolver(), FACE_UNLOCK_APP_ENABLED, DEFAULT) == ON;
+        return Settings.Secure.getIntForUser(
+                mContext.getContentResolver(), FACE_UNLOCK_APP_ENABLED, DEFAULT, getUserId()) == ON;
     }
 
     @Override
     public boolean setChecked(boolean isChecked) {
-        return Settings.Secure.putInt(mContext.getContentResolver(), FACE_UNLOCK_APP_ENABLED,
-                isChecked ? ON : OFF);
+        return Settings.Secure.putIntForUser(mContext.getContentResolver(), FACE_UNLOCK_APP_ENABLED,
+                isChecked ? ON : OFF, getUserId());
     }
 
     @Override
diff --git a/src/com/android/settings/biometrics/face/FaceSettingsAttentionPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsAttentionPreferenceController.java
index 77e1532..9e4fd38 100644
--- a/src/com/android/settings/biometrics/face/FaceSettingsAttentionPreferenceController.java
+++ b/src/com/android/settings/biometrics/face/FaceSettingsAttentionPreferenceController.java
@@ -20,6 +20,7 @@
 import android.hardware.face.FaceManager;
 import android.hardware.face.FaceManager.GetFeatureCallback;
 import android.hardware.face.FaceManager.SetFeatureCallback;
+import android.provider.Settings;
 
 import androidx.preference.PreferenceScreen;
 import androidx.preference.SwitchPreference;
@@ -31,7 +32,7 @@
  * Preference controller that manages the ability to use face authentication with/without
  * user attention. See {@link FaceManager#setRequireAttention(boolean, byte[])}.
  */
-public class FaceSettingsAttentionPreferenceController extends TogglePreferenceController {
+public class FaceSettingsAttentionPreferenceController extends FaceSettingsPreferenceController {
 
     public static final String KEY = "security_settings_face_require_attention";
 
@@ -46,6 +47,10 @@
                 mPreference.setEnabled(true);
                 if (!success) {
                     mPreference.setChecked(!mPreference.isChecked());
+                } else {
+                    Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                            Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED,
+                            mPreference.isChecked() ? 1 : 0, getUserId());
                 }
             }
         }
diff --git a/src/com/android/settings/biometrics/face/FaceSettingsConfirmPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsConfirmPreferenceController.java
index 08740cf..b721072 100644
--- a/src/com/android/settings/biometrics/face/FaceSettingsConfirmPreferenceController.java
+++ b/src/com/android/settings/biometrics/face/FaceSettingsConfirmPreferenceController.java
@@ -26,7 +26,7 @@
 /**
  * Preference controller giving the user an option to always require confirmation.
  */
-public class FaceSettingsConfirmPreferenceController extends TogglePreferenceController {
+public class FaceSettingsConfirmPreferenceController extends FaceSettingsPreferenceController {
 
     private static final String KEY = "security_settings_face_require_confirmation";
 
@@ -45,14 +45,14 @@
 
     @Override
     public boolean isChecked() {
-        return Settings.Secure.getInt(mContext.getContentResolver(),
-                FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, DEFAULT) == ON;
+        return Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, DEFAULT, getUserId()) == ON;
     }
 
     @Override
     public boolean setChecked(boolean isChecked) {
-        return Settings.Secure.putInt(mContext.getContentResolver(),
-                FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, isChecked ? ON : OFF);
+        return Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, isChecked ? ON : OFF, getUserId());
     }
 
     @Override
diff --git a/src/com/android/settings/biometrics/face/FaceSettingsKeyguardPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsKeyguardPreferenceController.java
index fe7d398..3a7865d 100644
--- a/src/com/android/settings/biometrics/face/FaceSettingsKeyguardPreferenceController.java
+++ b/src/com/android/settings/biometrics/face/FaceSettingsKeyguardPreferenceController.java
@@ -31,9 +31,9 @@
  * Preference controller for Face settings page controlling the ability to unlock the phone
  * with face.
  */
-public class FaceSettingsKeyguardPreferenceController extends TogglePreferenceController {
+public class FaceSettingsKeyguardPreferenceController extends FaceSettingsPreferenceController {
 
-    private static final String KEY = "security_settings_face_keyguard";
+    static final String KEY = "security_settings_face_keyguard";
 
     private static final int ON = 1;
     private static final int OFF = 0;
@@ -54,14 +54,14 @@
         } else if (adminDisabled()) {
             return false;
         }
-        return Settings.Secure.getInt(
-                mContext.getContentResolver(), FACE_UNLOCK_KEYGUARD_ENABLED, DEFAULT) == ON;
+        return Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                FACE_UNLOCK_KEYGUARD_ENABLED, DEFAULT, getUserId()) == ON;
     }
 
     @Override
     public boolean setChecked(boolean isChecked) {
-        return Settings.Secure.putInt(mContext.getContentResolver(), FACE_UNLOCK_KEYGUARD_ENABLED,
-                isChecked ? ON : OFF);
+        return Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                FACE_UNLOCK_KEYGUARD_ENABLED, isChecked ? ON : OFF, getUserId());
     }
 
     @Override
diff --git a/src/com/android/settings/biometrics/face/FaceSettingsPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsPreferenceController.java
new file mode 100644
index 0000000..b8ac118
--- /dev/null
+++ b/src/com/android/settings/biometrics/face/FaceSettingsPreferenceController.java
@@ -0,0 +1,41 @@
+/*
+ * 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.biometrics.face;
+
+import android.content.Context;
+
+import com.android.settings.core.TogglePreferenceController;
+
+/**
+ * Abstract base class for all face settings toggles.
+ */
+public abstract class FaceSettingsPreferenceController extends TogglePreferenceController {
+
+    private int mUserId;
+
+    public FaceSettingsPreferenceController(Context context, String preferenceKey) {
+        super(context, preferenceKey);
+    }
+
+    public void setUserId(int userId) {
+        mUserId = userId;
+    }
+
+    protected int getUserId() {
+        return mUserId;
+    }
+}
diff --git a/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java
index 5174482..600ea37 100644
--- a/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java
+++ b/src/com/android/settings/biometrics/face/FaceSettingsRemoveButtonPreferenceController.java
@@ -82,12 +82,11 @@
     }
 
     private Button mButton;
-    private List<Face> mFaces;
     private Listener mListener;
     private SettingsActivity mActivity;
+    private int mUserId;
 
     private final Context mContext;
-    private final int mUserId;
     private final FaceManager mFaceManager;
     private final FaceManager.RemovalCallback mRemovalCallback = new FaceManager.RemovalCallback() {
         @Override
@@ -100,8 +99,8 @@
         @Override
         public void onRemovalSucceeded(Face face, int remaining) {
             if (remaining == 0) {
-                mFaces = mFaceManager.getEnrolledFaces(mUserId);
-                if (!mFaces.isEmpty()) {
+                final List<Face> faces = mFaceManager.getEnrolledFaces(mUserId);
+                if (!faces.isEmpty()) {
                     mButton.setEnabled(true);
                 } else {
                     mListener.onRemoved();
@@ -117,16 +116,17 @@
         @Override
         public void onClick(DialogInterface dialog, int which) {
             if (which == DialogInterface.BUTTON_POSITIVE) {
-                if (mFaces.isEmpty()) {
+                final List<Face> faces = mFaceManager.getEnrolledFaces(mUserId);
+                if (faces.isEmpty()) {
                     Log.e(TAG, "No faces");
                     return;
                 }
-                if (mFaces.size() > 1) {
-                    Log.e(TAG, "Multiple enrollments: " + mFaces.size());
+                if (faces.size() > 1) {
+                    Log.e(TAG, "Multiple enrollments: " + faces.size());
                 }
 
                 // Remove the first/only face
-                mFaceManager.remove(mFaces.get(0), mUserId, mRemovalCallback);
+                mFaceManager.remove(faces.get(0), mUserId, mRemovalCallback);
             } else {
                 mButton.setEnabled(true);
             }
@@ -137,17 +137,16 @@
         super(context, preferenceKey);
         mContext = context;
         mFaceManager = context.getSystemService(FaceManager.class);
-        // TODO: Use the profile-specific userId instead
-        mUserId = UserHandle.myUserId();
-        if (mFaceManager != null) {
-            mFaces = mFaceManager.getEnrolledFaces(mUserId);
-        }
     }
 
     public FaceSettingsRemoveButtonPreferenceController(Context context) {
         this(context, KEY);
     }
 
+    public void setUserId(int userId) {
+        mUserId = userId;
+    }
+
     @Override
     public void updateState(Preference preference) {
         super.updateState(preference);
diff --git a/src/com/android/settings/development/DeviceIdentifierAccessRestrictionsPreferenceController.java b/src/com/android/settings/development/DeviceIdentifierAccessRestrictionsPreferenceController.java
index 05ac8c7..f709771 100644
--- a/src/com/android/settings/development/DeviceIdentifierAccessRestrictionsPreferenceController.java
+++ b/src/com/android/settings/development/DeviceIdentifierAccessRestrictionsPreferenceController.java
@@ -16,8 +16,10 @@
 
 package com.android.settings.development;
 
+import android.content.ContentResolver;
 import android.content.Context;
 import android.provider.DeviceConfig;
+import android.provider.Settings;
 
 import androidx.preference.Preference;
 import androidx.preference.SwitchPreference;
@@ -32,8 +34,30 @@
     private static final String DEVICE_IDENTIFIER_ACCESS_RESTRICTIONS_KEY =
             "device_identifier_access_restrictions";
 
+    // The settings that should be set when the new device identifier access restrictions are
+    // disabled.
+    private static final String[] RELAX_DEVICE_IDENTIFIER_CHECK_SETTINGS = {
+            Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_3P_CHECK_RELAXED,
+            Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_NON_PRIV_CHECK_RELAXED,
+            Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_PRIV_CHECK_RELAXED
+    };
+
+    private ContentResolver mContentResolver;
+
     public DeviceIdentifierAccessRestrictionsPreferenceController(Context context) {
         super(context);
+        mContentResolver = context.getContentResolver();
+    }
+
+    @Override
+    public boolean isAvailable() {
+        // If the new access restrictions have been disabled from the server side then do not
+        // display the option.
+        boolean disabledFromServerSide = Boolean.parseBoolean(
+                DeviceConfig.getProperty(DeviceConfig.Privacy.NAMESPACE,
+                        DeviceConfig.Privacy.
+                                PROPERTY_DEVICE_IDENTIFIER_ACCESS_RESTRICTIONS_DISABLED));
+        return !disabledFromServerSide;
     }
 
     @Override
@@ -48,16 +72,20 @@
     }
 
     private void writeSetting(boolean isEnabled) {
-        DeviceConfig.setProperty(DeviceConfig.Privacy.NAMESPACE,
-                DeviceConfig.Privacy.PROPERTY_DEVICE_IDENTIFIER_ACCESS_RESTRICTIONS_DISABLED,
-                String.valueOf(isEnabled), false);
+        for (String relaxCheckSetting : RELAX_DEVICE_IDENTIFIER_CHECK_SETTINGS) {
+            Settings.Global.putInt(mContentResolver, relaxCheckSetting, isEnabled ? 1 : 0);
+        }
     }
 
     @Override
     public void updateState(Preference preference) {
-        boolean isEnabled = Boolean.parseBoolean(
-                DeviceConfig.getProperty(DeviceConfig.Privacy.NAMESPACE,
-                        DeviceConfig.Privacy.PROPERTY_DEVICE_IDENTIFIER_ACCESS_RESTRICTIONS_DISABLED));
+        boolean isEnabled = true;
+        for (String relaxCheckSetting : RELAX_DEVICE_IDENTIFIER_CHECK_SETTINGS) {
+            if (Settings.Global.getInt(mContentResolver, relaxCheckSetting, 0) == 0) {
+                isEnabled = false;
+                break;
+            }
+        }
         ((SwitchPreference) mPreference).setChecked(isEnabled);
     }
 
@@ -65,6 +93,6 @@
     protected void onDeveloperOptionsSwitchDisabled() {
         super.onDeveloperOptionsSwitchDisabled();
         writeSetting(false);
-        ((SwitchPreference) mPreference).setChecked(true);
+        ((SwitchPreference) mPreference).setChecked(false);
     }
 }
diff --git a/src/com/android/settings/display/AdaptiveSleepSettings.java b/src/com/android/settings/display/AdaptiveSleepSettings.java
index 097d7dc..4c17a67 100644
--- a/src/com/android/settings/display/AdaptiveSleepSettings.java
+++ b/src/com/android/settings/display/AdaptiveSleepSettings.java
@@ -25,6 +25,7 @@
 import com.android.settings.dashboard.DashboardFragment;
 import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settingslib.search.SearchIndexable;
+import com.android.settingslib.widget.FooterPreference;
 
 import java.util.Arrays;
 import java.util.List;
@@ -37,8 +38,10 @@
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
-        mFooterPreferenceMixin.createFooterPreference()
-                .setTitle(R.string.adaptive_sleep_description);
+        final FooterPreference footerPreference =
+                mFooterPreferenceMixin.createFooterPreference();
+        footerPreference.setIcon(R.drawable.ic_privacy_shield_24dp);
+        footerPreference.setTitle(R.string.adaptive_sleep_privacy);
     }
 
     @Override
diff --git a/src/com/android/settings/network/telephony/WifiCallingPreferenceController.java b/src/com/android/settings/network/telephony/WifiCallingPreferenceController.java
index 96521ea..574b7dd 100644
--- a/src/com/android/settings/network/telephony/WifiCallingPreferenceController.java
+++ b/src/com/android/settings/network/telephony/WifiCallingPreferenceController.java
@@ -35,6 +35,7 @@
 
 import com.android.ims.ImsConfig;
 import com.android.ims.ImsManager;
+import com.android.settings.R;
 import com.android.settingslib.core.lifecycle.LifecycleObserver;
 import com.android.settingslib.core.lifecycle.events.OnStart;
 import com.android.settingslib.core.lifecycle.events.OnStop;
@@ -116,6 +117,9 @@
             preference.setSummary(null);
             preference.setIntent(intent);
         } else {
+            final String title = SubscriptionManager.getResourcesForSubId(mContext, mSubId)
+                    .getString(R.string.wifi_calling_settings_title);
+            preference.setTitle(title);
             int resId = com.android.internal.R.string.wifi_calling_off_summary;
             if (mImsManager.isWfcEnabledByUser()) {
                 boolean wfcRoamingEnabled = mEditableWfcRoamingMode && !mUseWfcHomeModeForRoaming;
diff --git a/src/com/android/settings/notification/AppBubbleNotificationSettings.java b/src/com/android/settings/notification/AppBubbleNotificationSettings.java
index 17909c0..f55c262 100644
--- a/src/com/android/settings/notification/AppBubbleNotificationSettings.java
+++ b/src/com/android/settings/notification/AppBubbleNotificationSettings.java
@@ -18,22 +18,23 @@
 
 import android.app.settings.SettingsEnums;
 import android.content.Context;
-import android.provider.SearchIndexableResource;
 import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.settings.R;
 import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.search.Indexable;
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.search.SearchIndexable;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 
 @SearchIndexable
-public class AppBubbleNotificationSettings extends NotificationSettingsBase {
+public class AppBubbleNotificationSettings extends NotificationSettingsBase implements
+        GlobalBubblePermissionObserverMixin.Listener {
     private static final String TAG = "AppBubNotiSettings";
+    private GlobalBubblePermissionObserverMixin mObserverMixin;
 
     @Override
     public int getMetricsCategory() {
@@ -65,6 +66,11 @@
     }
 
     @Override
+    public void onGlobalBubblePermissionChanged() {
+        updatePreferenceStates();
+    }
+
+    @Override
     public void onResume() {
         super.onResume();
 
@@ -79,19 +85,23 @@
             controller.displayPreference(getPreferenceScreen());
         }
         updatePreferenceStates();
+
+        mObserverMixin = new GlobalBubblePermissionObserverMixin(getContext(), this);
+        mObserverMixin.onStart();
     }
 
-    /**
-     * For Search.
-     */
-    public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+    @Override
+    public void onPause() {
+        mObserverMixin.onStop();
+        super.onPause();
+    }
+
+    public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
             new BaseSearchIndexProvider() {
+
                 @Override
-                public List<SearchIndexableResource> getXmlResourcesToIndex(
-                        Context context, boolean enabled) {
-                    final SearchIndexableResource sir = new SearchIndexableResource(context);
-                    sir.xmlResId = R.xml.app_bubble_notification_settings;
-                    return Arrays.asList(sir);
+                protected boolean isPageSearchEnabled(Context context) {
+                    return false;
                 }
 
                 @Override
diff --git a/src/com/android/settings/notification/BubblePreferenceController.java b/src/com/android/settings/notification/BubblePreferenceController.java
index 5dab374..e5a1a62 100644
--- a/src/com/android/settings/notification/BubblePreferenceController.java
+++ b/src/com/android/settings/notification/BubblePreferenceController.java
@@ -25,6 +25,7 @@
 import com.android.settings.core.PreferenceControllerMixin;
 import com.android.settingslib.RestrictedSwitchPreference;
 
+import androidx.fragment.app.FragmentManager;
 import androidx.preference.Preference;
 
 public class BubblePreferenceController extends NotificationPreferenceController
@@ -35,10 +36,18 @@
     private static final int SYSTEM_WIDE_ON = 1;
     private static final int SYSTEM_WIDE_OFF = 0;
 
+    private FragmentManager mFragmentManager;
+
     public BubblePreferenceController(Context context, NotificationBackend backend) {
         super(context, backend);
     }
 
+    public BubblePreferenceController(Context context, FragmentManager fragmentManager,
+            NotificationBackend backend) {
+        super(context, backend);
+        mFragmentManager = fragmentManager;
+    }
+
     @Override
     public String getPreferenceKey() {
         return KEY;
@@ -52,11 +61,11 @@
         if (mAppRow == null && mChannel == null) {
             return false;
         }
-        if (Settings.Secure.getInt(mContext.getContentResolver(),
-                NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON) == SYSTEM_WIDE_OFF) {
-            return false;
-        }
         if (mChannel != null) {
+            if (Settings.Secure.getInt(mContext.getContentResolver(),
+                    NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON) == SYSTEM_WIDE_OFF) {
+                return false;
+            }
             if (isDefaultChannel()) {
                 return true;
             } else {
@@ -74,7 +83,9 @@
                 pref.setChecked(mChannel.canBubble());
                 pref.setEnabled(isChannelConfigurable() && !pref.isDisabledByAdmin());
             } else {
-                pref.setChecked(mAppRow.allowBubbles);
+                pref.setChecked(mAppRow.allowBubbles
+                        && Settings.Secure.getInt(mContext.getContentResolver(),
+                        NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON) == SYSTEM_WIDE_ON);
                 pref.setSummary(mContext.getString(
                         R.string.bubbles_app_toggle_summary, mAppRow.label));
             }
@@ -87,11 +98,44 @@
         if (mChannel != null) {
             mChannel.setAllowBubbles(value);
             saveChannel();
-        } else if (mAppRow != null){
-            mAppRow.allowBubbles = value;
-            mBackend.setAllowBubbles(mAppRow.pkg, mAppRow.uid, value);
+            return true;
+        } else if (mAppRow != null) {
+            RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference;
+            // if the global setting is off, toggling app level permission requires extra
+            // confirmation
+            if (Settings.Secure.getInt(mContext.getContentResolver(),
+                    NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON) == SYSTEM_WIDE_OFF
+                    && !pref.isChecked()) {
+                new BubbleWarningDialogFragment()
+                        .setPkgInfo(mAppRow.pkg, mAppRow.uid)
+                        .show(mFragmentManager, "dialog");
+                return false;
+            } else {
+                mAppRow.allowBubbles = value;
+                mBackend.setAllowBubbles(mAppRow.pkg, mAppRow.uid, value);
+            }
         }
         return true;
     }
 
+    // Used in app level prompt that confirms the user is ok with turning on bubbles
+    // globally. If they aren't, undo what
+    public static void revertBubblesApproval(Context mContext, String pkg, int uid) {
+        NotificationBackend backend = new NotificationBackend();
+        backend.setAllowBubbles(pkg, uid, false);
+        // changing the global settings will cause the observer on the host page to reload
+        // correct preference state
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                NOTIFICATION_BUBBLES, SYSTEM_WIDE_OFF);
+    }
+
+    // Apply global bubbles approval
+    public static void applyBubblesApproval(Context mContext, String pkg, int uid) {
+        NotificationBackend backend = new NotificationBackend();
+        backend.setAllowBubbles(pkg, uid, true);
+        // changing the global settings will cause the observer on the host page to reload
+        // correct preference state
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON);
+    }
 }
diff --git a/src/com/android/settings/notification/BubbleSummaryPreferenceController.java b/src/com/android/settings/notification/BubbleSummaryPreferenceController.java
index 708bbcd..5f58f67 100644
--- a/src/com/android/settings/notification/BubbleSummaryPreferenceController.java
+++ b/src/com/android/settings/notification/BubbleSummaryPreferenceController.java
@@ -52,11 +52,11 @@
         if (mAppRow == null && mChannel == null) {
             return false;
         }
-        if (Settings.Secure.getInt(mContext.getContentResolver(),
-                NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON) == SYSTEM_WIDE_OFF) {
-            return false;
-        }
         if (mChannel != null) {
+            if (Settings.Secure.getInt(mContext.getContentResolver(),
+                    NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON) == SYSTEM_WIDE_OFF) {
+                return false;
+            }
             if (isDefaultChannel()) {
                 return true;
             } else {
@@ -91,7 +91,9 @@
             if (mChannel != null) {
                 canBubble |= mChannel.canBubble();
             } else {
-               canBubble |= mAppRow.allowBubbles;
+               canBubble |= mAppRow.allowBubbles
+                       && (Settings.Secure.getInt(mContext.getContentResolver(),
+                       NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON) == SYSTEM_WIDE_ON);
             }
         }
         return mContext.getString(canBubble ? R.string.switch_on_text : R.string.switch_off_text);
diff --git a/src/com/android/settings/notification/BubbleWarningDialogFragment.java b/src/com/android/settings/notification/BubbleWarningDialogFragment.java
new file mode 100644
index 0000000..5086fb0
--- /dev/null
+++ b/src/com/android/settings/notification/BubbleWarningDialogFragment.java
@@ -0,0 +1,70 @@
+/*
+ * 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.app.Dialog;
+import android.app.settings.SettingsEnums;
+import android.os.Bundle;
+
+import androidx.appcompat.app.AlertDialog;
+
+import com.android.settings.R;
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+
+public class BubbleWarningDialogFragment extends InstrumentedDialogFragment {
+    static final String KEY_PKG = "p";
+    static final String KEY_UID = "u";
+
+
+    @Override
+    public int getMetricsCategory() {
+        return SettingsEnums.DIALOG_APP_BUBBLE_SETTINGS;
+    }
+
+    public BubbleWarningDialogFragment setPkgInfo(String pkg, int uid) {
+        Bundle args = new Bundle();
+        args.putString(KEY_PKG, pkg);
+        args.putInt(KEY_UID, uid);
+        setArguments(args);
+        return this;
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final Bundle args = getArguments();
+        final String pkg = args.getString(KEY_PKG);
+        final int uid = args.getInt(KEY_UID);
+
+        final String title =
+                getResources().getString(R.string.bubbles_feature_disabled_dialog_title);
+        final String summary = getResources()
+                .getString(R.string.bubbles_feature_disabled_dialog_text);
+        return new AlertDialog.Builder(getContext())
+                .setMessage(summary)
+                .setTitle(title)
+                .setCancelable(true)
+                .setPositiveButton(R.string.bubbles_feature_disabled_button_approve,
+                        (dialog, id) ->
+                                BubblePreferenceController.applyBubblesApproval(
+                                        getContext(), pkg, uid))
+                .setNegativeButton(R.string.bubbles_feature_disabled_button_cancel,
+                        (dialog, id) ->
+                                BubblePreferenceController.revertBubblesApproval(
+                                        getContext(), pkg, uid))
+                .create();
+    }
+}
diff --git a/src/com/android/settings/notification/GlobalBubblePermissionObserverMixin.java b/src/com/android/settings/notification/GlobalBubblePermissionObserverMixin.java
new file mode 100644
index 0000000..398931d
--- /dev/null
+++ b/src/com/android/settings/notification/GlobalBubblePermissionObserverMixin.java
@@ -0,0 +1,59 @@
+/*
+ * 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 android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+
+public class GlobalBubblePermissionObserverMixin extends ContentObserver {
+
+    public interface Listener {
+        void onGlobalBubblePermissionChanged();
+    }
+
+    private final Context mContext;
+    private final Listener mListener;
+
+    public GlobalBubblePermissionObserverMixin(Context context, Listener listener) {
+        super(new Handler(Looper.getMainLooper()));
+        mContext = context;
+        mListener = listener;
+    }
+
+    @Override
+    public void onChange(boolean selfChange, Uri uri) {
+        if (mListener != null) {
+            mListener.onGlobalBubblePermissionChanged();
+        }
+    }
+
+    public void onStart() {
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Secure.getUriFor(
+                        Settings.Secure.NOTIFICATION_BUBBLES),
+                false /* notifyForDescendants */,
+                this /* observer */);
+    }
+
+    public void onStop() {
+        mContext.getContentResolver().unregisterContentObserver(this /* observer */);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/notification/HeaderPreferenceController.java b/src/com/android/settings/notification/HeaderPreferenceController.java
index 0c091b4..d942113 100644
--- a/src/com/android/settings/notification/HeaderPreferenceController.java
+++ b/src/com/android/settings/notification/HeaderPreferenceController.java
@@ -68,9 +68,13 @@
                 activity = mFragment.getActivity();
             }
 
+            if (activity == null) {
+                return;
+            }
+
             LayoutPreference pref = (LayoutPreference) preference;
             mHeaderController = EntityHeaderController.newInstance(
-                    mFragment.getActivity(), mFragment, pref.findViewById(R.id.entity_header));
+                    activity, mFragment, pref.findViewById(R.id.entity_header));
             pref = mHeaderController.setIcon(mAppRow.icon)
                     .setLabel(getLabel())
                     .setSummary(getSummary())
diff --git a/src/com/android/settings/notification/ZenModePreferenceController.java b/src/com/android/settings/notification/ZenModePreferenceController.java
index 22eb0c0..44ad2ff 100644
--- a/src/com/android/settings/notification/ZenModePreferenceController.java
+++ b/src/com/android/settings/notification/ZenModePreferenceController.java
@@ -35,9 +35,6 @@
 public class ZenModePreferenceController extends BasePreferenceController
         implements LifecycleObserver, OnResume, OnPause {
 
-
-    public static final String ZEN_MODE_KEY = "zen_mode";
-
     private SettingObserver mSettingObserver;
     private ZenModeSettings.SummaryBuilder mSummaryBuilder;
 
diff --git a/src/com/android/settings/notification/ZenModeSliceBuilder.java b/src/com/android/settings/notification/ZenModeSliceBuilder.java
index ceb36fe..e8b181a 100644
--- a/src/com/android/settings/notification/ZenModeSliceBuilder.java
+++ b/src/com/android/settings/notification/ZenModeSliceBuilder.java
@@ -18,7 +18,7 @@
 
 import static android.app.slice.Slice.EXTRA_TOGGLE_STATE;
 
-import static com.android.settings.notification.ZenModePreferenceController.ZEN_MODE_KEY;
+import static com.android.settings.notification.ZenModeSoundSettingsPreferenceController.ZEN_MODE_KEY;
 
 import android.annotation.ColorInt;
 import android.app.NotificationManager;
diff --git a/src/com/android/settings/notification/ZenModeSoundSettingsPreferenceController.java b/src/com/android/settings/notification/ZenModeSoundSettingsPreferenceController.java
new file mode 100644
index 0000000..842c49d
--- /dev/null
+++ b/src/com/android/settings/notification/ZenModeSoundSettingsPreferenceController.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+public class ZenModeSoundSettingsPreferenceController extends ZenModePreferenceController {
+
+    public static final String ZEN_MODE_KEY = "zen_mode";
+
+    public ZenModeSoundSettingsPreferenceController(Context context, String key) {
+        super(context, key);
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return AVAILABLE;
+    }
+}
diff --git a/src/com/android/settings/security/SecuritySettings.java b/src/com/android/settings/security/SecuritySettings.java
index 660eab5..7c3391c 100644
--- a/src/com/android/settings/security/SecuritySettings.java
+++ b/src/com/android/settings/security/SecuritySettings.java
@@ -23,6 +23,7 @@
 import android.provider.SearchIndexableResource;
 
 import com.android.settings.R;
+import com.android.settings.biometrics.face.FaceProfileStatusPreferenceController;
 import com.android.settings.biometrics.face.FaceStatusPreferenceController;
 import com.android.settings.biometrics.fingerprint.FingerprintProfileStatusPreferenceController;
 import com.android.settings.biometrics.fingerprint.FingerprintStatusPreferenceController;
@@ -125,6 +126,7 @@
         profileSecurityControllers.add(new LockUnificationPreferenceController(context, host));
         profileSecurityControllers.add(new VisiblePatternProfilePreferenceController(
                 context, lifecycle));
+        profileSecurityControllers.add(new FaceProfileStatusPreferenceController(context));
         profileSecurityControllers.add(new FingerprintProfileStatusPreferenceController(context));
         controllers.add(new PreferenceCategoryController(context, WORK_PROFILE_SECURITY_CATEGORY)
                 .setChildren(profileSecurityControllers));
diff --git a/src/com/android/settings/slices/CustomSliceRegistry.java b/src/com/android/settings/slices/CustomSliceRegistry.java
index fe000d1..e400815 100644
--- a/src/com/android/settings/slices/CustomSliceRegistry.java
+++ b/src/com/android/settings/slices/CustomSliceRegistry.java
@@ -19,7 +19,7 @@
 import static android.provider.SettingsSlicesContract.KEY_LOCATION;
 import static android.provider.SettingsSlicesContract.KEY_WIFI;
 
-import static com.android.settings.notification.ZenModePreferenceController.ZEN_MODE_KEY;
+import static com.android.settings.notification.ZenModeSoundSettingsPreferenceController.ZEN_MODE_KEY;
 
 import android.content.ContentResolver;
 import android.net.Uri;
diff --git a/src/com/android/settings/slices/CustomSliceable.java b/src/com/android/settings/slices/CustomSliceable.java
index 93d08a2..9566be1 100644
--- a/src/com/android/settings/slices/CustomSliceable.java
+++ b/src/com/android/settings/slices/CustomSliceable.java
@@ -91,8 +91,9 @@
      * @return a {@link PendingIntent} linked to {@link SliceBroadcastReceiver}.
      */
     default PendingIntent getBroadcastIntent(Context context) {
-        final Intent intent = new Intent(getUri().toString());
-        intent.setClass(context, SliceBroadcastReceiver.class);
+        final Intent intent = new Intent(getUri().toString())
+                .setData(getUri())
+                .setClass(context, SliceBroadcastReceiver.class);
         return PendingIntent.getBroadcast(context, 0 /* requestCode */, intent,
                 PendingIntent.FLAG_CANCEL_CURRENT);
     }
diff --git a/src/com/android/settings/slices/SliceBackgroundWorker.java b/src/com/android/settings/slices/SliceBackgroundWorker.java
index f19b1df..6bafc00 100644
--- a/src/com/android/settings/slices/SliceBackgroundWorker.java
+++ b/src/com/android/settings/slices/SliceBackgroundWorker.java
@@ -68,16 +68,17 @@
     }
 
     /**
-     * Returns the singleton instance of the {@link SliceBackgroundWorker} for specified {@link Uri}
-     * if exists
+     * Returns the singleton instance of {@link SliceBackgroundWorker} for specified {@link Uri} if
+     * exists
      */
     @Nullable
-    public static SliceBackgroundWorker getInstance(Uri uri) {
-        return LIVE_WORKERS.get(uri);
+    @SuppressWarnings("TypeParameterUnusedInFormals")
+    public static <T extends SliceBackgroundWorker> T getInstance(Uri uri) {
+        return (T) LIVE_WORKERS.get(uri);
     }
 
     /**
-     * Returns the singleton instance of the {@link SliceBackgroundWorker} for specified {@link
+     * Returns the singleton instance of {@link SliceBackgroundWorker} for specified {@link
      * CustomSliceable}
      */
     static SliceBackgroundWorker getInstance(Context context, Sliceable sliceable, Uri uri) {
@@ -165,4 +166,4 @@
     protected final void notifySliceChange() {
         mContext.getContentResolver().notifyChange(mUri, null);
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/settings/wifi/ConfigureWifiSettings.java b/src/com/android/settings/wifi/ConfigureWifiSettings.java
index 89c1575..718f7fb 100644
--- a/src/com/android/settings/wifi/ConfigureWifiSettings.java
+++ b/src/com/android/settings/wifi/ConfigureWifiSettings.java
@@ -74,7 +74,8 @@
 
     @Override
     protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
-        mWifiWakeupPreferenceController = new WifiWakeupPreferenceController(context, this);
+        mWifiWakeupPreferenceController = new WifiWakeupPreferenceController(context, this,
+                getSettingsLifecycle());
         mUseOpenWifiPreferenceController = new UseOpenWifiPreferenceController(context, this,
                 getSettingsLifecycle());
         final WifiManager wifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
diff --git a/src/com/android/settings/wifi/WifiWakeupPreferenceController.java b/src/com/android/settings/wifi/WifiWakeupPreferenceController.java
index 15bffd9..2726de4 100644
--- a/src/com/android/settings/wifi/WifiWakeupPreferenceController.java
+++ b/src/com/android/settings/wifi/WifiWakeupPreferenceController.java
@@ -19,8 +19,10 @@
 import static com.android.settings.wifi.ConfigureWifiSettings.WIFI_WAKEUP_REQUEST_CODE;
 
 import android.app.Service;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.location.LocationManager;
 import android.provider.Settings;
 import android.text.TextUtils;
@@ -36,12 +38,17 @@
 import com.android.settings.dashboard.DashboardFragment;
 import com.android.settings.utils.AnnotationSpan;
 import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnPause;
+import com.android.settingslib.core.lifecycle.events.OnResume;
 
 /**
  * {@link PreferenceControllerMixin} that controls whether the Wi-Fi Wakeup feature should be
  * enabled.
  */
-public class WifiWakeupPreferenceController extends AbstractPreferenceController {
+public class WifiWakeupPreferenceController extends AbstractPreferenceController implements
+        LifecycleObserver, OnPause, OnResume {
 
     private static final String TAG = "WifiWakeupPrefController";
     private static final String KEY_ENABLE_WIFI_WAKEUP = "enable_wifi_wakeup";
@@ -52,11 +59,21 @@
     SwitchPreference mPreference;
     @VisibleForTesting
     LocationManager mLocationManager;
+    private final BroadcastReceiver mLocationReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            updateState(mPreference);
+        }
+    };
+    private final IntentFilter mLocationFilter =
+            new IntentFilter(LocationManager.MODE_CHANGED_ACTION);
 
-    public WifiWakeupPreferenceController(Context context, DashboardFragment fragment) {
+    public WifiWakeupPreferenceController(Context context, DashboardFragment fragment,
+            Lifecycle lifecycle) {
         super(context);
         mFragment = fragment;
         mLocationManager = (LocationManager) context.getSystemService(Service.LOCATION_SERVICE);
+        lifecycle.addObserver(this);
     }
 
     @Override
@@ -155,4 +172,14 @@
         Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.WIFI_WAKEUP_ENABLED,
                 enabled ? 1 : 0);
     }
+
+    @Override
+    public void onResume() {
+        mContext.registerReceiver(mLocationReceiver, mLocationFilter);
+    }
+
+    @Override
+    public void onPause() {
+        mContext.unregisterReceiver(mLocationReceiver);
+    }
 }
diff --git a/src/com/android/settings/wifi/p2p/WifiP2pPreferenceController.java b/src/com/android/settings/wifi/p2p/WifiP2pPreferenceController.java
index 4f3b7fd..86cce1e 100644
--- a/src/com/android/settings/wifi/p2p/WifiP2pPreferenceController.java
+++ b/src/com/android/settings/wifi/p2p/WifiP2pPreferenceController.java
@@ -15,10 +15,12 @@
  */
 package com.android.settings.wifi.p2p;
 
+import android.app.Service;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.location.LocationManager;
 import android.net.wifi.WifiManager;
 
 import androidx.annotation.VisibleForTesting;
@@ -49,6 +51,17 @@
         }
     };
     private final IntentFilter mFilter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION);
+    private final LocationManager mLocationManager;
+    private final BroadcastReceiver mLocationReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (mWifiDirectPref != null) {
+                updateState(mWifiDirectPref);
+            }
+        }
+    };
+    private final IntentFilter mLocationFilter =
+            new IntentFilter(LocationManager.MODE_CHANGED_ACTION);
 
     private Preference mWifiDirectPref;
 
@@ -57,6 +70,7 @@
         super(context);
         mWifiManager = wifiManager;
         lifecycle.addObserver(this);
+        mLocationManager = (LocationManager) context.getSystemService(Service.LOCATION_SERVICE);
     }
 
     @Override
@@ -67,13 +81,21 @@
     }
 
     @Override
+    public void updateState(Preference preference) {
+        super.updateState(preference);
+        preference.setEnabled(mLocationManager.isLocationEnabled() && mWifiManager.isWifiEnabled());
+    }
+
+    @Override
     public void onResume() {
         mContext.registerReceiver(mReceiver, mFilter);
+        mContext.registerReceiver(mLocationReceiver, mLocationFilter);
     }
 
     @Override
     public void onPause() {
         mContext.unregisterReceiver(mReceiver);
+        mContext.unregisterReceiver(mLocationReceiver);
     }
 
     @Override
@@ -88,7 +110,9 @@
 
     private void togglePreferences() {
         if (mWifiDirectPref != null) {
-            mWifiDirectPref.setEnabled(mWifiManager.isWifiEnabled());
+            mWifiDirectPref.setEnabled(
+                    mWifiManager.isWifiEnabled()
+                    && mLocationManager.isLocationEnabled());
         }
     }
 }
diff --git a/src/com/android/settings/wifi/slice/ConnectToWifiHandler.java b/src/com/android/settings/wifi/slice/ConnectToWifiHandler.java
index 6cf55d2..ee15820 100644
--- a/src/com/android/settings/wifi/slice/ConnectToWifiHandler.java
+++ b/src/com/android/settings/wifi/slice/ConnectToWifiHandler.java
@@ -17,6 +17,8 @@
 package com.android.settings.wifi.slice;
 
 import android.app.Activity;
+import android.net.ConnectivityManager;
+import android.net.Network;
 import android.net.wifi.WifiManager;
 import android.os.Bundle;
 
@@ -36,10 +38,15 @@
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
+        final Network network = getIntent().getParcelableExtra(ConnectivityManager.EXTRA_NETWORK);
         final Bundle accessPointState = getIntent().getBundleExtra(
                 WifiDialogActivity.KEY_ACCESS_POINT_STATE);
 
-        if (accessPointState != null) {
+        if (network != null) {
+            final ConnectivityManager cm = getSystemService(ConnectivityManager.class);
+            // start captive portal app to sign in to network
+            cm.startCaptivePortalApp(network);
+        } else if (accessPointState != null) {
             connect(new AccessPoint(this, accessPointState));
         }
         finish();
diff --git a/src/com/android/settings/wifi/slice/ContextualWifiSlice.java b/src/com/android/settings/wifi/slice/ContextualWifiSlice.java
index 4a799d1..7dbba41 100644
--- a/src/com/android/settings/wifi/slice/ContextualWifiSlice.java
+++ b/src/com/android/settings/wifi/slice/ContextualWifiSlice.java
@@ -57,7 +57,7 @@
             sActiveUiSession = currentUiSession;
             sPreviouslyDisplayed = false;
         }
-        if (!sPreviouslyDisplayed && !TextUtils.equals(getActiveSSID(), WifiSsid.NONE)) {
+        if (!sPreviouslyDisplayed && hasWorkingNetwork()) {
             Log.d(TAG, "Wifi is connected, no point showing any suggestion.");
             return null;
         }
@@ -67,4 +67,8 @@
 
         return super.getSlice();
     }
+
+    private boolean hasWorkingNetwork() {
+        return !TextUtils.equals(getActiveSSID(), WifiSsid.NONE) && !isCaptivePortal();
+    }
 }
diff --git a/src/com/android/settings/wifi/slice/WifiScanWorker.java b/src/com/android/settings/wifi/slice/WifiScanWorker.java
index cf45d08..b846228 100644
--- a/src/com/android/settings/wifi/slice/WifiScanWorker.java
+++ b/src/com/android/settings/wifi/slice/WifiScanWorker.java
@@ -16,14 +16,27 @@
 
 package com.android.settings.wifi.slice;
 
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static com.android.settings.wifi.slice.WifiSlice.DEFAULT_EXPANDED_ROW_COUNT;
 
 import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.Network;
+import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
+import android.net.NetworkRequest;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
 
+import androidx.annotation.VisibleForTesting;
+
+import com.android.internal.util.Preconditions;
 import com.android.settings.slices.SliceBackgroundWorker;
+import com.android.settings.wifi.WifiUtils;
 import com.android.settingslib.wifi.AccessPoint;
 import com.android.settingslib.wifi.WifiTracker;
 
@@ -33,16 +46,23 @@
 /**
  * {@link SliceBackgroundWorker} for Wi-Fi, used by WifiSlice.
  */
-public class WifiScanWorker extends SliceBackgroundWorker<AccessPoint>
-        implements WifiTracker.WifiListener {
+public class WifiScanWorker extends SliceBackgroundWorker<AccessPoint> implements
+        WifiTracker.WifiListener {
+
+    private static final String TAG = "WifiScanWorker";
+
+    @VisibleForTesting
+    CaptivePortalNetworkCallback mCaptivePortalNetworkCallback;
 
     private final Context mContext;
 
     private WifiTracker mWifiTracker;
+    private ConnectivityManager mConnectivityManager;
 
     public WifiScanWorker(Context context, Uri uri) {
         super(context, uri);
         mContext = context;
+        mConnectivityManager = context.getSystemService(ConnectivityManager.class);
     }
 
     @Override
@@ -58,6 +78,7 @@
     @Override
     protected void onSliceUnpinned() {
         mWifiTracker.onStop();
+        unregisterCaptivePortalNetworkCallback();
     }
 
     @Override
@@ -124,4 +145,71 @@
         }
         return null;
     }
-}
\ No newline at end of file
+
+    public void registerCaptivePortalNetworkCallback(Network wifiNetwork) {
+        if (wifiNetwork == null) {
+            return;
+        }
+
+        if (mCaptivePortalNetworkCallback != null
+                && mCaptivePortalNetworkCallback.isSameNetwork(wifiNetwork)) {
+            return;
+        }
+
+        unregisterCaptivePortalNetworkCallback();
+
+        mCaptivePortalNetworkCallback = new CaptivePortalNetworkCallback(wifiNetwork);
+        mConnectivityManager.registerNetworkCallback(
+                new NetworkRequest.Builder()
+                        .clearCapabilities()
+                        .addTransportType(TRANSPORT_WIFI)
+                        .build(),
+                mCaptivePortalNetworkCallback,
+                new Handler(Looper.getMainLooper()));
+    }
+
+    public void unregisterCaptivePortalNetworkCallback() {
+        if (mCaptivePortalNetworkCallback != null) {
+            try {
+                mConnectivityManager.unregisterNetworkCallback(mCaptivePortalNetworkCallback);
+            } catch (RuntimeException e) {
+                Log.e(TAG, "Unregistering CaptivePortalNetworkCallback failed.", e);
+            }
+            mCaptivePortalNetworkCallback = null;
+        }
+    }
+
+    class CaptivePortalNetworkCallback extends NetworkCallback {
+
+        private final Network mNetwork;
+        private boolean mIsCaptivePortal;
+
+        CaptivePortalNetworkCallback(Network network) {
+            mNetwork = Preconditions.checkNotNull(network);
+        }
+
+        @Override
+        public void onCapabilitiesChanged(Network network,
+                NetworkCapabilities networkCapabilities) {
+            if (!mNetwork.equals(network)) {
+                return;
+            }
+
+            final boolean isCaptivePortal = WifiUtils.canSignIntoNetwork(networkCapabilities);
+            if (mIsCaptivePortal == isCaptivePortal) {
+                return;
+            }
+
+            mIsCaptivePortal = isCaptivePortal;
+            notifySliceChange();
+        }
+
+        /**
+         * Returns true if the supplied network is not null and is the same as the originally
+         * supplied value.
+         */
+        public boolean isSameNetwork(Network network) {
+            return mNetwork.equals(network);
+        }
+    }
+}
diff --git a/src/com/android/settings/wifi/slice/WifiSlice.java b/src/com/android/settings/wifi/slice/WifiSlice.java
index d3df5fc..3fe2950 100644
--- a/src/com/android/settings/wifi/slice/WifiSlice.java
+++ b/src/com/android/settings/wifi/slice/WifiSlice.java
@@ -31,6 +31,8 @@
 import android.graphics.PorterDuffColorFilter;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
+import android.net.ConnectivityManager;
+import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.State;
 import android.net.NetworkInfo.DetailedState;
@@ -78,10 +80,12 @@
 
     protected final Context mContext;
     protected final WifiManager mWifiManager;
+    protected final ConnectivityManager mConnectivityManager;
 
     public WifiSlice(Context context) {
         mContext = context;
         mWifiManager = mContext.getSystemService(WifiManager.class);
+        mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
     }
 
     @Override
@@ -100,13 +104,16 @@
             return listBuilder.build();
         }
 
-        final SliceBackgroundWorker worker = SliceBackgroundWorker.getInstance(getUri());
+        final WifiScanWorker worker = SliceBackgroundWorker.getInstance(getUri());
         final List<AccessPoint> results = worker != null ? worker.getResults() : null;
         final int apCount = results == null ? 0 : results.size();
+        final boolean isFirstApActive = apCount > 0 && results.get(0).isActive();
+        handleCaptivePortalCallback(worker, isFirstApActive);
 
         // Need a loading text when results are not ready or out of date.
         boolean needLoadingRow = true;
-        int index = apCount > 0 && results.get(0).isActive() ? 1 : 0;
+        // Skip checking the existence of the first access point if it's active
+        int index = isFirstApActive ? 1 : 0;
         // This loop checks the existence of reachable APs to determine the validity of the current
         // AP list.
         for (; index < apCount; index++) {
@@ -159,19 +166,35 @@
                         .setPrimaryAction(primarySliceAction));
     }
 
+    private void handleCaptivePortalCallback(WifiScanWorker worker, boolean isFirstApActive) {
+        if (worker == null) {
+            return;
+        }
+        if (isFirstApActive) {
+            worker.registerCaptivePortalNetworkCallback(mWifiManager.getCurrentNetwork());
+        } else {
+            worker.unregisterCaptivePortalNetworkCallback();
+        }
+    }
+
     private ListBuilder.RowBuilder getAccessPointRow(AccessPoint accessPoint) {
         final CharSequence title = getAccessPointName(accessPoint);
         final IconCompat levelIcon = getAccessPointLevelIcon(accessPoint);
+        final boolean isCaptivePortal = accessPoint.isActive() && isCaptivePortal();
         final ListBuilder.RowBuilder rowBuilder = new ListBuilder.RowBuilder()
                 .setTitleItem(levelIcon, ListBuilder.ICON_IMAGE)
                 .setSubtitle(title)
-                .setPrimaryAction(SliceAction.create(
-                        getAccessPointAction(accessPoint), levelIcon, ListBuilder.ICON_IMAGE,
-                        title));
+                .setPrimaryAction(SliceAction.createDeeplink(
+                        getAccessPointAction(accessPoint, isCaptivePortal), levelIcon,
+                        ListBuilder.ICON_IMAGE, title));
 
-        final IconCompat endIcon = getEndIcon(accessPoint);
-        if (endIcon != null) {
-            rowBuilder.addEndItem(endIcon, ListBuilder.ICON_IMAGE);
+        if (isCaptivePortal) {
+            rowBuilder.addEndItem(getCaptivePortalEndAction(accessPoint, title));
+        } else {
+            final IconCompat endIcon = getEndIcon(accessPoint);
+            if (endIcon != null) {
+                rowBuilder.addEndItem(endIcon, ListBuilder.ICON_IMAGE);
+            }
         }
         return rowBuilder;
     }
@@ -218,12 +241,22 @@
         return null;
     }
 
-    private PendingIntent getAccessPointAction(AccessPoint accessPoint) {
+    private SliceAction getCaptivePortalEndAction(AccessPoint accessPoint, CharSequence title) {
+        return SliceAction.createDeeplink(
+                getAccessPointAction(accessPoint, false /* isCaptivePortal */),
+                IconCompat.createWithResource(mContext, R.drawable.ic_settings_accent),
+                ListBuilder.ICON_IMAGE, title);
+    }
+
+    private PendingIntent getAccessPointAction(AccessPoint accessPoint, boolean isCaptivePortal) {
         final Bundle extras = new Bundle();
         accessPoint.saveWifiState(extras);
 
         Intent intent;
-        if (accessPoint.isActive()) {
+        if (isCaptivePortal) {
+            intent = new Intent(mContext, ConnectToWifiHandler.class);
+            intent.putExtra(ConnectivityManager.EXTRA_NETWORK, mWifiManager.getCurrentNetwork());
+        } else if (accessPoint.isActive()) {
             intent = new SubSettingLauncher(mContext)
                     .setTitleRes(R.string.pref_title_network_details)
                     .setDestination(WifiNetworkDetailsFragment.class.getName())
@@ -253,6 +286,12 @@
                 .setSubtitle(title);
     }
 
+    protected boolean isCaptivePortal() {
+        final NetworkCapabilities nc = mConnectivityManager.getNetworkCapabilities(
+                mWifiManager.getCurrentNetwork());
+        return WifiUtils.canSignIntoNetwork(nc);
+    }
+
     /**
      * Update the current wifi status to the boolean value keyed by
      * {@link android.app.slice.Slice#EXTRA_TOGGLE_STATE} on {@param intent}.
@@ -317,6 +356,12 @@
     }
 
     private CharSequence getSummary(AccessPoint accessPoint) {
+        if (isCaptivePortal()) {
+            final int id = mContext.getResources()
+                    .getIdentifier("network_available_sign_in", "string", "android");
+            return mContext.getText(id);
+        }
+
         if (accessPoint == null) {
             return getSummary();
         }
diff --git a/tests/robotests/src/com/android/settings/notification/BubblePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/BubblePreferenceControllerTest.java
index 6d13798..54bbd08 100644
--- a/tests/robotests/src/com/android/settings/notification/BubblePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/BubblePreferenceControllerTest.java
@@ -29,6 +29,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -53,6 +54,8 @@
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.shadows.ShadowApplication;
 
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentTransaction;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
 
@@ -68,6 +71,8 @@
     private UserManager mUm;
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private PreferenceScreen mScreen;
+    @Mock
+    private FragmentManager mFragmentManager;
 
     private BubblePreferenceController mController;
 
@@ -78,7 +83,8 @@
         shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNm);
         shadowApplication.setSystemService(Context.USER_SERVICE, mUm);
         mContext = RuntimeEnvironment.application;
-        mController = spy(new BubblePreferenceController(mContext, mBackend));
+        when(mFragmentManager.beginTransaction()).thenReturn(mock(FragmentTransaction.class));
+        mController = spy(new BubblePreferenceController(mContext, mFragmentManager, mBackend));
     }
 
     @Test
@@ -117,7 +123,16 @@
     }
 
     @Test
-    public void testIsAvailable_notIfOffGlobally() {
+    public void testIsAvailable_ifOffGlobally_app() {
+        NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
+        mController.onResume(appRow, null, null, null);
+        Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, 0);
+
+        assertTrue(mController.isAvailable());
+    }
+
+    @Test
+    public void testIsAvailable_notIfOffGlobally_channel() {
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
         NotificationChannel channel = mock(NotificationChannel.class);
         when(channel.getImportance()).thenReturn(IMPORTANCE_HIGH);
@@ -243,6 +258,19 @@
     }
 
     @Test
+    public void testUpdateState_app_offGlobally() {
+        Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, 0);
+        NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
+        appRow.label = "App!";
+        appRow.allowBubbles = true;
+        mController.onResume(appRow, null, null, null);
+
+        RestrictedSwitchPreference pref = new RestrictedSwitchPreference(mContext);
+        mController.updateState(pref);
+        assertFalse(pref.isChecked());
+    }
+
+    @Test
     public void testOnPreferenceChange_on_channel() {
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
         appRow.allowBubbles = true;
@@ -313,4 +341,23 @@
         assertFalse(appRow.allowBubbles);
         verify(mBackend, times(1)).setAllowBubbles(any(), anyInt(), eq(false));
     }
+
+    @Test
+    public void testOnPreferenceChange_on_app_offGlobally() {
+        Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, 0);
+        NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
+        appRow.allowBubbles = false;
+        mController.onResume(appRow, null, null, null);
+
+        RestrictedSwitchPreference pref = new RestrictedSwitchPreference(mContext);
+        when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(pref);
+        mController.displayPreference(mScreen);
+        mController.updateState(pref);
+
+        mController.onPreferenceChange(pref, true);
+
+        assertFalse(appRow.allowBubbles);
+        verify(mBackend, never()).setAllowBubbles(any(), anyInt(), eq(true));
+        verify(mFragmentManager, times(1)).beginTransaction();
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/notification/BubbleSummaryPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/BubbleSummaryPreferenceControllerTest.java
index 5158e82..0a0addc 100644
--- a/tests/robotests/src/com/android/settings/notification/BubbleSummaryPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/BubbleSummaryPreferenceControllerTest.java
@@ -110,6 +110,15 @@
     }
 
     @Test
+    public void testIsAvailable_app_globalOff() {
+        NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
+        mController.onResume(appRow, null, null, null);
+        Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, 0);
+
+        assertTrue(mController.isAvailable());
+    }
+
+    @Test
     public void testIsAvailable_defaultChannel() {
         NotificationBackend.AppRow appRow = new NotificationBackend.AppRow();
         appRow.allowBubbles = true;
@@ -141,6 +150,10 @@
 
         assertEquals("On", mController.getSummary());
 
+        Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, 0);
+        assertEquals("Off", mController.getSummary());
+
+        Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, 1);
         appRow.allowBubbles = false;
         mController.onResume(appRow, null, null, null);
 
diff --git a/tests/robotests/src/com/android/settings/notification/ZenModeSoundSettingsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/ZenModeSoundSettingsPreferenceControllerTest.java
new file mode 100644
index 0000000..a08a4d7
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/ZenModeSoundSettingsPreferenceControllerTest.java
@@ -0,0 +1,50 @@
+/*
+ * 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.android.settings.core.BasePreferenceController.AVAILABLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class ZenModeSoundSettingsPreferenceControllerTest {
+
+    private Context mContext;
+    private ZenModeSoundSettingsPreferenceController mController;
+    private static final String KEY_ZEN_MODE = "zen_mode";
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mController = new ZenModeSoundSettingsPreferenceController(mContext, KEY_ZEN_MODE);
+    }
+
+    @Test
+    public void getAvailabilityStatus_available() {
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/testutils/SliceTester.java b/tests/robotests/src/com/android/settings/testutils/SliceTester.java
index fdd4475..6fb2c49 100644
--- a/tests/robotests/src/com/android/settings/testutils/SliceTester.java
+++ b/tests/robotests/src/com/android/settings/testutils/SliceTester.java
@@ -18,6 +18,7 @@
 
 import static android.app.slice.Slice.HINT_TITLE;
 import static android.app.slice.Slice.SUBTYPE_COLOR;
+import static android.app.slice.SliceItem.FORMAT_IMAGE;
 import static android.app.slice.SliceItem.FORMAT_INT;
 import static android.app.slice.SliceItem.FORMAT_TEXT;
 
@@ -284,6 +285,31 @@
         return hasText;
     }
 
+    /**
+     * Assert any slice item contains icon.
+     *
+     * @param sliceItems All slice items of a Slice.
+     * @param icon Icon for asserting.
+     */
+    public static void assertAnySliceItemContainsIcon(List<SliceItem> sliceItems, IconCompat icon) {
+        boolean hasIcon = false;
+        for (SliceItem item : sliceItems) {
+            List<SliceItem> iconItems = SliceQuery.findAll(item, FORMAT_IMAGE,
+                    (String) null /* hints */, null /* non-hints */);
+            if (iconItems == null) {
+                continue;
+            }
+
+            for (SliceItem iconItem : iconItems) {
+                if (icon.toString().equals(iconItem.getIcon().toString())) {
+                    hasIcon = true;
+                    break;
+                }
+            }
+        }
+        assertThat(hasIcon).isTrue();
+    }
+
     private static void assertKeywords(SliceMetadata metadata, SliceData data) {
         final List<String> keywords = metadata.getSliceKeywords();
         final Set<String> expectedKeywords = Arrays.stream(data.getKeywords().split(","))
diff --git a/tests/robotests/src/com/android/settings/wifi/WifiWakeupPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/wifi/WifiWakeupPreferenceControllerTest.java
index 166b29b..ab5f4ea 100644
--- a/tests/robotests/src/com/android/settings/wifi/WifiWakeupPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/wifi/WifiWakeupPreferenceControllerTest.java
@@ -34,6 +34,7 @@
 import com.android.settings.R;
 import com.android.settings.dashboard.DashboardFragment;
 
+import com.android.settingslib.core.lifecycle.Lifecycle;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -53,12 +54,14 @@
     private LocationManager mLocationManager;
     @Mock
     private SwitchPreference mPreference;
+    @Mock
+    private Lifecycle mLifecycle;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mContext = RuntimeEnvironment.application;
-        mController = new WifiWakeupPreferenceController(mContext, mFragment);
+        mController = new WifiWakeupPreferenceController(mContext, mFragment, mLifecycle);
         mController.mLocationManager = mLocationManager;
         mController.mPreference = mPreference;
 
diff --git a/tests/robotests/src/com/android/settings/wifi/p2p/WifiP2PPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/wifi/p2p/WifiP2PPreferenceControllerTest.java
index 0968803..ec8d168 100644
--- a/tests/robotests/src/com/android/settings/wifi/p2p/WifiP2PPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/wifi/p2p/WifiP2PPreferenceControllerTest.java
@@ -22,20 +22,24 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.Service;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.location.LocationManager;
 import android.net.wifi.WifiManager;
 
 import androidx.lifecycle.LifecycleOwner;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
 
+import com.android.settings.dashboard.DashboardFragment;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 
 import org.junit.Before;
@@ -57,6 +61,8 @@
     private PreferenceScreen mScreen;
     @Mock
     private Preference mWifiDirectPreference;
+    @Mock
+    private LocationManager mLocationManager;
 
     private Lifecycle mLifecycle;
     private LifecycleOwner mLifecycleOwner;
@@ -69,6 +75,7 @@
         mLifecycle = new Lifecycle(mLifecycleOwner);
         when(mContext.getSystemService(WifiManager.class)).thenReturn(mWifiManager);
         when(mScreen.findPreference(anyString())).thenReturn(mWifiDirectPreference);
+        when(mContext.getSystemService(eq(Service.LOCATION_SERVICE))).thenReturn(mLocationManager);
         mController = new WifiP2pPreferenceController(mContext, mLifecycle, mWifiManager);
     }
 
@@ -80,19 +87,21 @@
     @Test
     public void testOnResume_shouldRegisterListener() {
         mLifecycle.handleLifecycleEvent(ON_RESUME);
-        verify(mContext).registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class));
+        verify(mContext, times(2)).registerReceiver(
+                any(BroadcastReceiver.class), any(IntentFilter.class));
     }
 
     @Test
     public void testOnPause_shouldUnregisterListener() {
         mLifecycle.handleLifecycleEvent(ON_RESUME);
         mLifecycle.handleLifecycleEvent(ON_PAUSE);
-        verify(mContext).unregisterReceiver(any(BroadcastReceiver.class));
+        verify(mContext, times(2)).unregisterReceiver(any(BroadcastReceiver.class));
     }
 
     @Test
     public void testWifiStateChange_shouldToggleEnabledState() {
         when(mWifiManager.isWifiEnabled()).thenReturn(true);
+        when(mLocationManager.isLocationEnabled()).thenReturn(true);
 
         //Sets the preferences.
         mController.displayPreference(mScreen);
@@ -110,11 +119,17 @@
     @Test
     public void testDisplayPreference_shouldToggleEnabledState() {
         when(mWifiManager.isWifiEnabled()).thenReturn(true);
+        when(mLocationManager.isLocationEnabled()).thenReturn(true);
         mController.displayPreference(mScreen);
         verify(mWifiDirectPreference).setEnabled(true);
 
         when(mWifiManager.isWifiEnabled()).thenReturn(false);
         mController.displayPreference(mScreen);
         verify(mWifiDirectPreference).setEnabled(false);
+
+        when(mWifiManager.isWifiEnabled()).thenReturn(true);
+        when(mLocationManager.isLocationEnabled()).thenReturn(false);
+        mController.displayPreference(mScreen);
+        verify(mWifiDirectPreference, times(2)).setEnabled(false);
     }
 }
diff --git a/tests/robotests/src/com/android/settings/wifi/slice/ContextualWifiSliceTest.java b/tests/robotests/src/com/android/settings/wifi/slice/ContextualWifiSliceTest.java
index 520d988..55bf2d3 100644
--- a/tests/robotests/src/com/android/settings/wifi/slice/ContextualWifiSliceTest.java
+++ b/tests/robotests/src/com/android/settings/wifi/slice/ContextualWifiSliceTest.java
@@ -16,6 +16,7 @@
 
 package com.android.settings.wifi.slice;
 
+import static org.mockito.ArgumentMatchers.any;
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.doReturn;
@@ -24,6 +25,7 @@
 
 import android.content.ContentResolver;
 import android.content.Context;
+import android.net.ConnectivityManager;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
 
@@ -53,6 +55,7 @@
     private Context mContext;
     private ContentResolver mResolver;
     private WifiManager mWifiManager;
+    private ConnectivityManager mConnectivityManager;
     private ContextualWifiSlice mWifiSlice;
     private FakeFeatureFactory mFeatureFactory;
 
@@ -70,6 +73,9 @@
         SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS);
         mWifiManager.setWifiEnabled(true);
 
+        mConnectivityManager = spy(mContext.getSystemService(ConnectivityManager.class));
+        doReturn(mConnectivityManager).when(mContext).getSystemService(ConnectivityManager.class);
+
         mWifiSlice = new ContextualWifiSlice(mContext);
         mWifiSlice.sPreviouslyDisplayed = false;
     }
@@ -125,6 +131,29 @@
     }
 
     @Test
+    public void getWifiSlice_isCaptivePortal_shouldHaveTitleAndToggle() {
+        mWifiSlice.sPreviouslyDisplayed = false;
+        final WifiConfiguration config = new WifiConfiguration();
+        config.SSID = "123";
+        mWifiManager.connect(config, null /* listener */);
+        doReturn(WifiSliceTest.makeCaptivePortalNetworkCapabilities()).when(mConnectivityManager)
+                .getNetworkCapabilities(any());
+
+        final Slice wifiSlice = mWifiSlice.getSlice();
+
+        final SliceMetadata metadata = SliceMetadata.from(mContext, wifiSlice);
+        assertThat(metadata.getTitle()).isEqualTo(mContext.getString(R.string.wifi_settings));
+
+        final List<SliceAction> toggles = metadata.getToggles();
+        assertThat(toggles).hasSize(1);
+
+        final SliceAction primaryAction = metadata.getPrimaryAction();
+        final IconCompat expectedToggleIcon = IconCompat.createWithResource(mContext,
+                R.drawable.ic_settings_wireless);
+        assertThat(primaryAction.getIcon().toString()).isEqualTo(expectedToggleIcon.toString());
+    }
+
+    @Test
     public void getWifiSlice_contextualWifiSlice_shouldReturnContextualWifiSliceUri() {
         mWifiSlice.sActiveUiSession = mFeatureFactory.slicesFeatureProvider.getUiSessionToken();
         mWifiSlice.sPreviouslyDisplayed = true;
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 7ddbce4..30e289b 100644
--- a/tests/robotests/src/com/android/settings/wifi/slice/WifiScanWorkerTest.java
+++ b/tests/robotests/src/com/android/settings/wifi/slice/WifiScanWorkerTest.java
@@ -27,6 +27,8 @@
 
 import android.content.ContentResolver;
 import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.Network;
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.State;
 import android.net.wifi.WifiManager;
@@ -55,6 +57,7 @@
     private Context mContext;
     private ContentResolver mResolver;
     private WifiManager mWifiManager;
+    private ConnectivityManager mConnectivityManager;
     private WifiScanWorker mWifiScanWorker;
 
     @Before
@@ -68,6 +71,7 @@
         SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS);
         mWifiManager.setWifiEnabled(true);
 
+        mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
         mWifiScanWorker = new WifiScanWorker(mContext, WIFI_SLICE_URI);
     }
 
@@ -89,7 +93,7 @@
     }
 
     @Test
-    public void SliceAccessPoint_sameState_shouldBeTheSame() {
+    public void AccessPointList_sameState_shouldBeTheSame() {
         final AccessPoint ap1 = createAccessPoint(AP_NAME, State.CONNECTED);
         final AccessPoint ap2 = createAccessPoint(AP_NAME, State.CONNECTED);
 
@@ -98,7 +102,7 @@
     }
 
     @Test
-    public void SliceAccessPoint_differentState_shouldBeDifferent() {
+    public void AccessPointList_differentState_shouldBeDifferent() {
         final AccessPoint ap1 = createAccessPoint(AP_NAME, State.CONNECTING);
         final AccessPoint ap2 = createAccessPoint(AP_NAME, State.CONNECTED);
 
@@ -107,7 +111,7 @@
     }
 
     @Test
-    public void SliceAccessPoint_differentLength_shouldBeDifferent() {
+    public void AccessPointList_differentLength_shouldBeDifferent() {
         final AccessPoint ap1 = createAccessPoint(AP_NAME, State.CONNECTED);
         final AccessPoint ap2 = createAccessPoint(AP_NAME, State.CONNECTED);
         final List<AccessPoint> list = new ArrayList<>();
@@ -116,4 +120,15 @@
 
         assertThat(mWifiScanWorker.areListsTheSame(list, Arrays.asList(ap1))).isFalse();
     }
+
+    @Test
+    public void NetworkCallback_onCapabilitiesChanged_shouldNotifyChange() {
+        final Network network = mConnectivityManager.getActiveNetwork();
+        mWifiScanWorker.registerCaptivePortalNetworkCallback(network);
+
+        mWifiScanWorker.mCaptivePortalNetworkCallback.onCapabilitiesChanged(network,
+                WifiSliceTest.makeCaptivePortalNetworkCapabilities());
+
+        verify(mResolver).notifyChange(WIFI_SLICE_URI, null);
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/wifi/slice/WifiSliceTest.java b/tests/robotests/src/com/android/settings/wifi/slice/WifiSliceTest.java
index b2718fc..b8c7a8c 100644
--- a/tests/robotests/src/com/android/settings/wifi/slice/WifiSliceTest.java
+++ b/tests/robotests/src/com/android/settings/wifi/slice/WifiSliceTest.java
@@ -23,6 +23,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
@@ -30,6 +31,8 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.net.Uri;
 import android.net.wifi.WifiManager;
@@ -70,6 +73,7 @@
     private Context mContext;
     private ContentResolver mResolver;
     private WifiManager mWifiManager;
+    private ConnectivityManager mConnectivityManager;
     private WifiSlice mWifiSlice;
 
     @Before
@@ -83,6 +87,9 @@
         SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS);
         mWifiManager.setWifiEnabled(true);
 
+        mConnectivityManager = spy(mContext.getSystemService(ConnectivityManager.class));
+        doReturn(mConnectivityManager).when(mContext).getSystemService(ConnectivityManager.class);
+
         mWifiSlice = new WifiSlice(mContext);
     }
 
@@ -227,6 +234,37 @@
     }
 
     @Test
+    public void getWifiSlice_isCaptivePortal_shouldHaveCaptivePortalItems() {
+        setWorkerResults(createAccessPoint(AP1_NAME, true, true));
+        doReturn(makeCaptivePortalNetworkCapabilities()).when(mConnectivityManager)
+                .getNetworkCapabilities(any());
+
+        final Slice wifiSlice = mWifiSlice.getSlice();
+        final List<SliceItem> sliceItems = wifiSlice.getItems();
+
+        SliceTester.assertAnySliceItemContainsTitle(sliceItems, AP1_NAME);
+        assertCaptivePortalItems(sliceItems);
+    }
+
+    private void assertCaptivePortalItems(List<SliceItem> sliceItems) {
+        final String expectedSummary = mContext.getString(mContext.getResources()
+                .getIdentifier("network_available_sign_in", "string", "android"));
+        SliceTester.assertAnySliceItemContainsSubtitle(sliceItems, expectedSummary);
+
+        final IconCompat expectedIcon = IconCompat.createWithResource(mContext,
+                R.drawable.ic_settings_accent);
+        SliceTester.assertAnySliceItemContainsIcon(sliceItems, expectedIcon);
+    }
+
+    static NetworkCapabilities makeCaptivePortalNetworkCapabilities() {
+        final NetworkCapabilities nc = new NetworkCapabilities();
+        nc.clearAll();
+        nc.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
+        nc.addCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL);
+        return nc;
+    }
+
+    @Test
     public void handleUriChange_updatesWifi() {
         final Intent intent = mWifiSlice.getIntent();
         intent.putExtra(android.app.slice.Slice.EXTRA_TOGGLE_STATE, true);