Merge "Get intent for backup settings from backup transport."
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index b14856c..6698814 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -166,11 +166,11 @@
                 android:parentActivityName="Settings">
         </activity>
 
-        <activity android:name="CreateShortcut" android:label="@string/settings_shortcut"
-            android:theme="@style/Theme.SubSettingsDialogWhenLarge">
+        <activity android:name="CreateShortcut"
+                  android:label="@string/settings_shortcut">
             <intent-filter>
-                <action android:name="android.intent.action.CREATE_SHORTCUT" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.CREATE_SHORTCUT"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
@@ -341,7 +341,6 @@
         </activity>
 
         <activity android:name=".Settings$WifiInfoActivity"
-                android:theme="@style/Theme.SubSettingsDialogWhenLarge"
                 android:taskAffinity="com.android.settings"
                 android:parentActivityName="Settings$WifiSettingsActivity">
             <intent-filter>
@@ -354,7 +353,6 @@
         </activity>
 
         <activity android:name=".wifi.WifiConfigInfo"
-                android:theme="@style/Theme.SubSettingsDialogWhenLarge"
                 android:taskAffinity="com.android.settings"
                 android:parentActivityName="Settings$WifiSettingsActivity">
             <intent-filter>
@@ -364,8 +362,7 @@
             </intent-filter>
         </activity>
 
-        <activity android:name=".Settings$WifiAPITestActivity"
-                android:theme="@style/Theme.SubSettingsDialogWhenLarge">
+        <activity android:name=".Settings$WifiAPITestActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.DEVELOPMENT_PREFERENCE" />
@@ -375,8 +372,7 @@
                        android:value="com.android.settings.wifi.WifiAPITest" />
         </activity>
 
-        <activity android:name=".wifi.WifiStatusTest"
-                android:theme="@style/Theme.SubSettingsDialogWhenLarge">
+        <activity android:name=".wifi.WifiStatusTest">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.DEVELOPMENT_PREFERENCE" />
@@ -460,7 +456,6 @@
         </activity-alias>
 
         <activity android:name=".bluetooth.DevicePickerActivity"
-                android:theme="@style/Theme.SubSettingsDialogWhenLarge"
                 android:label="@string/device_picker"
                 android:clearTaskOnLaunch="true">
             <intent-filter>
@@ -966,7 +961,6 @@
         </activity>
 
         <activity android:name="Settings$DeviceInfoSettingsActivity"
-                android:theme="@style/Theme.SubSettingsDialogWhenLarge"
                 android:label="@string/device_info_settings"
                 android:icon="@drawable/ic_settings_about"
                 android:taskAffinity="com.android.settings"
@@ -1391,7 +1385,6 @@
         </activity>
 
         <activity android:name="SetFullBackupPassword"
-                android:theme="@style/Theme.SubSettingsDialogWhenLarge"
                 android:exported="false">
         </activity>
 
@@ -1431,7 +1424,6 @@
 
         <activity android:name="DeviceAdminAdd"
                 android:label="@string/device_admin_add_title"
-                android:theme="@style/Theme.SubSettingsDialogWhenLarge"
                 android:clearTaskOnLaunch="true">
             <intent-filter>
                 <action android:name="android.app.action.ADD_DEVICE_ADMIN" />
@@ -1466,7 +1458,6 @@
 
         <activity android:name="Settings$IccLockSettingsActivity"
                 android:label="@string/sim_lock_settings"
-                android:theme="@style/Theme.SubSettingsDialogWhenLarge"
                 android:process="com.android.phone">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1788,7 +1779,6 @@
 
         <activity android:name=".Settings$StatusActivity"
                 android:label="@string/device_status_activity_title"
-                android:theme="@style/Theme.SubSettingsDialogWhenLarge"
                 android:taskAffinity="com.android.settings"
                 android:parentActivityName="Settings$DeviceInfoSettingsActivity">
             <intent-filter>
@@ -1803,7 +1793,6 @@
         <!-- Runs in the phone process since it needs access to the Phone object -->
         <activity android:name=".Settings$SimStatusActivity"
                 android:label="@string/sim_status_title"
-                android:theme="@style/Theme.SubSettingsDialogWhenLarge"
                 android:process="com.android.phone">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1817,7 +1806,6 @@
         <!-- Runs in the phone process since it needs access to the Phone object -->
         <activity android:name=".Settings$ImeiInformationActivity"
                 android:label="@string/imei_information_title"
-                android:theme="@style/Theme.SubSettingsDialogWhenLarge"
                 android:process="com.android.phone">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2017,6 +2005,9 @@
         <activity android:name=".AppPicker" android:label="@string/select_application"
                 android:theme="@android:style/Theme.DeviceDefault.Light.Dialog" />
 
+        <activity android:name=".webview.WebViewAppPicker" android:label="@string/select_webview_provider_dialog_title"
+                android:theme="@android:style/Theme.DeviceDefault.Light.Dialog" />
+
         <!-- Keep compatibility with old shortcuts. -->
         <activity-alias android:name="UsbSettings"
                   android:exported="true"
@@ -2371,8 +2362,7 @@
 
         <activity android:name="Settings$ChooseAccountActivity"
             android:label="@string/header_add_an_account"
-            android:configChanges="orientation|keyboardHidden|screenSize"
-            android:theme="@style/Theme.SubSettingsDialogWhenLarge">
+            android:configChanges="orientation|keyboardHidden|screenSize">
             <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
                        android:value="com.android.settings.accounts.ChooseAccountActivity" />
         </activity>
@@ -2953,7 +2943,9 @@
                         android:value="true" />
         </activity>
 
-        <activity android:name=".WebViewImplementation"
+        <!-- Keep compatibility with old WebView-picker implementation -->
+        <activity-alias android:name=".WebViewImplementation"
+                  android:targetActivity=".webview.WebViewAppPicker"
                   android:exported="true"
                   android:excludeFromRecents="true"
                   android:theme="@*android:style/Theme.DeviceDefault.Light.Dialog.Alert">
@@ -2963,7 +2955,7 @@
             </intent-filter>
             <meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
                 android:value="true" />
-        </activity>
+      </activity-alias>
 
         <!-- activity for gesture settings -->
         <activity android:name="Settings$GestureSettingsActivity"
@@ -3324,17 +3316,6 @@
                        android:value="com.android.settings.applications.ProcessStatsSummary" />
         </activity-alias>
 
-        <activity-alias android:name="BluetoothDashboardAlias"
-                        android:targetActivity="Settings$BluetoothSettingsActivity">
-            <intent-filter android:priority="7">
-                <action android:name="com.android.settings.action.SETTINGS"/>
-            </intent-filter>
-            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
-                       android:value="com.android.settings.bluetooth.BluetoothSettings" />
-            <meta-data android:name="com.android.settings.category"
-                       android:value="com.android.settings.category.ia.device" />
-        </activity-alias>
-
         <activity-alias android:name="CastDashboardAlias"
                         android:targetActivity="Settings$WifiDisplaySettingsActivity">
             <intent-filter android:priority="6">
diff --git a/res/drawable/ic_find_device_disabled.xml b/res/drawable/ic_find_device_disabled.xml
new file mode 100644
index 0000000..ac23101
--- /dev/null
+++ b/res/drawable/ic_find_device_disabled.xml
@@ -0,0 +1,37 @@
+<!--
+    Copyright (C) 2017 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:pathData="M0,0l24,0l0,24l-24,0z"
+        android:strokeColor="#000000"
+        android:fillColor="#00000000"
+        android:strokeWidth="1.33333335e-11"
+        android:strokeAlpha="0.00784313771"/>
+    <path
+        android:pathData="M5,20l0,2l14,0l0,-2z"
+        android:strokeColor="#00000000"
+        android:fillColor="#F09300"
+        android:strokeWidth="1"/>
+    <path
+        android:pathData="M12,5.917C13.15,5.917 14.083,6.85 14.083,8C14.083,8.613 13.813,9.158 13.392,9.542L16.417,12.567C17.229,11.017 17.833,9.404 17.833,8C17.833,4.779 15.221,2.167 12,2.167C10.35,2.167 8.863,2.854 7.804,3.954L10.458,6.608C10.837,6.188 11.387,5.917 12,5.917ZM15.646,13.917L11.792,10.063L11.7,9.971L4.725,3L3.667,4.063L6.317,6.713C6.221,7.125 6.167,7.554 6.167,8C6.167,12.375 12,18.833 12,18.833C12,18.833 13.392,17.292 14.813,15.208L17.604,18L18.667,16.938L15.646,13.917Z"
+        android:strokeColor="#00000000"
+        android:fillColor="#F09300"
+        android:strokeWidth="1"/>
+</vector>
diff --git a/res/drawable/ic_find_device_enabled.xml b/res/drawable/ic_find_device_enabled.xml
new file mode 100644
index 0000000..c47b9f0
--- /dev/null
+++ b/res/drawable/ic_find_device_enabled.xml
@@ -0,0 +1,32 @@
+<!--
+    Copyright (C) 2017 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:pathData="M0,0l24,0l0,24l-24,0z"
+        android:strokeColor="#000000"
+        android:fillColor="#00000000"
+        android:strokeWidth="1.33333335e-11"
+        android:strokeAlpha="0.00784313771"/>
+    <path
+        android:pathData="M18,8C18,4.69 15.31,2 12,2C8.69,2 6,4.69 6,8C6,12.5 12,19 12,19C12,19 18,12.5 18,8ZM10,8C10,6.9 10.9,6 12,6C13.1,6 14,6.9 14,8C14,9.1 13.11,10 12,10C10.9,10 10,9.1 10,8ZM5,20L5,22L19,22L19,20L5,20Z"
+        android:strokeColor="#00000000"
+        android:fillColor="#4A90E2"
+        android:strokeWidth="1"/>
+</vector>
diff --git a/res/drawable/ic_ota_update_available.xml b/res/drawable/ic_ota_update_available.xml
new file mode 100644
index 0000000..e2d7f33
--- /dev/null
+++ b/res/drawable/ic_ota_update_available.xml
@@ -0,0 +1,32 @@
+<!--
+    Copyright (C) 2017 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:pathData="M0,0l24,0l0,24l-24,0z"
+        android:strokeColor="#000000"
+        android:fillColor="#00000000"
+        android:strokeWidth="1.33333335e-11"
+        android:strokeAlpha="0.00784313771"/>
+    <path
+        android:pathData="M17,1.01L7,1C5.9,1 5,1.9 5,3L5,21C5,22.1 5.9,23 7,23L17,23C18.1,23 19,22.1 19,21L19,3C19,1.9 18.1,1.01 17,1.01ZM17,19L7,19L7,5L17,5L17,19ZM16,13L13,13L13,8L11,8L11,13L8,13L12,17L16,13Z"
+        android:strokeColor="#00000000"
+        android:fillColor="#4A90E2"
+        android:strokeWidth="1"/>
+</vector>
diff --git a/res/drawable/ic_ota_update_current.xml b/res/drawable/ic_ota_update_current.xml
new file mode 100644
index 0000000..d7a2f84
--- /dev/null
+++ b/res/drawable/ic_ota_update_current.xml
@@ -0,0 +1,32 @@
+<!--
+    Copyright (C) 2017 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:pathData="M0,0l24,0l0,24l-24,0z"
+        android:strokeColor="#000000"
+        android:fillColor="#00000000"
+        android:strokeWidth="1.33333335e-11"
+        android:strokeAlpha="0.00784313771"/>
+    <path
+        android:pathData="M17,1.01L7,1C5.9,1 5,1.9 5,3L5,21C5,22.1 5.9,23 7,23L17,23C18.1,23 19,22.1 19,21L19,3C19,1.9 18.1,1.01 17,1.01ZM17,19L7,19L7,5L17,5L17,19ZM10.627,13.093L9.144,11.746L8.054,12.79L10.627,15.181L16.156,10.044L15.066,9L10.627,13.093Z"
+        android:strokeColor="#00000000"
+        android:fillColor="#4A90E2"
+        android:strokeWidth="1"/>
+</vector>
diff --git a/res/drawable/ic_ota_update_none.xml b/res/drawable/ic_ota_update_none.xml
new file mode 100644
index 0000000..a01a459
--- /dev/null
+++ b/res/drawable/ic_ota_update_none.xml
@@ -0,0 +1,32 @@
+<!--
+    Copyright (C) 2017 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:pathData="M0,0l24,0l0,24l-24,0z"
+        android:strokeColor="#000000"
+        android:fillColor="#00000000"
+        android:strokeWidth="1.33333335e-11"
+        android:strokeAlpha="0.00784313771"/>
+    <path
+        android:pathData="M17,1.01L7,1C5.9,1 5,1.9 5,3L5,21C5,22.1 5.9,23 7,23L17,23C18.1,23 19,22.1 19,21L19,3C19,1.9 18.1,1.01 17,1.01ZM17,19L7,19L7,5L17,5L17,19ZM13.143,17.714L10.857,17.714L10.857,15.429L13.143,15.429L13.143,17.714ZM13.143,13.143L10.857,13.143L10.857,6.286L13.143,6.286L13.143,13.143Z"
+        android:strokeColor="#00000000"
+        android:fillColor="#F09300"
+        android:strokeWidth="1"/>
+</vector>
diff --git a/res/drawable/ic_ota_update_stale.xml b/res/drawable/ic_ota_update_stale.xml
new file mode 100644
index 0000000..9145be8
--- /dev/null
+++ b/res/drawable/ic_ota_update_stale.xml
@@ -0,0 +1,32 @@
+<!--
+    Copyright (C) 2017 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:pathData="M0,0l24,0l0,24l-24,0z"
+        android:strokeColor="#000000"
+        android:fillColor="#00000000"
+        android:strokeWidth="1.33333335e-11"
+        android:strokeAlpha="0.00784313771"/>
+    <path
+        android:pathData="M17,1.01L7,1C5.9,1 5,1.9 5,3L5,21C5,22.1 5.9,23 7,23L17,23C18.1,23 19,22.1 19,21L19,3C19,1.9 18.1,1.01 17,1.01ZM17,19L7,19L7,5L17,5L17,19ZM16,13L13,13L13,8L11,8L11,13L8,13L12,17L16,13Z"
+        android:strokeColor="#00000000"
+        android:fillColor="#F09300"
+        android:strokeWidth="1"/>
+</vector>
diff --git a/res/drawable/ic_package_verifier_disabled.xml b/res/drawable/ic_package_verifier_disabled.xml
new file mode 100644
index 0000000..5f19fec
--- /dev/null
+++ b/res/drawable/ic_package_verifier_disabled.xml
@@ -0,0 +1,31 @@
+<!--
+    Copyright (C) 2017 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:pathData="M5.785,3.762L12,1L21,5L21,11C21,13.231 20.381,15.402 19.306,17.283L5.785,3.762ZM4.386,4.384L3,5L3,11C3,16.555 6.835,21.735 12,23C14.622,22.358 16.902,20.706 18.511,18.509L4.386,4.384Z"
+        android:strokeColor="#00000000"
+        android:fillColor="#F09300"
+        android:strokeWidth="1"/>
+    <path
+        android:pathData="M2.808,2.808l17.678,17.678l-1.01,1.01l-17.678,-17.678z"
+        android:strokeColor="#00000000"
+        android:fillColor="#F09300"
+        android:strokeWidth="1"/>
+</vector>
diff --git a/res/drawable/ic_package_verifier_enabled.xml b/res/drawable/ic_package_verifier_enabled.xml
new file mode 100644
index 0000000..1059442
--- /dev/null
+++ b/res/drawable/ic_package_verifier_enabled.xml
@@ -0,0 +1,32 @@
+<!--
+    Copyright (C) 2017 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:pathData="M0,0l24,0l0,24l-24,0z"
+        android:strokeColor="#000000"
+        android:fillColor="#00000000"
+        android:strokeWidth="1.33333335e-11"
+        android:strokeAlpha="0.00784313771"/>
+    <path
+        android:pathData="M12,1L3,5L3,11C3,16.55 6.84,21.74 12,23C17.16,21.74 21,16.55 21,11L21,5L12,1Z"
+        android:strokeColor="#00000000"
+        android:fillColor="#4A90E2"
+        android:strokeWidth="1"/>
+</vector>
diff --git a/res/layout/preference_widget_master_switch.xml b/res/layout/preference_widget_master_switch.xml
new file mode 100644
index 0000000..51d938b
--- /dev/null
+++ b/res/layout/preference_widget_master_switch.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2017 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.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+  android:layout_width="wrap_content"
+  android:layout_height="match_parent">
+
+    <LinearLayout
+      android:layout_width="wrap_content"
+      android:layout_height="match_parent"
+      android:gravity="start|center_vertical"
+      android:orientation="horizontal"
+      android:paddingStart="20dp"
+      android:paddingEnd="20dp"
+      android:paddingTop="16dp"
+      android:paddingBottom="16dp">
+        <View
+          android:layout_width="1dip"
+          android:layout_height="match_parent"
+          android:background="?android:attr/colorSecondary"/>
+    </LinearLayout>
+
+    <Switch android:id="@+id/switchWidget"
+      android:layout_width="wrap_content"
+      android:layout_height="match_parent"
+      android:gravity="center_vertical" />
+
+</LinearLayout>
diff --git a/res/layout/preview_seek_bar_view_pager.xml b/res/layout/preview_seek_bar_view_pager.xml
index e5dfa94..5eb3543 100644
--- a/res/layout/preview_seek_bar_view_pager.xml
+++ b/res/layout/preview_seek_bar_view_pager.xml
@@ -37,7 +37,6 @@
         android:gravity="start|center"
         android:text="@string/screen_zoom_preview_title"
         android:textAppearance="@android:style/TextAppearance.Material.Widget.ActionBar.Title"
-        android:textColor="@color/seek_bar_preference_preview_text"
         android:importantForAccessibility="no" />
 </LinearLayout>
 
diff --git a/res/layout/search_breadcrumb_view.xml b/res/layout/search_breadcrumb_view.xml
new file mode 100644
index 0000000..a78b745
--- /dev/null
+++ b/res/layout/search_breadcrumb_view.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2017 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.
+  -->
+
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/breadcrumb"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:paddingTop="8dp"
+    android:textAppearance="?android:attr/textAppearanceListItemSecondary"
+    android:textColor="?android:attr/textColorSecondary"
+    android:ellipsize="marquee"/>
\ No newline at end of file
diff --git a/res/layout/search_icon_view.xml b/res/layout/search_icon_view.xml
new file mode 100644
index 0000000..d7e0205
--- /dev/null
+++ b/res/layout/search_icon_view.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2017 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.
+  -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/icon_container"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:minWidth="60dp"
+    android:gravity="center_horizontal|top"
+    android:orientation="horizontal"
+    android:paddingEnd="12dp"
+    android:paddingTop="4dp"
+    android:paddingBottom="4dp">
+    <com.android.internal.widget.PreferenceImageView
+        android:id="@android:id/icon"
+        android:layout_width="36dp"
+        android:layout_height="36dp"
+        android:scaleType="fitCenter"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/search_inline_switch_item.xml b/res/layout/search_inline_switch_item.xml
index 998c09c..40639b0 100644
--- a/res/layout/search_inline_switch_item.xml
+++ b/res/layout/search_inline_switch_item.xml
@@ -15,67 +15,49 @@
 -->
 
 <LinearLayout
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:minHeight="?android:attr/listPreferredItemHeight"
-        android:gravity="center_vertical"
-        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
-        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-        android:background="?android:attr/selectableItemBackground"
-        android:clipToPadding="false">
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:paddingTop="16dp"
+    android:paddingBottom="16dp"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:background="?android:attr/selectableItemBackground"
+    android:clipToPadding="false">
+
+    <include layout="@layout/search_icon_view"/>
 
     <LinearLayout
-            android:id="@+id/icon_container"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@android:id/title"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:minWidth="60dp"
-            android:gravity="start|center_vertical"
-            android:orientation="horizontal"
-            android:paddingEnd="12dp"
-            android:paddingTop="4dp"
-            android:paddingBottom="4dp">
-        <com.android.internal.widget.PreferenceImageView
-                android:id="@android:id/icon"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:maxWidth="48dp"
-                android:maxHeight="48dp"/>
+            android:singleLine="true"
+            android:textAppearance="?android:attr/textAppearanceListItem"
+            android:ellipsize="marquee"/>
+
+        <TextView
+            android:id="@android:id/summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceListItemSecondary"
+            android:textColor="?android:attr/textColorSecondary"
+            android:maxLength="60"
+            android:maxLines="10"/>
+
+        <include layout="@layout/search_breadcrumb_view"/>
     </LinearLayout>
 
-    <RelativeLayout
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_weight="1"
-            android:paddingTop="16dp"
-            android:paddingBottom="16dp">
-
-        <TextView
-                android:id="@android:id/title"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:singleLine="true"
-                android:textAppearance="?android:attr/textAppearanceListItem"
-                android:ellipsize="marquee"/>
-
-        <TextView
-                android:id="@android:id/summary"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_below="@android:id/title"
-                android:layout_alignStart="@android:id/title"
-                android:textAppearance="?android:attr/textAppearanceListItemSecondary"
-                android:textColor="?android:attr/textColorSecondary"
-                android:maxLength="60"
-                android:maxLines="10"/>
-
-    </RelativeLayout>
-
     <Switch
-            android:id="@+id/switchView"
-            android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:gravity="end|center_vertical"
-            android:paddingStart="16dp"
-            android:orientation="vertical"/>
+        android:id="@+id/switchView"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:gravity="top"
+        android:paddingStart="16dp"/>
 </LinearLayout>
\ No newline at end of file
diff --git a/res/layout/search_intent_item.xml b/res/layout/search_intent_item.xml
index fcb5532..31f7f26 100644
--- a/res/layout/search_intent_item.xml
+++ b/res/layout/search_intent_item.xml
@@ -15,58 +15,42 @@
 -->
 
 <LinearLayout
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:minHeight="?android:attr/listPreferredItemHeight"
-        android:gravity="center_vertical"
-        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
-        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-        android:background="?android:attr/selectableItemBackground"
-        android:clipToPadding="false">
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:paddingTop="16dp"
+    android:paddingBottom="16dp"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:background="?android:attr/selectableItemBackground"
+    android:clipToPadding="false">
+
+    <include layout="@layout/search_icon_view"/>
 
     <LinearLayout
-            android:id="@+id/icon_container"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@android:id/title"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:minWidth="60dp"
-            android:gravity="start|center_vertical"
-            android:orientation="horizontal"
-            android:paddingEnd="12dp"
-            android:paddingTop="4dp"
-            android:paddingBottom="4dp">
-        <com.android.internal.widget.PreferenceImageView
-                android:id="@android:id/icon"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:maxWidth="48dp"
-                android:maxHeight="48dp"/>
+            android:singleLine="true"
+            android:textAppearance="?android:attr/textAppearanceListItem"
+            android:ellipsize="marquee"/>
+
+        <TextView
+            android:id="@android:id/summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceListItemSecondary"
+            android:textColor="?android:attr/textColorSecondary"
+            android:maxLines="3"
+            android:ellipsize="marquee"/>
+
+        <include layout="@layout/search_breadcrumb_view"/>
     </LinearLayout>
-
-    <RelativeLayout
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_weight="1"
-            android:paddingTop="16dp"
-            android:paddingBottom="16dp">
-
-        <TextView
-                android:id="@android:id/title"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:singleLine="true"
-                android:textAppearance="?android:attr/textAppearanceListItem"
-                android:ellipsize="marquee"/>
-
-        <TextView
-                android:id="@android:id/summary"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_below="@android:id/title"
-                android:layout_alignStart="@android:id/title"
-                android:textAppearance="?android:attr/textAppearanceListItemSecondary"
-                android:textColor="?android:attr/textColorSecondary"
-                android:maxLines="10"/>
-
-    </RelativeLayout>
 </LinearLayout>
diff --git a/res/layout/search_saved_query_item.xml b/res/layout/search_saved_query_item.xml
new file mode 100644
index 0000000..71c8482
--- /dev/null
+++ b/res/layout/search_saved_query_item.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2017 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.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="?android:attr/selectableItemBackground"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:gravity="center_vertical"
+    android:paddingStart="@dimen/preference_no_icon_padding_start"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+    <TextView
+        android:id="@android:id/title"
+        android:textAppearance="?android:attr/textAppearanceListItem"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_weight="1"/>
+
+    <ImageView
+        android:id="@android:id/icon"
+        android:layout_width="@dimen/search_suggestion_item_image_size"
+        android:layout_height="@dimen/search_suggestion_item_image_size"
+        android:layout_marginStart="@dimen/search_suggestion_item_image_margin_start"
+        android:layout_marginEnd="@dimen/search_suggestion_item_image_margin_end"
+        android:scaleType="centerInside"
+        android:src="@drawable/ic_search_history"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 38eb74b..300d73a 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -99,8 +99,6 @@
     <color name="message_icon_background_outgoing">#4285f4</color>
     <color name="message_icon_text_outgoing">#ffffffff</color>
 
-    <color name="seek_bar_preference_preview_text">#fff</color>
-
     <color name="importance_disabled_slider_color">@*android:color/material_grey_300</color>
     <color name="importance_secondary_slider_color">#858383</color>
 
diff --git a/res/values/strings.xml b/res/values/strings.xml
index da83867..b2405a5 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1583,11 +1583,11 @@
     <string name="wifi_in_airplane_mode">In Airplane mode</string>
     <!-- Checkbox title for option to notify user when open networks are nearby -->
     <string name="wifi_notify_open_networks">Open network notification</string>
-    <!-- Checkbox summary for option to notify user when open networks are nearby [CHAR LIMIT=60]-->
+    <!-- Checkbox summary for option to notify user when open networks are nearby-->
     <string name="wifi_notify_open_networks_summary">Notify whenever a high quality open network is available</string>
     <!-- Checkbox title for option to enable Wi-Fi when saved networks are nearby -->
     <string name="wifi_wakeup">Turn Wi\u2011Fi back on</string>
-    <!-- Checkbox summary for option to enable Wi-Fi when saved networks are nearby [CHAR LIMIT=60]-->
+    <!-- Checkbox summary for option to enable Wi-Fi when saved networks are nearby-->
     <string name="wifi_wakeup_summary">Automatically turn on Wi\u2011Fi near saved networks</string>
     <!-- Checkbox title for option to toggle poor network detection -->
     <string name="wifi_poor_network_detection">Avoid poor connections</string>
@@ -5947,6 +5947,10 @@
     <string name="search_menu">Search settings</string>
     <!-- Text used as a search hint into the search box -->
     <string name="query_hint_text">Search settings</string>
+    <!-- Search breadcrumb connector symbol -->
+    <string name="search_breadcrumb_connector" translatable="false">
+        <xliff:g name="first_item">%1$s</xliff:g> > <xliff:g name="second_item">%2$s</xliff:g>
+    </string>
 
     <!-- Text used to identify the search query suggestions / recent searches -->
     <string name="search_recents_queries_label">Recent searches</string>
@@ -6229,6 +6233,9 @@
     <!-- [CHAR LIMIT=100] Notification Importance slider: unset importance level description -->
     <string name="notification_importance_none">Not set</string>
 
+    <!-- [CHAR LIMIT=100] Notification Importance slider: unspecified importance level description -->
+    <string name="notification_importance_unspecified">Let the app decide</string>
+
     <!-- [CHAR LIMIT=100] Notification Importance slider: blocked importance level description -->
     <string name="notification_importance_blocked">Never show notifications</string>
 
@@ -6992,6 +6999,9 @@
     <!-- Summary of power usage for an app [CHAR LIMIT=NONE] -->
     <string name="battery_summary"><xliff:g id="percentage" example="2">%1$d</xliff:g>%% use since last full charge</string>
 
+    <!-- Title of a group of settings that let you manage settings that affect battery life [CHAR LIMIT=60] -->
+    <string name="battery_power_management">Power management</string>
+
     <!-- Summary for app with no battery usage [CHAR LIMIT=NONE] -->
     <string name="no_battery_summary">No battery use since last full charge</string>
 
@@ -7634,6 +7644,8 @@
     <string name="notification_log_details_importance">importance</string>
     <!-- Notification log debug tool: header: notification importance explanation -->
     <string name="notification_log_details_explanation">explanation</string>
+    <!-- Notification log debug tool: header: notification importance -->
+    <string name="notification_log_details_badge">can show badge</string>
     <!-- Notification log debug tool: header: notification contentIntent field -->
     <string name="notification_log_details_content_intent">intent</string>
     <!-- Notification log debug tool: header: notification deleteIntent field -->
@@ -8094,4 +8106,9 @@
     <!-- On status for the automatic storage manager. [CHAR_LIMIT=10] -->
     <string name="storage_manager_indicator_on">On</string>
 
+    <!-- UI webview setting: WebView uninstalled-for-user explanatory text [CHAR LIMIT=30] -->
+    <string name="webview_uninstalled_for_user">Uninstalled for user <xliff:g id="user" example="John Doe">%s</xliff:g>\n</string>
+    <!-- UI webview setting: WebView disabled-for-user explanatory text [CHAR LIMIT=30] -->
+    <string name="webview_disabled_for_user">Disabled for user <xliff:g id="user" example="John Doe">%s</xliff:g>\n</string>
+
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 29cb390..092d997 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -293,10 +293,6 @@
     <!-- Scrollbar style OUTSIDE_OVERLAY -->
     <integer name="preference_scrollbar_style">33554432</integer>
 
-    <style name="AppListSwitchPreference" parent="@*android:style/Preference.Material.DialogPreference">
-        <item name="android:widgetLayout">@*android:layout/preference_widget_switch</item>
-    </style>
-
     <style name="TextAppearance.Medium" parent="@android:style/TextAppearance.Material.Medium" />
     <style name="TextAppearance.Small" parent="@android:style/TextAppearance.Material.Small" />
     <style name="TextAppearance.Switch" parent="@android:style/TextAppearance.Material.Title" />
diff --git a/res/values/themes.xml b/res/values/themes.xml
index 0241e1a..fe5b3ca 100644
--- a/res/values/themes.xml
+++ b/res/values/themes.xml
@@ -195,13 +195,6 @@
         <item name="switchBarBackgroundColor">?android:attr/colorSecondary</item>
     </style>
 
-    <style name="Theme.DialogWhenLarge" parent="@*android:style/Theme.DeviceDefault.Settings.DialogWhenLarge">
-        <!-- Redefine the ActionBar style for contentInsetStart -->
-        <item name="android:actionBarStyle">@style/Theme.ActionBar</item>
-
-        <item name="preferenceBackgroundColor">@drawable/preference_background</item>
-    </style>
-
     <style name="Theme.CryptKeeper" parent="@android:style/Theme.Material.NoActionBar">
         <item name="android:windowTranslucentStatus">false</item>
         <item name="android:windowTranslucentNavigation">false</item>
@@ -214,13 +207,6 @@
         <item name="*android:errorColor">@color/unlock_pattern_view_error_color</item>
     </style>
 
-    <style name="Theme.SubSettingsDialogWhenLarge" parent="Theme.DialogWhenLarge">
-        <item name="preferenceTheme">@style/PreferenceTheme</item>
-        <item name="android:actionBarWidgetTheme">@null</item>
-        <item name="android:actionBarTheme">@android:style/ThemeOverlay.Material.ActionBar</item>
-        <item name="preferenceBackgroundColor">@drawable/preference_background</item>
-    </style>
-
     <style name="ThemeOverlay.AlertDialog" parent="@android:style/ThemeOverlay.Material.Dialog.Alert">
         <item name="android:windowSoftInputMode">adjustResize</item>
     </style>
@@ -241,6 +227,7 @@
     </style>
 
     <style name="Theme.ConfirmDeviceCredentials" parent="Theme.SubSettings">
+        <item name="android:windowLightStatusBar">false</item>
         <item name="confirmDeviceCredentialsSideMargin">@dimen/confirm_credentials_side_margin</item>
         <item name="confirmDeviceCredentialsTopMargin">@dimen/confirm_credentials_top_margin</item>
     </style>
diff --git a/res/xml/advanced_apps.xml b/res/xml/advanced_apps.xml
deleted file mode 100644
index 0f9f82b..0000000
--- a/res/xml/advanced_apps.xml
+++ /dev/null
@@ -1,101 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 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.
--->
-
-<PreferenceScreen
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
-    android:key="applications_settings">
-
-    <Preference
-        android:key="manage_perms"
-        android:title="@string/app_permissions"
-        settings:keywords="@string/keywords_app_permissions">
-        <intent android:action="android.intent.action.MANAGE_PERMISSIONS"/>
-    </Preference>
-
-    <PreferenceCategory
-        android:title="@string/default_apps_title">
-
-        <Preference
-            android:key="domain_urls"
-            android:title="@string/domain_urls_title"
-            android:fragment="com.android.settings.applications.ManageDomainUrls" />
-
-        <Preference
-            android:key="assist_and_voice_input"
-            android:fragment="com.android.settings.applications.ManageAssist"
-            android:title="@string/assist_and_voice_input_title" />
-
-        <com.android.settings.applications.DefaultHomePreference
-            android:key="default_home"
-            android:title="@string/home_app"
-            android:summary="@string/no_default_home"
-            settings:keywords="@string/keywords_home" />
-
-        <com.android.settings.applications.DefaultBrowserPreference
-            android:key="default_browser"
-            android:title="@string/default_browser_title"
-            android:summary="@string/default_browser_title_none" />
-
-        <com.android.settings.applications.DefaultPhonePreference
-            android:key="default_phone_app"
-            android:title="@string/default_phone_title"
-            settings:keywords="@string/keywords_default_phone_app" />
-
-        <com.android.settings.applications.DefaultEmergencyPreference
-            android:key="default_emergency_app"
-            android:title="@string/default_emergency_app"
-            settings:keywords="@string/keywords_emergency_app" />
-
-        <com.android.settings.applications.DefaultSmsPreference
-            android:key="default_sms_app"
-            android:title="@string/sms_application_title"
-            settings:keywords="@string/keywords_more_default_sms_app" />
-
-        <com.android.settings.applications.DefaultNotificationAssistantPreference
-            android:key="default_notification_asst_app"
-            android:title="@string/default_notification_assistant" />
-
-    </PreferenceCategory>
-
-    <com.android.settings.WorkOnlyCategory
-        android:key="work_defaults"
-        android:title="@string/default_for_work">
-
-        <com.android.settings.applications.DefaultBrowserPreference
-            android:key="work_default_browser"
-            android:title="@string/default_browser_title"
-            android:summary="@string/default_browser_title_none"
-            settings:forWork="true" />
-
-        <com.android.settings.applications.DefaultPhonePreference
-            android:key="work_default_phone_app"
-            android:title="@string/default_phone_title"
-            settings:keywords="@string/keywords_default_phone_app"
-            settings:forWork="true" />
-
-    </com.android.settings.WorkOnlyCategory>
-
-    <PreferenceCategory
-        android:title="@string/advanced_apps">
-
-        <Preference
-            android:key="special_access"
-            android:fragment="com.android.settings.applications.SpecialAccessSettings"
-            android:title="@string/special_access" />
-
-    </PreferenceCategory>
-</PreferenceScreen>
diff --git a/res/xml/apn_editor.xml b/res/xml/apn_editor.xml
index a05d547..eac78e9 100644
--- a/res/xml/apn_editor.xml
+++ b/res/xml/apn_editor.xml
@@ -15,8 +15,7 @@
 -->
 
 <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
-        android:title="@string/apn_edit">
+                  android:title="@string/apn_edit">
     <EditTextPreference
         android:title="@string/apn_name"
         android:dialogTitle="@string/apn_name"
diff --git a/res/xml/app_default_settings.xml b/res/xml/app_default_settings.xml
index 5da5326..9b585c1 100644
--- a/res/xml/app_default_settings.xml
+++ b/res/xml/app_default_settings.xml
@@ -17,7 +17,8 @@
 
 <PreferenceScreen
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:settings="http://schemas.android.com/apk/res/com.android.settings">
+    xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
+    android:title="@string/app_default_dashboard_title">
 
     <Preference
         android:key="assist_and_voice_input"
@@ -25,40 +26,48 @@
         android:fragment="com.android.settings.applications.ManageAssist"
         android:order="-20"/>
 
-    <com.android.settings.applications.DefaultBrowserPreference
+    <Preference
         android:key="default_browser"
         android:title="@string/default_browser_title"
         android:summary="@string/default_browser_title_none"
-        android:order="-19"/>
+        android:fragment="com.android.settings.applications.defaultapps.DefaultBrowserPicker"
+        android:order="-19">
+        <extra android:name="for_work" android:value="false"/>
+    </Preference>
 
-    <com.android.settings.applications.DefaultHomePreference
+    <Preference
         android:key="default_home"
         android:title="@string/home_app"
         android:summary="@string/no_default_home"
         settings:keywords="@string/keywords_home"
+        android:fragment="com.android.settings.applications.defaultapps.DefaultHomePicker"
         android:order="-18"/>
 
-    <com.android.settings.applications.DefaultPhonePreference
+    <Preference
         android:key="default_phone_app"
         android:title="@string/default_phone_title"
+        android:fragment="com.android.settings.applications.defaultapps.DefaultPhonePicker"
         settings:keywords="@string/keywords_default_phone_app"
         android:order="-17"/>
 
-    <com.android.settings.applications.DefaultSmsPreference
+    <Preference
         android:key="default_sms_app"
         android:title="@string/sms_application_title"
+        android:fragment="com.android.settings.applications.defaultapps.DefaultSmsPicker"
         settings:keywords="@string/keywords_more_default_sms_app"
         android:order="-16"/>
 
-    <com.android.settings.applications.DefaultEmergencyPreference
+    <Preference
         android:key="default_emergency_app"
         android:title="@string/default_emergency_app"
         settings:keywords="@string/keywords_emergency_app"
+        android:fragment="com.android.settings.applications.defaultapps.DefaultEmergencyPicker"
         android:order="-15"/>
 
-    <com.android.settings.applications.DefaultNotificationAssistantPreference
+    <Preference
         android:key="default_notification_asst_app"
         android:title="@string/default_notification_assistant"
+        android:fragment="com.android.settings.applications.defaultapps.DefaultNotificationAssistantPicker"
         android:order="-14"/>
 
     <Preference
@@ -70,17 +79,21 @@
         android:key="work_defaults"
         android:title="@string/default_for_work">
 
-        <com.android.settings.applications.DefaultBrowserPreference
+        <Preference
             android:key="work_default_browser"
             android:title="@string/default_browser_title"
             android:summary="@string/default_browser_title_none"
-            settings:forWork="true"/>
+            android:fragment="com.android.settings.applications.defaultapps.DefaultBrowserPicker">
+            <extra android:name="for_work" android:value="true"/>
+        </Preference>
 
-        <com.android.settings.applications.DefaultPhonePreference
-            android:key="work_default_phone_app"
-            android:title="@string/default_phone_title"
-            settings:keywords="@string/keywords_default_phone_app"
-            settings:forWork="true"/>
+        <Preference
+            android:key="work_default_phone_app_new"
+            android:title="new phone pref work"
+            android:fragment="com.android.settings.applications.defaultapps.DefaultPhonePicker"
+            settings:keywords="@string/keywords_default_phone_app">
+            <extra android:name="for_work" android:value="true"/>
+        </Preference>
 
     </com.android.settings.WorkOnlyCategory>
 
diff --git a/res/xml/app_notification_settings.xml b/res/xml/app_notification_settings.xml
index 82660dc..7d51fa8 100644
--- a/res/xml/app_notification_settings.xml
+++ b/res/xml/app_notification_settings.xml
@@ -23,6 +23,15 @@
         android:key="block"
         android:title="@string/app_notification_block_title"
         android:summary="@string/app_notification_block_summary"
+        android:order="1"
+        settings:useAdditionalSummary="true"
+        settings:restrictedSwitchSummary="@string/enabled_by_admin" />
+
+    <!-- Show badge -->
+    <com.android.settingslib.RestrictedSwitchPreference
+        android:key="badge"
+        android:title="@string/notification_badge_title"
+        android:summary="@string/notification_badge_summary"
         android:order="2"
         settings:useAdditionalSummary="true"
         settings:restrictedSwitchSummary="@string/enabled_by_admin" />
diff --git a/res/xml/app_picker_prefs.xml b/res/xml/app_picker_prefs.xml
new file mode 100644
index 0000000..9a921ee
--- /dev/null
+++ b/res/xml/app_picker_prefs.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2017 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.
+  -->
+
+<PreferenceScreen>
+
+</PreferenceScreen>
\ No newline at end of file
diff --git a/res/xml/channel_notification_settings.xml b/res/xml/channel_notification_settings.xml
index e1d6d55..4af9fe8 100644
--- a/res/xml/channel_notification_settings.xml
+++ b/res/xml/channel_notification_settings.xml
@@ -27,54 +27,55 @@
         settings:useAdditionalSummary="true"
         settings:restrictedSwitchSummary="@string/enabled_by_admin" />
 
+    <!-- Importance -->
+    <com.android.settings.notification.RestrictedDropDownPreference
+        android:key="importance"
+        android:title="@string/notification_importance_title"
+        android:order="2"/>
+
+    <!-- Default ringtone -->
+    <com.android.settings.notification.DefaultNotificationTonePreference
+        android:key="ringtone"
+        android:title="@string/notification_ringtone_title"
+        android:dialogTitle="@string/notification_ringtone_title"
+        android:order="3"
+        android:ringtoneType="notification" />
+
+    <!-- Vibration -->
+    <com.android.settingslib.RestrictedSwitchPreference
+        android:key="vibrate"
+        android:title="@string/notification_vibrate_title"
+        android:order="4"
+        settings:useAdditionalSummary="true" />
+
     <!-- Show badge -->
     <com.android.settingslib.RestrictedSwitchPreference
         android:key="badge"
         android:title="@string/notification_badge_title"
         android:summary="@string/notification_badge_summary"
-        android:order="3"
+        android:order="5"
         settings:useAdditionalSummary="true"
         settings:restrictedSwitchSummary="@string/enabled_by_admin" />
 
-    <!-- Importance -->
-    <com.android.settings.notification.RestrictedDropDownPreference
-            android:key="importance"
-            android:title="@string/notification_importance_title"
-            android:order="4"/>
+    <!-- Lights -->
+    <com.android.settingslib.RestrictedSwitchPreference
+        android:key="lights"
+        android:title="@string/notification_show_lights_title"
+        android:order="6"
+        settings:useAdditionalSummary="true" />
 
     <!-- Visibility Override -->
     <com.android.settings.notification.RestrictedDropDownPreference
             android:key="visibility_override"
             android:title="@string/app_notification_visibility_override_title"
-            android:order="5" />
+            android:order="7" />
 
     <!-- Bypass DND -->
     <com.android.settingslib.RestrictedSwitchPreference
             android:key="bypass_dnd"
             android:title="@string/app_notification_override_dnd_title"
             android:summary="@string/app_notification_override_dnd_summary"
-            android:order="6"
-            settings:useAdditionalSummary="true" />
-
-    <!-- Lights -->
-    <com.android.settingslib.RestrictedSwitchPreference
-            android:key="lights"
-            android:title="@string/notification_show_lights_title"
-            android:order="7"
-            settings:useAdditionalSummary="true" />
-
-    <!-- Vibration -->
-    <com.android.settingslib.RestrictedSwitchPreference
-            android:key="vibrate"
-            android:title="@string/notification_vibrate_title"
             android:order="8"
             settings:useAdditionalSummary="true" />
 
-    <!-- Default ringtone -->
-    <com.android.settings.notification.DefaultNotificationTonePreference
-            android:key="ringtone"
-            android:title="@string/notification_ringtone_title"
-            android:dialogTitle="@string/notification_ringtone_title"
-            android:order="9"
-            android:ringtoneType="notification" />
 </PreferenceScreen>
diff --git a/res/xml/connected_devices.xml b/res/xml/connected_devices.xml
index abf493f..d70cc33 100644
--- a/res/xml/connected_devices.xml
+++ b/res/xml/connected_devices.xml
@@ -18,6 +18,13 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:title="@string/connected_devices_dashboard_title">
 
+    <com.android.settings.widget.MasterSwitchPreference
+      android:fragment="com.android.settings.bluetooth.BluetoothSettings"
+      android:key="toggle_bluetooth"
+      android:title="@string/bluetooth_settings_title"
+      android:icon="@drawable/ic_settings_bluetooth"
+      android:order="-7"/>
+
     <SwitchPreference
         android:key="toggle_nfc"
         android:title="@string/nfc_quick_toggle_title"
diff --git a/res/xml/data_usage_cellular.xml b/res/xml/data_usage_cellular.xml
index 8bfd37d..a5aa316 100644
--- a/res/xml/data_usage_cellular.xml
+++ b/res/xml/data_usage_cellular.xml
@@ -15,7 +15,8 @@
 -->
 
 <PreferenceScreen
-    xmlns:android="http://schemas.android.com/apk/res/android">
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:title="@string/data_usage_summary_title">
 
     <com.android.settings.datausage.TemplatePreferenceCategory
         android:key="mobile_category"
diff --git a/res/xml/data_usage_wifi.xml b/res/xml/data_usage_wifi.xml
index 62ff17f..ceb5fc8 100644
--- a/res/xml/data_usage_wifi.xml
+++ b/res/xml/data_usage_wifi.xml
@@ -15,7 +15,8 @@
 -->
 
 <PreferenceScreen
-    xmlns:android="http://schemas.android.com/apk/res/android">
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:title="@string/data_usage_summary_title">
 
     <com.android.settings.datausage.TemplatePreferenceCategory
         android:key="wifi_category"
diff --git a/res/xml/development_prefs.xml b/res/xml/development_prefs.xml
index 3c2bab9..22327c3 100644
--- a/res/xml/development_prefs.xml
+++ b/res/xml/development_prefs.xml
@@ -74,11 +74,9 @@
         android:title="@string/picture_color_mode"
         android:summary="@string/picture_color_mode_desc" />
 
-    <ListPreference
-        android:key="select_webview_provider"
+    <Preference android:key="select_webview_provider"
         android:title="@string/select_webview_provider_title"
-        android:dialogTitle="@string/select_webview_provider_dialog_title"
-        android:summary="%s" />
+        android:dialogTitle="@string/select_webview_provider_dialog_title" />
 
     <SwitchPreference
         android:key="enable_webview_multiprocess"
diff --git a/res/xml/double_tap_power_settings.xml b/res/xml/double_tap_power_settings.xml
index 3376f3c..54854a8 100644
--- a/res/xml/double_tap_power_settings.xml
+++ b/res/xml/double_tap_power_settings.xml
@@ -16,7 +16,8 @@
   -->
 
 <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
-                  xmlns:app="http://schemas.android.com/apk/res-auto">
+                  xmlns:app="http://schemas.android.com/apk/res-auto"
+                  android:title="@string/double_tap_power_for_camera_title">
 
     <com.android.settings.widget.VideoPreference
         android:key="gesture_double_tap_power_video"
diff --git a/res/xml/double_tap_screen_settings.xml b/res/xml/double_tap_screen_settings.xml
index a76a487..f7fe853 100644
--- a/res/xml/double_tap_screen_settings.xml
+++ b/res/xml/double_tap_screen_settings.xml
@@ -16,7 +16,8 @@
   -->
 
 <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
-                  xmlns:app="http://schemas.android.com/apk/res-auto">
+                  xmlns:app="http://schemas.android.com/apk/res-auto"
+                  android:title="@string/ambient_display_title">
 
     <com.android.settings.widget.VideoPreference
         android:key="gesture_double_tap_screen_video"
diff --git a/res/xml/double_twist_gesture_settings.xml b/res/xml/double_twist_gesture_settings.xml
index e8d0abe..f77d778 100644
--- a/res/xml/double_twist_gesture_settings.xml
+++ b/res/xml/double_twist_gesture_settings.xml
@@ -16,7 +16,8 @@
   -->
 
 <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
-                  xmlns:app="http://schemas.android.com/apk/res-auto">
+                  xmlns:app="http://schemas.android.com/apk/res-auto"
+                  android:title="@string/double_twist_for_camera_mode_title">
 
     <com.android.settings.widget.VideoPreference
         android:key="gesture_double_twist_video"
diff --git a/res/xml/input_and_gesture.xml b/res/xml/input_and_gesture.xml
index c30178b..9abc797 100644
--- a/res/xml/input_and_gesture.xml
+++ b/res/xml/input_and_gesture.xml
@@ -15,7 +15,9 @@
   limitations under the License.
   -->
 
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:title="@string/input_gesture_settings_title">
 
     <PreferenceCategory
         android:title="@string/keyboard_and_input_methods_category">
diff --git a/res/xml/pick_up_gesture_settings.xml b/res/xml/pick_up_gesture_settings.xml
index 6b67e64..78122aa 100644
--- a/res/xml/pick_up_gesture_settings.xml
+++ b/res/xml/pick_up_gesture_settings.xml
@@ -17,7 +17,8 @@
 
 <PreferenceScreen
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto">
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:title="@string/ambient_display_pickup_title">
 
     <com.android.settings.widget.VideoPreference
         android:key="gesture_pick_up_video"
diff --git a/res/xml/power_usage_summary.xml b/res/xml/power_usage_summary.xml
index 1ae0a44..4607c48 100644
--- a/res/xml/power_usage_summary.xml
+++ b/res/xml/power_usage_summary.xml
@@ -14,20 +14,42 @@
      limitations under the License.
 -->
 
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
-                  xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
-        android:title="@string/power_usage_summary_title"
-        settings:keywords="@string/keywords_battery">
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
+    android:title="@string/power_usage_summary_title"
+    settings:keywords="@string/keywords_battery">
+
+    <com.android.settings.fuelgauge.BatteryHistoryPreference
+        android:key="battery_history"/>
+
+    <PreferenceCategory
+        android:key="power_management"
+        android:title="@string/battery_power_management">
 
         <com.android.settings.fuelgauge.BatterySaverPreference
             android:title="@string/battery_saver"
-            android:fragment="com.android.settings.fuelgauge.BatterySaverSettings" />
+            android:fragment="com.android.settings.fuelgauge.BatterySaverSettings"/>
 
-        <com.android.settings.fuelgauge.BatteryHistoryPreference
-            android:key="battery_history" />
+        <!-- Cross-listed item, if you change this, also change it in ia_display_settings.xml -->
+        <SwitchPreference
+            android:key="auto_brightness"
+            android:title="@string/auto_brightness_title"
+            android:summary="@string/auto_brightness_summary"
+            settings:keywords="@string/keywords_display_auto_brightness"/>
 
-        <PreferenceCategory
-            android:key="app_list"
-            android:title="@string/power_usage_list_summary" />
+        <!-- Cross-listed item, if you change this, also change it in ia_display_settings.xml -->
+        <com.android.settings.TimeoutListPreference
+            android:key="screen_timeout"
+            android:title="@string/screen_timeout"
+            android:summary="@string/screen_timeout_summary"
+            android:entries="@array/screen_timeout_entries"
+            android:entryValues="@array/screen_timeout_values"/>
+
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:key="app_list"
+        android:title="@string/power_usage_list_summary"/>
 
 </PreferenceScreen>
diff --git a/res/xml/special_access.xml b/res/xml/special_access.xml
index f8a5bf4..7d85195 100644
--- a/res/xml/special_access.xml
+++ b/res/xml/special_access.xml
@@ -16,7 +16,8 @@
 
 <PreferenceScreen
         xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:settings="http://schemas.android.com/apk/res/com.android.settings">
+        xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
+        android:title="@string/special_access">
 
     <Preference
         android:key="high_power_apps"
diff --git a/res/xml/swipe_to_notification_settings.xml b/res/xml/swipe_to_notification_settings.xml
index a4dedfb..b26af38 100644
--- a/res/xml/swipe_to_notification_settings.xml
+++ b/res/xml/swipe_to_notification_settings.xml
@@ -16,7 +16,8 @@
   -->
 
 <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
-                  xmlns:app="http://schemas.android.com/apk/res-auto">
+                  xmlns:app="http://schemas.android.com/apk/res-auto"
+                  android:title="@string/fingerprint_swipe_for_notifications_title">
 
     <com.android.settings.widget.VideoPreference
         android:key="gesture_swipe_down_fingerprint_video"
diff --git a/src/com/android/settings/AppListPreference.java b/src/com/android/settings/AppListPreference.java
index 1fe0c13..4cf4996 100644
--- a/src/com/android/settings/AppListPreference.java
+++ b/src/com/android/settings/AppListPreference.java
@@ -49,7 +49,11 @@
  * Extends ListPreference to allow us to show the icons for a given list of applications. We do this
  * because the names of applications are very similar and the user may not be able to determine what
  * app they are selecting without an icon.
+ *
+ * @deprecated Selecting app from a list should be done in full UI. Use DefaultAppPickerFragment
+ * instead.
  */
+@Deprecated
 public class AppListPreference extends CustomListPreference {
 
     public static final String ITEM_NONE_VALUE = "";
diff --git a/src/com/android/settings/AppListSwitchPreference.java b/src/com/android/settings/AppListSwitchPreference.java
deleted file mode 100644
index 007f243..0000000
--- a/src/com/android/settings/AppListSwitchPreference.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.android.settings;
-
-import android.content.Context;
-import android.support.v7.preference.PreferenceViewHolder;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.widget.Checkable;
-
-/**
- * A hybrid of AppListPreference and SwitchPreference, representing a preference which can be on or
- * off but must have a selected value when turned on.
- *
- * It is invalid to show this preference when zero valid apps are present.
- */
-public class AppListSwitchPreference extends AppListPreference {
-    private static final String TAG = "AppListSwitchPref";
-
-    private Checkable mSwitch;
-
-    public AppListSwitchPreference(Context context, AttributeSet attrs) {
-        super(context, attrs, 0, R.style.AppListSwitchPreference);
-    }
-
-    @Override
-    public void onBindViewHolder(PreferenceViewHolder view) {
-        super.onBindViewHolder(view);
-        mSwitch = (Checkable) view.findViewById(com.android.internal.R.id.switch_widget);
-        mSwitch.setChecked(getValue() != null);
-    }
-
-    @Override
-    protected void onClick() {
-        if (getValue() != null) {
-            // Turning off the current value.
-            if (callChangeListener(null)) {
-                setValue(null);
-            }
-        } else if (getEntryValues() == null || getEntryValues().length == 0) {
-            Log.e(TAG, "Attempting to show dialog with zero entries: " + getKey());
-        } else if (getEntryValues().length == 1) {
-            // Suppress the dialog and just toggle the preference with the only choice.
-            String value = getEntryValues()[0].toString();
-            if (callChangeListener(value)) {
-                setValue(value);
-            }
-        } else {
-            super.onClick();
-        }
-    }
-
-    @Override
-    public void setValue(String value) {
-        super.setValue(value);
-        if (mSwitch != null) {
-            mSwitch.setChecked(value != null);
-        }
-    }
-}
diff --git a/src/com/android/settings/ConfirmDeviceCredentialBaseFragment.java b/src/com/android/settings/ConfirmDeviceCredentialBaseFragment.java
index 5aa66bb..cabc805 100644
--- a/src/com/android/settings/ConfirmDeviceCredentialBaseFragment.java
+++ b/src/com/android/settings/ConfirmDeviceCredentialBaseFragment.java
@@ -90,9 +90,6 @@
         final UserManager userManager = UserManager.get(getActivity());
         mEffectiveUserId = userManager.getCredentialOwnerProfile(mUserId);
         mLockPatternUtils = new LockPatternUtils(getActivity());
-        mIsStrongAuthRequired = isFingerprintDisallowedByStrongAuth();
-        mAllowFpAuthentication = mAllowFpAuthentication && !isFingerprintDisabledByAdmin()
-                && !mReturnCredentials && !mIsStrongAuthRequired;
     }
 
     @Override
@@ -141,6 +138,10 @@
     @Override
     public void onResume() {
         super.onResume();
+        mIsStrongAuthRequired = isFingerprintDisallowedByStrongAuth();
+        mAllowFpAuthentication = getActivity().getIntent().getBooleanExtra(
+                        ALLOW_FP_AUTHENTICATION, false)
+                && !isFingerprintDisabledByAdmin() && !mReturnCredentials && !mIsStrongAuthRequired;
         refreshLockScreen();
     }
 
diff --git a/src/com/android/settings/DevelopmentSettings.java b/src/com/android/settings/DevelopmentSettings.java
index 8184905..749eb11 100644
--- a/src/com/android/settings/DevelopmentSettings.java
+++ b/src/com/android/settings/DevelopmentSettings.java
@@ -78,7 +78,7 @@
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityManager;
 import android.webkit.IWebViewUpdateService;
-import android.webkit.WebViewProviderInfo;
+import android.webkit.WebViewFactory;
 import android.widget.Switch;
 import android.widget.Toast;
 
@@ -92,6 +92,7 @@
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settings.search.Indexable;
+import com.android.settings.webview.WebViewAppPreferenceController;
 import com.android.settings.widget.SwitchBar;
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
@@ -126,7 +127,6 @@
     private static final String ENABLE_TERMINAL = "enable_terminal";
     private static final String KEEP_SCREEN_ON = "keep_screen_on";
     private static final String BT_HCI_SNOOP_LOG = "bt_hci_snoop_log";
-    private static final String WEBVIEW_PROVIDER_KEY = "select_webview_provider";
     private static final String WEBVIEW_MULTIPROCESS_KEY = "enable_webview_multiprocess";
     private static final String ENABLE_OEM_UNLOCK = "oem_unlock_enable";
     private static final String HDCP_CHECKING_KEY = "hdcp_checking";
@@ -230,6 +230,7 @@
 
     private static final int RESULT_DEBUG_APP = 1000;
     private static final int RESULT_MOCK_LOCATION_APP = 1001;
+    private static final int RESULT_WEBVIEW_APP = 1002;
 
     private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
     private static final String FLASH_LOCKED_PROP = "ro.boot.flash.locked";
@@ -310,8 +311,8 @@
     private ListPreference mAnimatorDurationScale;
     private ListPreference mOverlayDisplayDevices;
 
+    private WebViewAppPreferenceController mWebViewAppPrefController;
     private SwitchPreference mWebViewMultiprocess;
-    private ListPreference mWebViewProvider;
 
     private ListPreference mSimulateColorSpace;
 
@@ -371,8 +372,7 @@
         mWindowManager = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
         mBackupManager = IBackupManager.Stub.asInterface(
                 ServiceManager.getService(Context.BACKUP_SERVICE));
-        mWebViewUpdateService =
-                IWebViewUpdateService.Stub.asInterface(ServiceManager.getService("webviewupdate"));
+        mWebViewUpdateService = WebViewFactory.getUpdateService();
         mOemUnlockManager = (PersistentDataBlockManager) getActivity()
                 .getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
         mTelephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
@@ -384,6 +384,7 @@
 
         mBugReportController = new BugReportPreferenceController(getActivity());
         mBugReportInPowerController = new BugReportInPowerPreferenceController(getActivity());
+        mWebViewAppPrefController = new WebViewAppPreferenceController(getActivity());
 
         setIfOnlyAvailableForAdmins(true);
         if (isUiRestricted() || !Utils.isDeviceProvisioned(getActivity())) {
@@ -414,6 +415,7 @@
 
         mBugReportController.displayPreference(getPreferenceScreen());
         mBugReportInPowerController.displayPreference(getPreferenceScreen());
+        mWebViewAppPrefController.displayPreference(getPreferenceScreen());
 
         mKeepScreenOn = (RestrictedSwitchPreference) findAndInitSwitchPref(KEEP_SCREEN_ON);
         mBtHciSnoopLog = findAndInitSwitchPref(BT_HCI_SNOOP_LOG);
@@ -483,7 +485,6 @@
             mLogpersist = null;
         }
         mUsbConfiguration = addListPreference(USB_CONFIGURATION_KEY);
-        mWebViewProvider = addListPreference(WEBVIEW_PROVIDER_KEY);
         mWebViewMultiprocess = findAndInitSwitchPref(WEBVIEW_MULTIPROCESS_KEY);
         mBluetoothDisableAbsVolume = findAndInitSwitchPref(BLUETOOTH_DISABLE_ABSOLUTE_VOLUME_KEY);
 
@@ -544,7 +545,6 @@
             removePreference(KEY_COLOR_MODE);
             mColorModePreference = null;
         }
-        updateWebViewProviderOptions();
 
         mColorTemperaturePreference = (SwitchPreference) findPreference(COLOR_TEMPERATURE_KEY);
         if (getResources().getBoolean(R.bool.config_enableColorTemperature)) {
@@ -629,6 +629,7 @@
             pref.setEnabled(enabled && !mDisabledPrefs.contains(pref));
         }
         mBugReportInPowerController.enablePreference(enabled);
+        mWebViewAppPrefController.enablePreference(enabled);
         updateAllOptions();
     }
 
@@ -793,8 +794,8 @@
         updateSimulateColorSpace();
         updateUSBAudioOptions();
         updateForceResizableOptions();
+        mWebViewAppPrefController.updateState(null);
         updateWebViewMultiprocessOptions();
-        updateWebViewProviderOptions();
         updateOemUnlockOptions();
         if (mColorTemperaturePreference != null) {
             updateColorTemperature();
@@ -831,39 +832,6 @@
         pokeSystemProperties();
     }
 
-    private void updateWebViewProviderOptions() {
-        try {
-            WebViewProviderInfo[] providers = mWebViewUpdateService.getValidWebViewPackages();
-            if (providers == null) {
-                Log.e(TAG, "No WebView providers available");
-                return;
-            }
-            ArrayList<String> options = new ArrayList<String>();
-            ArrayList<String> values = new ArrayList<String>();
-            for (int n = 0; n < providers.length; n++) {
-                if (Utils.isPackageEnabled(getActivity(), providers[n].packageName)) {
-                    options.add(providers[n].description);
-                    values.add(providers[n].packageName);
-                }
-            }
-            mWebViewProvider.setEntries(options.toArray(new String[options.size()]));
-            mWebViewProvider.setEntryValues(values.toArray(new String[values.size()]));
-
-            String value = mWebViewUpdateService.getCurrentWebViewPackageName();
-            if (value == null) {
-                value = "";
-            }
-
-            for (int i = 0; i < values.size(); i++) {
-                if (value.contentEquals(values.get(i))) {
-                    mWebViewProvider.setValueIndex(i);
-                    return;
-                }
-            }
-        } catch (RemoteException e) {
-        }
-    }
-
     private void updateWebViewMultiprocessOptions() {
         try {
             updateSwitchPreference(mWebViewMultiprocess,
@@ -918,17 +886,6 @@
                 mBtHciSnoopLog.isChecked() ? 1 : 0);
     }
 
-    private boolean writeWebViewProviderOptions(Object newValue) {
-        try {
-            String updatedProvider = mWebViewUpdateService.changeProviderAndSetting(
-                    newValue == null ? "" : newValue.toString());
-            updateWebViewProviderOptions();
-            return newValue != null && newValue.equals(updatedProvider);
-        } catch (RemoteException e) {
-        }
-        return false;
-    }
-
     private void writeDebuggerOptions() {
         try {
             ActivityManager.getService().setDebugApp(
@@ -2327,6 +2284,8 @@
                 writeMockLocation();
                 updateMockLocation();
             }
+        } else if (requestCode == RESULT_WEBVIEW_APP) {
+            mWebViewAppPrefController.onActivityResult(resultCode, data);
         } else if (requestCode == REQUEST_CODE_ENABLE_OEM_UNLOCK) {
             if (resultCode == Activity.RESULT_OK) {
                 if (mEnableOemUnlock.isChecked()) {
@@ -2349,6 +2308,10 @@
         if (mBugReportInPowerController.handlePreferenceTreeClick(preference)) {
             return true;
         }
+        if (mWebViewAppPrefController.handlePreferenceTreeClick(preference)) {
+            startActivityForResult(
+                    mWebViewAppPrefController.getActivityIntent(), RESULT_WEBVIEW_APP);
+        }
 
         if (preference == mEnableAdb) {
             if (mEnableAdb.isChecked()) {
@@ -2502,21 +2465,6 @@
             updateHdcpValues();
             pokeSystemProperties();
             return true;
-        } else if (preference == mWebViewProvider) {
-            if (newValue == null) {
-                Log.e(TAG, "Tried to set a null WebView provider");
-                return false;
-            }
-            if (writeWebViewProviderOptions(newValue)) {
-                return true;
-            } else {
-                // The user chose a package that became invalid since the list was last updated,
-                // show a Toast to explain the situation.
-                Toast toast = Toast.makeText(getActivity(),
-                        R.string.select_webview_provider_toast_text, Toast.LENGTH_SHORT);
-                toast.show();
-            }
-            return false;
         } else if ((preference == mBluetoothSelectA2dpCodec) ||
                    (preference == mBluetoothSelectA2dpSampleRate) ||
                    (preference == mBluetoothSelectA2dpBitsPerSample) ||
diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java
index 7fe1167..8bb6834 100644
--- a/src/com/android/settings/SettingsActivity.java
+++ b/src/com/android/settings/SettingsActivity.java
@@ -389,14 +389,10 @@
         final boolean isSubSettings = this instanceof SubSettings ||
                 intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, false);
 
-        // If this is a sub settings, then apply the SubSettings Theme for the ActionBar content insets
+        // If this is a sub settings, then apply the SubSettings Theme for the ActionBar content
+        // insets
         if (isSubSettings) {
-            // Check also that we are not a Theme Dialog as we don't want to override them
-            final int themeResId = getThemeResId();
-            if (themeResId != R.style.Theme_DialogWhenLarge &&
-                    themeResId != R.style.Theme_SubSettingsDialogWhenLarge) {
-                setTheme(R.style.Theme_SubSettings);
-            }
+            setTheme(R.style.Theme_SubSettings);
         }
 
         setContentView(mIsShowingDashboard ?
diff --git a/src/com/android/settings/SettingsInitialize.java b/src/com/android/settings/SettingsInitialize.java
index 07fec07..66fc4d6 100644
--- a/src/com/android/settings/SettingsInitialize.java
+++ b/src/com/android/settings/SettingsInitialize.java
@@ -44,6 +44,8 @@
     private static final String TAG = "Settings";
     private static final String PRIMARY_PROFILE_SETTING =
             "com.android.settings.PRIMARY_PROFILE_CONTROLLED";
+    private static final String SETTINGS_PACKAGE = "com.android.settings";
+    private static final String WEBVIEW_IMPLEMENTATION_ACTIVITY = ".WebViewImplementation";
 
     @Override
     public void onReceive(Context context, Intent broadcast) {
@@ -100,7 +102,7 @@
             return;
         }
         ComponentName settingsComponentName =
-            new ComponentName(context, WebViewImplementation.class);
+            new ComponentName(SETTINGS_PACKAGE, SETTINGS_PACKAGE + WEBVIEW_IMPLEMENTATION_ACTIVITY);
         pm.setComponentEnabledSetting(settingsComponentName,
                 userInfo.isAdmin() ?
                         PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
diff --git a/src/com/android/settings/SettingsPreferenceFragment.java b/src/com/android/settings/SettingsPreferenceFragment.java
index ee500a9..3bad5f3 100644
--- a/src/com/android/settings/SettingsPreferenceFragment.java
+++ b/src/com/android/settings/SettingsPreferenceFragment.java
@@ -727,10 +727,6 @@
         getActivity().setResult(result);
     }
 
-    protected final Context getPrefContext() {
-        return getPreferenceManager().getContext();
-    }
-
     public boolean startFragment(Fragment caller, String fragmentClass, int titleRes,
             int requestCode, Bundle extras) {
         final Activity activity = getActivity();
diff --git a/src/com/android/settings/WebViewImplementation.java b/src/com/android/settings/WebViewImplementation.java
deleted file mode 100644
index 349f5e9..0000000
--- a/src/com/android/settings/WebViewImplementation.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2016 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;
-
-import android.annotation.Nullable;
-import android.app.AlertDialog;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnCancelListener;
-import android.content.DialogInterface.OnClickListener;
-import android.content.DialogInterface.OnDismissListener;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.UserManager;
-import android.util.Log;
-import android.webkit.IWebViewUpdateService;
-import android.webkit.WebViewProviderInfo;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.settings.core.InstrumentedActivity;
-
-import java.util.ArrayList;
-
-public class WebViewImplementation extends InstrumentedActivity implements
-        OnCancelListener, OnDismissListener {
-
-    private static final String TAG = "WebViewImplementation";
-
-    private IWebViewUpdateService mWebViewUpdateService;
-
-    @Override
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        if (!UserManager.get(this).isAdminUser()) {
-            finish();
-            return;
-        }
-        mWebViewUpdateService  =
-                IWebViewUpdateService.Stub.asInterface(ServiceManager.getService("webviewupdate"));
-        try {
-            WebViewProviderInfo[] providers = mWebViewUpdateService.getValidWebViewPackages();
-            if (providers == null) {
-                Log.e(TAG, "No WebView providers available");
-                finish();
-                return;
-            }
-
-            String currentValue = mWebViewUpdateService.getCurrentWebViewPackageName();
-            if (currentValue == null) {
-                currentValue = "";
-            }
-
-            int currentIndex = -1;
-            ArrayList<String> options = new ArrayList<>();
-            final ArrayList<String> values = new ArrayList<>();
-            for (WebViewProviderInfo provider : providers) {
-                if (Utils.isPackageEnabled(this, provider.packageName)) {
-                    options.add(provider.description);
-                    values.add(provider.packageName);
-                    if (currentValue.contentEquals(provider.packageName)) {
-                        currentIndex = values.size() - 1;
-                    }
-                }
-            }
-
-            new AlertDialog.Builder(this)
-                    .setTitle(R.string.select_webview_provider_dialog_title)
-                    .setSingleChoiceItems(options.toArray(new String[0]), currentIndex,
-                            new OnClickListener() {
-                        @Override
-                        public void onClick(DialogInterface dialog, int which) {
-                            try {
-                                mWebViewUpdateService.changeProviderAndSetting(values.get(which));
-                            } catch (RemoteException e) {
-                                Log.w(TAG, "Problem reaching webviewupdate service", e);
-                            }
-                            finish();
-                        }
-                    }).setNegativeButton(android.R.string.cancel, null)
-                    .setOnCancelListener(this)
-                    .setOnDismissListener(this)
-                    .show();
-        } catch (RemoteException e) {
-            Log.w(TAG, "Problem reaching webviewupdate service", e);
-            finish();
-        }
-    }
-
-    @Override
-    public int getMetricsCategory() {
-        return MetricsEvent.WEBVIEW_IMPLEMENTATION;
-    }
-
-    @Override
-    public void onCancel(DialogInterface dialog) {
-        finish();
-    }
-
-    @Override
-    public void onDismiss(DialogInterface dialog) {
-        finish();
-    }
-}
diff --git a/src/com/android/settings/applications/AdvancedAppSettings.java b/src/com/android/settings/applications/AdvancedAppSettings.java
index 5bce24d..e8b621a 100644
--- a/src/com/android/settings/applications/AdvancedAppSettings.java
+++ b/src/com/android/settings/applications/AdvancedAppSettings.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2017 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.
@@ -20,14 +20,20 @@
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settings.R;
-import com.android.settings.Utils;
+import com.android.settings.applications.defaultapps.DefaultBrowserPreferenceController;
+import com.android.settings.applications.defaultapps.DefaultEmergencyPreferenceController;
+import com.android.settings.applications.defaultapps.DefaultHomePreferenceController;
+import com.android.settings.applications.defaultapps.DefaultPhonePreferenceController;
+import com.android.settings.applications.defaultapps.DefaultSmsPreferenceController;
+import com.android.settings.applications.defaultapps.DefaultWorkBrowserPreferenceController;
+import com.android.settings.applications.defaultapps.DefaultWorkPhonePreferenceController;
 import com.android.settings.core.PreferenceController;
 import com.android.settings.dashboard.DashboardFragment;
-import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settings.search.Indexable;
 import com.android.settingslib.drawer.CategoryKey;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
@@ -47,14 +53,20 @@
 
     @Override
     protected int getPreferenceScreenResId() {
-        return mDashboardFeatureProvider.isEnabled()
-                ? R.xml.app_default_settings
-                : R.xml.advanced_apps;
+        return R.xml.app_default_settings;
     }
 
     @Override
     protected List<PreferenceController> getPreferenceControllers(Context context) {
-        return null;
+        final List<PreferenceController> controllers = new ArrayList<>();
+        controllers.add(new DefaultBrowserPreferenceController(context));
+        controllers.add(new DefaultWorkBrowserPreferenceController(context));
+        controllers.add(new DefaultPhonePreferenceController(context));
+        controllers.add(new DefaultWorkPhonePreferenceController(context));
+        controllers.add(new DefaultSmsPreferenceController(context));
+        controllers.add(new DefaultEmergencyPreferenceController(context));
+        controllers.add(new DefaultHomePreferenceController(context));
+        return controllers;
     }
 
     @Override
@@ -68,16 +80,8 @@
                 public List<SearchIndexableResource> getXmlResourcesToIndex(
                         Context context, boolean enabled) {
                     final SearchIndexableResource sir = new SearchIndexableResource(context);
-                    sir.xmlResId = FeatureFactory.getFactory(context)
-                            .getDashboardFeatureProvider(context).isEnabled()
-                            ? R.xml.app_default_settings
-                            : R.xml.advanced_apps;
+                    sir.xmlResId = R.xml.app_default_settings;
                     return Arrays.asList(sir);
                 }
-
-                @Override
-                public List<String> getNonIndexableKeys(Context context) {
-                    return Utils.getNonIndexable(R.xml.advanced_apps, context);
-                }
             };
 }
diff --git a/src/com/android/settings/applications/AppAndNotificationDashboardFragment.java b/src/com/android/settings/applications/AppAndNotificationDashboardFragment.java
index be70f86..5942897 100644
--- a/src/com/android/settings/applications/AppAndNotificationDashboardFragment.java
+++ b/src/com/android/settings/applications/AppAndNotificationDashboardFragment.java
@@ -40,12 +40,6 @@
     }
 
     @Override
-    public void onAttach(Context context) {
-        super.onAttach(context);
-        mProgressiveDisclosureMixin.setTileLimit(3);
-    }
-
-    @Override
     protected String getCategoryKey() {
         return CategoryKey.CATEGORY_APPS;
     }
diff --git a/src/com/android/settings/applications/DefaultBrowserPreference.java b/src/com/android/settings/applications/DefaultBrowserPreference.java
deleted file mode 100644
index 9d84e1e..0000000
--- a/src/com/android/settings/applications/DefaultBrowserPreference.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Copyright (C) 2015 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.applications;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.Log;
-
-import com.android.internal.content.PackageMonitor;
-import com.android.settings.AppListPreference;
-import com.android.settings.R;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class DefaultBrowserPreference extends AppListPreference {
-
-    private static final String TAG = "DefaultBrowserPref";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
-    private static final long DELAY_UPDATE_BROWSER_MILLIS = 500;
-    private static final Intent BROWSE_PROBE = new Intent()
-            .setAction(Intent.ACTION_VIEW)
-            .addCategory(Intent.CATEGORY_BROWSABLE)
-            .setData(Uri.parse("http:"));
-
-    private final Handler mHandler = new Handler();
-    private final PackageManager mPm;
-
-    public DefaultBrowserPreference(Context context, AttributeSet attrs) {
-        super(context, attrs);
-
-        mPm = context.getPackageManager();
-        refreshBrowserApps();
-    }
-
-    @Override
-    public void onAttached() {
-        super.onAttached();
-        updateDefaultBrowserPreference();
-        mPackageMonitor.register(getContext(), getContext().getMainLooper(), false);
-    }
-
-    @Override
-    public void onDetached() {
-        mPackageMonitor.unregister();
-        super.onDetached();
-    }
-
-    @Override
-    protected boolean persistString(String newValue) {
-        final CharSequence packageName = newValue;
-        if (TextUtils.isEmpty(packageName)) {
-            return false;
-        }
-        boolean result = mPm.setDefaultBrowserPackageNameAsUser(
-                packageName.toString(), mUserId);
-        if (result) {
-            setSummary("%s");
-        }
-        return result && super.persistString(newValue);
-    }
-
-    public void refreshBrowserApps() {
-        List<String> browsers = resolveBrowserApps();
-
-        setPackageNames(browsers.toArray(new String[browsers.size()]), null);
-    }
-
-    private void updateDefaultBrowserPreference() {
-        refreshBrowserApps();
-
-        final PackageManager pm = getContext().getPackageManager();
-
-        String packageName = pm.getDefaultBrowserPackageNameAsUser(mUserId);
-        if (!TextUtils.isEmpty(packageName)) {
-            // Check if the default Browser package is still there
-            final Intent intent = new Intent(BROWSE_PROBE)
-                    .setPackage(packageName);
-
-            final ResolveInfo info = mPm.resolveActivityAsUser(intent, 0, mUserId);
-            if (info != null) {
-                setValue(packageName);
-                setSummary("%s");
-            } else {
-                setSummary(R.string.default_browser_title_none);
-            }
-        } else {
-            if (DEBUG) Log.d(TAG, "No default browser app.");
-            setSoleAppLabelAsSummary();
-        }
-    }
-
-    private List<String> resolveBrowserApps() {
-        List<String> result = new ArrayList<>();
-
-        // Resolve that intent and check that the handleAllWebDataURI boolean is set
-        List<ResolveInfo> list = mPm.queryIntentActivitiesAsUser(BROWSE_PROBE,
-                PackageManager.MATCH_ALL, mUserId);
-
-        final int count = list.size();
-        for (int i = 0; i < count; i++) {
-            ResolveInfo info = list.get(i);
-            if (info.activityInfo == null || result.contains(info.activityInfo.packageName)
-                    || !info.handleAllWebDataURI) {
-                continue;
-            }
-
-            result.add(info.activityInfo.packageName);
-        }
-
-        return result;
-    }
-
-    @Override
-    protected CharSequence getSoleAppLabel() {
-        // Resolve that intent and check that the handleAllWebDataURI boolean is set
-        List<ResolveInfo> list = mPm.queryIntentActivitiesAsUser(BROWSE_PROBE,
-                PackageManager.MATCH_ALL, mUserId);
-        if (list.size() == 1) {
-            return list.get(0).loadLabel(mPm);
-        }
-        return null;
-    }
-
-    private final Runnable mUpdateRunnable = new Runnable() {
-        @Override
-        public void run() {
-            updateDefaultBrowserPreference();
-        }
-    };
-
-    private final PackageMonitor mPackageMonitor = new PackageMonitor() {
-        @Override
-        public void onPackageAdded(String packageName, int uid) {
-            sendUpdate();
-        }
-
-        @Override
-        public void onPackageAppeared(String packageName, int reason) {
-            sendUpdate();
-        }
-
-        @Override
-        public void onPackageDisappeared(String packageName, int reason) {
-            sendUpdate();
-        }
-
-        @Override
-        public void onPackageRemoved(String packageName, int uid) {
-            sendUpdate();
-        }
-
-        private void sendUpdate() {
-            mHandler.postDelayed(mUpdateRunnable, DELAY_UPDATE_BROWSER_MILLIS);
-        }
-    };
-
-    public static boolean hasBrowserPreference(String pkg, Context context) {
-        Intent intent = new Intent();
-        intent.setAction(Intent.ACTION_VIEW);
-        intent.addCategory(Intent.CATEGORY_BROWSABLE);
-        intent.setData(Uri.parse("http:"));
-        intent.setPackage(pkg);
-        final List<ResolveInfo> resolveInfos =
-                context.getPackageManager().queryIntentActivities(intent, 0);
-        return resolveInfos != null && resolveInfos.size() != 0;
-    }
-
-    public static boolean isBrowserDefault(String pkg, Context context) {
-        String defaultPackage = context.getPackageManager()
-                .getDefaultBrowserPackageNameAsUser(UserHandle.myUserId());
-        return defaultPackage != null && defaultPackage.equals(pkg);
-    }
-}
diff --git a/src/com/android/settings/applications/DefaultEmergencyPreference.java b/src/com/android/settings/applications/DefaultEmergencyPreference.java
deleted file mode 100644
index dd4dc2e..0000000
--- a/src/com/android/settings/applications/DefaultEmergencyPreference.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright (C) 2015 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.applications;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.AsyncTask;
-import android.provider.Settings;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-import android.util.ArraySet;
-import android.util.AttributeSet;
-
-import com.android.settings.AppListPreference;
-import com.android.settings.R;
-import com.android.settings.SelfAvailablePreference;
-import com.android.settings.Utils;
-
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-
-/**
- * A preference for choosing the default emergency app
- */
-public class DefaultEmergencyPreference extends AppListPreference
-        implements SelfAvailablePreference {
-
-    private static final boolean DEFAULT_EMERGENCY_APP_IS_CONFIGURABLE = false;
-    private final ContentResolver mContentResolver;
-
-    public static final Intent QUERY_INTENT = new Intent(
-            TelephonyManager.ACTION_EMERGENCY_ASSISTANCE);
-
-    public DefaultEmergencyPreference(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        mContentResolver = context.getContentResolver();
-        load();
-    }
-
-    @Override
-    protected CharSequence getConfirmationMessage(String value) {
-        return Utils.isPackageDirectBootAware(getContext(), value) ? null
-                : getContext().getText(R.string.direct_boot_unaware_dialog_message);
-    }
-
-    @Override
-    protected boolean persistString(String value) {
-        String previousValue = Settings.Secure.getString(mContentResolver,
-                Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION);
-
-        if (!TextUtils.isEmpty(value) && !Objects.equals(value, previousValue)) {
-            Settings.Secure.putString(mContentResolver,
-                    Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION,
-                    value);
-        }
-        setSummary(getEntry());
-        return true;
-    }
-
-    private void load() {
-        new AsyncTask<Void, Void, Set<String>>() {
-            @Override
-            protected Set<String> doInBackground(Void[] params) {
-                return resolveAssistPackageAndQueryApps();
-            }
-
-            @Override
-            protected void onPostExecute(Set<String> entries) {
-                String currentPkg = Settings.Secure.getString(mContentResolver,
-                        Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION);
-                setPackageNames(entries.toArray(new String[entries.size()]), currentPkg);
-            }
-        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
-    }
-
-    private Set<String> resolveAssistPackageAndQueryApps() {
-        Set<String> packages = new ArraySet<>();
-
-        PackageManager packageManager = getContext().getPackageManager();
-        List<ResolveInfo> infos = packageManager.queryIntentActivities(QUERY_INTENT, 0);
-
-        PackageInfo bestMatch = null;
-        final int size = infos.size();
-        for (int i = 0; i < size; i++) {
-            ResolveInfo info = infos.get(i);
-            if (info == null || info.activityInfo == null
-                    || packages.contains(info.activityInfo.packageName)) {
-                continue;
-            }
-
-            String packageName = info.activityInfo.packageName;
-
-            packages.add(packageName);
-
-            PackageInfo packageInfo;
-            try {
-                packageInfo = packageManager.getPackageInfo(packageName, 0);
-            } catch (PackageManager.NameNotFoundException e) {
-                continue;
-            }
-
-            // Get earliest installed system app.
-            if (isSystemApp(packageInfo) && (bestMatch == null ||
-                    bestMatch.firstInstallTime > packageInfo.firstInstallTime)) {
-                bestMatch = packageInfo;
-            }
-        }
-
-        String defaultPackage = Settings.Secure.getString(mContentResolver,
-                Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION);
-        boolean defaultMissing = TextUtils.isEmpty(defaultPackage)
-                || !packages.contains(defaultPackage);
-        if (bestMatch != null && defaultMissing) {
-            Settings.Secure.putString(mContentResolver,
-                    Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION,
-                    bestMatch.packageName);
-        }
-
-        return packages;
-    }
-
-    private static boolean isCapable(Context context) {
-        return TelephonyManager.EMERGENCY_ASSISTANCE_ENABLED
-                && context.getResources().getBoolean(
-                com.android.internal.R.bool.config_voice_capable);
-    }
-
-    private static boolean isSystemApp(PackageInfo info) {
-        return info.applicationInfo != null
-                && (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
-    }
-
-    public boolean isAvailable(Context context) {
-        return DEFAULT_EMERGENCY_APP_IS_CONFIGURABLE
-                && isCapable(context)
-                && context.getPackageManager().resolveActivity(QUERY_INTENT, 0) != null;
-    }
-
-    public static boolean hasEmergencyPreference(String pkg, Context context) {
-        Intent i = new Intent(QUERY_INTENT);
-        i.setPackage(pkg);
-        final List<ResolveInfo> resolveInfos =
-                context.getPackageManager().queryIntentActivities(i, 0);
-        return resolveInfos != null && resolveInfos.size() != 0;
-    }
-
-    public static boolean isEmergencyDefault(String pkg, Context context) {
-        String defaultPackage = Settings.Secure.getString(context.getContentResolver(),
-                Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION);
-        return defaultPackage != null && defaultPackage.equals(pkg);
-    }
-}
diff --git a/src/com/android/settings/applications/DefaultHomePreference.java b/src/com/android/settings/applications/DefaultHomePreference.java
deleted file mode 100644
index 46fba66..0000000
--- a/src/com/android/settings/applications/DefaultHomePreference.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright (C) 2016 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.applications;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.UserInfo;
-import android.os.Build;
-import android.os.UserManager;
-import android.util.AttributeSet;
-
-import com.android.settings.AppListPreference;
-import com.android.settings.R;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class DefaultHomePreference extends AppListPreference {
-
-    private final ArrayList<ComponentName> mAllHomeComponents = new ArrayList<>();
-    private final IntentFilter mHomeFilter;
-    private final String mPackageName;
-
-    public DefaultHomePreference(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        mPackageName = getContext().getPackageName();
-        mHomeFilter = new IntentFilter(Intent.ACTION_MAIN);
-        mHomeFilter.addCategory(Intent.CATEGORY_HOME);
-        mHomeFilter.addCategory(Intent.CATEGORY_DEFAULT);
-        refreshHomeOptions();
-    }
-
-    @Override
-    public void performClick() {
-        refreshHomeOptions();
-        super.performClick();
-    }
-
-    @Override
-    protected boolean persistString(String value) {
-        if (value != null) {
-            ComponentName component = ComponentName.unflattenFromString(value);
-            getContext().getPackageManager().replacePreferredActivity(mHomeFilter,
-                    IntentFilter.MATCH_CATEGORY_EMPTY,
-                    mAllHomeComponents.toArray(new ComponentName[0]), component);
-            setSummary(getEntry());
-        } else {
-            // If there is only 1 launcher, use its label as summary text.
-            setSoleAppLabelAsSummary();
-        }
-        return super.persistString(value);
-    }
-
-    @Override
-    protected CharSequence getSoleAppLabel() {
-        final PackageManager pm = getContext().getPackageManager();
-        final List<ResolveInfo> homeActivities = new ArrayList<>();
-        final List<CharSequence> appLabels = new ArrayList<>();
-
-        pm.getHomeActivities(homeActivities);
-        for (ResolveInfo candidate : homeActivities) {
-            final ActivityInfo info = candidate.activityInfo;
-            if (info.packageName.equals(mPackageName)) {
-                continue;
-            }
-            appLabels.add(info.loadLabel(pm));
-        }
-        return appLabels.size() == 1 ? appLabels.get(0) : null;
-    }
-
-    public void refreshHomeOptions() {
-        ArrayList<ResolveInfo> homeActivities = new ArrayList<>();
-        PackageManager pm = getContext().getPackageManager();
-        ComponentName currentDefaultHome = pm.getHomeActivities(homeActivities);
-        ArrayList<ComponentName> components = new ArrayList<>();
-        mAllHomeComponents.clear();
-        List<CharSequence> summaries = new ArrayList<>();
-
-        boolean mustSupportManagedProfile = hasManagedProfile();
-        for (ResolveInfo candidate : homeActivities) {
-            final ActivityInfo info = candidate.activityInfo;
-            ComponentName activityName = new ComponentName(info.packageName, info.name);
-            mAllHomeComponents.add(activityName);
-            if (info.packageName.equals(mPackageName)) {
-                continue;
-            }
-            components.add(activityName);
-            if (mustSupportManagedProfile && !launcherHasManagedProfilesFeature(candidate, pm)) {
-                summaries.add(getContext().getString(R.string.home_work_profile_not_supported));
-            } else {
-                summaries.add(null);
-            }
-        }
-        setComponentNames(components.toArray(new ComponentName[0]), currentDefaultHome,
-                summaries.toArray(new CharSequence[0]));
-    }
-
-    private boolean launcherHasManagedProfilesFeature(ResolveInfo resolveInfo, PackageManager pm) {
-        try {
-            ApplicationInfo appInfo = pm.getApplicationInfo(
-                    resolveInfo.activityInfo.packageName, 0 /* default flags */);
-            return versionNumberAtLeastL(appInfo.targetSdkVersion);
-        } catch (PackageManager.NameNotFoundException e) {
-            return false;
-        }
-    }
-
-    private boolean versionNumberAtLeastL(int versionNumber) {
-        return versionNumber >= Build.VERSION_CODES.LOLLIPOP;
-    }
-
-    private boolean hasManagedProfile() {
-        UserManager userManager = getContext().getSystemService(UserManager.class);
-        List<UserInfo> profiles = userManager.getProfiles(getContext().getUserId());
-        for (UserInfo userInfo : profiles) {
-            if (userInfo.isManagedProfile()) return true;
-        }
-        return false;
-    }
-
-    public static boolean hasHomePreference(String pkg, Context context) {
-        ArrayList<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>();
-        PackageManager pm = context.getPackageManager();
-        pm.getHomeActivities(homeActivities);
-        for (int i = 0; i < homeActivities.size(); i++) {
-            if (homeActivities.get(i).activityInfo.packageName.equals(pkg)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public static boolean isHomeDefault(String pkg, Context context) {
-        ArrayList<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>();
-        PackageManager pm = context.getPackageManager();
-        ComponentName def = pm.getHomeActivities(homeActivities);
-
-        return def != null && def.getPackageName().equals(pkg);
-    }
-}
diff --git a/src/com/android/settings/applications/DefaultPhonePreference.java b/src/com/android/settings/applications/DefaultPhonePreference.java
deleted file mode 100644
index e151274..0000000
--- a/src/com/android/settings/applications/DefaultPhonePreference.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2015 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.applications;
-
-import android.content.Context;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.telecom.DefaultDialerManager;
-import android.telecom.TelecomManager;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-
-import com.android.settings.AppListPreference;
-import com.android.settings.R;
-import com.android.settings.SelfAvailablePreference;
-import com.android.settings.Utils;
-
-import java.util.List;
-import java.util.Objects;
-
-public class DefaultPhonePreference extends AppListPreference implements SelfAvailablePreference {
-    public DefaultPhonePreference(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        loadDialerApps();
-    }
-
-    @Override
-    protected CharSequence getConfirmationMessage(String value) {
-        return Utils.isPackageDirectBootAware(getContext(), value) ? null
-                : getContext().getText(R.string.direct_boot_unaware_dialog_message);
-    }
-
-    @Override
-    protected boolean persistString(String value) {
-        if (!TextUtils.isEmpty(value) && !Objects.equals(value, getDefaultPackage())) {
-            DefaultDialerManager.setDefaultDialerApplication(getContext(), value, mUserId);
-        }
-        setSummary(getEntry());
-        return true;
-    }
-
-    private void loadDialerApps() {
-        List<String> dialerPackages =
-                DefaultDialerManager.getInstalledDialerApplications(getContext(), mUserId);
-
-        final String[] dialers = new String[dialerPackages.size()];
-        for (int i = 0; i < dialerPackages.size(); i++) {
-            dialers[i] = dialerPackages.get(i);
-        }
-        setPackageNames(dialers, getDefaultPackage(), getSystemPackage());
-    }
-
-    private String getDefaultPackage() {
-        return DefaultDialerManager.getDefaultDialerApplication(getContext(), mUserId);
-    }
-
-    private String getSystemPackage() {
-        TelecomManager tm = TelecomManager.from(getContext());
-        return tm.getSystemDialerPackage();
-    }
-
-    @Override
-    public boolean isAvailable(Context context) {
-        final TelephonyManager tm =
-                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
-        if (!tm.isVoiceCapable()) {
-            return false;
-        }
-
-        final UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
-        final boolean hasUserRestriction =
-                um.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS);
-        final CharSequence[] entries = getEntries();
-        return !hasUserRestriction
-                && entries != null
-                && entries.length > 0;
-    }
-
-    public static boolean hasPhonePreference(String pkg, Context context) {
-        List<String> dialerPackages =
-                DefaultDialerManager.getInstalledDialerApplications(context, UserHandle.myUserId());
-        return dialerPackages.contains(pkg);
-    }
-
-    public static boolean isPhoneDefault(String pkg, Context context) {
-        String def = DefaultDialerManager.getDefaultDialerApplication(context,
-                UserHandle.myUserId());
-        return def != null && def.equals(pkg);
-    }
-}
diff --git a/src/com/android/settings/applications/DefaultSmsPreference.java b/src/com/android/settings/applications/DefaultSmsPreference.java
deleted file mode 100644
index 96ac9a2..0000000
--- a/src/com/android/settings/applications/DefaultSmsPreference.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2015 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.applications;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-
-import com.android.internal.telephony.SmsApplication;
-import com.android.internal.telephony.SmsApplication.SmsApplicationData;
-import com.android.settings.AppListPreference;
-import com.android.settings.R;
-import com.android.settings.SelfAvailablePreference;
-import com.android.settings.Utils;
-
-import java.util.Collection;
-import java.util.Objects;
-
-public class DefaultSmsPreference extends AppListPreference implements SelfAvailablePreference {
-    public DefaultSmsPreference(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        loadSmsApps();
-    }
-
-    private void loadSmsApps() {
-        Collection<SmsApplicationData> smsApplications =
-                SmsApplication.getApplicationCollection(getContext());
-
-        int count = smsApplications.size();
-        String[] packageNames = new String[count];
-        int i = 0;
-        for (SmsApplicationData smsApplicationData : smsApplications) {
-            packageNames[i++] = smsApplicationData.mPackageName;
-        }
-        setPackageNames(packageNames, getDefaultPackage());
-    }
-
-    private String getDefaultPackage() {
-        ComponentName appName = SmsApplication.getDefaultSmsApplication(getContext(), true);
-        if (appName != null) {
-            return appName.getPackageName();
-        }
-        return null;
-    }
-
-    @Override
-    protected CharSequence getConfirmationMessage(String value) {
-        return Utils.isPackageDirectBootAware(getContext(), value) ? null
-                : getContext().getText(R.string.direct_boot_unaware_dialog_message);
-    }
-
-    @Override
-    protected boolean persistString(String value) {
-        if (!TextUtils.isEmpty(value) && !Objects.equals(value, getDefaultPackage())) {
-            SmsApplication.setDefaultApplication(value, getContext());
-        }
-        setSummary(getEntry());
-        return true;
-    }
-
-    @Override
-    public boolean isAvailable(Context context) {
-        boolean isRestrictedUser =
-                UserManager.get(context)
-                        .getUserInfo(UserHandle.myUserId()).isRestricted();
-        TelephonyManager tm =
-                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
-        return !isRestrictedUser && tm.isSmsCapable();
-    }
-
-    public static boolean hasSmsPreference(String pkg, Context context) {
-        Collection<SmsApplicationData> smsApplications =
-                SmsApplication.getApplicationCollection(context);
-        for (SmsApplicationData data : smsApplications) {
-            if (data.mPackageName.equals(pkg)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public static boolean isSmsDefault(String pkg, Context context) {
-        ComponentName appName = SmsApplication.getDefaultSmsApplication(context, true);
-        return appName != null && appName.getPackageName().equals(pkg);
-    }
-}
diff --git a/src/com/android/settings/applications/InstalledAppDetails.java b/src/com/android/settings/applications/InstalledAppDetails.java
index 930957e..8bde62a 100755
--- a/src/com/android/settings/applications/InstalledAppDetails.java
+++ b/src/com/android/settings/applications/InstalledAppDetails.java
@@ -21,8 +21,6 @@
 import android.app.ActivityManager;
 import android.app.AlertDialog;
 import android.app.LoaderManager.LoaderCallbacks;
-import android.app.Notification;
-import android.app.NotificationManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
@@ -52,8 +50,6 @@
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.provider.Settings;
-import android.service.notification.NotificationListenerService.Ranking;
 import android.support.annotation.VisibleForTesting;
 import android.support.v7.preference.Preference;
 import android.support.v7.preference.Preference.OnPreferenceClickListener;
@@ -78,7 +74,6 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.os.BatterySipper;
 import com.android.internal.os.BatteryStatsHelper;
-import com.android.internal.widget.LockPatternUtils;
 import com.android.settings.AppHeader;
 import com.android.settings.DeviceAdminAdd;
 import com.android.settings.R;
@@ -86,6 +81,11 @@
 import com.android.settings.SettingsPreferenceFragment;
 import com.android.settings.Utils;
 import com.android.settings.applications.PermissionsSummaryHelper.PermissionsResultCallback;
+import com.android.settings.applications.defaultapps.DefaultBrowserPreferenceController;
+import com.android.settings.applications.defaultapps.DefaultEmergencyPreferenceController;
+import com.android.settings.applications.defaultapps.DefaultHomePreferenceController;
+import com.android.settings.applications.defaultapps.DefaultPhonePreferenceController;
+import com.android.settings.applications.defaultapps.DefaultSmsPreferenceController;
 import com.android.settings.dashboard.DashboardFeatureProvider;
 import com.android.settings.datausage.AppDataUsage;
 import com.android.settings.datausage.DataUsageList;
@@ -908,27 +908,28 @@
             return;
         }
         final PreferenceScreen screen = getPreferenceScreen();
-        if (DefaultHomePreference.hasHomePreference(mPackageName, getContext())) {
+        final Context context = getContext();
+        if (DefaultHomePreferenceController.hasHomePreference(mPackageName, context)) {
             screen.addPreference(new ShortcutPreference(getPrefContext(),
                     AdvancedAppSettings.class, "default_home", R.string.home_app,
                     R.string.configure_apps));
         }
-        if (DefaultBrowserPreference.hasBrowserPreference(mPackageName, getContext())) {
+        if (DefaultBrowserPreferenceController.hasBrowserPreference(mPackageName, context)) {
             screen.addPreference(new ShortcutPreference(getPrefContext(),
                     AdvancedAppSettings.class, "default_browser", R.string.default_browser_title,
                     R.string.configure_apps));
         }
-        if (DefaultPhonePreference.hasPhonePreference(mPackageName, getContext())) {
+        if (DefaultPhonePreferenceController.hasPhonePreference(mPackageName, context)) {
             screen.addPreference(new ShortcutPreference(getPrefContext(),
                     AdvancedAppSettings.class, "default_phone_app", R.string.default_phone_title,
                     R.string.configure_apps));
         }
-        if (DefaultEmergencyPreference.hasEmergencyPreference(mPackageName, getContext())) {
+        if (DefaultEmergencyPreferenceController.hasEmergencyPreference(mPackageName, context)) {
             screen.addPreference(new ShortcutPreference(getPrefContext(),
                     AdvancedAppSettings.class, "default_emergency_app",
                     R.string.default_emergency_app, R.string.configure_apps));
         }
-        if (DefaultSmsPreference.hasSmsPreference(mPackageName, getContext())) {
+        if (DefaultSmsPreferenceController.hasSmsPreference(mPackageName, context)) {
             screen.addPreference(new ShortcutPreference(getPrefContext(),
                     AdvancedAppSettings.class, "default_sms_app", R.string.sms_application_title,
                     R.string.configure_apps));
@@ -1021,29 +1022,33 @@
     }
 
     private void updateDynamicPrefs() {
+        final Context context = getContext();
         Preference pref = findPreference("default_home");
+
         if (pref != null) {
-            pref.setSummary(DefaultHomePreference.isHomeDefault(mPackageName, getContext())
+            pref.setSummary(DefaultHomePreferenceController.isHomeDefault(mPackageName, context)
                     ? R.string.yes : R.string.no);
         }
         pref = findPreference("default_browser");
         if (pref != null) {
-            pref.setSummary(DefaultBrowserPreference.isBrowserDefault(mPackageName, getContext())
+            pref.setSummary(
+                    DefaultBrowserPreferenceController.isBrowserDefault(mPackageName, context)
                     ? R.string.yes : R.string.no);
         }
         pref = findPreference("default_phone_app");
         if (pref != null) {
-            pref.setSummary(DefaultPhonePreference.isPhoneDefault(mPackageName, getContext())
+            pref.setSummary(
+                    DefaultPhonePreferenceController.isPhoneDefault(mPackageName, context)
                     ? R.string.yes : R.string.no);
         }
         pref = findPreference("default_emergency_app");
         if (pref != null) {
-            pref.setSummary(DefaultEmergencyPreference.isEmergencyDefault(mPackageName,
+            pref.setSummary(DefaultEmergencyPreferenceController.isEmergencyDefault(mPackageName,
                     getContext()) ? R.string.yes : R.string.no);
         }
         pref = findPreference("default_sms_app");
         if (pref != null) {
-            pref.setSummary(DefaultSmsPreference.isSmsDefault(mPackageName, getContext())
+            pref.setSummary(DefaultSmsPreferenceController.isSmsDefault(mPackageName, context)
                     ? R.string.yes : R.string.no);
         }
         pref = findPreference("system_alert_window");
diff --git a/src/com/android/settings/applications/PackageManagerWrapper.java b/src/com/android/settings/applications/PackageManagerWrapper.java
index 4166ccf..2be92ed 100644
--- a/src/com/android/settings/applications/PackageManagerWrapper.java
+++ b/src/com/android/settings/applications/PackageManagerWrapper.java
@@ -16,7 +16,9 @@
 
 package com.android.settings.applications;
 
+import android.content.ComponentName;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
@@ -58,11 +60,42 @@
      */
     List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, int flags, int userId);
 
-
     /**
      * Calls {@code PackageManager.getInstallReason()}.
      *
      * @see android.content.pm.PackageManager#getInstallReason
      */
     int getInstallReason(String packageName, UserHandle user);
+
+    /**
+     * Calls {@code PackageManager.getApplicationInfoAsUser}
+     */
+    ApplicationInfo getApplicationInfoAsUser(String packageName, int i, int userId)
+            throws PackageManager.NameNotFoundException;
+
+    /**
+     * Calls {@code PackageManager.setDefaultBrowserPackageNameAsUser}
+     */
+    boolean setDefaultBrowserPackageNameAsUser(String packageName, int userId);
+
+    /**
+     * Calls {@code PackageManager.getDefaultBrowserPackageNameAsUser}
+     */
+    String getDefaultBrowserPackageNameAsUser(int userId);
+
+    /**
+     * Calls {@code PackageManager.getHomeActivities}
+     */
+    ComponentName getHomeActivities(List<ResolveInfo> homeActivities);
+
+    /**
+     * Calls {@code PackageManager.queryIntentServicesAsUser}
+     */
+    List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int i, int user);
+
+    /**
+     * Calls {@code PackageManager.replacePreferredActivity}
+     */
+    void replacePreferredActivity(IntentFilter homeFilter, int matchCategoryEmpty,
+            ComponentName[] componentNames, ComponentName component);
 }
diff --git a/src/com/android/settings/applications/PackageManagerWrapperImpl.java b/src/com/android/settings/applications/PackageManagerWrapperImpl.java
index 2427cce..698c14c 100644
--- a/src/com/android/settings/applications/PackageManagerWrapperImpl.java
+++ b/src/com/android/settings/applications/PackageManagerWrapperImpl.java
@@ -16,7 +16,9 @@
 
 package com.android.settings.applications;
 
+import android.content.ComponentName;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
@@ -56,4 +58,36 @@
     public int getInstallReason(String packageName, UserHandle user) {
         return mPm.getInstallReason(packageName, user);
     }
+
+    @Override
+    public ApplicationInfo getApplicationInfoAsUser(String packageName, int i, int userId)
+            throws PackageManager.NameNotFoundException {
+        return mPm.getApplicationInfoAsUser(packageName, i, userId);
+    }
+
+    @Override
+    public boolean setDefaultBrowserPackageNameAsUser(String packageName, int userId) {
+        return mPm.setDefaultBrowserPackageNameAsUser(packageName, userId);
+    }
+
+    @Override
+    public String getDefaultBrowserPackageNameAsUser(int userId) {
+        return mPm.getDefaultBrowserPackageNameAsUser(userId);
+    }
+
+    @Override
+    public ComponentName getHomeActivities(List<ResolveInfo> homeActivities) {
+        return mPm.getHomeActivities(homeActivities);
+    }
+
+    @Override
+    public List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int i, int user) {
+        return mPm.queryIntentServicesAsUser(intent, i, user);
+    }
+
+    @Override
+    public void replacePreferredActivity(IntentFilter homeFilter, int matchCategoryEmpty,
+            ComponentName[] componentNames, ComponentName component) {
+        mPm.replacePreferredActivity(homeFilter, matchCategoryEmpty, componentNames, component);
+    }
 }
diff --git a/src/com/android/settings/applications/defaultapps/DefaultAppInfo.java b/src/com/android/settings/applications/defaultapps/DefaultAppInfo.java
new file mode 100644
index 0000000..7801591
--- /dev/null
+++ b/src/com/android/settings/applications/defaultapps/DefaultAppInfo.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2017 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.applications.defaultapps;
+
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.os.RemoteException;
+import android.os.UserHandle;
+
+/**
+ * Data model representing an app in DefaultAppPicker UI.
+ */
+class DefaultAppInfo {
+
+    public final int userId;
+    public final ComponentName componentName;
+    public final PackageItemInfo packageItemInfo;
+    public final String summary;
+
+    public DefaultAppInfo(int uid, ComponentName cn, String summary) {
+        packageItemInfo = null;
+        userId = uid;
+        componentName = cn;
+        this.summary = summary;
+    }
+
+    public DefaultAppInfo(PackageItemInfo info) {
+        userId = UserHandle.myUserId();
+        packageItemInfo = info;
+        componentName = null;
+        summary = null;
+    }
+
+    public CharSequence loadLabel(PackageManager pm) {
+        if (componentName != null) {
+            try {
+                final ActivityInfo actInfo = AppGlobals.getPackageManager().getActivityInfo(
+                        componentName, 0, userId);
+                if (actInfo != null) {
+                    return actInfo.loadLabel(pm);
+                } else {
+                    final ApplicationInfo appInfo = pm.getApplicationInfoAsUser(
+                            componentName.getPackageName(), 0, userId);
+                    return appInfo.loadLabel(pm);
+                }
+            } catch (RemoteException | PackageManager.NameNotFoundException e) {
+                return null;
+            }
+        } else if (packageItemInfo != null) {
+            return packageItemInfo.loadLabel(pm);
+        } else {
+            return null;
+        }
+
+    }
+
+    public Drawable loadIcon(PackageManager pm) {
+        if (componentName != null) {
+            try {
+                final ActivityInfo actInfo = AppGlobals.getPackageManager().getActivityInfo(
+                        componentName, 0, userId);
+                if (actInfo != null) {
+                    return actInfo.loadIcon(pm);
+                } else {
+                    final ApplicationInfo appInfo = pm.getApplicationInfoAsUser(
+                            componentName.getPackageName(), 0, userId);
+                    return appInfo.loadIcon(pm);
+                }
+            } catch (RemoteException | PackageManager.NameNotFoundException e) {
+                return null;
+            }
+        }
+        if (packageItemInfo != null) {
+            return packageItemInfo.loadIcon(pm);
+        } else {
+            return null;
+        }
+    }
+
+    public String getKey() {
+        if (componentName != null) {
+            return componentName.flattenToString();
+        } else if (packageItemInfo != null) {
+            return packageItemInfo.packageName;
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/src/com/android/settings/applications/defaultapps/DefaultAppPickerFragment.java b/src/com/android/settings/applications/defaultapps/DefaultAppPickerFragment.java
new file mode 100644
index 0000000..b322dab
--- /dev/null
+++ b/src/com/android/settings/applications/defaultapps/DefaultAppPickerFragment.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2017 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.applications.defaultapps;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.Fragment;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.R;
+import com.android.settings.Utils;
+import com.android.settings.applications.PackageManagerWrapper;
+import com.android.settings.applications.PackageManagerWrapperImpl;
+import com.android.settings.core.InstrumentedPreferenceFragment;
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settings.widget.RadioButtonPreference;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A generic app picker fragment that shows a list of app as radio button group.
+ */
+public abstract class DefaultAppPickerFragment extends InstrumentedPreferenceFragment implements
+        RadioButtonPreference.OnClickListener {
+
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    static final String EXTRA_FOR_WORK = "for_work";
+
+    private final Map<String, DefaultAppInfo> mCandidates = new ArrayMap<>();
+
+    protected PackageManagerWrapper mPm;
+    protected UserManager mUserManager;
+    protected int mUserId;
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        mPm = new PackageManagerWrapperImpl(context.getPackageManager());
+        mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+        final Bundle arguments = getArguments();
+
+        boolean mForWork = false;
+        if (arguments != null) {
+            mForWork = arguments.getBoolean(EXTRA_FOR_WORK);
+        }
+        final UserHandle managedProfile = Utils.getManagedProfile(mUserManager);
+        mUserId = mForWork && managedProfile != null
+                ? managedProfile.getIdentifier()
+                : UserHandle.myUserId();
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        final View view = super.onCreateView(inflater, container, savedInstanceState);
+        setHasOptionsMenu(true);
+        return view;
+    }
+
+    @Override
+    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+        super.onCreatePreferences(savedInstanceState, rootKey);
+        addPreferencesFromResource(R.xml.app_picker_prefs);
+        mCandidates.clear();
+        final List<DefaultAppInfo> candidateList = getCandidates();
+        if (candidateList != null) {
+            for (DefaultAppInfo info : candidateList) {
+                mCandidates.put(info.getKey(), info);
+            }
+        }
+        final String defaultAppKey = getDefaultAppKey();
+        final String systemDefaultAppKey = getSystemDefaultAppKey();
+        final PreferenceScreen screen = getPreferenceScreen();
+        screen.removeAll();
+        if (shouldShowItemNone()) {
+            final RadioButtonPreference nonePref = new RadioButtonPreference(getPrefContext());
+            nonePref.setIcon(R.drawable.ic_remove_circle);
+            nonePref.setTitle(R.string.app_list_preference_none);
+            nonePref.setChecked(TextUtils.isEmpty(defaultAppKey));
+            nonePref.setOnClickListener(this);
+            screen.addPreference(nonePref);
+        }
+        for (Map.Entry<String, DefaultAppInfo> app : mCandidates.entrySet()) {
+            final RadioButtonPreference pref = new RadioButtonPreference(getPrefContext());
+            final String appKey = app.getKey();
+
+            pref.setTitle(app.getValue().loadLabel(mPm.getPackageManager()));
+            pref.setIcon(app.getValue().loadIcon(mPm.getPackageManager()));
+            pref.setKey(appKey);
+            if (TextUtils.equals(defaultAppKey, appKey)) {
+                pref.setChecked(true);
+            }
+            if (TextUtils.equals(systemDefaultAppKey, appKey)) {
+                pref.setSummary(R.string.system_app);
+            }
+            pref.setOnClickListener(this);
+            screen.addPreference(pref);
+        }
+    }
+
+    @Override
+    public void onRadioButtonClicked(RadioButtonPreference selected) {
+        final String selectedKey = selected.getKey();
+        final String confirmationMessage = getConfirmationMessage(mCandidates.get(selectedKey));
+        final Activity activity = getActivity();
+        if (TextUtils.isEmpty(confirmationMessage)) {
+            onRadioButtonConfirmed(selectedKey);
+        } else if (activity != null) {
+            final DialogFragment fragment = ConfirmationDialogFragment.newInstance(
+                    this, selectedKey, confirmationMessage);
+            fragment.show(activity.getFragmentManager(), ConfirmationDialogFragment.TAG);
+        }
+    }
+
+    private void onRadioButtonConfirmed(String selectedKey) {
+        final boolean success = setDefaultAppKey(selectedKey);
+        if (success) {
+            final PreferenceScreen screen = getPreferenceScreen();
+            if (screen != null) {
+                final int count = screen.getPreferenceCount();
+                for (int i = 0; i < count; i++) {
+                    final Preference pref = screen.getPreference(i);
+                    if (pref instanceof RadioButtonPreference) {
+                        final RadioButtonPreference radioPref = (RadioButtonPreference) pref;
+                        final boolean newCheckedState =
+                                TextUtils.equals(pref.getKey(), selectedKey);
+                        if (radioPref.isChecked() != newCheckedState) {
+                            radioPref.setChecked(TextUtils.equals(pref.getKey(), selectedKey));
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    protected boolean shouldShowItemNone() {
+        return false;
+    }
+
+    protected String getSystemDefaultAppKey() {
+        return null;
+    }
+
+    protected abstract List<DefaultAppInfo> getCandidates();
+
+    protected abstract String getDefaultAppKey();
+
+    protected abstract boolean setDefaultAppKey(String key);
+
+    protected String getConfirmationMessage(DefaultAppInfo appInfo) {
+        return null;
+    }
+
+    public static class ConfirmationDialogFragment extends InstrumentedDialogFragment
+            implements DialogInterface.OnClickListener {
+
+        public static final String TAG = "DefaultAppConfirm";
+        public static final String EXTRA_KEY = "extra_key";
+        public static final String EXTRA_MESSAGE = "extra_message";
+
+        @Override
+        public int getMetricsCategory() {
+            return MetricsProto.MetricsEvent.DEFAULT_APP_PICKER_CONFIRMATION_DIALOG;
+        }
+
+        public static ConfirmationDialogFragment newInstance(DefaultAppPickerFragment parent,
+                String key, String message) {
+            final ConfirmationDialogFragment fragment = new ConfirmationDialogFragment();
+            final Bundle argument = new Bundle();
+            argument.putString(EXTRA_KEY, key);
+            argument.putString(EXTRA_MESSAGE, message);
+            fragment.setArguments(argument);
+            fragment.setTargetFragment(parent, 0);
+            return fragment;
+        }
+
+        @Override
+        public Dialog onCreateDialog(Bundle savedInstanceState) {
+            final Bundle bundle = getArguments();
+            final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
+                    .setMessage(bundle.getString(EXTRA_MESSAGE))
+                    .setPositiveButton(android.R.string.ok, this)
+                    .setNegativeButton(android.R.string.cancel, null);
+            return builder.create();
+        }
+
+        @Override
+        public void onClick(DialogInterface dialog, int which) {
+            final Fragment fragment = getTargetFragment();
+            if (fragment instanceof DefaultAppPickerFragment) {
+                final Bundle bundle = getArguments();
+                ((DefaultAppPickerFragment) fragment).onRadioButtonConfirmed(
+                        bundle.getString(EXTRA_KEY));
+            }
+        }
+    }
+
+}
diff --git a/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java
new file mode 100644
index 0000000..a9433ac
--- /dev/null
+++ b/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2017 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.applications.defaultapps;
+
+import android.content.Context;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.support.v7.preference.Preference;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.settings.applications.PackageManagerWrapper;
+import com.android.settings.applications.PackageManagerWrapperImpl;
+import com.android.settings.core.PreferenceController;
+
+public abstract class DefaultAppPreferenceController extends PreferenceController {
+
+    private static final String TAG = "DefaultAppPrefControl";
+
+    protected final PackageManagerWrapper mPackageManager;
+    protected final UserManager mUserManager;
+
+    protected int mUserId;
+
+    public DefaultAppPreferenceController(Context context) {
+        super(context);
+        mPackageManager = new PackageManagerWrapperImpl(context.getPackageManager());
+        mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+        mUserId = UserHandle.myUserId();
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        final DefaultAppInfo app = getDefaultAppInfo();
+        CharSequence defaultAppLabel = null;
+        if (app != null) {
+            defaultAppLabel = app.loadLabel(mPackageManager.getPackageManager());
+        }
+        if (!TextUtils.isEmpty(defaultAppLabel)) {
+            preference.setSummary(defaultAppLabel);
+        } else {
+            Log.d(TAG, "No default app");
+        }
+    }
+
+    protected abstract DefaultAppInfo getDefaultAppInfo();
+}
diff --git a/src/com/android/settings/applications/defaultapps/DefaultBrowserPicker.java b/src/com/android/settings/applications/defaultapps/DefaultBrowserPicker.java
new file mode 100644
index 0000000..70d9415
--- /dev/null
+++ b/src/com/android/settings/applications/defaultapps/DefaultBrowserPicker.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017 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.applications.defaultapps;
+
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+
+import com.android.internal.logging.nano.MetricsProto;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Fragment for choosing default browser.
+ */
+public class DefaultBrowserPicker extends DefaultAppPickerFragment {
+
+    @Override
+    public int getMetricsCategory() {
+        return MetricsProto.MetricsEvent.DEFAULT_BROWSER_PICKER;
+    }
+
+    @Override
+    protected String getDefaultAppKey() {
+        return mPm.getDefaultBrowserPackageNameAsUser(mUserId);
+    }
+
+    @Override
+    protected boolean setDefaultAppKey(String packageName) {
+        return mPm.setDefaultBrowserPackageNameAsUser(packageName, mUserId);
+    }
+
+    @Override
+    protected List<DefaultAppInfo> getCandidates() {
+        final List<DefaultAppInfo> candidates = new ArrayList<>();
+
+        // Resolve that intent and check that the handleAllWebDataURI boolean is set
+        final List<ResolveInfo> list = mPm.queryIntentActivitiesAsUser(
+                DefaultBrowserPreferenceController.BROWSE_PROBE, PackageManager.MATCH_ALL, mUserId);
+
+        final int count = list.size();
+        for (int i = 0; i < count; i++) {
+            ResolveInfo info = list.get(i);
+            if (info.activityInfo == null || !info.handleAllWebDataURI) {
+                continue;
+            }
+            try {
+                candidates.add(new DefaultAppInfo(
+                        mPm.getApplicationInfoAsUser(info.activityInfo.packageName, 0, mUserId)));
+            } catch (PackageManager.NameNotFoundException e) {
+                // Skip unknown packages.
+            }
+        }
+
+        return candidates;
+    }
+}
diff --git a/src/com/android/settings/applications/defaultapps/DefaultBrowserPreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultBrowserPreferenceController.java
new file mode 100644
index 0000000..dca300b
--- /dev/null
+++ b/src/com/android/settings/applications/defaultapps/DefaultBrowserPreferenceController.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2017 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.applications.defaultapps;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.support.v7.preference.Preference;
+import android.text.TextUtils;
+
+import java.util.List;
+
+public class DefaultBrowserPreferenceController extends DefaultAppPreferenceController {
+
+    static final Intent BROWSE_PROBE = new Intent()
+            .setAction(Intent.ACTION_VIEW)
+            .addCategory(Intent.CATEGORY_BROWSABLE)
+            .setData(Uri.parse("http:"));
+
+    public DefaultBrowserPreferenceController(Context context) {
+        super(context);
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return "default_browser";
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        super.updateState(preference);
+        final DefaultAppInfo defaultApp = getDefaultAppInfo();
+        final CharSequence defaultAppLabel = defaultApp != null
+                ? defaultApp.loadLabel(mPackageManager.getPackageManager()) : null;
+        if (TextUtils.isEmpty(defaultAppLabel)) {
+            final String onlyAppLabel = getOnlyAppLabel();
+            if (!TextUtils.isEmpty(onlyAppLabel)) {
+                preference.setSummary(onlyAppLabel);
+            }
+        }
+    }
+
+    private String getOnlyAppLabel() {
+        // Resolve that intent and check that the handleAllWebDataURI boolean is set
+        final List<ResolveInfo> list = mPackageManager.queryIntentActivitiesAsUser(BROWSE_PROBE,
+                PackageManager.MATCH_ALL, mUserId);
+        if (list != null && list.size() == 1) {
+            return list.get(0).loadLabel(mPackageManager.getPackageManager()).toString();
+        }
+        return null;
+    }
+
+    @Override
+    protected DefaultAppInfo getDefaultAppInfo() {
+        try {
+            return new DefaultAppInfo(mPackageManager.getPackageManager().getApplicationInfo(
+                    mPackageManager.getDefaultBrowserPackageNameAsUser(mUserId), 0));
+        } catch (PackageManager.NameNotFoundException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Whether or not the pkg contains browser capability
+     */
+    public static boolean hasBrowserPreference(String pkg, Context context) {
+        final Intent intent = new Intent(BROWSE_PROBE);
+        intent.setPackage(pkg);
+        final List<ResolveInfo> resolveInfos =
+                context.getPackageManager().queryIntentActivities(intent, 0);
+        return resolveInfos != null && resolveInfos.size() != 0;
+    }
+
+    /**
+     * Whether or not the pkg is the default browser
+     */
+    public static boolean isBrowserDefault(String pkg, Context context) {
+        String defaultPackage = context.getPackageManager()
+                .getDefaultBrowserPackageNameAsUser(UserHandle.myUserId());
+        return defaultPackage != null && defaultPackage.equals(pkg);
+    }
+}
diff --git a/src/com/android/settings/applications/defaultapps/DefaultEmergencyPicker.java b/src/com/android/settings/applications/defaultapps/DefaultEmergencyPicker.java
new file mode 100644
index 0000000..b3cf882
--- /dev/null
+++ b/src/com/android/settings/applications/defaultapps/DefaultEmergencyPicker.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2017 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.applications.defaultapps;
+
+import android.content.ContentResolver;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.provider.Settings;
+import android.text.TextUtils;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.R;
+import com.android.settings.Utils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DefaultEmergencyPicker extends DefaultAppPickerFragment {
+
+    @Override
+    public int getMetricsCategory() {
+        return MetricsProto.MetricsEvent.DEFAULT_EMERGENCY_APP_PICKER;
+    }
+
+    @Override
+    protected List<DefaultAppInfo> getCandidates() {
+        final List<DefaultAppInfo> candidates = new ArrayList<>();
+        final List<ResolveInfo> infos = mPm.getPackageManager().queryIntentActivities(
+                DefaultEmergencyPreferenceController.QUERY_INTENT, 0);
+        PackageInfo bestMatch = null;
+        for (ResolveInfo info : infos) {
+            try {
+                final PackageInfo packageInfo =
+                        mPm.getPackageManager().getPackageInfo(info.activityInfo.packageName, 0);
+                final ApplicationInfo appInfo = packageInfo.applicationInfo;
+                candidates.add(new DefaultAppInfo(appInfo));
+                // Get earliest installed system app.
+                if (isSystemApp(appInfo) && (bestMatch == null ||
+                        bestMatch.firstInstallTime > packageInfo.firstInstallTime)) {
+                    bestMatch = packageInfo;
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                // Skip unknown packages.
+            }
+            if (bestMatch != null) {
+                final String defaultKey = getDefaultAppKey();
+                if (TextUtils.isEmpty(defaultKey)) {
+                    setDefaultAppKey(bestMatch.packageName);
+                }
+            }
+        }
+        return candidates;
+    }
+
+    @Override
+    protected String getConfirmationMessage(DefaultAppInfo info) {
+        return Utils.isPackageDirectBootAware(getContext(), info.getKey()) ? null
+                : getContext().getString(R.string.direct_boot_unaware_dialog_message);
+    }
+
+    @Override
+    protected String getDefaultAppKey() {
+        return Settings.Secure.getString(getContext().getContentResolver(),
+                Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION);
+    }
+
+    @Override
+    protected boolean setDefaultAppKey(String key) {
+        final ContentResolver contentResolver = getContext().getContentResolver();
+        final String previousValue = Settings.Secure.getString(contentResolver,
+                Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION);
+
+        if (!TextUtils.isEmpty(key) && !TextUtils.equals(key, previousValue)) {
+            Settings.Secure.putString(contentResolver,
+                    Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION,
+                    key);
+            return true;
+        }
+        return false;
+    }
+
+    private boolean isSystemApp(ApplicationInfo info) {
+        return info != null && (info.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+    }
+}
diff --git a/src/com/android/settings/applications/defaultapps/DefaultEmergencyPreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultEmergencyPreferenceController.java
new file mode 100644
index 0000000..7713dbd
--- /dev/null
+++ b/src/com/android/settings/applications/defaultapps/DefaultEmergencyPreferenceController.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2017 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.applications.defaultapps;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+
+import java.util.List;
+
+public class DefaultEmergencyPreferenceController extends DefaultAppPreferenceController {
+
+    private static final boolean DEFAULT_EMERGENCY_APP_IS_CONFIGURABLE = false;
+
+    public static final Intent QUERY_INTENT = new Intent(
+            TelephonyManager.ACTION_EMERGENCY_ASSISTANCE);
+
+    public DefaultEmergencyPreferenceController(Context context) {
+        super(context);
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return DEFAULT_EMERGENCY_APP_IS_CONFIGURABLE
+                && isCapable()
+                && mPackageManager.getPackageManager().resolveActivity(QUERY_INTENT, 0) != null;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return "default_emergency_app";
+    }
+
+    @Override
+    protected DefaultAppInfo getDefaultAppInfo() {
+        return null;
+    }
+
+    private boolean isCapable() {
+        return TelephonyManager.EMERGENCY_ASSISTANCE_ENABLED
+                && mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_voice_capable);
+    }
+
+    public static boolean hasEmergencyPreference(String pkg, Context context) {
+        Intent i = new Intent(QUERY_INTENT);
+        i.setPackage(pkg);
+        final List<ResolveInfo> resolveInfos =
+                context.getPackageManager().queryIntentActivities(i, 0);
+        return resolveInfos != null && resolveInfos.size() != 0;
+    }
+
+    public static boolean isEmergencyDefault(String pkg, Context context) {
+        String defaultPackage = Settings.Secure.getString(context.getContentResolver(),
+                Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION);
+        return defaultPackage != null && defaultPackage.equals(pkg);
+    }
+}
diff --git a/src/com/android/settings/applications/defaultapps/DefaultHomePicker.java b/src/com/android/settings/applications/defaultapps/DefaultHomePicker.java
new file mode 100644
index 0000000..5e48bf1
--- /dev/null
+++ b/src/com/android/settings/applications/defaultapps/DefaultHomePicker.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2017 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.applications.defaultapps;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
+import android.os.Build;
+import android.text.TextUtils;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DefaultHomePicker extends DefaultAppPickerFragment {
+
+    private String mPackageName;
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        mPackageName = context.getPackageName();
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        return MetricsProto.MetricsEvent.DEFAULT_HOME_PICKER;
+    }
+
+    @Override
+    protected List<DefaultAppInfo> getCandidates() {
+        final boolean mustSupportManagedProfile = hasManagedProfile();
+        final List<DefaultAppInfo> candidates = new ArrayList<>();
+        final List<ResolveInfo> homeActivities = new ArrayList<>();
+        mPm.getHomeActivities(homeActivities);
+
+        for (ResolveInfo resolveInfo : homeActivities) {
+            final ActivityInfo info = resolveInfo.activityInfo;
+            final ComponentName activityName = new ComponentName(info.packageName, info.name);
+            if (info.packageName.equals(mPackageName)) {
+                continue;
+            }
+
+            final String summary;
+            if (mustSupportManagedProfile && !launcherHasManagedProfilesFeature(resolveInfo)) {
+                summary = getContext().getString(R.string.home_work_profile_not_supported);
+            } else {
+                summary = null;
+            }
+            final DefaultAppInfo candidate = new DefaultAppInfo(mUserId, activityName, summary);
+            candidates.add(candidate);
+        }
+        return candidates;
+    }
+
+    @Override
+    protected String getDefaultAppKey() {
+        final ArrayList<ResolveInfo> homeActivities = new ArrayList<>();
+        final ComponentName currentDefaultHome = mPm.getHomeActivities(homeActivities);
+        if (currentDefaultHome != null) {
+            return currentDefaultHome.flattenToString();
+        }
+        return null;
+    }
+
+    @Override
+    protected boolean setDefaultAppKey(String key) {
+        if (!TextUtils.isEmpty(key)) {
+            final ComponentName component = ComponentName.unflattenFromString(key);
+            final List<ResolveInfo> homeActivities = new ArrayList<>();
+            mPm.getHomeActivities(homeActivities);
+            final List<ComponentName> allComponents = new ArrayList<>();
+            for (ResolveInfo info : homeActivities) {
+                final ActivityInfo appInfo = info.activityInfo;
+                ComponentName activityName = new ComponentName(appInfo.packageName, appInfo.name);
+                allComponents.add(activityName);
+            }
+            mPm.replacePreferredActivity(
+                    DefaultHomePreferenceController.HOME_FILTER,
+                    IntentFilter.MATCH_CATEGORY_EMPTY,
+                    allComponents.toArray(new ComponentName[0]),
+                    component);
+            return true;
+        }
+        return false;
+    }
+
+    private boolean hasManagedProfile() {
+        final Context context = getContext();
+        List<UserInfo> profiles = mUserManager.getProfiles(context.getUserId());
+        for (UserInfo userInfo : profiles) {
+            if (userInfo.isManagedProfile()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean launcherHasManagedProfilesFeature(ResolveInfo resolveInfo) {
+        try {
+            ApplicationInfo appInfo = mPm.getPackageManager().getApplicationInfo(
+                    resolveInfo.activityInfo.packageName, 0 /* default flags */);
+            return versionNumberAtLeastL(appInfo.targetSdkVersion);
+        } catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
+    }
+
+    private boolean versionNumberAtLeastL(int versionNumber) {
+        return versionNumber >= Build.VERSION_CODES.LOLLIPOP;
+    }
+}
diff --git a/src/com/android/settings/applications/defaultapps/DefaultHomePreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultHomePreferenceController.java
new file mode 100644
index 0000000..055c23b
--- /dev/null
+++ b/src/com/android/settings/applications/defaultapps/DefaultHomePreferenceController.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2017 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.applications.defaultapps;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.support.v7.preference.Preference;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DefaultHomePreferenceController extends DefaultAppPreferenceController {
+
+    static final IntentFilter HOME_FILTER;
+
+    private final String mPackageName;
+
+    static {
+        HOME_FILTER = new IntentFilter(Intent.ACTION_MAIN);
+        HOME_FILTER.addCategory(Intent.CATEGORY_HOME);
+        HOME_FILTER.addCategory(Intent.CATEGORY_DEFAULT);
+    }
+
+    public DefaultHomePreferenceController(Context context) {
+        super(context);
+        mPackageName = mContext.getPackageName();
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return "default_home";
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        super.updateState(preference);
+        final DefaultAppInfo defaultApp = getDefaultAppInfo();
+        final CharSequence defaultAppLabel = defaultApp != null
+                ? defaultApp.loadLabel(mPackageManager.getPackageManager()) : null;
+        if (TextUtils.isEmpty(defaultAppLabel)) {
+            final String onlyAppLabel = getOnlyAppLabel();
+            if (!TextUtils.isEmpty(onlyAppLabel)) {
+                preference.setSummary(onlyAppLabel);
+            }
+        }
+    }
+
+    @Override
+    protected DefaultAppInfo getDefaultAppInfo() {
+        final ArrayList<ResolveInfo> homeActivities = new ArrayList<>();
+        final ComponentName currentDefaultHome = mPackageManager.getHomeActivities(homeActivities);
+
+        return new DefaultAppInfo(mUserId, currentDefaultHome, null /* summary */);
+    }
+
+    private String getOnlyAppLabel() {
+        final List<ResolveInfo> homeActivities = new ArrayList<>();
+        final List<ActivityInfo> appLabels = new ArrayList<>();
+
+        mPackageManager.getHomeActivities(homeActivities);
+        for (ResolveInfo candidate : homeActivities) {
+            final ActivityInfo info = candidate.activityInfo;
+            if (info.packageName.equals(mPackageName)) {
+                continue;
+            }
+            appLabels.add(info);
+        }
+        return appLabels.size() == 1
+                ? appLabels.get(0).loadLabel(mPackageManager.getPackageManager()).toString()
+                : null;
+    }
+
+    public static boolean hasHomePreference(String pkg, Context context) {
+        ArrayList<ResolveInfo> homeActivities = new ArrayList<>();
+        PackageManager pm = context.getPackageManager();
+        pm.getHomeActivities(homeActivities);
+        for (int i = 0; i < homeActivities.size(); i++) {
+            if (homeActivities.get(i).activityInfo.packageName.equals(pkg)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean isHomeDefault(String pkg, Context context) {
+        ArrayList<ResolveInfo> homeActivities = new ArrayList<>();
+        PackageManager pm = context.getPackageManager();
+        ComponentName def = pm.getHomeActivities(homeActivities);
+
+        return def != null && def.getPackageName().equals(pkg);
+    }
+}
diff --git a/src/com/android/settings/applications/DefaultNotificationAssistantPreference.java b/src/com/android/settings/applications/defaultapps/DefaultNotificationAssistantPicker.java
similarity index 62%
rename from src/com/android/settings/applications/DefaultNotificationAssistantPreference.java
rename to src/com/android/settings/applications/defaultapps/DefaultNotificationAssistantPicker.java
index 91fc0c8..af67917 100644
--- a/src/com/android/settings/applications/DefaultNotificationAssistantPreference.java
+++ b/src/com/android/settings/applications/defaultapps/DefaultNotificationAssistantPicker.java
@@ -1,5 +1,5 @@
-/**
- * Copyright (C) 2016 The Android Open Source Project
+/*
+ * Copyright (C) 2017 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.
@@ -14,65 +14,48 @@
  * limitations under the License.
  */
 
-package com.android.settings.applications;
-
-import com.android.settings.AppListPreference;
+package com.android.settings.applications.defaultapps;
 
 import android.app.ActivityManager;
 import android.content.ComponentName;
-import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.provider.Settings;
 import android.service.notification.NotificationAssistantService;
-import android.util.AttributeSet;
 import android.util.Slog;
 
-import java.util.ArrayList;
-import java.util.List;
-
 import com.android.settings.R;
 import com.android.settings.utils.ManagedServiceSettings;
 
-public class DefaultNotificationAssistantPreference extends AppListPreference {
+import java.util.ArrayList;
+import java.util.List;
+
+public class DefaultNotificationAssistantPicker extends DefaultAppPickerFragment {
     private static final String TAG = "DefaultNotiAssist";
 
-    private PackageManager mPm;
-    private final ManagedServiceSettings.Config mConfig;
-    private final Context mContext;
+    private final ManagedServiceSettings.Config mConfig = getConfig();
 
-    public DefaultNotificationAssistantPreference(Context context, AttributeSet attrs) {
-        super(context, attrs);
-
-        mContext = context;
-        mPm = context.getPackageManager();
-        mConfig = getConfig();
-        setShowItemNone(true);
-        updateList(getServices());
+    @Override
+    public int getMetricsCategory() {
+        return 0;
     }
 
     @Override
-    protected boolean persistString(String value) {
-        Settings.Secure.putString(mContext.getContentResolver(), mConfig.setting, value);
-        setSummary(getEntry());
+    protected String getDefaultAppKey() {
+        return Settings.Secure.getString(getContext().getContentResolver(), mConfig.setting);
+    }
+
+    @Override
+    protected boolean setDefaultAppKey(String value) {
+        Settings.Secure.putString(getContext().getContentResolver(), mConfig.setting, value);
         return true;
     }
 
-    private void updateList(List<ServiceInfo> services) {
-        final ComponentName[] assistants = new ComponentName[services.size()];
-        for (int i = 0; i < services.size(); i++) {
-            assistants[i] = new ComponentName(services.get(i).packageName, services.get(i).name);
-        }
-        final String assistant =
-                Settings.Secure.getString(mContext.getContentResolver(), mConfig.setting);
-        setComponentNames(assistants, assistant == null ? null
-                : ComponentName.unflattenFromString(assistant));
-    }
-
-    private List<ServiceInfo> getServices() {
-        List<ServiceInfo> services = new ArrayList<>();
+    @Override
+    protected List<DefaultAppInfo> getCandidates() {
+        List<DefaultAppInfo> candidates = new ArrayList<>();
         final int user = ActivityManager.getCurrentUser();
 
         List<ResolveInfo> installedServices = mPm.queryIntentServicesAsUser(
@@ -91,9 +74,16 @@
                         + mConfig.permission);
                 continue;
             }
-            services.add(info);
+
+            candidates.add(new DefaultAppInfo(
+                    mUserId, new ComponentName(info.packageName, info.name), null /* summary */));
         }
-        return services;
+        return candidates;
+    }
+
+    @Override
+    protected boolean shouldShowItemNone() {
+        return true;
     }
 
     private ManagedServiceSettings.Config getConfig() {
@@ -108,4 +98,4 @@
         c.emptyText = R.string.no_notification_listeners;
         return c;
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/settings/applications/defaultapps/DefaultPhonePicker.java b/src/com/android/settings/applications/defaultapps/DefaultPhonePicker.java
new file mode 100644
index 0000000..0fa9390
--- /dev/null
+++ b/src/com/android/settings/applications/defaultapps/DefaultPhonePicker.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2017 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.applications.defaultapps;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.telecom.DefaultDialerManager;
+import android.telecom.TelecomManager;
+import android.text.TextUtils;
+
+import com.android.internal.logging.nano.MetricsProto;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DefaultPhonePicker extends DefaultAppPickerFragment {
+
+    private DefaultKeyUpdater mDefaultKeyUpdater;
+
+    @Override
+    public int getMetricsCategory() {
+        return MetricsProto.MetricsEvent.DEFAULT_PHONE_PICKER;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        mDefaultKeyUpdater = new DefaultKeyUpdater(
+                (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE));
+    }
+
+    @Override
+    protected List<DefaultAppInfo> getCandidates() {
+        final List<DefaultAppInfo> candidates = new ArrayList<>();
+        final List<String> dialerPackages =
+                DefaultDialerManager.getInstalledDialerApplications(getContext(), mUserId);
+        for (String packageName : dialerPackages) {
+            try {
+                candidates.add(new DefaultAppInfo(
+                        mPm.getApplicationInfoAsUser(packageName, 0, mUserId)));
+            } catch (PackageManager.NameNotFoundException e) {
+                // Skip unknown packages.
+            }
+        }
+        return candidates;
+    }
+
+    @Override
+    protected String getDefaultAppKey() {
+        return mDefaultKeyUpdater.getDefaultDialerApplication(getContext(), mUserId);
+    }
+
+    @Override
+    protected String getSystemDefaultAppKey() {
+        return mDefaultKeyUpdater.getSystemDialerPackage();
+    }
+
+    @Override
+    protected boolean setDefaultAppKey(String key) {
+        if (!TextUtils.isEmpty(key) && !TextUtils.equals(key, getDefaultAppKey())) {
+            return mDefaultKeyUpdater.setDefaultDialerApplication(getContext(), key, mUserId);
+        }
+        return false;
+    }
+
+    /**
+     * Wrapper class to handle default phone app update.
+     */
+    static class DefaultKeyUpdater {
+        private final TelecomManager mTelecomManager;
+
+        public DefaultKeyUpdater(TelecomManager telecomManager) {
+            mTelecomManager = telecomManager;
+        }
+
+        public String getSystemDialerPackage() {
+            return mTelecomManager.getSystemDialerPackage();
+        }
+
+        public String getDefaultDialerApplication(Context context, int uid) {
+            return DefaultDialerManager.getDefaultDialerApplication(context, uid);
+        }
+
+        public boolean setDefaultDialerApplication(Context context, String key, int uid) {
+            return DefaultDialerManager.setDefaultDialerApplication(context, key, uid);
+        }
+    }
+}
diff --git a/src/com/android/settings/applications/defaultapps/DefaultPhonePreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultPhonePreferenceController.java
new file mode 100644
index 0000000..fa0d28f
--- /dev/null
+++ b/src/com/android/settings/applications/defaultapps/DefaultPhonePreferenceController.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2017 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.applications.defaultapps;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.telecom.DefaultDialerManager;
+import android.telephony.TelephonyManager;
+
+import java.util.List;
+
+public class DefaultPhonePreferenceController extends DefaultAppPreferenceController {
+
+    public DefaultPhonePreferenceController(Context context) {
+        super(context);
+    }
+
+    @Override
+    public boolean isAvailable() {
+        final TelephonyManager tm =
+                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        if (!tm.isVoiceCapable()) {
+            return false;
+        }
+        final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+        final boolean hasUserRestriction =
+                um.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS);
+
+        return !hasUserRestriction;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return "default_phone_app";
+    }
+
+    @Override
+    protected DefaultAppInfo getDefaultAppInfo() {
+        try {
+            return new DefaultAppInfo(mPackageManager.getPackageManager().getApplicationInfo(
+                    DefaultDialerManager.getDefaultDialerApplication(mContext, mUserId), 0));
+        } catch (PackageManager.NameNotFoundException e) {
+            return null;
+        }
+    }
+
+    public static boolean hasPhonePreference(String pkg, Context context) {
+        List<String> dialerPackages =
+                DefaultDialerManager.getInstalledDialerApplications(context, UserHandle.myUserId());
+        return dialerPackages.contains(pkg);
+    }
+
+    public static boolean isPhoneDefault(String pkg, Context context) {
+        String def = DefaultDialerManager.getDefaultDialerApplication(context,
+                UserHandle.myUserId());
+        return def != null && def.equals(pkg);
+    }
+}
diff --git a/src/com/android/settings/applications/defaultapps/DefaultSmsPicker.java b/src/com/android/settings/applications/defaultapps/DefaultSmsPicker.java
new file mode 100644
index 0000000..31bb6f6
--- /dev/null
+++ b/src/com/android/settings/applications/defaultapps/DefaultSmsPicker.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2017 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.applications.defaultapps;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.text.TextUtils;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.telephony.SmsApplication;
+import com.android.settings.R;
+import com.android.settings.Utils;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public class DefaultSmsPicker extends DefaultAppPickerFragment {
+
+    private DefaultKeyUpdater mDefaultKeyUpdater = new DefaultKeyUpdater();
+
+    @Override
+    public int getMetricsCategory() {
+        return MetricsProto.MetricsEvent.DEFAULT_SMS_PICKER;
+    }
+
+    @Override
+    protected List<DefaultAppInfo> getCandidates() {
+        final Collection<SmsApplication.SmsApplicationData> smsApplications =
+                SmsApplication.getApplicationCollection(getContext());
+        final List<DefaultAppInfo> candidates = new ArrayList<>(smsApplications.size());
+
+        for (SmsApplication.SmsApplicationData smsApplicationData : smsApplications) {
+            try {
+                candidates.add(new DefaultAppInfo(
+                        mPm.getApplicationInfoAsUser(smsApplicationData.mPackageName, 0, mUserId)));
+            } catch (PackageManager.NameNotFoundException e) {
+                // Skip unknown packages.
+            }
+        }
+
+        return candidates;
+    }
+
+    @Override
+    protected String getDefaultAppKey() {
+        return mDefaultKeyUpdater.getDefaultApplication(getContext());
+    }
+
+    @Override
+    protected boolean setDefaultAppKey(String key) {
+        if (!TextUtils.isEmpty(key) && !TextUtils.equals(key, getDefaultAppKey())) {
+            mDefaultKeyUpdater.setDefaultApplication(getContext(), key);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    protected String getConfirmationMessage(DefaultAppInfo info) {
+        return Utils.isPackageDirectBootAware(getContext(), info.getKey()) ? null
+                : getContext().getString(R.string.direct_boot_unaware_dialog_message);
+    }
+
+    /**
+     * Wrapper class to handle default phone app update.
+     */
+    static class DefaultKeyUpdater {
+
+        public String getDefaultApplication(Context context) {
+            final ComponentName appName = SmsApplication.getDefaultSmsApplication(context, true);
+            if (appName != null) {
+                return appName.getPackageName();
+            }
+            return null;
+        }
+
+        public void setDefaultApplication(Context context, String key) {
+            SmsApplication.setDefaultApplication(key, context);
+        }
+    }
+}
diff --git a/src/com/android/settings/applications/defaultapps/DefaultSmsPreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultSmsPreferenceController.java
new file mode 100644
index 0000000..90b9d83
--- /dev/null
+++ b/src/com/android/settings/applications/defaultapps/DefaultSmsPreferenceController.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017 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.applications.defaultapps;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.telephony.TelephonyManager;
+
+import com.android.internal.telephony.SmsApplication;
+
+import java.util.Collection;
+
+public class DefaultSmsPreferenceController extends DefaultAppPreferenceController {
+
+    public DefaultSmsPreferenceController(Context context) {
+        super(context);
+    }
+
+    @Override
+    public boolean isAvailable() {
+        boolean isRestrictedUser = mUserManager.getUserInfo(mUserId).isRestricted();
+        TelephonyManager tm =
+                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        return !isRestrictedUser && tm.isSmsCapable();
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return "default_sms_app";
+    }
+
+    @Override
+    protected DefaultAppInfo getDefaultAppInfo() {
+        final ComponentName app = SmsApplication.getDefaultSmsApplication(mContext, true);
+        if (app != null) {
+            return new DefaultAppInfo(mUserId, app, null /* summary */);
+        }
+        return null;
+    }
+
+    public static boolean hasSmsPreference(String pkg, Context context) {
+        Collection<SmsApplication.SmsApplicationData> smsApplications =
+                SmsApplication.getApplicationCollection(context);
+        for (SmsApplication.SmsApplicationData data : smsApplications) {
+            if (data.mPackageName.equals(pkg)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean isSmsDefault(String pkg, Context context) {
+        ComponentName appName = SmsApplication.getDefaultSmsApplication(context, true);
+        return appName != null && appName.getPackageName().equals(pkg);
+    }
+}
diff --git a/src/com/android/settings/applications/defaultapps/DefaultWorkBrowserPreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultWorkBrowserPreferenceController.java
new file mode 100644
index 0000000..0cee3c5
--- /dev/null
+++ b/src/com/android/settings/applications/defaultapps/DefaultWorkBrowserPreferenceController.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 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.applications.defaultapps;
+
+import android.content.Context;
+import android.os.UserHandle;
+
+import com.android.settings.Utils;
+
+public class DefaultWorkBrowserPreferenceController extends DefaultBrowserPreferenceController {
+
+    public DefaultWorkBrowserPreferenceController(Context context) {
+        super(context);
+        final UserHandle managedProfile = Utils.getManagedProfile(mUserManager);
+        if (managedProfile != null) {
+            mUserId = managedProfile.getIdentifier();
+        }
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return Utils.getManagedProfile(mUserManager) != null;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return "work_default_browser";
+    }
+}
diff --git a/src/com/android/settings/applications/defaultapps/DefaultWorkPhonePreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultWorkPhonePreferenceController.java
new file mode 100644
index 0000000..f793fa3
--- /dev/null
+++ b/src/com/android/settings/applications/defaultapps/DefaultWorkPhonePreferenceController.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 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.applications.defaultapps;
+
+import android.content.Context;
+
+public class DefaultWorkPhonePreferenceController extends DefaultPhonePreferenceController {
+
+    public DefaultWorkPhonePreferenceController(Context context) {
+        super(context);
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return "work_default_phone_app";
+    }
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothEnabler.java b/src/com/android/settings/bluetooth/BluetoothEnabler.java
index e3791e9..aaf9f3c 100644
--- a/src/com/android/settings/bluetooth/BluetoothEnabler.java
+++ b/src/com/android/settings/bluetooth/BluetoothEnabler.java
@@ -24,14 +24,13 @@
 import android.os.Handler;
 import android.os.Message;
 import android.provider.Settings;
-import android.widget.Switch;
 import android.widget.Toast;
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settings.R;
 import com.android.settings.core.instrumentation.MetricsFeatureProvider;
 import com.android.settings.search.Index;
-import com.android.settings.widget.SwitchBar;
+import com.android.settings.widget.SwitchWidgetController;
 import com.android.settingslib.WirelessUtils;
 import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -41,9 +40,8 @@
  * preference. It turns on/off Bluetooth and ensures the summary of the
  * preference reflects the current state.
  */
-public final class BluetoothEnabler implements SwitchBar.OnSwitchChangeListener {
-    private final Switch mSwitch;
-    private final SwitchBar mSwitchBar;
+public final class BluetoothEnabler implements SwitchWidgetController.OnSwitchChangeListener {
+    private final SwitchWidgetController mSwitchWidget;
     private final MetricsFeatureProvider mMetricsFeatureProvider;
     private Context mContext;
     private boolean mValidListener;
@@ -76,19 +74,18 @@
         }
     };
 
-    public BluetoothEnabler(Context context, SwitchBar switchBar,
-            MetricsFeatureProvider metricsFeatureProvider) {
+    public BluetoothEnabler(Context context, SwitchWidgetController switchWidget,
+            MetricsFeatureProvider metricsFeatureProvider, LocalBluetoothManager manager) {
         mContext = context;
         mMetricsFeatureProvider = metricsFeatureProvider;
-        mSwitchBar = switchBar;
-        mSwitch = switchBar.getSwitch();
+        mSwitchWidget = switchWidget;
+        mSwitchWidget.setListener(this);
         mValidListener = false;
 
-        LocalBluetoothManager manager = Utils.getLocalBtManager(context);
         if (manager == null) {
             // Bluetooth is not supported
             mLocalAdapter = null;
-            mSwitch.setEnabled(false);
+            mSwitchWidget.setEnabled(false);
         } else {
             mLocalAdapter = manager.getBluetoothAdapter();
         }
@@ -96,16 +93,16 @@
     }
 
     public void setupSwitchBar() {
-        mSwitchBar.show();
+        mSwitchWidget.setupView();
     }
 
     public void teardownSwitchBar() {
-        mSwitchBar.hide();
+        mSwitchWidget.teardownView();
     }
 
     public void resume(Context context) {
         if (mLocalAdapter == null) {
-            mSwitch.setEnabled(false);
+            mSwitchWidget.setEnabled(false);
             return;
         }
 
@@ -116,7 +113,7 @@
         // Bluetooth state is not sticky, so set it manually
         handleStateChanged(mLocalAdapter.getBluetoothState());
 
-        mSwitchBar.addOnSwitchChangeListener(this);
+        mSwitchWidget.startListening();
         mContext.registerReceiver(mReceiver, mIntentFilter);
         mValidListener = true;
     }
@@ -125,47 +122,48 @@
         if (mLocalAdapter == null) {
             return;
         }
-
-        mSwitchBar.removeOnSwitchChangeListener(this);
-        mContext.unregisterReceiver(mReceiver);
-        mValidListener = false;
+        if (mValidListener) {
+            mSwitchWidget.stopListening();
+            mContext.unregisterReceiver(mReceiver);
+            mValidListener = false;
+        }
     }
 
     void handleStateChanged(int state) {
         switch (state) {
             case BluetoothAdapter.STATE_TURNING_ON:
-                mSwitch.setEnabled(false);
+                mSwitchWidget.setEnabled(false);
                 break;
             case BluetoothAdapter.STATE_ON:
                 setChecked(true);
-                mSwitch.setEnabled(true);
+                mSwitchWidget.setEnabled(true);
                 updateSearchIndex(true);
                 break;
             case BluetoothAdapter.STATE_TURNING_OFF:
-                mSwitch.setEnabled(false);
+                mSwitchWidget.setEnabled(false);
                 break;
             case BluetoothAdapter.STATE_OFF:
                 setChecked(false);
-                mSwitch.setEnabled(true);
+                mSwitchWidget.setEnabled(true);
                 updateSearchIndex(false);
                 break;
             default:
                 setChecked(false);
-                mSwitch.setEnabled(true);
+                mSwitchWidget.setEnabled(true);
                 updateSearchIndex(false);
         }
     }
 
     private void setChecked(boolean isChecked) {
-        if (isChecked != mSwitch.isChecked()) {
+        if (isChecked != mSwitchWidget.isChecked()) {
             // set listener to null, so onCheckedChanged won't be called
             // if the checked status on Switch isn't changed by user click
             if (mValidListener) {
-                mSwitchBar.removeOnSwitchChangeListener(this);
+                mSwitchWidget.stopListening();
             }
-            mSwitch.setChecked(isChecked);
+            mSwitchWidget.setChecked(isChecked);
             if (mValidListener) {
-                mSwitchBar.addOnSwitchChangeListener(this);
+                mSwitchWidget.startListening();
             }
         }
     }
@@ -180,13 +178,14 @@
     }
 
     @Override
-    public void onSwitchChanged(Switch switchView, boolean isChecked) {
+    public boolean onSwitchToggled(boolean isChecked) {
         // Show toast message if Bluetooth is not allowed in airplane mode
         if (isChecked &&
                 !WirelessUtils.isRadioAllowed(mContext, Settings.Global.RADIO_BLUETOOTH)) {
             Toast.makeText(mContext, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show();
             // Reset switch to off
-            switchView.setChecked(false);
+            mSwitchWidget.setChecked(false);
+            return false;
         }
 
         mMetricsFeatureProvider.action(mContext, MetricsEvent.ACTION_BLUETOOTH_TOGGLE, isChecked);
@@ -197,12 +196,13 @@
             // a) The switch should be OFF but it should still be togglable (enabled = True)
             // b) The switch bar should have OFF text.
             if (isChecked && !status) {
-                switchView.setChecked(false);
-                mSwitch.setEnabled(true);
-                mSwitchBar.setTextViewLabel(false);
-                return;
+                mSwitchWidget.setChecked(false);
+                mSwitchWidget.setEnabled(true);
+                mSwitchWidget.updateTitle(false);
+                return false;
             }
         }
-        mSwitch.setEnabled(false);
+        mSwitchWidget.setEnabled(false);
+        return true;
     }
 }
diff --git a/src/com/android/settings/bluetooth/BluetoothMasterSwitchPreferenceController.java b/src/com/android/settings/bluetooth/BluetoothMasterSwitchPreferenceController.java
new file mode 100644
index 0000000..955454f
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothMasterSwitchPreferenceController.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2017 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.bluetooth;
+
+import android.content.Context;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.core.PreferenceController;
+import com.android.settings.core.lifecycle.LifecycleObserver;
+import com.android.settings.core.lifecycle.events.OnPause;
+import com.android.settings.core.lifecycle.events.OnResume;
+import com.android.settings.core.lifecycle.events.OnStart;
+import com.android.settings.core.lifecycle.events.OnStop;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.widget.MasterSwitchPreference;
+import com.android.settings.widget.MasterSwitchController;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+public class BluetoothMasterSwitchPreferenceController extends PreferenceController
+        implements BluetoothSummaryHelper.OnSummaryChangeListener,
+        LifecycleObserver, OnResume, OnPause, OnStart, OnStop {
+
+    private static final String KEY_TOGGLE_BLUETOOTH = "toggle_bluetooth";
+
+    private LocalBluetoothManager mBluetoothManager;
+    private MasterSwitchPreference mBtPreference;
+    private BluetoothEnabler mBluetoothEnabler;
+    private BluetoothSummaryHelper mSummaryHelper;
+
+    public BluetoothMasterSwitchPreferenceController(Context context,
+            LocalBluetoothManager bluetoothManager) {
+        super(context);
+        mBluetoothManager = bluetoothManager;
+        mSummaryHelper = new BluetoothSummaryHelper(mContext, mBluetoothManager);
+        mSummaryHelper.setOnSummaryChangeListener(this);
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        mBtPreference = (MasterSwitchPreference) screen.findPreference(KEY_TOGGLE_BLUETOOTH);
+        mBluetoothEnabler = new BluetoothEnabler(mContext,
+            new MasterSwitchController(mBtPreference),
+            FeatureFactory.getFactory(mContext).getMetricsFeatureProvider(), mBluetoothManager);
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_TOGGLE_BLUETOOTH;
+    }
+
+    public void onResume() {
+        mSummaryHelper.setListening(true);
+    }
+
+    @Override
+    public void onPause() {
+        mSummaryHelper.setListening(false);
+    }
+
+    @Override
+    public void onStart() {
+        if (mBluetoothEnabler != null) {
+            mBluetoothEnabler.resume(mContext);
+        }
+    }
+
+    @Override
+    public void onStop() {
+        if (mBluetoothEnabler != null) {
+            mBluetoothEnabler.pause();
+        }
+    }
+
+    @Override
+    public void onSummaryChanged(String summary) {
+        if (mBtPreference != null) {
+            mBtPreference.setSummary(summary);
+        }
+    }
+
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothSettings.java b/src/com/android/settings/bluetooth/BluetoothSettings.java
index d709726..ba53ea3 100644
--- a/src/com/android/settings/bluetooth/BluetoothSettings.java
+++ b/src/com/android/settings/bluetooth/BluetoothSettings.java
@@ -45,6 +45,7 @@
 import com.android.settings.LinkifyUtils;
 import com.android.settings.R;
 import com.android.settings.SettingsActivity;
+import com.android.settings.bluetooth.BluetoothSummaryHelper.OnSummaryChangeListener;
 import com.android.settings.dashboard.SummaryLoader;
 import com.android.settings.location.ScanningSettings;
 import com.android.settings.search.BaseSearchIndexProvider;
@@ -52,13 +53,12 @@
 import com.android.settings.search.SearchIndexableRaw;
 import com.android.settings.widget.FooterPreference;
 import com.android.settings.widget.SwitchBar;
-import com.android.settingslib.bluetooth.BluetoothCallback;
+import com.android.settings.widget.SwitchBarController;
 import com.android.settingslib.bluetooth.BluetoothDeviceFilter;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import java.util.Locale;
 import java.util.Set;
@@ -148,7 +148,8 @@
         final SettingsActivity activity = (SettingsActivity) getActivity();
         mSwitchBar = activity.getSwitchBar();
 
-        mBluetoothEnabler = new BluetoothEnabler(activity, mSwitchBar, mMetricsFeatureProvider);
+        mBluetoothEnabler = new BluetoothEnabler(activity, new SwitchBarController(mSwitchBar),
+            mMetricsFeatureProvider, Utils.getLocalBtManager(activity));
         mBluetoothEnabler.setupSwitchBar();
     }
 
@@ -508,113 +509,35 @@
     }
 
     @VisibleForTesting
-    static class SummaryProvider
-            implements SummaryLoader.SummaryProvider, BluetoothCallback {
+    static class SummaryProvider implements SummaryLoader.SummaryProvider, OnSummaryChangeListener {
 
         private final LocalBluetoothManager mBluetoothManager;
         private final Context mContext;
         private final SummaryLoader mSummaryLoader;
 
-        private boolean mEnabled;
-        private int mConnectionState;
+        @VisibleForTesting
+        BluetoothSummaryHelper mSummaryHelper;
 
         public SummaryProvider(Context context, SummaryLoader summaryLoader,
                 LocalBluetoothManager bluetoothManager) {
             mBluetoothManager = bluetoothManager;
             mContext = context;
             mSummaryLoader = summaryLoader;
+            mSummaryHelper = new BluetoothSummaryHelper(mContext, mBluetoothManager);
+            mSummaryHelper.setOnSummaryChangeListener(this);
         }
 
         @Override
         public void setListening(boolean listening) {
-            BluetoothAdapter defaultAdapter = BluetoothAdapter.getDefaultAdapter();
-            if (defaultAdapter == null) return;
-            if (listening) {
-                mEnabled = defaultAdapter.isEnabled();
-                mConnectionState = defaultAdapter.getConnectionState();
-                mSummaryLoader.setSummary(this, getSummary());
-                mBluetoothManager.getEventManager().registerCallback(this);
-            } else {
-                mBluetoothManager.getEventManager().unregisterCallback(this);
+            mSummaryHelper.setListening(listening);
+        }
+
+        @Override
+        public void onSummaryChanged(String summary) {
+            if (mSummaryLoader != null) {
+                mSummaryLoader.setSummary(this, summary);
             }
         }
-
-        private CharSequence getSummary() {
-            if (!mEnabled) {
-                return mContext.getString(R.string.bluetooth_disabled);
-            } else if (mConnectionState == BluetoothAdapter.STATE_CONNECTED) {
-                return mContext.getString(R.string.bluetooth_connected);
-            } else {
-                return mContext.getString(R.string.bluetooth_disconnected);
-            }
-        }
-
-        @Override
-        public void onBluetoothStateChanged(int bluetoothState) {
-            mEnabled = bluetoothState == BluetoothAdapter.STATE_ON
-                    || bluetoothState == BluetoothAdapter.STATE_TURNING_ON;
-            mSummaryLoader.setSummary(this, getSummary());
-        }
-
-        @Override
-        public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
-            mConnectionState = state;
-            updateConnected();
-            mSummaryLoader.setSummary(this, getSummary());
-        }
-
-        @Override
-        public void onScanningStateChanged(boolean started) {
-
-        }
-
-        @Override
-        public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
-
-        }
-
-        @Override
-        public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
-
-        }
-
-        @Override
-        public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
-
-        }
-
-        private void updateConnected() {
-            // Make sure our connection state is up to date.
-            int state = mBluetoothManager.getBluetoothAdapter().getConnectionState();
-            if (state != mConnectionState) {
-                mConnectionState = state;
-                return;
-            }
-            final Collection<CachedBluetoothDevice> devices = getDevices();
-            if (devices == null) {
-                mConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
-                return;
-            }
-            if (mConnectionState == BluetoothAdapter.STATE_CONNECTED) {
-                CachedBluetoothDevice connectedDevice = null;
-                for (CachedBluetoothDevice device : devices) {
-                    if (device.isConnected()) {
-                        connectedDevice = device;
-                    }
-                }
-                if (connectedDevice == null) {
-                    // If somehow we think we are connected, but have no connected devices, we
-                    // aren't connected.
-                    mConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
-                }
-            }
-        }
-
-        private Collection<CachedBluetoothDevice> getDevices() {
-            return mBluetoothManager != null
-                    ? mBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy()
-                    : null;
-        }
     }
 
     public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY
diff --git a/src/com/android/settings/bluetooth/BluetoothSummaryHelper.java b/src/com/android/settings/bluetooth/BluetoothSummaryHelper.java
new file mode 100644
index 0000000..2bd6f70
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothSummaryHelper.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2017 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.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.text.TextUtils;
+import com.android.settings.R;
+import com.android.settingslib.bluetooth.BluetoothCallback;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import java.util.Collection;
+
+/**
+ * Helper class that listeners to bluetooth callback and notify client when there is update in
+ * bluetooth summary info.
+ */
+public final class BluetoothSummaryHelper implements BluetoothCallback {
+
+    private OnSummaryChangeListener mListener;
+
+    private final LocalBluetoothManager mBluetoothManager;
+    private final LocalBluetoothAdapter mBluetoothAdapter;
+    private final Context mContext;
+
+    private boolean mEnabled;
+    private int mConnectionState;
+    private String mSummary;
+
+    public interface OnSummaryChangeListener {
+        /**
+         * Called when bluetooth summary has changed.
+         *
+         * @param summary The new bluetooth summary .
+         */
+        void onSummaryChanged(String summary);
+    }
+
+    public BluetoothSummaryHelper(Context context, LocalBluetoothManager bluetoothManager) {
+        mContext = context;
+        mBluetoothManager = bluetoothManager;
+        mBluetoothAdapter = mBluetoothManager != null
+            ? mBluetoothManager.getBluetoothAdapter() : null;
+    }
+
+    @Override
+    public void onBluetoothStateChanged(int bluetoothState) {
+        mEnabled = bluetoothState == BluetoothAdapter.STATE_ON
+            || bluetoothState == BluetoothAdapter.STATE_TURNING_ON;
+        notifyChangeIfNeeded();
+    }
+
+    @Override
+    public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
+        mConnectionState = state;
+        updateConnected();
+        notifyChangeIfNeeded();
+    }
+
+    @Override
+    public void onScanningStateChanged(boolean started) {
+    }
+
+    @Override
+    public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
+    }
+
+    @Override
+    public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
+    }
+
+    @Override
+    public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
+    }
+
+    public void setOnSummaryChangeListener(OnSummaryChangeListener listener) {
+        mListener = listener;
+    }
+
+    public void setListening(boolean listening) {
+        if (mBluetoothAdapter == null) {
+            return;
+        }
+        if (listening) {
+            mEnabled = mBluetoothAdapter.isEnabled();
+            mConnectionState = mBluetoothAdapter.getConnectionState();
+            notifyChangeIfNeeded();
+            mBluetoothManager.getEventManager().registerCallback(this);
+        } else {
+            mBluetoothManager.getEventManager().unregisterCallback(this);
+        }
+    }
+
+    private void notifyChangeIfNeeded() {
+        String summary = getSummary();
+        if (!TextUtils.equals(mSummary, summary)) {
+            mSummary = summary;
+            if (mListener != null) {
+                mListener.onSummaryChanged(summary);
+            }
+        }
+    }
+
+    private String getSummary() {
+        if (!mEnabled) {
+            return mContext.getString(R.string.bluetooth_disabled);
+        }
+        switch (mConnectionState) {
+            case BluetoothAdapter.STATE_CONNECTED:
+                return mContext.getString(R.string.bluetooth_connected);
+            case BluetoothAdapter.STATE_CONNECTING:
+                return mContext.getString(R.string.bluetooth_connecting);
+            case BluetoothAdapter.STATE_DISCONNECTING:
+                return mContext.getString(R.string.bluetooth_disconnecting);
+            default:
+                return mContext.getString(R.string.bluetooth_disconnected);
+        }
+    }
+
+    private void updateConnected() {
+        if (mBluetoothAdapter == null) {
+            return;
+        }
+        // Make sure our connection state is up to date.
+        int state = mBluetoothAdapter.getConnectionState();
+        if (state != mConnectionState) {
+            mConnectionState = state;
+            return;
+        }
+        final Collection<CachedBluetoothDevice> devices = getDevices();
+        if (devices == null) {
+            mConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
+            return;
+        }
+        if (mConnectionState == BluetoothAdapter.STATE_CONNECTED) {
+            CachedBluetoothDevice connectedDevice = null;
+            for (CachedBluetoothDevice device : devices) {
+                if (device.isConnected()) {
+                    connectedDevice = device;
+                    break;
+                }
+            }
+            if (connectedDevice == null) {
+                // If somehow we think we are connected, but have no connected devices, we
+                // aren't connected.
+                mConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
+            }
+        }
+    }
+
+    private Collection<CachedBluetoothDevice> getDevices() {
+        return mBluetoothManager != null
+            ? mBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy()
+            : null;
+    }
+
+}
diff --git a/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java b/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java
index b8ee1ff..fe0e1d2 100644
--- a/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java
+++ b/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java
@@ -20,7 +20,10 @@
 
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.settings.R;
+import com.android.settings.bluetooth.BluetoothMasterSwitchPreferenceController;
+import com.android.settings.bluetooth.Utils;
 import com.android.settings.core.PreferenceController;
+import com.android.settings.core.lifecycle.Lifecycle;
 import com.android.settings.dashboard.DashboardFragment;
 import com.android.settings.deviceinfo.UsbBackend;
 import com.android.settings.nfc.NfcPreferenceController;
@@ -37,6 +40,7 @@
 
     private static final String TAG = "ConnectedDeviceFrag";
     private UsbModePreferenceController mUsbPrefController;
+    private BluetoothMasterSwitchPreferenceController mBluetoothPreferenceController;
 
     @Override
     public int getMetricsCategory() {
@@ -61,13 +65,19 @@
     @Override
     protected List<PreferenceController> getPreferenceControllers(Context context) {
         final List<PreferenceController> controllers = new ArrayList<>();
+        final Lifecycle lifecycle = getLifecycle();
         final NfcPreferenceController nfcPreferenceController =
                 new NfcPreferenceController(context);
-        getLifecycle().addObserver(nfcPreferenceController);
+        lifecycle.addObserver(nfcPreferenceController);
         controllers.add(nfcPreferenceController);
         mUsbPrefController = new UsbModePreferenceController(context, new UsbBackend(context));
-        getLifecycle().addObserver(mUsbPrefController);
+        lifecycle.addObserver(mUsbPrefController);
         controllers.add(mUsbPrefController);
+        mBluetoothPreferenceController =
+            new BluetoothMasterSwitchPreferenceController(
+                context, Utils.getLocalBtManager(context));
+        lifecycle.addObserver(mBluetoothPreferenceController);
+        controllers.add(mBluetoothPreferenceController);
         return controllers;
     }
 
diff --git a/src/com/android/settings/core/InstrumentedPreferenceFragment.java b/src/com/android/settings/core/InstrumentedPreferenceFragment.java
index 29ba287..ce0daa9 100644
--- a/src/com/android/settings/core/InstrumentedPreferenceFragment.java
+++ b/src/com/android/settings/core/InstrumentedPreferenceFragment.java
@@ -76,4 +76,8 @@
         mDividerDecoration.setDivider(divider);
         super.setDivider(new ColorDrawable(Color.TRANSPARENT));
     }
+
+    protected final Context getPrefContext() {
+        return getPreferenceManager().getContext();
+    }
 }
diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java
index 682fa36..6b08af8 100644
--- a/src/com/android/settings/core/gateway/SettingsGateway.java
+++ b/src/com/android/settings/core/gateway/SettingsGateway.java
@@ -302,5 +302,6 @@
             "com.android.settings.DateTimeDashboardAlias",
             "com.android.settings.AccessibilityDashboardAlias",
             "com.android.settings.AboutDeviceDashboardAlias",
+            "com.android.settings.EnterprisePrivacyDashboardAlias",
     };
 }
diff --git a/src/com/android/settings/dashboard/DashboardFragmentRegistry.java b/src/com/android/settings/dashboard/DashboardFragmentRegistry.java
new file mode 100644
index 0000000..54d4fd0
--- /dev/null
+++ b/src/com/android/settings/dashboard/DashboardFragmentRegistry.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2017 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.dashboard;
+
+import android.util.ArrayMap;
+
+import com.android.settings.DevelopmentSettings;
+import com.android.settings.DisplaySettings;
+import com.android.settings.SecuritySettings;
+import com.android.settings.accounts.UserAndAccountDashboardFragment;
+import com.android.settings.applications.AdvancedAppSettings;
+import com.android.settings.applications.AppAndNotificationDashboardFragment;
+import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment;
+import com.android.settings.deviceinfo.StorageDashboardFragment;
+import com.android.settings.inputmethod.InputAndGestureSettings;
+import com.android.settings.inputmethod.InputMethodAndLanguageSettings;
+import com.android.settings.network.NetworkDashboardFragment;
+import com.android.settings.notification.SoundSettings;
+import com.android.settings.system.SystemDashboardFragment;
+import com.android.settingslib.drawer.CategoryKey;
+
+import java.util.Map;
+
+/**
+ * A registry to keep track of which page hosts which category.
+ * TODO: Remove DashboardFragment#getCategoryKey() and just use this registry instead.
+ */
+public class DashboardFragmentRegistry {
+
+    /**
+     * Map from parent fragment to category key. The parent fragment hosts child with
+     * category_key.
+     */
+    public static final Map<String, String> PARENT_TO_CATEGORY_KEY_MAP;
+
+    /**
+     * Map from category_key to parent. This is a helper to look up which fragment hosts the
+     * category_key.
+     */
+    public static final Map<String, String> CATEGORY_KEY_TO_PARENT_MAP;
+
+    static {
+        PARENT_TO_CATEGORY_KEY_MAP = new ArrayMap<>();
+        PARENT_TO_CATEGORY_KEY_MAP.put(
+                NetworkDashboardFragment.class.getName(), CategoryKey.CATEGORY_NETWORK);
+        PARENT_TO_CATEGORY_KEY_MAP.put(ConnectedDeviceDashboardFragment.class.getName(),
+                CategoryKey.CATEGORY_DEVICE);
+        PARENT_TO_CATEGORY_KEY_MAP.put(AppAndNotificationDashboardFragment.class.getName(),
+                CategoryKey.CATEGORY_APPS);
+        PARENT_TO_CATEGORY_KEY_MAP.put(AdvancedAppSettings.class.getName(),
+                CategoryKey.CATEGORY_APPS_DEFAULT);
+        PARENT_TO_CATEGORY_KEY_MAP.put(DisplaySettings.class.getName(),
+                CategoryKey.CATEGORY_DISPLAY);
+        PARENT_TO_CATEGORY_KEY_MAP.put(SoundSettings.class.getName(),
+                CategoryKey.CATEGORY_SOUND);
+        PARENT_TO_CATEGORY_KEY_MAP.put(StorageDashboardFragment.class.getName(),
+                CategoryKey.CATEGORY_STORAGE);
+        PARENT_TO_CATEGORY_KEY_MAP.put(SecuritySettings.class.getName(),
+                CategoryKey.CATEGORY_SECURITY);
+        PARENT_TO_CATEGORY_KEY_MAP.put(UserAndAccountDashboardFragment.class.getName(),
+                CategoryKey.CATEGORY_ACCOUNT);
+        PARENT_TO_CATEGORY_KEY_MAP.put(UserAndAccountDashboardFragment.class.getName(),
+                CategoryKey.CATEGORY_ACCOUNT);
+        PARENT_TO_CATEGORY_KEY_MAP.put(
+                SystemDashboardFragment.class.getName(), CategoryKey.CATEGORY_SYSTEM);
+        PARENT_TO_CATEGORY_KEY_MAP.put(
+                InputAndGestureSettings.class.getName(), CategoryKey.CATEGORY_SYSTEM_INPUT);
+        PARENT_TO_CATEGORY_KEY_MAP.put(InputMethodAndLanguageSettings.class.getName(),
+                CategoryKey.CATEGORY_SYSTEM_LANGUAGE);
+        PARENT_TO_CATEGORY_KEY_MAP.put(DevelopmentSettings.class.getName(),
+                CategoryKey.CATEGORY_SYSTEM_DEVELOPMENT);
+
+        CATEGORY_KEY_TO_PARENT_MAP = new ArrayMap<>(PARENT_TO_CATEGORY_KEY_MAP.size());
+
+        for (Map.Entry<String, String> parentToKey : PARENT_TO_CATEGORY_KEY_MAP.entrySet()) {
+            CATEGORY_KEY_TO_PARENT_MAP.put(parentToKey.getValue(), parentToKey.getKey());
+        }
+    }
+}
diff --git a/src/com/android/settings/dashboard/SiteMapManager.java b/src/com/android/settings/dashboard/SiteMapManager.java
new file mode 100644
index 0000000..3144398
--- /dev/null
+++ b/src/com/android/settings/dashboard/SiteMapManager.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2017 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.dashboard;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.support.annotation.VisibleForTesting;
+import android.support.annotation.WorkerThread;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.settings.SettingsActivity;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.search.IndexDatabaseHelper;
+import com.android.settings.search.IndexDatabaseHelper.IndexColumns;
+import com.android.settings.search.IndexDatabaseHelper.SiteMapColumns;
+import com.android.settingslib.drawer.DashboardCategory;
+import com.android.settingslib.drawer.Tile;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static com.android.settings.dashboard.DashboardFragmentRegistry.CATEGORY_KEY_TO_PARENT_MAP;
+
+/**
+ * A manager class that maintains a "site map" and look up breadcrumb for a certain page on demand.
+ * <p/>
+ * The methods on this class can only be called on a background thread.
+ */
+public class SiteMapManager {
+
+    private static final String TAG = "SiteMapManager";
+    private static final boolean DEBUG_TIMING = false;
+
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    public static final String[] SITE_MAP_COLUMNS = {
+            SiteMapColumns.PARENT_CLASS,
+            SiteMapColumns.PARENT_TITLE,
+            SiteMapColumns.CHILD_CLASS,
+            SiteMapColumns.CHILD_TITLE
+    };
+
+    private static final String[] CLASS_TO_SCREEN_TITLE_COLUMNS = {
+            IndexColumns.CLASS_NAME,
+            IndexColumns.SCREEN_TITLE,
+    };
+
+    private final List<SiteMapPair> mPairs = new ArrayList<>();
+
+    private boolean mInitialized;
+
+    /**
+     * Given a fragment class name and its screen title, build a breadcrumb from Settings root to
+     * this screen.
+     * <p/>
+     * Not all screens have a full breadcrumb path leading up to root, it's because either some
+     * page in the breadcrumb path is not indexed, or it's only reachable via search.
+     */
+    @WorkerThread
+    public synchronized List<String> buildBreadCrumb(Context context, String clazz,
+            String screenTitle) {
+        init(context);
+        final long startTime = System.currentTimeMillis();
+        final List<String> breadcrumbs = new ArrayList<>();
+        if (!mInitialized) {
+            Log.w(TAG, "SiteMap is not initialized yet, skipping");
+            return breadcrumbs;
+        }
+        breadcrumbs.add(screenTitle);
+        String currentClass = clazz;
+        String currentTitle = screenTitle;
+        // Look up current page's parent, if found add it to breadcrumb string list, and repeat.
+        while (true) {
+            final SiteMapPair pair = lookUpParent(currentClass, currentTitle);
+            if (pair == null) {
+                if (DEBUG_TIMING) {
+                    Log.d(TAG, "BreadCrumb timing: " + (System.currentTimeMillis() - startTime));
+                }
+                return breadcrumbs;
+            }
+            breadcrumbs.add(0, pair.parentTitle);
+            currentClass = pair.parentClass;
+            currentTitle = pair.parentTitle;
+        }
+    }
+
+    /**
+     * Initialize a list of {@link SiteMapPair}s. Each pair knows about a single parent-child
+     * page relationship.
+     *
+     * We get the knowledge of such mPairs from 2 sources:
+     * 1. Static indexing time: we know which page(s) a parent can open by parsing its pref xml.
+     * 2. IA: We know from {@link DashboardFeatureProvider} which page can be dynamically
+     * injected to where.
+     */
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    @WorkerThread
+    synchronized void init(Context context) {
+        if (mInitialized) {
+            // Make sure only init once.
+            return;
+        }
+        final long startTime = System.currentTimeMillis();
+        // First load site map from static index table.
+        final Context appContext = context.getApplicationContext();
+        final SQLiteDatabase db = IndexDatabaseHelper.getInstance(appContext).getReadableDatabase();
+        Cursor sitemap = db.query(IndexDatabaseHelper.Tables.TABLE_SITE_MAP, SITE_MAP_COLUMNS, null,
+                null, null, null, null);
+        while (sitemap.moveToNext()) {
+            final SiteMapPair pair = new SiteMapPair(
+                    sitemap.getString(sitemap.getColumnIndex(SiteMapColumns.PARENT_CLASS)),
+                    sitemap.getString(sitemap.getColumnIndex(SiteMapColumns.PARENT_TITLE)),
+                    sitemap.getString(sitemap.getColumnIndex(SiteMapColumns.CHILD_CLASS)),
+                    sitemap.getString(sitemap.getColumnIndex(SiteMapColumns.CHILD_TITLE)));
+            mPairs.add(pair);
+        }
+        sitemap.close();
+
+        // Then prepare a local map that contains class name -> screen title mapping. This is needed
+        // to figure out the display name for any fragment if it's injected dynamically through IA.
+        final Map<String, String> classToTitleMap = new HashMap<>();
+        final Cursor titleQuery = db.query(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX,
+                CLASS_TO_SCREEN_TITLE_COLUMNS, null, null, null, null, null);
+        while (titleQuery.moveToNext()) {
+            classToTitleMap.put(
+                    titleQuery.getString(titleQuery.getColumnIndex(IndexColumns.CLASS_NAME)),
+                    titleQuery.getString(titleQuery.getColumnIndex(IndexColumns.SCREEN_TITLE)));
+        }
+        titleQuery.close();
+
+        // Loop through all IA categories and pages and build additional SiteMapPairs
+        List<DashboardCategory> categories = FeatureFactory.getFactory(context)
+                .getDashboardFeatureProvider(context).getAllCategories();
+
+        for (DashboardCategory category : categories) {
+            // Find the category key first.
+            final String parentClass = CATEGORY_KEY_TO_PARENT_MAP.get(category.key);
+            if (parentClass == null) {
+                continue;
+            }
+            // Use the key to look up parent (which page hosts this key)
+            final String parentName = classToTitleMap.get(parentClass);
+            if (parentName == null) {
+                continue;
+            }
+            // Build parent-child mPairs for all children listed under this key.
+            for (Tile tile : category.tiles) {
+                final String childTitle = tile.title.toString();
+                String childClass = null;
+                if (tile.metaData != null) {
+                    childClass = tile.metaData.getString(
+                            SettingsActivity.META_DATA_KEY_FRAGMENT_CLASS);
+                }
+                if (childClass == null) {
+                    continue;
+                }
+                mPairs.add(new SiteMapPair(parentClass, parentName, childClass, childTitle));
+            }
+        }
+        // Done.
+        mInitialized = true;
+        if (DEBUG_TIMING) {
+            Log.d(TAG, "Init timing: " + (System.currentTimeMillis() - startTime));
+        }
+    }
+
+    @WorkerThread
+    private SiteMapPair lookUpParent(String clazz, String title) {
+        for (SiteMapPair pair : mPairs) {
+            if (TextUtils.equals(pair.childClass, clazz)
+                    && TextUtils.equals(title, pair.childTitle)) {
+                return pair;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Data model for a parent-child page pair.
+     */
+    private static class SiteMapPair {
+        public final String parentClass;
+        public final String parentTitle;
+        public final String childClass;
+        public final String childTitle;
+
+        public SiteMapPair(String parentClass, String parentTitle, String childClass,
+                String childTitle) {
+            this.parentClass = parentClass;
+            this.parentTitle = parentTitle;
+            this.childClass = childClass;
+            this.childTitle = childTitle;
+        }
+    }
+}
diff --git a/src/com/android/settings/dashboard/SuggestionFeatureProvider.java b/src/com/android/settings/dashboard/SuggestionFeatureProvider.java
index 769c6e8..b20edee 100644
--- a/src/com/android/settings/dashboard/SuggestionFeatureProvider.java
+++ b/src/com/android/settings/dashboard/SuggestionFeatureProvider.java
@@ -32,4 +32,4 @@
     /** Return true if the suggestion has already been completed and does not need to be shown */
     boolean isSuggestionCompleted(Context context);
 
-}
\ No newline at end of file
+}
diff --git a/src/com/android/settings/fuelgauge/PowerUsageBase.java b/src/com/android/settings/fuelgauge/PowerUsageBase.java
index 1af9df1..60d1ecd 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageBase.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageBase.java
@@ -32,12 +32,12 @@
 
 import com.android.internal.os.BatteryStatsHelper;
 import com.android.settings.R;
-import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.dashboard.DashboardFragment;
 
 /**
  * Common base class for things that need to show the battery usage graph.
  */
-public abstract class PowerUsageBase extends SettingsPreferenceFragment {
+public abstract class PowerUsageBase extends DashboardFragment {
 
     // +1 to allow ordering for PowerUsageSummary.
     @VisibleForTesting
diff --git a/src/com/android/settings/fuelgauge/PowerUsageDetail.java b/src/com/android/settings/fuelgauge/PowerUsageDetail.java
index 5f9a305..dbc5360 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageDetail.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageDetail.java
@@ -34,6 +34,7 @@
 import android.os.Bundle;
 import android.os.Process;
 import android.os.UserHandle;
+import android.provider.SearchIndexableResource;
 import android.support.v7.preference.Preference;
 import android.support.v7.preference.Preference.OnPreferenceClickListener;
 import android.support.v7.preference.PreferenceCategory;
@@ -42,7 +43,6 @@
 import android.util.Log;
 import android.view.View;
 import android.widget.Button;
-
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.os.BatterySipper;
 import com.android.internal.os.BatterySipper.DrainType;
@@ -58,13 +58,17 @@
 import com.android.settings.applications.InstalledAppDetails;
 import com.android.settings.applications.LayoutPreference;
 import com.android.settings.bluetooth.BluetoothSettings;
+import com.android.settings.core.PreferenceController;
 import com.android.settings.location.LocationSettings;
 import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settings.wifi.WifiSettings;
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.io.Writer;
+import java.util.Arrays;
+import java.util.List;
 
 public class PowerUsageDetail extends PowerUsageBase implements Button.OnClickListener {
 
@@ -355,7 +359,6 @@
         mPm = activity.getPackageManager();
         mDpm = (DevicePolicyManager) activity.getSystemService(Context.DEVICE_POLICY_SERVICE);
 
-        addPreferencesFromResource(R.xml.power_usage_details);
         mDetailsParent = (PreferenceCategory) findPreference(KEY_DETAILS_PARENT);
         mControlsParent = (PreferenceCategory) findPreference(KEY_CONTROLS_PARENT);
         mMessagesParent = (PreferenceCategory) findPreference(KEY_MESSAGES_PARENT);
@@ -385,6 +388,26 @@
     }
 
     @Override
+    protected String getCategoryKey() {
+        return null;
+    }
+
+    @Override
+    protected String getLogTag() {
+        return TAG;
+    }
+
+    @Override
+    protected int getPreferenceScreenResId() {
+        return R.xml.power_usage_details;
+    }
+
+    @Override
+    protected List<PreferenceController> getPreferenceControllers(Context context) {
+        return null;
+    }
+
+    @Override
     public void onActivityResult(int requestCode, int resultCode, Intent data) {
         super.onActivityResult(requestCode, resultCode, data);
         if (mHighPower != null) {
@@ -810,4 +833,19 @@
             }
         }
     }
+
+    public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+            new BaseSearchIndexProvider() {
+                @Override
+                public List<SearchIndexableResource> getXmlResourcesToIndex(
+                        Context context, boolean enabled) {
+                    if (!FeatureFactory.getFactory(context).getDashboardFeatureProvider(context)
+                            .isEnabled()) {
+                        return null;
+                    }
+                    final SearchIndexableResource sir = new SearchIndexableResource(context);
+                    sir.xmlResId = R.xml.power_usage_details;
+                    return Arrays.asList(sir);
+                }
+            };
 }
diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java
index 3d49719..c4a780f 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java
@@ -26,6 +26,7 @@
 import android.os.Message;
 import android.os.Process;
 import android.os.UserHandle;
+import android.provider.SearchIndexableResource;
 import android.support.annotation.VisibleForTesting;
 import android.support.v7.preference.Preference;
 import android.support.v7.preference.PreferenceGroup;
@@ -44,11 +45,17 @@
 import com.android.settings.Settings.HighPowerApplicationsActivity;
 import com.android.settings.SettingsActivity;
 import com.android.settings.applications.ManageApplications;
+import com.android.settings.core.PreferenceController;
 import com.android.settings.dashboard.SummaryLoader;
+import com.android.settings.display.AutoBrightnessPreferenceController;
+import com.android.settings.display.TimeoutPreferenceController;
 import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settingslib.BatteryInfo;
+import com.android.settingslib.drawer.CategoryKey;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
@@ -89,7 +96,6 @@
         super.onCreate(icicle);
         setAnimationAllowed(true);
 
-        addPreferencesFromResource(R.xml.power_usage_summary);
         mHistPref = (BatteryHistoryPreference) findPreference(KEY_BATTERY_HISTORY);
         mAppListGroup = (PreferenceGroup) findPreference(KEY_APP_LIST);
     }
@@ -133,6 +139,29 @@
     }
 
     @Override
+    protected String getCategoryKey() {
+        return CategoryKey.CATEGORY_BATTERY;
+    }
+
+    @Override
+    protected String getLogTag() {
+        return TAG;
+    }
+
+    @Override
+    protected int getPreferenceScreenResId() {
+        return R.xml.power_usage_summary;
+    }
+
+    @Override
+    protected List<PreferenceController> getPreferenceControllers(Context context) {
+        final List<PreferenceController> controllers = new ArrayList<>();
+        controllers.add(new AutoBrightnessPreferenceController(context));
+        controllers.add(new TimeoutPreferenceController(context));
+        return controllers;
+    }
+
+    @Override
     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
         if (DEBUG) {
             menu.add(Menu.NONE, MENU_STATS_TYPE, Menu.NONE, R.string.menu_stats_total)
@@ -209,6 +238,7 @@
      * We want to coalesce some UIDs. For example, dex2oat runs under a shared gid that
      * exists for all users of the same app. We detect this case and merge the power use
      * for dex2oat to the device OWNER's use of the app.
+     *
      * @return A sorted list of apps using power.
      */
     private static List<BatterySipper> getCoalescedUsageList(final List<BatterySipper> sippers) {
@@ -314,6 +344,8 @@
             final List<BatterySipper> usageList = getCoalescedUsageList(
                     USE_FAKE_DATA ? getFakeStats() : mStatsHelper.getUsageList());
 
+            final double screenPowerMah = removeScreenBatterySipper(usageList);
+
             final int dischargeAmount = USE_FAKE_DATA ? 5000
                     : stats != null ? stats.getDischargeAmount(mStatsType) : 0;
             final int numSippers = usageList.size();
@@ -322,16 +354,23 @@
                 if (shouldHideSipper(sipper)) {
                     continue;
                 }
-                double totalPower = USE_FAKE_DATA ? 4000 : mStatsHelper.getTotalPower();
+
+                // Deduct the screen power from total power, used to calculate percentOfTotal
+                double totalPower = USE_FAKE_DATA ?
+                        4000 : mStatsHelper.getTotalPower() - screenPowerMah;
+
+                // With deduction in totalPower, percentOfTotal is higher because it adds the part
+                // used in screen
                 final double percentOfTotal =
                         ((sipper.totalPowerMah / totalPower) * dischargeAmount);
+
                 if (((int) (percentOfTotal + .5)) < 1) {
                     continue;
                 }
                 if (sipper.drainType == BatterySipper.DrainType.OVERCOUNTED) {
                     // Don't show over-counted unless it is at least 2/3 the size of
                     // the largest real entry, and its percent of total is more significant
-                    if (sipper.totalPowerMah < ((mStatsHelper.getMaxRealPower()*2)/3)) {
+                    if (sipper.totalPowerMah < ((mStatsHelper.getMaxRealPower() * 2) / 3)) {
                         continue;
                     }
                     if (percentOfTotal < 10) {
@@ -344,7 +383,7 @@
                 if (sipper.drainType == BatterySipper.DrainType.UNACCOUNTED) {
                     // Don't show over-counted unless it is at least 1/2 the size of
                     // the largest real entry, and its percent of total is more significant
-                    if (sipper.totalPowerMah < (mStatsHelper.getMaxRealPower()/2)) {
+                    if (sipper.totalPowerMah < (mStatsHelper.getMaxRealPower() / 2)) {
                         continue;
                     }
                     if (percentOfTotal < 5) {
@@ -419,6 +458,19 @@
         }
     }
 
+    @VisibleForTesting
+    double removeScreenBatterySipper(List<BatterySipper> sippers) {
+        for (int i = 0, size = sippers.size(); i < size; i++) {
+            final BatterySipper sipper = sippers.get(i);
+            if (sipper.drainType == DrainType.SCREEN) {
+                sippers.remove(i);
+                return sipper.totalPowerMah;
+            }
+        }
+
+        return 0;
+    }
+
     private static List<BatterySipper> getFakeStats() {
         ArrayList<BatterySipper> stats = new ArrayList<>();
         float use = 5;
@@ -508,6 +560,21 @@
         }
     }
 
+    public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+            new BaseSearchIndexProvider() {
+                @Override
+                public List<SearchIndexableResource> getXmlResourcesToIndex(
+                        Context context, boolean enabled) {
+                    if (!FeatureFactory.getFactory(context).getDashboardFeatureProvider(context)
+                            .isEnabled()) {
+                        return null;
+                    }
+                    final SearchIndexableResource sir = new SearchIndexableResource(context);
+                    sir.xmlResId = R.xml.power_usage_summary;
+                    return Arrays.asList(sir);
+                }
+            };
+
     public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY
             = new SummaryLoader.SummaryProviderFactory() {
         @Override
diff --git a/src/com/android/settings/inputmethod/InputAndGestureSettings.java b/src/com/android/settings/inputmethod/InputAndGestureSettings.java
index 43d8b230b..4ad5af2 100644
--- a/src/com/android/settings/inputmethod/InputAndGestureSettings.java
+++ b/src/com/android/settings/inputmethod/InputAndGestureSettings.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.os.UserHandle;
+import android.provider.SearchIndexableResource;
 import android.support.annotation.VisibleForTesting;
 
 import com.android.internal.hardware.AmbientDisplayConfiguration;
@@ -31,9 +32,12 @@
 import com.android.settings.gestures.DoubleTwistPreferenceController;
 import com.android.settings.gestures.PickupGesturePreferenceController;
 import com.android.settings.gestures.SwipeToNotificationPreferenceController;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settingslib.drawer.CategoryKey;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 public class InputAndGestureSettings extends DashboardFragment {
@@ -90,4 +94,19 @@
     void setAmbientDisplayConfig(AmbientDisplayConfiguration ambientConfig) {
         mAmbientDisplayConfig = ambientConfig;
     }
+
+    public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+            new BaseSearchIndexProvider() {
+                @Override
+                public List<SearchIndexableResource> getXmlResourcesToIndex(
+                        Context context, boolean enabled) {
+                    if (!FeatureFactory.getFactory(context).getDashboardFeatureProvider(context)
+                            .isEnabled()) {
+                        return null;
+                    }
+                    final SearchIndexableResource sir = new SearchIndexableResource(context);
+                    sir.xmlResId = R.xml.input_and_gesture;
+                    return Arrays.asList(sir);
+                }
+            };
 }
diff --git a/src/com/android/settings/notification/AppNotificationSettings.java b/src/com/android/settings/notification/AppNotificationSettings.java
index b740b92..d4cd6f8 100644
--- a/src/com/android/settings/notification/AppNotificationSettings.java
+++ b/src/com/android/settings/notification/AppNotificationSettings.java
@@ -19,6 +19,7 @@
 import android.app.Activity;
 import android.app.Notification;
 import android.app.NotificationChannel;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
@@ -35,11 +36,13 @@
 import com.android.settings.Utils;
 import com.android.settings.applications.AppHeaderController;
 import com.android.settings.applications.AppInfoBase;
+import com.android.settings.core.PreferenceController;
 import com.android.settings.dashboard.DashboardFeatureProvider;
 import com.android.settings.notification.NotificationBackend.AppRow;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settingslib.RestrictedPreference;
 import com.android.settingslib.RestrictedSwitchPreference;
+import com.android.settingslib.drawer.CategoryKey;
 
 import java.text.Collator;
 import java.util.Collections;
@@ -88,10 +91,12 @@
         addPreferencesFromResource(R.xml.app_notification_settings);
 
         mBlock = (RestrictedSwitchPreference) getPreferenceScreen().findPreference(KEY_BLOCK);
+        mBadge = (RestrictedSwitchPreference) getPreferenceScreen().findPreference(KEY_BADGE);
         mChannels = (PreferenceCategory) findPreference(KEY_CHANNELS);
 
         if (mPkgInfo != null) {
-            setupBlock(mAppRow.systemApp, mAppRow.banned);
+            setupBlock();
+            setupBadge();
             // load settings intent
             ArrayMap<String, AppRow> rows = new ArrayMap<String, AppRow>();
             rows.put(mAppRow.pkg, mAppRow);
@@ -113,6 +118,7 @@
                     if (channel.isDeleted()) {
                         channelPref.setTitle(
                                 getString(R.string.deleted_channel_name, channel.getName()));
+                        channelPref.setEnabled(false);
                     } else {
                         Bundle channelArgs = new Bundle();
                         channelArgs.putInt(AppInfoBase.ARG_PACKAGE_UID, mUid);
@@ -153,16 +159,27 @@
             finish();
             return;
         }
-        if (mBlock != null) {
-            mBlock.setDisabledByAdmin(mSuspendedAppsAdmin);
-        }
     }
 
-    private void setupBlock(boolean notBlockable, boolean banned) {
-        if (notBlockable) {
+    private void setupBadge() {
+        mBadge.setDisabledByAdmin(mSuspendedAppsAdmin);
+        mBadge.setChecked(mAppRow.showBadge);
+        mBadge.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+            @Override
+            public boolean onPreferenceChange(Preference preference, Object newValue) {
+                final boolean value = (Boolean) newValue;
+                mBackend.setShowBadge(mPkg, mUid, value);
+                return true;
+            }
+        });
+    }
+
+    private void setupBlock() {
+        if (mAppRow.systemApp) {
             setVisible(mBlock, false);
         } else {
-            mBlock.setChecked(banned);
+            mBlock.setDisabledByAdmin(mSuspendedAppsAdmin);
+            mBlock.setChecked(mAppRow.banned);
             mBlock.setOnPreferenceChangeListener(
                     new Preference.OnPreferenceChangeListener() {
                         @Override
@@ -180,6 +197,7 @@
 
     private void updateDependents(boolean banned) {
         setVisible(mChannels, !(mChannelList.isEmpty() || banned));
+        setVisible(mBadge, !banned);
     }
 
     private List<ResolveInfo> queryNotificationConfigActivities() {
diff --git a/src/com/android/settings/notification/ChannelNotificationSettings.java b/src/com/android/settings/notification/ChannelNotificationSettings.java
index e73feb5..2749e03 100644
--- a/src/com/android/settings/notification/ChannelNotificationSettings.java
+++ b/src/com/android/settings/notification/ChannelNotificationSettings.java
@@ -26,6 +26,7 @@
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.admin.DevicePolicyManager;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.UserInfo;
 import android.net.Uri;
@@ -47,15 +48,17 @@
 import com.android.settingslib.RestrictedSwitchPreference;
 
 import java.util.ArrayList;
+import java.util.List;
 
 public class ChannelNotificationSettings extends NotificationSettingsBase {
+    private static final String TAG = "ChannelSettings";
+
     protected static final String KEY_BYPASS_DND = "bypass_dnd";
     protected static final String KEY_VISIBILITY_OVERRIDE = "visibility_override";
     protected static final String KEY_IMPORTANCE = "importance";
     protected static final String KEY_LIGHTS = "lights";
     protected static final String KEY_VIBRATE = "vibrate";
     protected static final String KEY_RINGTONE = "ringtone";
-    protected static final String KEY_BADGE = "badge";
 
     protected RestrictedSwitchPreference mLights;
     protected RestrictedSwitchPreference mVibrate;
@@ -114,8 +117,8 @@
                     .getApplicationFeatureProvider(activity)
                     .newAppHeaderController(this /* fragment */, null /* appHeader */)
                     .setIcon(mAppRow.icon)
-                    .setLabel(mAppRow.label)
-                    .setSummary(mChannel.getName())
+                    .setLabel(mChannel.getName())
+                    .setSummary(mAppRow.label)
                     .setPackageName(mAppRow.pkg)
                     .setUid(mAppRow.uid)
                     .setButtonActions(AppHeaderController.ActionType.ACTION_APP_INFO,
@@ -138,8 +141,6 @@
         mImportance.setDisabledByAdmin(mSuspendedAppsAdmin);
         mPriority.setDisabledByAdmin(mSuspendedAppsAdmin);
         mVisibilityOverride.setDisabledByAdmin(mSuspendedAppsAdmin);
-        mBlock.setDisabledByAdmin(mSuspendedAppsAdmin);
-        mBadge.setDisabledByAdmin(mSuspendedAppsAdmin);
     }
 
     private void setupLights() {
@@ -204,6 +205,7 @@
             }
         });
         mBadge.setDisabledByAdmin(mSuspendedAppsAdmin);
+        mBadge.setEnabled(mAppRow.showBadge);
         mBadge.setChecked(mChannel.canShowBadge());
         mBadge.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
             @Override
@@ -218,15 +220,20 @@
 
         mImportance.setDisabledByAdmin(mSuspendedAppsAdmin);
         final int numImportances = IMPORTANCE_HIGH - IMPORTANCE_MIN + 1;
-        String[] summaries = new String[numImportances];
-        String[] values = new String[numImportances];
+        List<String> summaries = new ArrayList<>();
+        List<String> values = new ArrayList<>();;
         for (int i = 0; i < numImportances; i++) {
             int importance = i + 1;
-            summaries[i] = getSummary(importance);
-            values[i] = String.valueOf(importance);
+            summaries.add(getSummary(importance));
+            values.add(String.valueOf(importance));
         }
-        mImportance.setEntryValues(values);
-        mImportance.setEntries(summaries);
+        if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(mChannel.getId())) {
+            // Add option to reset to letting the app decide
+            summaries.add(getSummary(NotificationManager.IMPORTANCE_UNSPECIFIED));
+            values.add(String.valueOf(NotificationManager.IMPORTANCE_UNSPECIFIED));
+        }
+        mImportance.setEntryValues(values.toArray(new String[0]));
+        mImportance.setEntries(summaries.toArray(new String[0]));
         mImportance.setValue(String.valueOf(mChannel.getImportance()));
         mImportance.setSummary("%s");
 
@@ -245,6 +252,8 @@
 
     private String getSummary(int importance) {
         switch (importance) {
+            case NotificationManager.IMPORTANCE_UNSPECIFIED:
+                return getContext().getString(R.string.notification_importance_unspecified);
             case NotificationManager.IMPORTANCE_NONE:
                 return getContext().getString(R.string.notification_importance_blocked);
             case NotificationManager.IMPORTANCE_MIN:
diff --git a/src/com/android/settings/notification/NotificationBackend.java b/src/com/android/settings/notification/NotificationBackend.java
index 692e1f6..124579f 100644
--- a/src/com/android/settings/notification/NotificationBackend.java
+++ b/src/com/android/settings/notification/NotificationBackend.java
@@ -26,6 +26,7 @@
 import android.content.pm.ParceledListSlice;
 import android.graphics.drawable.Drawable;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.util.Log;
 
 import com.android.settingslib.Utils;
@@ -48,6 +49,8 @@
         }
         row.icon = app.loadIcon(pm);
         row.banned = getNotificationsBanned(row.pkg, row.uid);
+        row.showBadge = canShowBadge(row.pkg, row.uid);
+        row.userId = UserHandle.getUserId(row.uid);
         return row;
     }
 
@@ -87,6 +90,25 @@
         }
     }
 
+    public boolean canShowBadge(String pkg, int uid) {
+        try {
+            return sINM.canShowBadge(pkg, uid);
+        } catch (Exception e) {
+            Log.w(TAG, "Error calling NoMan", e);
+            return false;
+        }
+    }
+
+    public boolean setShowBadge(String pkg, int uid, boolean showBadge) {
+        try {
+            sINM.setShowBadge(pkg, uid, showBadge);
+            return true;
+        } catch (Exception e) {
+            Log.w(TAG, "Error calling NoMan", e);
+            return false;
+        }
+    }
+
     public NotificationChannel getChannel(String pkg, int uid, String channelId) {
         if (channelId == null) {
             return null;
@@ -129,6 +151,8 @@
         public boolean banned;
         public boolean first;  // first app in section
         public boolean systemApp;
+        public boolean showBadge;
+        public int userId;
     }
 
     public static class ChannelRow extends AppRow {
diff --git a/src/com/android/settings/notification/NotificationSettingsBase.java b/src/com/android/settings/notification/NotificationSettingsBase.java
index f3e4390..1535269 100644
--- a/src/com/android/settings/notification/NotificationSettingsBase.java
+++ b/src/com/android/settings/notification/NotificationSettingsBase.java
@@ -16,36 +16,28 @@
 
 package com.android.settings.notification;
 
-import com.android.internal.widget.LockPatternUtils;
 import com.android.settings.R;
 import com.android.settings.SettingsPreferenceFragment;
 import com.android.settings.applications.AppInfoBase;
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.settingslib.RestrictedSwitchPreference;
 
-import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
-import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.UserInfo;
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
-import android.service.notification.NotificationListenerService.Ranking;
 import android.support.v7.preference.Preference;
 import android.text.TextUtils;
 import android.util.Log;
 import android.widget.Toast;
 
-import java.util.ArrayList;
-
-import static com.android.settings.notification.RestrictedDropDownPreference.RestrictedItem;
 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 
 abstract public class NotificationSettingsBase extends SettingsPreferenceFragment {
@@ -55,6 +47,7 @@
     protected static final String ARG_CHANNEL = "channel";
 
     protected static final String KEY_BLOCK = "block";
+    protected static final String KEY_BADGE = "badge";
 
     protected PackageManager mPm;
     protected UserManager mUm;
@@ -145,6 +138,7 @@
         mSuspendedAppsAdmin = RestrictedLockUtils.checkIfApplicationIsSuspended(
                 mContext, mPkg, mUserId);
         mBlock.setDisabledByAdmin(mSuspendedAppsAdmin);
+        mBadge.setDisabledByAdmin(mSuspendedAppsAdmin);
     }
 
     protected void setVisible(Preference p, boolean visible) {
diff --git a/src/com/android/settings/notification/NotificationStation.java b/src/com/android/settings/notification/NotificationStation.java
index 04897e4..50bcd95 100644
--- a/src/com/android/settings/notification/NotificationStation.java
+++ b/src/com/android/settings/notification/NotificationStation.java
@@ -56,7 +56,7 @@
 public class NotificationStation extends SettingsPreferenceFragment {
     private static final String TAG = NotificationStation.class.getSimpleName();
 
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = true;
     private static final boolean DUMP_EXTRAS = true;
     private static final boolean DUMP_PARCEL = true;
     private Handler mHandler;
@@ -362,6 +362,11 @@
                                         .append(delim)
                                         .append(rank.getImportanceExplanation());
                             }
+                            sb.append("\n")
+                                    .append(bold(getString(
+                                            R.string.notification_log_details_badge)))
+                                    .append(delim)
+                                    .append(Boolean.toString(rank.canShowBadge()));
                         } else {
                             if (mRanking == null) {
                                 sb.append("\n")
diff --git a/src/com/android/settings/search/Index.java b/src/com/android/settings/search/Index.java
index cd6e562..b79a7f4 100644
--- a/src/com/android/settings/search/Index.java
+++ b/src/com/android/settings/search/Index.java
@@ -266,7 +266,7 @@
         StringBuilder sb = new StringBuilder();
 
         sb.append("SELECT ");
-        sb.append(IndexDatabaseHelper.SavedQueriesColums.QUERY);
+        sb.append(IndexDatabaseHelper.SavedQueriesColumns.QUERY);
         sb.append(" FROM ");
         sb.append(Tables.TABLE_SAVED_QUERIES);
 
@@ -274,7 +274,7 @@
             sb.append(" ORDER BY rowId DESC");
         } else {
             sb.append(" WHERE ");
-            sb.append(IndexDatabaseHelper.SavedQueriesColums.QUERY);
+            sb.append(IndexDatabaseHelper.SavedQueriesColumns.QUERY);
             sb.append(" LIKE ");
             sb.append("'");
             sb.append(query);
@@ -1299,8 +1299,8 @@
             final long now = new Date().getTime();
 
             final ContentValues values = new ContentValues();
-            values.put(IndexDatabaseHelper.SavedQueriesColums.QUERY, params[0]);
-            values.put(IndexDatabaseHelper.SavedQueriesColums.TIME_STAMP, now);
+            values.put(IndexDatabaseHelper.SavedQueriesColumns.QUERY, params[0]);
+            values.put(IndexDatabaseHelper.SavedQueriesColumns.TIME_STAMP, now);
 
             final SQLiteDatabase database = getWritableDatabase();
             if (database == null) {
@@ -1312,7 +1312,7 @@
             try {
                 // First, delete all saved queries that are the same
                 database.delete(Tables.TABLE_SAVED_QUERIES,
-                        IndexDatabaseHelper.SavedQueriesColums.QUERY + " = ?",
+                        IndexDatabaseHelper.SavedQueriesColumns.QUERY + " = ?",
                         new String[] { params[0] });
 
                 // Second, insert the saved query
diff --git a/src/com/android/settings/search/IndexDatabaseHelper.java b/src/com/android/settings/search/IndexDatabaseHelper.java
index ba53e94..60378c2 100644
--- a/src/com/android/settings/search/IndexDatabaseHelper.java
+++ b/src/com/android/settings/search/IndexDatabaseHelper.java
@@ -28,12 +28,13 @@
     private static final String TAG = "IndexDatabaseHelper";
 
     private static final String DATABASE_NAME = "search_index.db";
-    private static final int DATABASE_VERSION = 116;
+    private static final int DATABASE_VERSION = 117;
 
     private static final String INDEX = "index";
 
     public interface Tables {
         String TABLE_PREFS_INDEX = "prefs_index";
+        String TABLE_SITE_MAP = "site_map";
         String TABLE_META_INDEX = "meta_index";
         String TABLE_SAVED_QUERIES = "saved_queries";
     }
@@ -67,11 +68,19 @@
         String BUILD = "build";
     }
 
-    public interface SavedQueriesColums {
+    public interface SavedQueriesColumns {
         String QUERY = "query";
         String TIME_STAMP = "timestamp";
     }
 
+    public interface SiteMapColumns {
+        String DOCID = "docid";
+        String PARENT_CLASS = "parent_class";
+        String CHILD_CLASS = "child_class";
+        String PARENT_TITLE = "parent_title";
+        String CHILD_TITLE = "child_title";
+    }
+
     private static final String CREATE_INDEX_TABLE =
             "CREATE VIRTUAL TABLE " + Tables.TABLE_PREFS_INDEX + " USING fts4" +
                     "(" +
@@ -127,11 +136,22 @@
     private static final String CREATE_SAVED_QUERIES_TABLE =
             "CREATE TABLE " + Tables.TABLE_SAVED_QUERIES +
                     "(" +
-                    SavedQueriesColums.QUERY + " VARCHAR(64) NOT NULL" +
+                    SavedQueriesColumns.QUERY + " VARCHAR(64) NOT NULL" +
                     ", " +
-                    SavedQueriesColums.TIME_STAMP + " INTEGER" +
+                    SavedQueriesColumns.TIME_STAMP + " INTEGER" +
                     ")";
 
+    private static final String CREATE_SITE_MAP_TABLE =
+            "CREATE VIRTUAL TABLE " + Tables.TABLE_SITE_MAP + " USING fts4" +
+                    "(" +
+                    SiteMapColumns.PARENT_CLASS +
+                    ", " +
+                    SiteMapColumns.CHILD_CLASS +
+                    ", " +
+                    SiteMapColumns.PARENT_TITLE +
+                    ", " +
+                    SiteMapColumns.CHILD_TITLE +
+                    ")";
     private static final String INSERT_BUILD_VERSION =
             "INSERT INTO " + Tables.TABLE_META_INDEX +
                     " VALUES ('" + Build.VERSION.INCREMENTAL + "');";
@@ -164,6 +184,7 @@
         db.execSQL(CREATE_INDEX_TABLE);
         db.execSQL(CREATE_META_TABLE);
         db.execSQL(CREATE_SAVED_QUERIES_TABLE);
+        db.execSQL(CREATE_SITE_MAP_TABLE);
         db.execSQL(INSERT_BUILD_VERSION);
         Log.i(TAG, "Bootstrapped database");
     }
@@ -241,5 +262,6 @@
         db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_META_INDEX);
         db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_PREFS_INDEX);
         db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_SAVED_QUERIES);
+        db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_SITE_MAP);
     }
 }
diff --git a/src/com/android/settings/search/SearchIndexableResources.java b/src/com/android/settings/search/SearchIndexableResources.java
index 8a158cd..4f9f92c 100644
--- a/src/com/android/settings/search/SearchIndexableResources.java
+++ b/src/com/android/settings/search/SearchIndexableResources.java
@@ -20,7 +20,6 @@
 import android.support.annotation.DrawableRes;
 import android.support.annotation.VisibleForTesting;
 import android.support.annotation.XmlRes;
-
 import com.android.settings.DateTimeSettings;
 import com.android.settings.DevelopmentSettings;
 import com.android.settings.DeviceInfoSettings;
@@ -47,6 +46,7 @@
 import com.android.settings.display.ScreenZoomSettings;
 import com.android.settings.enterprise.EnterprisePrivacySettings;
 import com.android.settings.fuelgauge.BatterySaverSettings;
+import com.android.settings.fuelgauge.PowerUsageDetail;
 import com.android.settings.fuelgauge.PowerUsageSummary;
 import com.android.settings.gestures.DoubleTapPowerSettings;
 import com.android.settings.gestures.DoubleTapScreenSettings;
@@ -55,6 +55,7 @@
 import com.android.settings.gestures.PickupGestureSettings;
 import com.android.settings.gestures.SwipeToNotificationSettings;
 import com.android.settings.inputmethod.AvailableVirtualKeyboardFragment;
+import com.android.settings.inputmethod.InputAndGestureSettings;
 import com.android.settings.inputmethod.InputMethodAndLanguageSettings;
 import com.android.settings.inputmethod.PhysicalKeyboardFragment;
 import com.android.settings.inputmethod.VirtualKeyboardFragment;
@@ -123,6 +124,7 @@
         addIndex(StorageSettings.class, NO_DATA_RES_ID, R.drawable.ic_settings_storage);
         addIndex(PowerUsageSummary.class,
                 R.xml.power_usage_summary, R.drawable.ic_settings_battery);
+        addIndex(PowerUsageDetail.class, NO_DATA_RES_ID, R.drawable.ic_settings_battery);
         addIndex(BatterySaverSettings.class,
                 R.xml.battery_saver_settings, R.drawable.ic_settings_battery);
         addIndex(AdvancedAppSettings.class, NO_DATA_RES_ID, R.drawable.ic_settings_applications);
@@ -136,6 +138,7 @@
         addIndex(DoubleTwistGestureSettings.class, NO_DATA_RES_ID, R.drawable.ic_settings_gestures);
         addIndex(SwipeToNotificationSettings.class, NO_DATA_RES_ID,
                 R.drawable.ic_settings_gestures);
+        addIndex(InputAndGestureSettings.class, NO_DATA_RES_ID, R.drawable.ic_settings_language);
         addIndex(LocationSettings.class, R.xml.location_settings, R.drawable.ic_settings_location);
         addIndex(ScanningSettings.class, R.xml.location_scanning, R.drawable.ic_settings_location);
         addIndex(SecuritySettings.class, NO_DATA_RES_ID, R.drawable.ic_settings_security);
diff --git a/src/com/android/settings/search2/CursorToSearchResultConverter.java b/src/com/android/settings/search2/CursorToSearchResultConverter.java
index 540932c..6549c18 100644
--- a/src/com/android/settings/search2/CursorToSearchResultConverter.java
+++ b/src/com/android/settings/search2/CursorToSearchResultConverter.java
@@ -28,8 +28,10 @@
 import android.os.Bundle;
 import android.text.TextUtils;
 import android.util.Log;
+
 import com.android.settings.SettingsActivity;
 import com.android.settings.Utils;
+import com.android.settings.dashboard.SiteMapManager;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -39,18 +41,20 @@
 import java.util.Map;
 import java.util.Set;
 
-import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_ID;
-import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_INTENT_ACTION_TARGET_CLASS;
-import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_SCREEN_TITLE;
-import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_TITLE;
-import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_SUMMARY_ON;
 import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_CLASS_NAME;
 import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_ICON;
+import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_ID;
 import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_INTENT_ACTION;
-import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE;
+import static com.android.settings.search2.DatabaseResultLoader
+        .COLUMN_INDEX_INTENT_ACTION_TARGET_CLASS;
+import static com.android.settings.search2.DatabaseResultLoader
+        .COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE;
 import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_KEY;
-import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_PAYLOAD_TYPE;
 import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_PAYLOAD;
+import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_PAYLOAD_TYPE;
+import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_SCREEN_TITLE;
+import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_SUMMARY_ON;
+import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_TITLE;
 
 /**
  * Controller to Build search results from {@link Cursor} Objects.
@@ -78,7 +82,8 @@
         mQueryText = queryText;
     }
 
-    public List<SearchResult> convertCursor(Cursor cursorResults, int baseRank) {
+    public List<SearchResult> convertCursor(SiteMapManager sitemapManager,
+            Cursor cursorResults, int baseRank) {
         if (cursorResults == null) {
             return null;
         }
@@ -86,8 +91,8 @@
         final List<SearchResult> results = new ArrayList<>();
 
         while (cursorResults.moveToNext()) {
-            SearchResult result = buildSingleSearchResultFromCursor(contextMap, cursorResults,
-                    baseRank);
+            SearchResult result = buildSingleSearchResultFromCursor(sitemapManager,
+                    contextMap, cursorResults, baseRank);
             if (result != null) {
                 results.add(result);
             }
@@ -96,8 +101,8 @@
         return results;
     }
 
-    private SearchResult buildSingleSearchResultFromCursor(Map<String, Context> contextMap,
-            Cursor cursor, int baseRank) {
+    private SearchResult buildSingleSearchResultFromCursor(SiteMapManager sitemapManager,
+            Map<String, Context> contextMap, Cursor cursor, int baseRank) {
         final String docId = cursor.getString(COLUMN_INDEX_ID);
         /* Make sure that this result has not yet been added as a result. Checking the docID
            covers the case of multiple queries matching the same row, but we need to also to check
@@ -128,7 +133,7 @@
             return null;
         }
 
-        final List<String> breadcrumbs = getBreadcrumbs(cursor);
+        final List<String> breadcrumbs = getBreadcrumbs(sitemapManager, cursor);
         final int rank = getRank(breadcrumbs, baseRank);
 
         final SearchResult.Builder builder = new SearchResult.Builder();
@@ -210,8 +215,12 @@
         return null;
     }
 
-    private List<String> getBreadcrumbs(Cursor cursor) {
-        return new ArrayList<>();
+    private List<String> getBreadcrumbs(SiteMapManager siteMapManager, Cursor cursor) {
+        final String screenTitle = cursor.getString(COLUMN_INDEX_SCREEN_TITLE);
+        final String screenClass = cursor.getString(COLUMN_INDEX_CLASS_NAME);
+        final List<String> breadcrumbs = siteMapManager.buildBreadCrumb(mContext, screenClass,
+                screenTitle);
+        return breadcrumbs;
     }
 
     /** Uses the breadcrumbs to determine the offset to the base rank.
diff --git a/src/com/android/settings/search2/DatabaseIndexingManager.java b/src/com/android/settings/search2/DatabaseIndexingManager.java
index 2cd0cea..c75f93f 100644
--- a/src/com/android/settings/search2/DatabaseIndexingManager.java
+++ b/src/com/android/settings/search2/DatabaseIndexingManager.java
@@ -34,7 +34,6 @@
 import android.provider.SearchIndexableResource;
 import android.provider.SearchIndexablesContract;
 import android.text.TextUtils;
-import android.util.ArrayMap;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Xml;
@@ -56,6 +55,7 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import static android.provider.SearchIndexablesContract.COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE;
@@ -279,7 +279,7 @@
      * @param className the class name (typically a fragment name).
      * @param rebuild true means that you want to delete the data from the Index first.
      * @param includeInSearchResults true means that you want the bit "enabled" set so that the
-     *                               data will be seen included into the search results
+     * data will be seen included into the search results
      */
     public void updateFromClassNameResource(String className, final boolean rebuild,
             boolean includeInSearchResults) {
@@ -287,7 +287,7 @@
             throw new IllegalArgumentException("class name cannot be null!");
         }
         final SearchIndexableResource res = SearchIndexableResources.getResourceByName(className);
-        if (res == null ) {
+        if (res == null) {
             Log.e(LOG_TAG, "Cannot find SearchIndexableResources for class name: " + className);
             return;
         }
@@ -581,12 +581,13 @@
             String title;
             String summary;
             String keywords;
+            String childFragment;
             ResultPayload payload;
 
-            ArrayMap<String, PreferenceController> controllerUriMap = null;
+            Map<String, PreferenceController> controllerUriMap = null;
 
             if (fragmentName != null) {
-                controllerUriMap = (ArrayMap) DatabaseIndexingUtils
+                controllerUriMap = DatabaseIndexingUtils
                         .getPreferenceControllerUriMap(fragmentName, context);
             }
 
@@ -654,8 +655,10 @@
                     }
 
                     payload = DatabaseIndexingUtils.getPayloadFromUriMap(controllerUriMap, key);
+                    childFragment = XmlParserUtils.getDataChildFragment(context, attrs);
 
                     builder.setEntries(entries)
+                            .setChildClassName(childFragment)
                             .setPayload(payload);
 
                     // Insert rows for the child nodes of PreferenceScreen
@@ -753,7 +756,7 @@
     }
 
     private void updateOneRowWithFilteredData(SQLiteDatabase database, DatabaseRow.Builder builder,
-            String title, String summaryOn, String summaryOff,String keywords) {
+            String title, String summaryOn, String summaryOff, String keywords) {
 
         final String updatedTitle = DatabaseIndexingUtils.normalizeHyphen(title);
         final String updatedSummaryOn = DatabaseIndexingUtils.normalizeHyphen(summaryOn);
@@ -783,14 +786,8 @@
             return;
         }
 
-        // The DocID should contains more than the title string itself (you may have two settings
-        // with the same title). So we need to use a combination of the title and the screenTitle.
-        StringBuilder sb = new StringBuilder(row.updatedTitle);
-        sb.append(row.screenTitle);
-        int docId = sb.toString().hashCode();
-
         ContentValues values = new ContentValues();
-        values.put(IndexDatabaseHelper.IndexColumns.DOCID, docId);
+        values.put(IndexDatabaseHelper.IndexColumns.DOCID, row.getDocId());
         values.put(IndexDatabaseHelper.IndexColumns.LOCALE, row.locale);
         values.put(IndexDatabaseHelper.IndexColumns.DATA_RANK, row.rank);
         values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE, row.updatedTitle);
@@ -816,6 +813,18 @@
         values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD, row.payload);
 
         database.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX, null, values);
+
+        if (!TextUtils.isEmpty(row.className) && !TextUtils.isEmpty(row.childClassName)) {
+            ContentValues siteMapPair = new ContentValues();
+            final int pairDocId = Objects.hash(row.className, row.childClassName);
+            siteMapPair.put(IndexDatabaseHelper.SiteMapColumns.DOCID, pairDocId);
+            siteMapPair.put(IndexDatabaseHelper.SiteMapColumns.PARENT_CLASS, row.className);
+            siteMapPair.put(IndexDatabaseHelper.SiteMapColumns.PARENT_TITLE, row.screenTitle);
+            siteMapPair.put(IndexDatabaseHelper.SiteMapColumns.CHILD_CLASS, row.childClassName);
+            siteMapPair.put(IndexDatabaseHelper.SiteMapColumns.CHILD_TITLE, row.updatedTitle);
+
+            database.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_SITE_MAP, null, siteMapPair);
+        }
     }
 
     /**
@@ -919,7 +928,7 @@
                 }
                 if (!TextUtils.isEmpty(data.className)) {
                     delete(database, IndexDatabaseHelper.IndexColumns.CLASS_NAME, data.className);
-                } else  {
+                } else {
                     if (data instanceof SearchIndexableRaw) {
                         final SearchIndexableRaw raw = (SearchIndexableRaw) data;
                         if (!TextUtils.isEmpty(raw.title)) {
@@ -938,7 +947,7 @@
 
         private int delete(SQLiteDatabase database, String columName, String value) {
             final String whereClause = columName + "=?";
-            final String[] whereArgs = new String[] { value };
+            final String[] whereArgs = new String[]{value};
 
             return database.delete(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX, whereClause,
                     whereArgs);
@@ -955,6 +964,7 @@
         public final String normalizedSummaryOff;
         public final String entries;
         public final String className;
+        public final String childClassName;
         public final String screenTitle;
         public final int iconResId;
         public final int rank;
@@ -978,6 +988,7 @@
             normalizedSummaryOff = builder.mNormalizedSummaryOff;
             entries = builder.mEntries;
             className = builder.mClassName;
+            childClassName = builder.mChildClassName;
             screenTitle = builder.mScreenTitle;
             iconResId = builder.mIconResId;
             rank = builder.mRank;
@@ -993,6 +1004,16 @@
                     : null;
         }
 
+        /**
+         * Returns the doc id for this row.
+         */
+        public int getDocId() {
+            // The DocID should contains more than the title string itself (you may have two
+            // settings with the same title). So we need to use a combination of multiple
+            // attributes from this row.
+            return Objects.hash(updatedTitle, screenTitle, key, payloadType);
+        }
+
         public static class Builder {
             private String mLocale;
             private String mUpdatedTitle;
@@ -1003,6 +1024,7 @@
             private String mNormalizedSummaryOff;
             private String mEntries;
             private String mClassName;
+            private String mChildClassName;
             private String mScreenTitle;
             private int mIconResId;
             private int mRank;
@@ -1013,7 +1035,8 @@
             private boolean mEnabled;
             private String mKey;
             private int mUserId;
-            @ResultPayload.PayloadType private int mPayloadType;
+            @ResultPayload.PayloadType
+            private int mPayloadType;
             private ResultPayload mPayload;
 
             public Builder setLocale(String locale) {
@@ -1061,6 +1084,11 @@
                 return this;
             }
 
+            public Builder setChildClassName(String childClassName) {
+                mChildClassName = childClassName;
+                return this;
+            }
+
             public Builder setScreenTitle(String screenTitle) {
                 mScreenTitle = screenTitle;
                 return this;
@@ -1114,7 +1142,7 @@
             public Builder setPayload(ResultPayload payload) {
                 mPayload = payload;
 
-                if(mPayload != null) {
+                if (mPayload != null) {
                     setPayloadType(mPayload.getType());
                 }
                 return this;
@@ -1122,6 +1150,7 @@
 
             /**
              * Payload type is added when a Payload is added to the Builder in {setPayload}
+             *
              * @param payloadType PayloadType
              * @return The Builder
              */
diff --git a/src/com/android/settings/search2/DatabaseIndexingUtils.java b/src/com/android/settings/search2/DatabaseIndexingUtils.java
index 9fdf732..bd06ef3 100644
--- a/src/com/android/settings/search2/DatabaseIndexingUtils.java
+++ b/src/com/android/settings/search2/DatabaseIndexingUtils.java
@@ -105,7 +105,7 @@
      * @return The Payload from the {@link PreferenceController} specified by the key, if it exists.
      * Otherwise null.
      */
-    public static ResultPayload getPayloadFromUriMap(ArrayMap<String, PreferenceController> uriMap,
+    public static ResultPayload getPayloadFromUriMap(Map<String, PreferenceController> uriMap,
             String key) {
         if (uriMap == null) {
             return null;
diff --git a/src/com/android/settings/search2/DatabaseResultLoader.java b/src/com/android/settings/search2/DatabaseResultLoader.java
index f7acd25..6c8def6 100644
--- a/src/com/android/settings/search2/DatabaseResultLoader.java
+++ b/src/com/android/settings/search2/DatabaseResultLoader.java
@@ -20,6 +20,8 @@
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 
+import com.android.settings.dashboard.SiteMapManager;
+import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.search.IndexDatabaseHelper;
 import com.android.settings.utils.AsyncLoader;
 
@@ -35,11 +37,6 @@
  */
 public class DatabaseResultLoader extends AsyncLoader<List<SearchResult>> {
     private static final String LOG = "DatabaseResultLoader";
-    private final String mQueryText;
-
-    protected final SQLiteDatabase mDatabase;
-
-    private final CursorToSearchResultConverter mConverter;
 
     /* These indices are used to match the columns of the this loader's SELECT statement.
      These are not necessarily the same order nor similar coverage as the schema defined in
@@ -99,8 +96,15 @@
      */
     private static final int[] BASE_RANKS = {1, 4, 7};
 
+    private final String mQueryText;
+    private final SQLiteDatabase mDatabase;
+    private final CursorToSearchResultConverter mConverter;
+    private final SiteMapManager mSiteMapManager;
+
     public DatabaseResultLoader(Context context, String queryText) {
         super(context);
+        mSiteMapManager = FeatureFactory.getFactory(context)
+                .getSearchFeatureProvider().getSiteMapManager();
         mDatabase = IndexDatabaseHelper.getInstance(context).getReadableDatabase();
         mQueryText = cleanQuery(queryText);
         mConverter = new CursorToSearchResultConverter(context, mQueryText);
@@ -144,7 +148,7 @@
 
         final Cursor resultCursor = mDatabase.query(TABLE_PREFS_INDEX, SELECT_COLUMNS, whereClause,
                 selection, null, null, null);
-        return mConverter.convertCursor(resultCursor, baseRank);
+        return mConverter.convertCursor(mSiteMapManager, resultCursor, baseRank);
     }
 
     @Override
@@ -155,6 +159,7 @@
 
     /**
      * A generic method to make the query suitable for searching the database.
+     *
      * @return the cleaned query string
      */
     private static String cleanQuery(String query) {
diff --git a/src/com/android/settings/search2/InlineSwitchViewHolder.java b/src/com/android/settings/search2/InlineSwitchViewHolder.java
index ad99af8..ac49b1c 100644
--- a/src/com/android/settings/search2/InlineSwitchViewHolder.java
+++ b/src/com/android/settings/search2/InlineSwitchViewHolder.java
@@ -21,41 +21,32 @@
 import android.view.View;
 import android.widget.CompoundButton;
 import android.widget.Switch;
-import android.widget.TextView;
 
-import com.android.internal.widget.PreferenceImageView;
 import com.android.settings.R;
 
 /**
  * ViewHolder for Settings represented as SwitchPreferences.
  */
 public class InlineSwitchViewHolder extends SearchViewHolder {
-    public final TextView titleView;
-    public final TextView summaryView;
-    public final PreferenceImageView iconView;
+
     public final Switch switchView;
 
     private final Context mContext;
 
-    private final String TAG = "SwitchViewHolder";
-
     public InlineSwitchViewHolder(View view, Context context) {
         super(view);
         mContext = context;
-        titleView = (TextView) view.findViewById(android.R.id.title);
-        summaryView = (TextView) view.findViewById(android.R.id.summary);
-        iconView = (PreferenceImageView) view.findViewById(android.R.id.icon);
         switchView = (Switch) view.findViewById(R.id.switchView);
     }
 
     @Override
     public void onBind(SearchFragment fragment, SearchResult result) {
+        super.onBind(fragment, result);
         if (mContext == null) {
             return;
         }
         final InlineSwitchPayload payload = (InlineSwitchPayload) result.payload;
         switchView.setChecked(payload.getSwitchValue(mContext));
-
         switchView.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
             @Override
             public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
@@ -63,9 +54,5 @@
                 payload.setSwitchValue(mContext, isChecked);
             }
         });
-
-        titleView.setText(result.title);
-        summaryView.setText(result.summary);
-        iconView.setImageDrawable(result.icon);
     }
 }
diff --git a/src/com/android/settings/search2/InstalledAppResultLoader.java b/src/com/android/settings/search2/InstalledAppResultLoader.java
index 449e52c..0b828a3 100644
--- a/src/com/android/settings/search2/InstalledAppResultLoader.java
+++ b/src/com/android/settings/search2/InstalledAppResultLoader.java
@@ -20,6 +20,7 @@
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
 import android.net.Uri;
 import android.os.UserHandle;
@@ -27,7 +28,11 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 
+import com.android.settings.R;
+import com.android.settings.applications.ManageApplications;
 import com.android.settings.applications.PackageManagerWrapper;
+import com.android.settings.dashboard.SiteMapManager;
+import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.utils.AsyncLoader;
 
 import java.util.ArrayList;
@@ -41,14 +46,21 @@
 
     private static final int NAME_NO_MATCH = -1;
     private static final int NAME_EXACT_MATCH = 0;
+    private static final Intent LAUNCHER_PROBE = new Intent(Intent.ACTION_MAIN)
+            .addCategory(Intent.CATEGORY_LAUNCHER);
 
+    private List<String> mBreadcrumb;
+    private SiteMapManager mSiteMapManager;
     private final String mQuery;
     private final UserManager mUserManager;
     private final PackageManagerWrapper mPackageManager;
 
+
     public InstalledAppResultLoader(Context context, PackageManagerWrapper pmWrapper,
             String query) {
         super(context);
+        mSiteMapManager = FeatureFactory.getFactory(context)
+                .getSearchFeatureProvider().getSiteMapManager();
         mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
         mPackageManager = pmWrapper;
         mQuery = query;
@@ -67,7 +79,7 @@
                                     | (user.isAdmin() ? PackageManager.MATCH_ANY_USER : 0),
                             user.id);
             for (ApplicationInfo info : apps) {
-                if (info.isSystemApp()) {
+                if (!shouldIncludeAsCandidate(info, user)) {
                     continue;
                 }
                 final CharSequence label = info.loadLabel(pm);
@@ -83,6 +95,7 @@
                 builder.addIcon(info.loadIcon(pm))
                         .addTitle(info.loadLabel(pm))
                         .addRank(wordDiff)
+                        .addBreadcrumbs(getBreadCrumb())
                         .addPayload(new IntentPayload(intent));
                 results.add(builder.build());
             }
@@ -91,6 +104,22 @@
         return results;
     }
 
+    private boolean shouldIncludeAsCandidate(ApplicationInfo info, UserInfo user) {
+        if ((info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0
+                || (info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+            return true;
+        }
+        final Intent launchIntent = new Intent(LAUNCHER_PROBE)
+                .setPackage(info.packageName);
+        final List<ResolveInfo> intents = mPackageManager.queryIntentActivitiesAsUser(
+                launchIntent,
+                PackageManager.MATCH_DISABLED_COMPONENTS
+                        | PackageManager.MATCH_DIRECT_BOOT_AWARE
+                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+                user.id);
+        return intents != null && intents.size() != 0;
+    }
+
     @Override
     protected void onDiscardResult(List<SearchResult> result) {
 
@@ -107,7 +136,7 @@
      * perfectly, and larger values means they are less similar.
      * <p/>
      * Example:
-     * appName: Abcde, query: Abcde, Returns NAME_EXACT_MATCH
+     * appName: Abcde, query: Abcde, Returns {@link #NAME_EXACT_MATCH}
      * appName: Abcde, query: ade, Returns 2
      * appName: Abcde, query: ae, Returns 3
      * appName: Abcde, query: ea, Returns NAME_NO_MATCH
@@ -136,4 +165,14 @@
         // to infinity.
         return valueText.length - queryTokens.length;
     }
+
+    private List<String> getBreadCrumb() {
+        if (mBreadcrumb == null || mBreadcrumb.isEmpty()) {
+            final Context context = getContext();
+            mBreadcrumb = mSiteMapManager.buildBreadCrumb(
+                    context, ManageApplications.class.getName(),
+                    context.getString(R.string.applications_settings));
+        }
+        return mBreadcrumb;
+    }
 }
diff --git a/src/com/android/settings/search2/IntentSearchViewHolder.java b/src/com/android/settings/search2/IntentSearchViewHolder.java
index c9952fa..d02ef1c 100644
--- a/src/com/android/settings/search2/IntentSearchViewHolder.java
+++ b/src/com/android/settings/search2/IntentSearchViewHolder.java
@@ -16,10 +16,6 @@
 package com.android.settings.search2;
 
 import android.view.View;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.android.settings.R;
 
 /**
  * ViewHolder for intent based search results.
@@ -27,25 +23,14 @@
  */
 public class IntentSearchViewHolder extends SearchViewHolder {
 
-    public final TextView titleView;
-    public final TextView summaryView;
-    public final ImageView iconView;
-
     public IntentSearchViewHolder(View view) {
         super(view);
-        titleView = (TextView) view.findViewById(android.R.id.title);
-        summaryView = (TextView) view.findViewById(android.R.id.summary);
-        iconView = (ImageView) view.findViewById(android.R.id.icon);
     }
 
     @Override
     public void onBind(final SearchFragment fragment, final SearchResult result) {
-        titleView.setText(result.title);
-        summaryView.setText(result.summary);
-        iconView.setImageDrawable(result.icon);
-        if (result.icon == null) {
-            iconView.setBackgroundResource(R.drawable.empty_icon);
-        }
+        super.onBind(fragment, result);
+
         itemView.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
diff --git a/src/com/android/settings/search2/ResultPayload.java b/src/com/android/settings/search2/ResultPayload.java
index 3842def..4294234 100644
--- a/src/com/android/settings/search2/ResultPayload.java
+++ b/src/com/android/settings/search2/ResultPayload.java
@@ -29,7 +29,7 @@
 public abstract class ResultPayload implements Parcelable {
 
     @IntDef({PayloadType.INLINE_SLIDER, PayloadType.INLINE_SWITCH,
-            PayloadType.INTENT})
+            PayloadType.INTENT, PayloadType.SAVED_QUERY})
     @Retention(RetentionPolicy.SOURCE)
     public @interface PayloadType {
         /**
@@ -46,6 +46,11 @@
          * Result is a inline widget, using a toggle widget as UI.
          */
         int INLINE_SWITCH = 2;
+
+        /**
+         * Result is a recently saved query.
+         */
+        int SAVED_QUERY = 3;
     }
 
     @IntDef({SettingsSource.UNKNOWN, SettingsSource.SYSTEM, SettingsSource.SECURE,
@@ -59,5 +64,6 @@
     }
 
 
-    @ResultPayload.PayloadType public abstract int getType();
+    @ResultPayload.PayloadType
+    public abstract int getType();
 }
diff --git a/src/com/android/settings/search2/SavedQueryLoader.java b/src/com/android/settings/search2/SavedQueryLoader.java
new file mode 100644
index 0000000..b034b44
--- /dev/null
+++ b/src/com/android/settings/search2/SavedQueryLoader.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 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.search2;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.support.annotation.VisibleForTesting;
+
+import com.android.settings.search.IndexDatabaseHelper;
+import com.android.settings.search.IndexDatabaseHelper.SavedQueriesColumns;
+import com.android.settings.utils.AsyncLoader;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Loader for recently searched queries.
+ */
+public class SavedQueryLoader extends AsyncLoader<List<SearchResult>> {
+
+    // Max number of proposed suggestions
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    static final int MAX_PROPOSED_SUGGESTIONS = 5;
+
+    private final SQLiteDatabase mDatabase;
+
+    public SavedQueryLoader(Context context) {
+        super(context);
+        mDatabase = IndexDatabaseHelper.getInstance(context).getReadableDatabase();
+    }
+
+    @Override
+    protected void onDiscardResult(List<SearchResult> result) {
+
+    }
+
+    @Override
+    public List<SearchResult> loadInBackground() {
+        Cursor cursor = mDatabase.query(IndexDatabaseHelper.Tables.TABLE_SAVED_QUERIES /* table */,
+                new String[]{SavedQueriesColumns.QUERY} /* columns */,
+                null /* selection */,
+                null /* selectionArgs */,
+                null /* groupBy */,
+                null /* having */,
+                "rowId DESC" /* orderBy */,
+                String.valueOf(MAX_PROPOSED_SUGGESTIONS) /* limit */);
+        return convertCursorToResult(cursor);
+    }
+
+    private List<SearchResult> convertCursorToResult(Cursor cursor) {
+        final List<SearchResult> results = new ArrayList<>();
+        while (cursor.moveToNext()) {
+            final SavedQueryPayload payload = new SavedQueryPayload(
+                    cursor.getString(cursor.getColumnIndex(SavedQueriesColumns.QUERY)));
+            results.add(new SearchResult.Builder()
+                    .addTitle(payload.query)
+                    .addPayload(payload)
+                    .build());
+        }
+        return results;
+    }
+}
diff --git a/src/com/android/settings/search2/SavedQueryPayload.java b/src/com/android/settings/search2/SavedQueryPayload.java
new file mode 100644
index 0000000..6316894
--- /dev/null
+++ b/src/com/android/settings/search2/SavedQueryPayload.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 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.search2;
+
+import android.os.Parcel;
+import android.support.annotation.VisibleForTesting;
+
+/**
+ * {@link ResultPayload} for saved query.
+ */
+public class SavedQueryPayload extends ResultPayload {
+
+    public final String query;
+
+    public SavedQueryPayload(String query) {
+        this.query = query;
+    }
+
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    SavedQueryPayload(Parcel in) {
+        query = in.readString();
+    }
+
+    @Override
+    public int getType() {
+        return PayloadType.SAVED_QUERY;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(query);
+    }
+
+    public static final Creator<SavedQueryPayload> CREATOR = new Creator<SavedQueryPayload>() {
+        @Override
+        public SavedQueryPayload createFromParcel(Parcel in) {
+            return new SavedQueryPayload(in);
+        }
+
+        @Override
+        public SavedQueryPayload[] newArray(int size) {
+            return new SavedQueryPayload[size];
+        }
+    };
+}
diff --git a/src/com/android/settings/search2/SavedQueryRecorder.java b/src/com/android/settings/search2/SavedQueryRecorder.java
new file mode 100644
index 0000000..e2325e8
--- /dev/null
+++ b/src/com/android/settings/search2/SavedQueryRecorder.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2017 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.search2;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
+import android.util.Log;
+
+import com.android.settings.search.IndexDatabaseHelper;
+import com.android.settings.utils.AsyncLoader;
+
+import static com.android.settings.search.IndexDatabaseHelper.Tables.TABLE_SAVED_QUERIES;
+
+/**
+ * A background task to update saved queries.
+ */
+public class SavedQueryRecorder extends AsyncLoader<Void> {
+
+    private static final String LOG_TAG = "SavedQueryRecorder";
+
+    // Max number of saved search queries (who will be used for proposing suggestions)
+    private static long MAX_SAVED_SEARCH_QUERY = 64;
+
+    private final String mQuery;
+
+    public SavedQueryRecorder(Context context, String query) {
+        super(context);
+        mQuery = query;
+    }
+
+    @Override
+    protected void onDiscardResult(Void result) {
+
+    }
+
+    @Override
+    public Void loadInBackground() {
+        final long now = System.currentTimeMillis();
+
+        final ContentValues values = new ContentValues();
+        values.put(IndexDatabaseHelper.SavedQueriesColumns.QUERY, mQuery);
+        values.put(IndexDatabaseHelper.SavedQueriesColumns.TIME_STAMP, now);
+
+        final SQLiteDatabase database = getWritableDatabase();
+        if (database == null) {
+            return null;
+        }
+
+        long lastInsertedRowId;
+        try {
+            // First, delete all saved queries that are the same
+            database.delete(TABLE_SAVED_QUERIES,
+                    IndexDatabaseHelper.SavedQueriesColumns.QUERY + " = ?",
+                    new String[]{mQuery});
+
+            // Second, insert the saved query
+            lastInsertedRowId = database.insertOrThrow(TABLE_SAVED_QUERIES, null, values);
+
+            // Last, remove "old" saved queries
+            final long delta = lastInsertedRowId - MAX_SAVED_SEARCH_QUERY;
+            if (delta > 0) {
+                int count = database.delete(TABLE_SAVED_QUERIES,
+                        "rowId <= ?",
+                        new String[]{Long.toString(delta)});
+                Log.d(LOG_TAG, "Deleted '" + count + "' saved Search query(ies)");
+            }
+        } catch (Exception e) {
+            Log.d(LOG_TAG, "Cannot update saved Search queries", e);
+        }
+        return null;
+    }
+
+    private SQLiteDatabase getWritableDatabase() {
+        try {
+            return IndexDatabaseHelper.getInstance(getContext()).getWritableDatabase();
+        } catch (SQLiteException e) {
+            Log.e(LOG_TAG, "Cannot open writable database", e);
+            return null;
+        }
+    }
+}
diff --git a/src/com/android/settings/search2/SavedQueryViewHolder.java b/src/com/android/settings/search2/SavedQueryViewHolder.java
new file mode 100644
index 0000000..a32ed05
--- /dev/null
+++ b/src/com/android/settings/search2/SavedQueryViewHolder.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 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.search2;
+
+import android.view.View;
+import android.widget.TextView;
+
+public class SavedQueryViewHolder extends SearchViewHolder {
+
+    public final TextView titleView;
+
+    public SavedQueryViewHolder(View view) {
+        super(view);
+        titleView = (TextView) view.findViewById(android.R.id.title);
+    }
+
+    @Override
+    public void onBind(SearchFragment fragment, SearchResult result) {
+        titleView.setText(result.title);
+        itemView.setOnClickListener(v -> {
+            fragment.onSavedQueryClicked(result.title);
+        });
+    }
+}
diff --git a/src/com/android/settings/search2/SearchFeatureProvider.java b/src/com/android/settings/search2/SearchFeatureProvider.java
index 8a616a7..a9be5a1 100644
--- a/src/com/android/settings/search2/SearchFeatureProvider.java
+++ b/src/com/android/settings/search2/SearchFeatureProvider.java
@@ -19,6 +19,8 @@
 import android.content.Context;
 import android.view.Menu;
 
+import com.android.settings.dashboard.SiteMapManager;
+
 /**
  * FeatureProvider for Settings Search
  */
@@ -48,11 +50,21 @@
     InstalledAppResultLoader getInstalledAppSearchLoader(Context context, String query);
 
     /**
+     * Returns a new loader to get all recently saved queries search terms.
+     */
+    SavedQueryLoader getSavedQueryLoader(Context context);
+
+    /**
      * Returns the manager for indexing Settings data.
      */
     DatabaseIndexingManager getIndexingManager(Context context);
 
     /**
+     * Returns the manager for looking up breadcrumbs.
+     */
+    SiteMapManager getSiteMapManager();
+
+    /**
      * Updates the Settings indexes
      */
     void updateIndex(Context context);
diff --git a/src/com/android/settings/search2/SearchFeatureProviderImpl.java b/src/com/android/settings/search2/SearchFeatureProviderImpl.java
index a76d905..b575b15 100644
--- a/src/com/android/settings/search2/SearchFeatureProviderImpl.java
+++ b/src/com/android/settings/search2/SearchFeatureProviderImpl.java
@@ -25,6 +25,7 @@
 
 import com.android.settings.R;
 import com.android.settings.applications.PackageManagerWrapperImpl;
+import com.android.settings.dashboard.SiteMapManager;
 import com.android.settings.search.Index;
 
 /**
@@ -35,6 +36,7 @@
     private static final String TAG = "SearchFeatureProvider";
 
     private DatabaseIndexingManager mDatabaseIndexingManager;
+    private SiteMapManager mSiteMapManager;
 
     @Override
     public boolean isEnabled(Context context) {
@@ -73,6 +75,11 @@
     }
 
     @Override
+    public SavedQueryLoader getSavedQueryLoader(Context context) {
+        return new SavedQueryLoader(context);
+    }
+
+    @Override
     public DatabaseIndexingManager getIndexingManager(Context context) {
         if (mDatabaseIndexingManager == null) {
             mDatabaseIndexingManager = new DatabaseIndexingManager(context.getApplicationContext(),
@@ -81,6 +88,13 @@
         return mDatabaseIndexingManager;
     }
 
+    public SiteMapManager getSiteMapManager() {
+        if (mSiteMapManager == null) {
+            mSiteMapManager = new SiteMapManager();
+        }
+        return mSiteMapManager;
+    }
+
     @Override
     public void updateIndex(Context context) {
         long indexStartTime = System.currentTimeMillis();
diff --git a/src/com/android/settings/search2/SearchFragment.java b/src/com/android/settings/search2/SearchFragment.java
index e26f5ed..b688a45 100644
--- a/src/com/android/settings/search2/SearchFragment.java
+++ b/src/com/android/settings/search2/SearchFragment.java
@@ -52,8 +52,9 @@
     private static final String STATE_RESULT_CLICK_COUNT = "state_result_click_count";
 
     // Loader IDs
-    private static final int LOADER_ID_DATABASE = 0;
-    private static final int LOADER_ID_INSTALLED_APPS = 1;
+    private static final int LOADER_ID_RECENTS = 0;
+    private static final int LOADER_ID_DATABASE = 1;
+    private static final int LOADER_ID_INSTALLED_APPS = 2;
 
     // Logging
     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
@@ -61,6 +62,10 @@
 
     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
     String mQuery;
+
+    private final SaveQueryRecorderCallback mSaveQueryRecorderCallback =
+            new SaveQueryRecorderCallback();
+
     private boolean mNeverEnteredQuery = true;
     private int mResultClickCount;
     private MetricsFeatureProvider mMetricsFeatureProvider;
@@ -68,6 +73,7 @@
 
     private SearchResultsAdapter mSearchAdapter;
     private RecyclerView mResultsRecyclerView;
+    private SearchView mSearchView;
 
     @Override
     public int getMetricsCategory() {
@@ -86,18 +92,21 @@
         super.onCreate(savedInstanceState);
         setHasOptionsMenu(true);
         mSearchAdapter = new SearchResultsAdapter(this);
+        final LoaderManager loaderManager = getLoaderManager();
         if (savedInstanceState != null) {
             mQuery = savedInstanceState.getString(STATE_QUERY);
             mNeverEnteredQuery = savedInstanceState.getBoolean(STATE_NEVER_ENTERED_QUERY);
             mResultClickCount = savedInstanceState.getInt(STATE_RESULT_CLICK_COUNT);
-            final LoaderManager loaderManager = getLoaderManager();
             loaderManager.initLoader(LOADER_ID_DATABASE, null, this);
             loaderManager.initLoader(LOADER_ID_INSTALLED_APPS, null, this);
+        } else {
+            loaderManager.initLoader(LOADER_ID_RECENTS, null, this);
         }
 
         final Activity activity = getActivity();
         final ActionBar actionBar = activity.getActionBar();
-        actionBar.setCustomView(makeSearchView(actionBar, mQuery));
+        mSearchView = makeSearchView(actionBar, mQuery);
+        actionBar.setCustomView(mSearchView);
         actionBar.setDisplayShowCustomEnabled(true);
         actionBar.setDisplayShowTitleEnabled(false);
 
@@ -151,7 +160,10 @@
         mSearchAdapter.clearResults();
 
         if (TextUtils.isEmpty(mQuery)) {
-            getLoaderManager().destroyLoader(LOADER_ID_DATABASE);
+            final LoaderManager loaderManager = getLoaderManager();
+            loaderManager.destroyLoader(LOADER_ID_DATABASE);
+            loaderManager.destroyLoader(LOADER_ID_INSTALLED_APPS);
+            loaderManager.restartLoader(LOADER_ID_RECENTS, null /* args */, this /* callback */);
         } else {
             restartLoaders();
         }
@@ -161,6 +173,10 @@
 
     @Override
     public boolean onQueryTextSubmit(String query) {
+        // Save submitted query.
+        getLoaderManager().restartLoader(SaveQueryRecorderCallback.LOADER_ID_SAVE_QUERY_TASK, null,
+                mSaveQueryRecorderCallback);
+
         return true;
     }
 
@@ -173,6 +189,8 @@
                 return mSearchFeatureProvider.getDatabaseSearchLoader(activity, mQuery);
             case LOADER_ID_INSTALLED_APPS:
                 return mSearchFeatureProvider.getInstalledAppSearchLoader(activity, mQuery);
+            case LOADER_ID_RECENTS:
+                return mSearchFeatureProvider.getSavedQueryLoader(activity);
             default:
                 return null;
         }
@@ -191,6 +209,12 @@
         mResultClickCount++;
     }
 
+    public void onSavedQueryClicked(CharSequence query) {
+        final String queryString = query.toString();
+        mSearchView.setQuery(queryString, false /* submit */);
+        onQueryTextChange(queryString);
+    }
+
     private void restartLoaders() {
         final LoaderManager loaderManager = getLoaderManager();
         loaderManager.restartLoader(LOADER_ID_DATABASE, null /* args */, this /* callback */);
@@ -207,4 +231,25 @@
         searchView.setLayoutParams(lp);
         return searchView;
     }
+
+    private class SaveQueryRecorderCallback implements LoaderManager.LoaderCallbacks<Void> {
+        // TODO: make a generic background task manager to handle one-off tasks like this one.
+
+        private static final int LOADER_ID_SAVE_QUERY_TASK = 0;
+
+        @Override
+        public Loader<Void> onCreateLoader(int id, Bundle args) {
+            return new SavedQueryRecorder(getActivity(), mQuery);
+        }
+
+        @Override
+        public void onLoadFinished(Loader<Void> loader, Void data) {
+
+        }
+
+        @Override
+        public void onLoaderReset(Loader<Void> loader) {
+
+        }
+    }
 }
diff --git a/src/com/android/settings/search2/SearchResultsAdapter.java b/src/com/android/settings/search2/SearchResultsAdapter.java
index c318b41..999a485 100644
--- a/src/com/android/settings/search2/SearchResultsAdapter.java
+++ b/src/com/android/settings/search2/SearchResultsAdapter.java
@@ -49,13 +49,16 @@
         final Context context = parent.getContext();
         final LayoutInflater inflater = LayoutInflater.from(context);
         final View view;
-        switch(viewType) {
+        switch (viewType) {
             case PayloadType.INTENT:
                 view = inflater.inflate(R.layout.search_intent_item, parent, false);
                 return new IntentSearchViewHolder(view);
             case PayloadType.INLINE_SWITCH:
                 view = inflater.inflate(R.layout.search_inline_switch_item, parent, false);
                 return new InlineSwitchViewHolder(view, context);
+            case PayloadType.SAVED_QUERY:
+                view = inflater.inflate(R.layout.search_saved_query_item, parent, false);
+                return new SavedQueryViewHolder(view);
             default:
                 return null;
         }
diff --git a/src/com/android/settings/search2/SearchViewHolder.java b/src/com/android/settings/search2/SearchViewHolder.java
index 315ec65..0b10b06 100644
--- a/src/com/android/settings/search2/SearchViewHolder.java
+++ b/src/com/android/settings/search2/SearchViewHolder.java
@@ -15,8 +15,14 @@
  */
 package com.android.settings.search2;
 
+import android.content.Context;
 import android.support.v7.widget.RecyclerView;
+import android.text.TextUtils;
 import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.settings.R;
 
 /**
  * The ViewHolder for the Search RecyclerView.
@@ -25,9 +31,47 @@
  */
 public abstract class SearchViewHolder extends RecyclerView.ViewHolder {
 
+    public final TextView titleView;
+    public final TextView summaryView;
+    public final TextView breadcrumbView;
+    public final ImageView iconView;
+
     public SearchViewHolder(View view) {
         super(view);
+        titleView = (TextView) view.findViewById(android.R.id.title);
+        summaryView = (TextView) view.findViewById(android.R.id.summary);
+        iconView = (ImageView) view.findViewById(android.R.id.icon);
+        breadcrumbView = (TextView) view.findViewById(R.id.breadcrumb);
     }
 
-    public abstract void onBind(SearchFragment fragment, SearchResult result);
+    public void onBind(SearchFragment fragment, SearchResult result) {
+        titleView.setText(result.title);
+        if (TextUtils.isEmpty(result.summary)) {
+            summaryView.setVisibility(View.GONE);
+        } else {
+            summaryView.setText(result.summary);
+            summaryView.setVisibility(View.VISIBLE);
+        }
+        iconView.setImageDrawable(result.icon);
+        if (result.icon == null) {
+            iconView.setBackgroundResource(R.drawable.empty_icon);
+        }
+        bindBreadcrumbView(result);
+    }
+
+    private void bindBreadcrumbView(SearchResult result) {
+        if (result.breadcrumbs == null || result.breadcrumbs.isEmpty()) {
+            breadcrumbView.setVisibility(View.GONE);
+            return;
+        }
+        final Context context = breadcrumbView.getContext();
+        String breadcrumb = result.breadcrumbs.get(0);
+        final int count = result.breadcrumbs.size();
+        for (int i = 1; i < count; i++) {
+            breadcrumb = context.getString(R.string.search_breadcrumb_connector,
+                    breadcrumb, result.breadcrumbs.get(i));
+        }
+        breadcrumbView.setText(breadcrumb);
+        breadcrumbView.setVisibility(View.VISIBLE);
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/settings/search2/XmlParserUtils.java b/src/com/android/settings/search2/XmlParserUtils.java
index 748d4b0..90b1c1f 100644
--- a/src/com/android/settings/search2/XmlParserUtils.java
+++ b/src/com/android/settings/search2/XmlParserUtils.java
@@ -24,9 +24,6 @@
 
 import com.android.settings.R;
 
-import java.text.Normalizer;
-import java.util.regex.Pattern;
-
 /**
  * Utility class to parse elements of XML preferences
  */
@@ -74,6 +71,14 @@
         return getData(context, attrs, R.styleable.Preference, R.styleable.Preference_keywords);
     }
 
+    /**
+     * Returns the fragment name if this preference launches a child fragment.
+     */
+    public static String getDataChildFragment(Context context, AttributeSet attrs) {
+        return getData(context, attrs, R.styleable.Preference,
+                R.styleable.Preference_android_fragment);
+    }
+
     private static String getData(Context context, AttributeSet set, int[] attrs, int resId) {
         final TypedArray sa = context.obtainStyledAttributes(set, attrs);
         final TypedValue tv = sa.peekValue(resId);
diff --git a/src/com/android/settings/webview/UserPackageWrapper.java b/src/com/android/settings/webview/UserPackageWrapper.java
new file mode 100644
index 0000000..8fbb10c
--- /dev/null
+++ b/src/com/android/settings/webview/UserPackageWrapper.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 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.webview;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.UserInfo;
+import android.webkit.UserPackage;
+
+import java.util.List;
+
+/**
+ * Wrapper class around android.webkit.UserPackage - to be able to use UserPackage in Robolectric
+ * tests (such tests currently don't support mocking hidden classes).
+ */
+interface UserPackageWrapper {
+    UserInfo getUserInfo();
+    PackageInfo getPackageInfo();
+    boolean isEnabledPackage();
+    boolean isInstalledPackage();
+}
diff --git a/src/com/android/settings/webview/UserPackageWrapperImpl.java b/src/com/android/settings/webview/UserPackageWrapperImpl.java
new file mode 100644
index 0000000..1ea7c2e
--- /dev/null
+++ b/src/com/android/settings/webview/UserPackageWrapperImpl.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2017 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.webview;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.UserInfo;
+import android.webkit.UserPackage;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Default implementation of UserPackageWrapper.
+ */
+class UserPackageWrapperImpl implements UserPackageWrapper {
+    private final UserPackage mUserPackage;
+
+    UserPackageWrapperImpl(UserPackage userPackage) {
+        mUserPackage = userPackage;
+    }
+
+    public UserInfo getUserInfo() {
+        return mUserPackage.getUserInfo();
+    }
+
+    public PackageInfo getPackageInfo() {
+        return mUserPackage.getPackageInfo();
+    }
+
+    public boolean isEnabledPackage() {
+        return mUserPackage.isEnabledPackage();
+    }
+
+    public boolean isInstalledPackage() {
+        return mUserPackage.isInstalledPackage();
+    }
+}
diff --git a/src/com/android/settings/webview/WebViewAppListAdapter.java b/src/com/android/settings/webview/WebViewAppListAdapter.java
new file mode 100644
index 0000000..85dbf7c
--- /dev/null
+++ b/src/com/android/settings/webview/WebViewAppListAdapter.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2017 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.webview;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.support.annotation.VisibleForTesting;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+
+import com.android.settings.applications.AppViewHolder;
+import com.android.settings.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Custom list adapter for Settings to choose WebView package.
+ * Note: parts of this class are copied from AppPicker.java.
+ */
+class WebViewAppListAdapter extends ArrayAdapter<WebViewApplicationInfo> {
+    private final LayoutInflater mInflater;
+
+    public WebViewAppListAdapter(Context context,
+            WebViewUpdateServiceWrapper webviewUpdateServiceWrapper) {
+        super(context, 0);
+        mInflater = LayoutInflater.from(context);
+
+        final List<WebViewApplicationInfo> packageInfoList =
+                new ArrayList<WebViewApplicationInfo>();
+        List<ApplicationInfo> pkgs =
+                webviewUpdateServiceWrapper.getValidWebViewApplicationInfos(getContext());
+        for (ApplicationInfo ai : pkgs) {
+            WebViewApplicationInfo info = new WebViewApplicationInfo(ai,
+                    ai.loadLabel(context.getPackageManager()).toString(),
+                    getDisabledReason(webviewUpdateServiceWrapper, context, ai.packageName));
+            packageInfoList.add(info);
+        }
+        addAll(packageInfoList);
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        // A ViewHolder keeps references to children views to avoid unnecessary calls
+        // to findViewById() on each row.
+        AppViewHolder holder = AppViewHolder.createOrRecycle(mInflater, convertView);
+        convertView = holder.rootView;
+        WebViewApplicationInfo info = getItem(position);
+        holder.appName.setText(info.label);
+        if (info.info != null) {
+            holder.appIcon.setImageDrawable(info.info.loadIcon(getContext().getPackageManager()));
+            // Allow disable-description to wrap - to be able to show several lines of text in case
+            // a package is disabled/uninstalled for several users.
+            holder.summary.setSingleLine(false);
+            if (!isEnabled(position)) {
+                holder.summary.setText(info.disabledReason);
+            } else {
+                holder.summary.setText("");
+            }
+        } else {
+            holder.appIcon.setImageDrawable(null);
+            holder.summary.setText("");
+        }
+        holder.disabled.setVisibility(View.GONE);
+        // Only allow a package to be chosen if it is enabled and installed for all users.
+        convertView.setEnabled(isEnabled(position));
+        return convertView;
+    }
+
+    @Override
+    public boolean isEnabled (int position) {
+        WebViewApplicationInfo info = getItem(position);
+        return info.disabledReason == null;
+    }
+
+    @Override
+    public boolean areAllItemsEnabled() {
+        int numItems = getCount();
+        for (int n = 0; n < numItems; n++) {
+            if (!isEnabled(n)) return false;
+        }
+        return true;
+    }
+
+    /**
+     * Returns the reason why a package cannot be used as WebView implementation.
+     * This is either because of it being disabled, uninstalled, or hidden for any user.
+     */
+    @VisibleForTesting
+    static String getDisabledReason(WebViewUpdateServiceWrapper webviewUpdateServiceWrapper,
+            Context context, String packageName) {
+        StringBuilder disabledReason = new StringBuilder();
+        List<UserPackageWrapper> userPackages =
+                webviewUpdateServiceWrapper.getPackageInfosAllUsers(context, packageName);
+        for (UserPackageWrapper userPackage : userPackages) {
+            if (!userPackage.isInstalledPackage()) {
+                // Package uninstalled/hidden
+                disabledReason.append(context.getString(
+                        R.string.webview_uninstalled_for_user, userPackage.getUserInfo().name));
+            } else if (!userPackage.isEnabledPackage()) {
+                // Package disabled
+                disabledReason.append(context.getString(
+                    R.string.webview_disabled_for_user, userPackage.getUserInfo().name));
+            }
+        }
+        if (disabledReason.length() == 0) return null;
+        return disabledReason.toString();
+    }
+}
+
diff --git a/src/com/android/settings/webview/WebViewAppPicker.java b/src/com/android/settings/webview/WebViewAppPicker.java
new file mode 100644
index 0000000..2417b00
--- /dev/null
+++ b/src/com/android/settings/webview/WebViewAppPicker.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2017 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.webview;
+
+import android.app.ListActivity;
+import android.content.pm.PackageInfo;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.support.annotation.VisibleForTesting;
+import android.util.Log;
+import android.view.View;
+import android.webkit.WebViewFactory;
+import android.widget.ListView;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settings.core.instrumentation.Instrumentable;
+import com.android.settings.core.instrumentation.VisibilityLoggerMixin;
+
+public class WebViewAppPicker extends ListActivity implements Instrumentable {
+    private static final String TAG = WebViewAppPicker.class.getSimpleName();
+    private WebViewAppListAdapter mAdapter;
+    private WebViewUpdateServiceWrapper mWebViewUpdateServiceWrapper;
+
+    private final VisibilityLoggerMixin mVisibilityLoggerMixin =
+            new VisibilityLoggerMixin(getMetricsCategory());
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        if (mWebViewUpdateServiceWrapper == null) {
+            setWebViewUpdateServiceWrapper(createDefaultWebViewUpdateServiceWrapper());
+        }
+        mAdapter = new WebViewAppListAdapter(this, mWebViewUpdateServiceWrapper);
+        setListAdapter(mAdapter);
+
+        mVisibilityLoggerMixin.onAttach(this);
+    }
+
+    @Override
+    protected void onListItemClick(ListView l, View v, int position, long id) {
+        WebViewApplicationInfo app = mAdapter.getItem(position);
+
+        if (mWebViewUpdateServiceWrapper.setWebViewProvider(app.info.packageName)) {
+            Intent intent = new Intent();
+            intent.setAction(app.info.packageName);
+            setResult(RESULT_OK, intent);
+        } else {
+            mWebViewUpdateServiceWrapper.showInvalidChoiceToast(this);
+        }
+        finish();
+    }
+
+    private WebViewUpdateServiceWrapper createDefaultWebViewUpdateServiceWrapper() {
+        return new WebViewUpdateServiceWrapper();
+    }
+
+    @VisibleForTesting
+    void setWebViewUpdateServiceWrapper(WebViewUpdateServiceWrapper wvusWrapper) {
+        mWebViewUpdateServiceWrapper = wvusWrapper;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mVisibilityLoggerMixin.onResume();
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        mVisibilityLoggerMixin.onPause();
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        return MetricsEvent.WEBVIEW_IMPLEMENTATION;
+    }
+}
diff --git a/src/com/android/settings/webview/WebViewAppPreferenceController.java b/src/com/android/settings/webview/WebViewAppPreferenceController.java
new file mode 100644
index 0000000..eb5467a
--- /dev/null
+++ b/src/com/android/settings/webview/WebViewAppPreferenceController.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2017 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.webview;
+
+import android.app.Activity;
+import android.content.pm.PackageInfo;
+import android.content.Context;
+import android.content.Intent;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.DevelopmentSettings;
+import com.android.settings.core.PreferenceController;
+
+public class WebViewAppPreferenceController extends PreferenceController {
+
+    private static final String WEBVIEW_APP_KEY = "select_webview_provider";
+
+    private Context mContext;
+    private Preference mPreference;
+    private final WebViewUpdateServiceWrapper mWebViewUpdateServiceWrapper;
+
+    public WebViewAppPreferenceController(Context context) {
+        this(context, new WebViewUpdateServiceWrapper());
+    }
+
+    public WebViewAppPreferenceController(Context context,
+            WebViewUpdateServiceWrapper webviewUpdateServiceWrapper) {
+        super(context);
+        mContext = context;
+        mWebViewUpdateServiceWrapper = webviewUpdateServiceWrapper;
+    }
+
+    @Override
+    public boolean handlePreferenceTreeClick(Preference preference) {
+        if (getPreferenceKey().equals(preference.getKey())) {
+            return true;
+        }
+        return false;
+    }
+
+    public Intent getActivityIntent() {
+        return new Intent(mContext, WebViewAppPicker.class);
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        mPreference.setSummary(getCurrentWebViewPackageLabel(mContext));
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        if (isAvailable()) {
+            mPreference = screen.findPreference(WEBVIEW_APP_KEY);
+        }
+    }
+
+    /**
+     * Handle the return-value from the WebViewAppPicker Activity.
+     */
+    public void onActivityResult(int resultCode, Intent data) {
+        if (resultCode == Activity.RESULT_OK) {
+            updateState(null);
+        }
+    }
+
+    private String getCurrentWebViewPackageLabel(Context context) {
+        PackageInfo webViewPackage = mWebViewUpdateServiceWrapper.getCurrentWebViewPackage();
+        if (webViewPackage == null) return "";
+        return webViewPackage.applicationInfo.loadLabel(context.getPackageManager()).toString();
+    }
+
+
+    @Override
+    public String getPreferenceKey() {
+        return WEBVIEW_APP_KEY;
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+
+    public void enablePreference(boolean enabled) {
+        if (isAvailable()) {
+            mPreference.setEnabled(enabled);
+        }
+    }
+}
diff --git a/src/com/android/settings/webview/WebViewApplicationInfo.java b/src/com/android/settings/webview/WebViewApplicationInfo.java
new file mode 100644
index 0000000..6879c59
--- /dev/null
+++ b/src/com/android/settings/webview/WebViewApplicationInfo.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 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.webview;
+
+import android.content.pm.ApplicationInfo;
+
+final class WebViewApplicationInfo {
+    final ApplicationInfo info;
+    final String label;
+    final String disabledReason;
+
+    public WebViewApplicationInfo(ApplicationInfo info, String label, String disabledReason) {
+        this.info = info;
+        this.label = label;
+        this.disabledReason = disabledReason;
+    }
+}
diff --git a/src/com/android/settings/webview/WebViewUpdateServiceWrapper.java b/src/com/android/settings/webview/WebViewUpdateServiceWrapper.java
new file mode 100644
index 0000000..b40be19
--- /dev/null
+++ b/src/com/android/settings/webview/WebViewUpdateServiceWrapper.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2017 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.webview;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.util.Log;
+import android.webkit.UserPackage;
+import android.webkit.WebViewFactory;
+import android.webkit.WebViewProviderInfo;
+import android.widget.Toast;
+
+import com.android.settings.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class WebViewUpdateServiceWrapper {
+    private static final String TAG = "WVUSWrapper";
+
+    public WebViewUpdateServiceWrapper() {}
+
+    /**
+     * Fetch the package currently used as WebView implementation.
+     */
+    public PackageInfo getCurrentWebViewPackage() {
+        try {
+            return WebViewFactory.getUpdateService().getCurrentWebViewPackage();
+        } catch (RemoteException e) {
+            Log.e(TAG, e.toString());
+        }
+        return null;
+    }
+
+    /**
+     * Fetches ApplicationInfo objects for all currently valid WebView packages.
+     * A WebView package is considered valid if it can be used as a WebView implementation. The
+     * validity of a package is not dependent on whether the package is installed/enabled.
+     */
+    public List<ApplicationInfo> getValidWebViewApplicationInfos(Context context) {
+        WebViewProviderInfo[] providers = null;
+        try {
+            providers = WebViewFactory.getUpdateService().getValidWebViewPackages();
+        } catch (RemoteException e) {
+        }
+        List<ApplicationInfo> pkgs = new ArrayList<>();
+        for (WebViewProviderInfo provider : providers) {
+            try {
+                pkgs.add(context.getPackageManager().getApplicationInfo(
+                        provider.packageName, PACKAGE_FLAGS));
+            } catch (PackageManager.NameNotFoundException e) {
+            }
+        }
+        return pkgs;
+    }
+
+    /**
+     * Change WebView provider to {@param packageName}.
+     * @return whether the change succeeded.
+     */
+    public boolean setWebViewProvider(String packageName) {
+        try {
+            return packageName.equals(
+                    WebViewFactory.getUpdateService().changeProviderAndSetting(packageName));
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException when trying to change provider to " + packageName, e);
+        }
+        return false;
+    }
+
+    /**
+     * Fetch PackageInfos for the package named {@param packageName} for all users on the device.
+     */
+    public List<UserPackageWrapper> getPackageInfosAllUsers(Context context, String packageName) {
+        List<UserPackageWrapper> userPackageWrappers = new ArrayList<>();
+        List<UserPackage> userPackages =
+                UserPackage.getPackageInfosAllUsers(context, packageName, PACKAGE_FLAGS);
+        for (UserPackage userPackage : userPackages) {
+            userPackageWrappers.add(new UserPackageWrapperImpl(userPackage));
+        }
+        return userPackageWrappers;
+    }
+
+    /**
+     * Show a toast to explain the chosen package can no longer be chosen.
+     */
+    public void showInvalidChoiceToast(Context context) {
+        // The user chose a package that became invalid since the list was last updated,
+        // show a Toast to explain the situation.
+        Toast toast = Toast.makeText(context,
+                R.string.select_webview_provider_toast_text, Toast.LENGTH_SHORT);
+        toast.show();
+    }
+
+    static final int PACKAGE_FLAGS = PackageManager.MATCH_ANY_USER;
+}
diff --git a/src/com/android/settings/widget/MasterSwitchController.java b/src/com/android/settings/widget/MasterSwitchController.java
new file mode 100644
index 0000000..53e5fe7
--- /dev/null
+++ b/src/com/android/settings/widget/MasterSwitchController.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017 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.widget;
+
+import android.support.v7.preference.Preference;
+
+/*
+ * The switch controller that is used to update the switch widget in the MasterSwitchPreference
+ * layout.
+ */
+public class MasterSwitchController extends SwitchWidgetController implements
+    Preference.OnPreferenceChangeListener {
+
+    private final MasterSwitchPreference mPreference;
+
+    public MasterSwitchController(MasterSwitchPreference preference) {
+        mPreference = preference;
+    }
+
+    @Override
+    public void updateTitle(boolean isChecked) {
+    }
+
+    @Override
+    public void startListening() {
+        mPreference.setOnPreferenceChangeListener(this);
+    }
+
+    @Override
+    public void stopListening() {
+        mPreference.setOnPreferenceChangeListener(null);
+    }
+
+    @Override
+    public void setChecked(boolean checked) {
+        mPreference.setChecked(checked);
+    }
+
+    @Override
+    public boolean isChecked() {
+        return mPreference.isChecked();
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        mPreference.setSwitchEnabled(enabled);
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        if (mListener != null) {
+            return mListener.onSwitchToggled((Boolean) newValue);
+        }
+        return false;
+    }
+}
diff --git a/src/com/android/settings/widget/MasterSwitchPreference.java b/src/com/android/settings/widget/MasterSwitchPreference.java
new file mode 100644
index 0000000..8130ca5
--- /dev/null
+++ b/src/com/android/settings/widget/MasterSwitchPreference.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2017 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.widget;
+
+import android.content.Context;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceViewHolder;
+import android.util.AttributeSet;
+import android.widget.CompoundButton;
+import android.widget.Switch;
+
+import com.android.settings.R;
+
+/**
+ * A custom preference that provides inline switch toggle. It has a mandatory field for title, and
+ * optional fields for icon and sub-text.
+ */
+public class MasterSwitchPreference extends Preference {
+
+    private Switch mSwitch;
+    private boolean mChecked;
+
+    public MasterSwitchPreference(Context context, AttributeSet attrs,
+            int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        init();
+    }
+
+    public MasterSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init();
+    }
+
+    public MasterSwitchPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    public MasterSwitchPreference(Context context) {
+        super(context);
+        init();
+    }
+
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder holder) {
+        super.onBindViewHolder(holder);
+        mSwitch = (Switch) holder.itemView.findViewById(R.id.switchWidget);
+        if (mSwitch != null) {
+            mSwitch.setChecked(mChecked);
+            mSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+                @Override
+                public void onCheckedChanged(CompoundButton button, boolean isChecked) {
+                    if (!callChangeListener(isChecked)) {
+                        button.setChecked(!isChecked);
+                    } else {
+                        persistBoolean(isChecked);
+                        mChecked = isChecked;
+                    }
+                }
+            });
+        }
+    }
+
+    public boolean isChecked() {
+        return isEnabled() && mChecked;
+    }
+
+    public void setChecked(boolean checked) {
+        mChecked = checked;
+        if (mSwitch != null) {
+            mSwitch.setChecked(checked);
+        }
+    }
+
+    public boolean isSwitchEnabled() {
+        return isEnabled() && mSwitch != null && mSwitch.isEnabled();
+    }
+
+    public void setSwitchEnabled(boolean enabled) {
+        if (mSwitch != null) {
+            mSwitch.setEnabled(enabled);
+        }
+    }
+
+    private void init() {
+        setWidgetLayoutResource(R.layout.preference_widget_master_switch);
+    }
+}
diff --git a/src/com/android/settings/widget/SwitchBarController.java b/src/com/android/settings/widget/SwitchBarController.java
new file mode 100644
index 0000000..70fd7ba
--- /dev/null
+++ b/src/com/android/settings/widget/SwitchBarController.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2017 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.widget;
+
+import android.widget.Switch;
+
+/*
+ * The switch controller that is used to update the switch widget in the SwitchBar layout.
+ */
+public class SwitchBarController extends SwitchWidgetController implements
+    SwitchBar.OnSwitchChangeListener {
+
+    private final SwitchBar mSwitchBar;
+    private final Switch mSwitch;
+
+    public SwitchBarController(SwitchBar switchBar) {
+        mSwitchBar = switchBar;
+        mSwitch = switchBar.getSwitch();
+    }
+
+    @Override
+    public void setupView() {
+        mSwitchBar.show();
+    }
+
+    @Override
+    public void teardownView() {
+        mSwitchBar.hide();
+    }
+
+    @Override
+    public void updateTitle(boolean isChecked) {
+        mSwitchBar.setTextViewLabel(isChecked);
+    }
+
+    @Override
+    public void startListening() {
+        mSwitchBar.addOnSwitchChangeListener(this);
+    }
+
+    @Override
+    public void stopListening() {
+        mSwitchBar.removeOnSwitchChangeListener(this);
+    }
+
+    @Override
+    public void setChecked(boolean checked) {
+        mSwitch.setChecked(checked);
+    }
+
+    @Override
+    public boolean isChecked() {
+        return mSwitch.isChecked();
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        mSwitch.setEnabled(enabled);
+    }
+
+    @Override
+    public void onSwitchChanged(Switch switchView, boolean isChecked) {
+        if (mListener != null) {
+            mListener.onSwitchToggled(isChecked);
+        }
+    }
+}
diff --git a/src/com/android/settings/widget/SwitchWidgetController.java b/src/com/android/settings/widget/SwitchWidgetController.java
new file mode 100644
index 0000000..c5a8c87
--- /dev/null
+++ b/src/com/android/settings/widget/SwitchWidgetController.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 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.widget;
+
+/*
+ * A controller class for general switch widget handling. We have different containers that provide
+ * different forms of switch layout. Provide a centralized control for updating the switch widget.
+ */
+public abstract class SwitchWidgetController {
+
+    protected OnSwitchChangeListener mListener;
+
+    public interface OnSwitchChangeListener {
+        /**
+         * Called when the checked state of the Switch has changed.
+         *
+         * @param isChecked  The new checked state of switchView.
+         */
+        boolean onSwitchToggled(boolean isChecked);
+    }
+
+    public void setupView() {
+    }
+
+    public void teardownView() {
+    }
+
+    public void setListener(OnSwitchChangeListener listener) {
+        mListener = listener;
+    }
+
+    public abstract void updateTitle(boolean isChecked);
+
+    public abstract void startListening();
+
+    public abstract void stopListening();
+
+    public abstract void setChecked(boolean checked);
+
+    public abstract boolean isChecked();
+
+    public abstract void setEnabled(boolean enabled);
+
+}
diff --git a/tests/robotests/assets/grandfather_not_implementing_indexable b/tests/robotests/assets/grandfather_not_implementing_indexable
index 81adf8b..a178596 100644
--- a/tests/robotests/assets/grandfather_not_implementing_indexable
+++ b/tests/robotests/assets/grandfather_not_implementing_indexable
@@ -89,4 +89,5 @@
 com.android.settings.accessibility.ToggleDaltonizerPreferenceFragment
 com.android.settings.applications.ConvertToFbe
 com.android.settings.localepicker.LocaleListEditor
-com.android.settings.qstile.DevelopmentTileConfigActivity$DevelopmentTileConfigFragment
\ No newline at end of file
+com.android.settings.qstile.DevelopmentTileConfigActivity$DevelopmentTileConfigFragment
+com.android.settings.applications.ExternalSourcesDetails
diff --git a/tests/robotests/src/com/android/settings/applications/AdvancedAppSettingsTest.java b/tests/robotests/src/com/android/settings/applications/AdvancedAppSettingsTest.java
index 7da30ed..b8f3fc4 100644
--- a/tests/robotests/src/com/android/settings/applications/AdvancedAppSettingsTest.java
+++ b/tests/robotests/src/com/android/settings/applications/AdvancedAppSettingsTest.java
@@ -21,7 +21,6 @@
 import com.android.settings.R;
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
-import com.android.settings.testutils.FakeFeatureFactory;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -33,7 +32,6 @@
 import org.robolectric.shadows.ShadowApplication;
 
 import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.when;
 
 @RunWith(SettingsRobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
@@ -42,27 +40,20 @@
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private Context mContext;
 
-    private FakeFeatureFactory mFeatureFactory;
     private AdvancedAppSettings mFragment;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        FakeFeatureFactory.setupForTest(mContext);
-        mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
+
         mFragment = new AdvancedAppSettings();
         mFragment.onAttach(ShadowApplication.getInstance().getApplicationContext());
     }
 
     @Test
-    public void getPreferenceScreenResId_differentIAEnabledState_shouldUseDifferentPrefLayout() {
-        when(mFeatureFactory.dashboardFeatureProvider.isEnabled()).thenReturn(true);
+    public void getPreferenceScreenResId_shouldUseAppDefaultSettingPrefLayout() {
         assertThat(mFragment.getPreferenceScreenResId()).isEqualTo(
                 R.xml.app_default_settings);
-
-        when(mFeatureFactory.dashboardFeatureProvider.isEnabled()).thenReturn(false);
-        assertThat(mFragment.getPreferenceScreenResId()).isEqualTo(
-                R.xml.advanced_apps);
     }
 
 }
diff --git a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAppInfoTest.java b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAppInfoTest.java
new file mode 100644
index 0000000..6c31927
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAppInfoTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2017 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.applications.defaultapps;
+
+
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class DefaultAppInfoTest {
+
+    @Mock
+    private ActivityInfo mActivityInfo;
+    @Mock
+    private ApplicationInfo mApplicationInfo;
+    @Mock
+    private ComponentName mComponentName;
+    @Mock
+    private PackageManager mPackageManager;
+
+    private DefaultAppInfo mInfo;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void initInfoWithActivityInfo_shouldLoadInfo() {
+        mActivityInfo.packageName = "test";
+        mInfo = new DefaultAppInfo(mActivityInfo);
+        mInfo.loadLabel(mPackageManager);
+        mInfo.loadIcon(mPackageManager);
+
+        assertThat(mInfo.getKey()).isEqualTo(mActivityInfo.packageName);
+        verify(mActivityInfo).loadLabel(mPackageManager);
+        verify(mActivityInfo).loadIcon(mPackageManager);
+    }
+
+    @Test
+    public void initInfoWithApplicationInfo_shouldLoadInfo() {
+        mApplicationInfo.packageName = "test";
+
+        mInfo = new DefaultAppInfo(mApplicationInfo);
+        mInfo.loadLabel(mPackageManager);
+        mInfo.loadIcon(mPackageManager);
+
+        assertThat(mInfo.getKey()).isEqualTo(mApplicationInfo.packageName);
+        verify(mApplicationInfo).loadLabel(mPackageManager);
+        verify(mApplicationInfo).loadIcon(mPackageManager);
+    }
+
+    @Test
+    public void initInfoWithComponent_shouldLoadInfo() {
+        when(mComponentName.getPackageName()).thenReturn("com.android.settings");
+
+        mInfo = new DefaultAppInfo(0 /* uid */, mComponentName, null /*summary */);
+        mInfo.getKey();
+
+        verify(mComponentName).flattenToString();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAppPickerFragmentTest.java b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAppPickerFragmentTest.java
new file mode 100644
index 0000000..4f560c9
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAppPickerFragmentTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2017 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.applications.defaultapps;
+
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.UserManager;
+import android.support.v4.app.FragmentManager;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.widget.RadioButtonPreference;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class DefaultAppPickerFragmentTest {
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Activity mActivity;
+    @Mock
+    private PreferenceScreen mScreen;
+    @Mock
+    private UserManager mUserManager;
+    @Mock
+    private FragmentManager mFragmentManager;
+
+    private TestFragment mFragment;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mFragment = spy(new TestFragment());
+        final Bundle bundle = new Bundle();
+        bundle.putBoolean(DefaultAppPickerFragment.EXTRA_FOR_WORK, false);
+        mFragment.setArguments(bundle);
+
+        when(mActivity.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+        doReturn(mActivity).when(mFragment).getContext();
+        doReturn(mScreen).when(mFragment).getPreferenceScreen();
+    }
+
+    @Test
+    public void onAttach_userIsInitialized() {
+        mFragment.onAttach((Context) mActivity);
+
+        verify(mActivity).getPackageManager();
+        verify(mActivity).getSystemService(Context.USER_SERVICE);
+    }
+
+    @Test
+    public void clickPreference_noCofirmation_shouldDirectlyConfirm() {
+        final RadioButtonPreference pref =
+                new RadioButtonPreference(RuntimeEnvironment.application);
+        pref.setKey("TEST");
+
+        mFragment.onRadioButtonClicked(pref);
+
+        assertThat(mFragment.setDefaultAppKeyCalled).isTrue();
+    }
+
+    @Test
+    public void clickPreference_hasCofirmation_shouldShowConfirmation() {
+        final RadioButtonPreference pref =
+                new RadioButtonPreference(RuntimeEnvironment.application);
+        pref.setKey("TEST");
+        doReturn("confirmation_text").when(mFragment)
+                .getConfirmationMessage(any(DefaultAppInfo.class));
+        doReturn(mActivity).when(mFragment).getActivity();
+
+        mFragment.onRadioButtonClicked(pref);
+    }
+
+    public static class TestFragment extends DefaultAppPickerFragment {
+
+        boolean setDefaultAppKeyCalled;
+
+        @Override
+        public int getMetricsCategory() {
+            return 0;
+        }
+
+        @Override
+        protected List<DefaultAppInfo> getCandidates() {
+            return new ArrayList<>();
+        }
+
+        @Override
+        protected String getDefaultAppKey() {
+            return null;
+        }
+
+        @Override
+        protected boolean setDefaultAppKey(String key) {
+            setDefaultAppKeyCalled = true;
+            return true;
+        }
+
+        @Override
+        public Context getContext() {
+            return RuntimeEnvironment.application;
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceControllerTest.java
new file mode 100644
index 0000000..f8d4d12
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceControllerTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2017 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.applications.defaultapps;
+
+
+import android.content.Context;
+import android.os.UserManager;
+import android.support.v7.preference.Preference;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class DefaultAppPreferenceControllerTest {
+
+    private static final String TEST_APP_NAME = "test";
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+    @Mock
+    private UserManager mUserManager;
+    @Mock
+    private Preference mPreference;
+
+    private TestPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+    }
+
+    @Test
+    public void updateState_hasDefaultApp_shouldUpdateAppName() {
+        mController = new TestPreferenceController(mContext);
+
+        when(mController.mAppInfo.loadLabel(mContext.getPackageManager()))
+                .thenReturn(TEST_APP_NAME);
+
+        mController.updateState(mPreference);
+
+        verify(mPreference).setSummary(TEST_APP_NAME);
+    }
+
+    @Test
+    public void updateState_hasNoApp_shouldNotUpdateAppName() {
+        mController = new TestPreferenceController(mContext);
+
+        mController.updateState(mPreference);
+
+        verify(mPreference, never()).setSummary(any(CharSequence.class));
+    }
+
+    private static class TestPreferenceController extends DefaultAppPreferenceController {
+
+        private DefaultAppInfo mAppInfo;
+
+        public TestPreferenceController(Context context) {
+            super(context);
+            mAppInfo = mock(DefaultAppInfo.class);
+        }
+
+        @Override
+        public boolean isAvailable() {
+            return true;
+        }
+
+        @Override
+        public String getPreferenceKey() {
+            return "test";
+        }
+
+        @Override
+        protected DefaultAppInfo getDefaultAppInfo() {
+            return mAppInfo;
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultBrowserPickerTest.java b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultBrowserPickerTest.java
new file mode 100644
index 0000000..e7b11d9
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultBrowserPickerTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017 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.applications.defaultapps;
+
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.UserManager;
+import android.provider.Settings;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.applications.PackageManagerWrapper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class DefaultBrowserPickerTest {
+
+    private static final String TEST_APP_KEY = "";
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Activity mActivity;
+    @Mock
+    private UserManager mUserManager;
+    @Mock
+    private PackageManagerWrapper mPackageManager;
+
+    private DefaultBrowserPicker mPicker;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mActivity.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+
+        mPicker = new DefaultBrowserPicker();
+        mPicker.onAttach((Context) mActivity);
+
+        ReflectionHelpers.setField(mPicker, "mPm", mPackageManager);
+    }
+
+    @Test
+    public void setDefaultAppKey_shouldUpdateDefaultBrowser() {
+        mPicker.setDefaultAppKey(TEST_APP_KEY);
+        verify(mPackageManager)
+                .setDefaultBrowserPackageNameAsUser(eq(TEST_APP_KEY), anyInt());
+    }
+
+    @Test
+    public void getDefaultAppKey_shouldReturnDefaultBrowser() {
+        mPicker.getDefaultAppKey();
+        verify(mPackageManager)
+                .getDefaultBrowserPackageNameAsUser(anyInt());
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultBrowserPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultBrowserPreferenceControllerTest.java
new file mode 100644
index 0000000..e06dfee
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultBrowserPreferenceControllerTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2017 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.applications.defaultapps;
+
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.UserManager;
+import android.support.v7.preference.Preference;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.applications.PackageManagerWrapper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class DefaultBrowserPreferenceControllerTest {
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private UserManager mUserManager;
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private PackageManagerWrapper mPackageManager;
+
+    private DefaultBrowserPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+
+        mController = new DefaultBrowserPreferenceController(mContext);
+        ReflectionHelpers.setField(mController, "mPackageManager", mPackageManager);
+    }
+
+    @Test
+    public void isAlwaysAvailable() {
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void getSoleAppLabel_hasNoApp_shouldNotReturnLabel() {
+        when(mPackageManager.queryIntentActivitiesAsUser(any(Intent.class), anyInt(), anyInt()))
+                .thenReturn(null);
+        final Preference pref = mock(Preference.class);
+
+        mController.updateState(pref);
+        verify(pref, never()).setSummary(any(String.class));
+    }
+
+    @Test
+    public void getDefaultApp_shouldGetDefaultBrowserPackage() {
+        mController.getDefaultAppInfo();
+
+        verify(mPackageManager).getDefaultBrowserPackageNameAsUser(anyInt());
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultEmergencyPickerTest.java b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultEmergencyPickerTest.java
new file mode 100644
index 0000000..ceccba1
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultEmergencyPickerTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2017 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.applications.defaultapps;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.UserManager;
+import android.provider.Settings;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.applications.PackageManagerWrapper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class DefaultEmergencyPickerTest {
+
+    private static final String TEST_APP_KEY = "test_app";
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Activity mActivity;
+    @Mock
+    private UserManager mUserManager;
+    @Mock
+    private PackageManagerWrapper mPackageManager;
+
+    private DefaultEmergencyPicker mPicker;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mActivity.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+
+        mPicker = spy(new DefaultEmergencyPicker());
+        mPicker.onAttach((Context) mActivity);
+
+        ReflectionHelpers.setField(mPicker, "mPm", mPackageManager);
+
+        doReturn(RuntimeEnvironment.application).when(mPicker).getContext();
+    }
+
+    @Test
+    public void setDefaultAppKey_shouldUpdateDefault() {
+        assertThat(mPicker.setDefaultAppKey(TEST_APP_KEY)).isTrue();
+        assertThat(mPicker.getDefaultAppKey()).isEqualTo(TEST_APP_KEY);
+    }
+
+    @Test
+    public void getDefaultAppKey_shouldReturnDefault() {
+        Settings.Secure.putString(RuntimeEnvironment.application.getContentResolver(),
+                Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION,
+                TEST_APP_KEY);
+
+        assertThat(mPicker.getDefaultAppKey()).isEqualTo(TEST_APP_KEY);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultHomePickerTest.java b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultHomePickerTest.java
new file mode 100644
index 0000000..d63ae6d
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultHomePickerTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2017 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.applications.defaultapps;
+
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.IntentFilter;
+import android.os.UserManager;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.applications.PackageManagerWrapper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyList;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class DefaultHomePickerTest {
+
+    private static final String TEST_APP_KEY = "com.android.settings/DefaultEmergencyPickerTest";
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Activity mActivity;
+    @Mock
+    private UserManager mUserManager;
+    @Mock
+    private PackageManagerWrapper mPackageManager;
+
+    private DefaultHomePicker mPicker;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mActivity.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+
+        mPicker = new DefaultHomePicker();
+        mPicker.onAttach((Context) mActivity);
+
+        ReflectionHelpers.setField(mPicker, "mPm", mPackageManager);
+    }
+
+    @Test
+    public void setDefaultAppKey_shouldUpdateDefault() {
+        assertThat(mPicker.setDefaultAppKey(TEST_APP_KEY)).isTrue();
+
+        verify(mPackageManager).replacePreferredActivity(any(IntentFilter.class),
+                anyInt(), any(ComponentName[].class), any(ComponentName.class));
+    }
+
+    @Test
+    public void getDefaultAppKey_shouldReturnDefault() {
+        final ComponentName cn = mock(ComponentName.class);
+        when(mPackageManager.getHomeActivities(anyList()))
+                .thenReturn(cn);
+        mPicker.getDefaultAppKey();
+        verify(cn).flattenToString();
+    }
+
+}
diff --git a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultHomePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultHomePreferenceControllerTest.java
new file mode 100644
index 0000000..6d6e2f8
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultHomePreferenceControllerTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2017 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.applications.defaultapps;
+
+
+import android.content.Context;
+import android.os.UserManager;
+import android.support.v7.preference.Preference;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.applications.PackageManagerWrapper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.anyList;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class DefaultHomePreferenceControllerTest {
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private UserManager mUserManager;
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private PackageManagerWrapper mPackageManager;
+
+    private DefaultHomePreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+
+        mController = spy(new DefaultHomePreferenceController(mContext));
+        ReflectionHelpers.setField(mController, "mPackageManager", mPackageManager);
+    }
+
+    @Test
+    public void isAlwaysAvailable() {
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void getDefaultApp_shouldGetDefaultBrowserPackage() {
+        assertThat(mController.getDefaultAppInfo()).isNotNull();
+
+        verify(mPackageManager).getHomeActivities(anyList());
+    }
+
+    @Test
+    public void updateState_noDefaultApp_shouldAskPackageManagerForOnlyApp() {
+        doReturn(null).when(mController).getDefaultAppInfo();
+
+        mController.updateState(mock(Preference.class));
+
+        verify(mPackageManager).getHomeActivities(anyList());
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultNotificationAssistantPickerTest.java b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultNotificationAssistantPickerTest.java
new file mode 100644
index 0000000..322830c
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultNotificationAssistantPickerTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2017 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.applications.defaultapps;
+
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.UserManager;
+import android.provider.Settings;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.applications.PackageManagerWrapper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class DefaultNotificationAssistantPickerTest {
+
+    private static final String TEST_APP_KEY = "com.android.settings/PickerTest";
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Activity mActivity;
+    @Mock
+    private UserManager mUserManager;
+    @Mock
+    private PackageManagerWrapper mPackageManager;
+
+    private DefaultNotificationAssistantPicker mPicker;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mActivity.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+
+        mPicker = spy(new DefaultNotificationAssistantPicker());
+        mPicker.onAttach((Context) mActivity);
+
+        ReflectionHelpers.setField(mPicker, "mPm", mPackageManager);
+        doReturn(RuntimeEnvironment.application).when(mPicker).getContext();
+    }
+
+    @Test
+    public void setDefaultAppKey_shouldUpdateDefault() {
+        mPicker.setDefaultAppKey(TEST_APP_KEY);
+
+        assertThat(mPicker.getDefaultAppKey()).isEqualTo(TEST_APP_KEY);
+    }
+
+    @Test
+    public void getDefaultAppKey_shouldReturnDefault() {
+        Settings.Secure.putString(RuntimeEnvironment.application.getContentResolver(),
+                Settings.Secure.ENABLED_NOTIFICATION_ASSISTANT,
+                TEST_APP_KEY);
+
+        assertThat(mPicker.getDefaultAppKey()).isEqualTo(TEST_APP_KEY);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultPhonePickerTest.java b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultPhonePickerTest.java
new file mode 100644
index 0000000..0c5d5f1
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultPhonePickerTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2017 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.applications.defaultapps;
+
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.UserManager;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.applications.PackageManagerWrapper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class DefaultPhonePickerTest {
+
+    private static final String TEST_APP_KEY = "com.android.settings/PickerTest";
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Activity mActivity;
+    @Mock
+    private UserManager mUserManager;
+    @Mock
+    private DefaultPhonePicker.DefaultKeyUpdater mDefaultKeyUpdater;
+    @Mock
+    private PackageManagerWrapper mPackageManager;
+
+    private DefaultPhonePicker mPicker;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mActivity.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+        when(mActivity.getSystemService(Context.TELECOM_SERVICE)).thenReturn(null);
+        mPicker = spy(new DefaultPhonePicker());
+        mPicker.onAttach((Context) mActivity);
+
+        ReflectionHelpers.setField(mPicker, "mPm", mPackageManager);
+        ReflectionHelpers.setField(mPicker, "mDefaultKeyUpdater", mDefaultKeyUpdater);
+        doReturn(RuntimeEnvironment.application).when(mPicker).getContext();
+    }
+
+    @Test
+    public void getSystemDefaultPackage_shouldAskDefaultKeyUpdater() {
+        mPicker.getSystemDefaultAppKey();
+
+        verify(mDefaultKeyUpdater).getSystemDialerPackage();
+    }
+
+    @Test
+    public void setDefaultAppKey_shouldUpdateDefault() {
+        mPicker.setDefaultAppKey(TEST_APP_KEY);
+
+        verify(mDefaultKeyUpdater).setDefaultDialerApplication(
+                any(Context.class), eq(TEST_APP_KEY), anyInt());
+    }
+
+    @Test
+    public void getDefaultAppKey_shouldReturnDefault() {
+        mPicker.getDefaultAppKey();
+        verify(mDefaultKeyUpdater).getDefaultDialerApplication(any(Context.class), anyInt());
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultSmsPickerTest.java b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultSmsPickerTest.java
new file mode 100644
index 0000000..3da6fec
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultSmsPickerTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2017 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.applications.defaultapps;
+
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.UserManager;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.applications.PackageManagerWrapper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class DefaultSmsPickerTest {
+
+    private static final String TEST_APP_KEY = "com.android.settings/PickerTest";
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Activity mActivity;
+    @Mock
+    private UserManager mUserManager;
+    @Mock
+    private DefaultSmsPicker.DefaultKeyUpdater mDefaultKeyUpdater;
+    @Mock
+    private PackageManagerWrapper mPackageManager;
+
+    private DefaultSmsPicker mPicker;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mActivity.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+        mPicker = spy(new DefaultSmsPicker());
+        mPicker.onAttach((Context) mActivity);
+
+        ReflectionHelpers.setField(mPicker, "mPm", mPackageManager);
+        ReflectionHelpers.setField(mPicker, "mDefaultKeyUpdater", mDefaultKeyUpdater);
+        doReturn(RuntimeEnvironment.application).when(mPicker).getContext();
+    }
+
+    @Test
+    public void setDefaultAppKey_shouldUpdateDefault() {
+        mPicker.setDefaultAppKey(TEST_APP_KEY);
+
+        verify(mDefaultKeyUpdater).setDefaultApplication(any(Context.class), eq(TEST_APP_KEY));
+    }
+
+    @Test
+    public void getDefaultAppKey_shouldReturnDefault() {
+        mPicker.getDefaultAppKey();
+
+        verify(mDefaultKeyUpdater).getDefaultApplication(any(Context.class));
+    }
+
+}
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothMasterSwitchPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothMasterSwitchPreferenceControllerTest.java
new file mode 100644
index 0000000..0e39c5d
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothMasterSwitchPreferenceControllerTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2017 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.bluetooth;
+
+import android.content.Context;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.Preference.OnPreferenceChangeListener;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.widget.MasterSwitchPreference;
+import com.android.settingslib.bluetooth.BluetoothCallback;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class BluetoothMasterSwitchPreferenceControllerTest {
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private LocalBluetoothManager mBluetoothManager;
+    @Mock
+    private PreferenceScreen mScreen;
+    @Mock
+    private MasterSwitchPreference mPreference;
+
+    private Context mContext;
+    private BluetoothMasterSwitchPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application.getApplicationContext();
+        mController = new BluetoothMasterSwitchPreferenceController(mContext, mBluetoothManager);
+        when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
+    }
+
+    @Test
+    public void isAvailable_shouldAlwaysReturnTrue() {
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void onResume_shouldRegisterCallback() {
+        mController.onResume();
+
+        verify(mBluetoothManager.getEventManager()).registerCallback(any(BluetoothCallback.class));
+    }
+
+    @Test
+    public void onPause_shouldUnregisterCallback() {
+        mController.onPause();
+
+        verify(mBluetoothManager.getEventManager()).unregisterCallback(
+            any(BluetoothCallback.class));
+    }
+
+    @Test
+    public void onStart_shouldRegisterPreferenceChangeListener() {
+        mController.displayPreference(mScreen);
+        mController.onStart();
+
+        verify(mPreference).setOnPreferenceChangeListener(any(OnPreferenceChangeListener.class));
+    }
+
+    @Test
+    public void onStop_shouldRegisterPreferenceChangeListener() {
+        mController.displayPreference(mScreen);
+        mController.onStart();
+
+        mController.onStop();
+
+        verify(mPreference).setOnPreferenceChangeListener(null);
+    }
+
+    @Test
+    public void onSummaryUpdated_shouldUpdatePreferenceSummary() {
+        mController.displayPreference(mScreen);
+
+        mController.onSummaryChanged("test summary");
+
+        verify(mPreference).setSummary("test summary");
+    }
+
+}
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothSettingsSummaryProviderTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothSettingsSummaryProviderTest.java
index 7ac7cb1..2822b1e 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothSettingsSummaryProviderTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothSettingsSummaryProviderTest.java
@@ -16,14 +16,11 @@
 
 package com.android.settings.bluetooth;
 
-import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
 
-import com.android.settings.R;
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
 import com.android.settings.dashboard.SummaryLoader;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 
 import org.junit.Before;
@@ -34,15 +31,10 @@
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
-import org.robolectric.shadows.ShadowBluetoothAdapter;
 
 import java.util.ArrayList;
 import java.util.List;
 
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -67,102 +59,27 @@
     }
 
     @Test
-    public void setListening_shouldUpdateSummary() {
+    public void setListening_shouldRegister() {
         mSummaryProvider.setListening(true);
 
-        verify(mBluetoothManager.getEventManager()).registerCallback(mSummaryProvider);
-        verify(mSummaryLoader).setSummary(eq(mSummaryProvider), anyString());
+        verify(mBluetoothManager.getEventManager()).registerCallback(
+            mSummaryProvider.mSummaryHelper);
     }
 
     @Test
     public void setNotListening_shouldUnregister() {
         mSummaryProvider.setListening(false);
 
-        verify(mBluetoothManager.getEventManager()).unregisterCallback(mSummaryProvider);
+        verify(mBluetoothManager.getEventManager()).unregisterCallback(
+            mSummaryProvider.mSummaryHelper);
     }
 
     @Test
-    public void updateSummary_btDisabled_shouldShowDisabledMessage() {
-        ShadowBluetoothAdapter.getDefaultAdapter().disable();
-        mSummaryProvider.setListening(true);
+    public void onSummaryChanged_shouldSetSummary() {
+        final String summary = "Bluetooth summary";
+        mSummaryProvider.onSummaryChanged(summary);
 
-        verify(mSummaryLoader).setSummary(mSummaryProvider,
-                mContext.getString(R.string.bluetooth_disabled));
-    }
-
-    @Test
-    public void updateSummary_btEnabled_noDevice_shouldShowDisconnectedMessage() {
-        ShadowBluetoothAdapter.getDefaultAdapter().enable();
-        mSummaryProvider.setListening(true);
-
-        verify(mSummaryLoader).setSummary(mSummaryProvider,
-                mContext.getString(R.string.bluetooth_disconnected));
-    }
-
-    @Test
-    public void updateState_btEnabled_noDevice_shouldShowDisconnectedMessage() {
-        ShadowBluetoothAdapter.getDefaultAdapter().enable();
-        mSummaryProvider.onBluetoothStateChanged(BluetoothAdapter.STATE_TURNING_ON);
-
-        verify(mSummaryLoader).setSummary(mSummaryProvider,
-                mContext.getString(R.string.bluetooth_disconnected));
-    }
-
-    @Test
-    public void updateState_btDisabled_shouldShowDisabledMessage() {
-        ShadowBluetoothAdapter.getDefaultAdapter().enable();
-        mSummaryProvider.onBluetoothStateChanged(BluetoothAdapter.STATE_TURNING_OFF);
-
-        verify(mSummaryLoader).setSummary(mSummaryProvider,
-                mContext.getString(R.string.bluetooth_disabled));
-    }
-
-    @Test
-    public void updateConnectionState_disconnected_shouldShowDisconnectedMessage() {
-        ShadowBluetoothAdapter.getDefaultAdapter().enable();
-        when(mBluetoothManager.getBluetoothAdapter().getConnectionState())
-                .thenReturn(BluetoothAdapter.STATE_DISCONNECTED);
-
-        mSummaryProvider.setListening(true);
-        mSummaryProvider.onConnectionStateChanged(null /* device */,
-                BluetoothAdapter.STATE_DISCONNECTED);
-
-        verify(mSummaryLoader, times(2)).setSummary(mSummaryProvider,
-                mContext.getString(R.string.bluetooth_disconnected));
-    }
-
-
-    @Test
-    public void updateConnectionState_connected_shouldShowConnectedMessage() {
-        ShadowBluetoothAdapter.getDefaultAdapter().enable();
-        when(mBluetoothManager.getBluetoothAdapter().getConnectionState())
-                .thenReturn(BluetoothAdapter.STATE_CONNECTED);
-        final List<CachedBluetoothDevice> devices = new ArrayList<>();
-        devices.add(mock(CachedBluetoothDevice.class));
-        when(devices.get(0).isConnected()).thenReturn(true);
-        when(mBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy())
-                .thenReturn(devices);
-
-        mSummaryProvider.setListening(true);
-        mSummaryProvider.onConnectionStateChanged(null /* device */,
-                BluetoothAdapter.STATE_CONNECTED);
-
-        verify(mSummaryLoader).setSummary(mSummaryProvider,
-                mContext.getString(R.string.bluetooth_connected));
-    }
-
-    @Test
-    public void updateConnectionState_inconsistentState_shouldShowDisconnectedMessage() {
-        ShadowBluetoothAdapter.getDefaultAdapter().enable();
-        when(mBluetoothManager.getBluetoothAdapter().getConnectionState())
-                .thenReturn(BluetoothAdapter.STATE_CONNECTED);
-
-        mSummaryProvider.setListening(true);
-        mSummaryProvider.onConnectionStateChanged(null /* device */,
-                BluetoothAdapter.STATE_CONNECTED);
-
-        verify(mSummaryLoader, times(2)).setSummary(mSummaryProvider,
-                mContext.getString(R.string.bluetooth_disconnected));
+        verify(mSummaryLoader).setSummary(mSummaryProvider, summary);
     }
 
 }
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothSummaryHelperTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothSummaryHelperTest.java
new file mode 100644
index 0000000..f25e6d9
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothSummaryHelperTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2017 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.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+
+import com.android.settings.R;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowBluetoothAdapter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class BluetoothSummaryHelperTest {
+
+    private Context mContext;
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private LocalBluetoothManager mBluetoothManager;
+    @Mock
+    private LocalBluetoothAdapter mBtAdapter;
+
+    private BluetoothSummaryHelper mHelper;
+    @Mock
+    private SummaryListener mListener;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mBluetoothManager.getBluetoothAdapter()).thenReturn(mBtAdapter);
+        when(mBtAdapter.isEnabled()).thenReturn(true);
+        when(mBtAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTED);
+        mContext = RuntimeEnvironment.application.getApplicationContext();
+        mHelper = new BluetoothSummaryHelper(mContext, mBluetoothManager);
+        mHelper.setOnSummaryChangeListener(mListener);
+    }
+
+    @Test
+    public void setListening_shouldRegisterListener() {
+        mHelper.setListening(true);
+
+        verify(mBluetoothManager.getEventManager()).registerCallback(mHelper);
+    }
+
+    @Test
+    public void setNotListening_shouldUnregisterListener() {
+        mHelper.setListening(false);
+
+        verify(mBluetoothManager.getEventManager()).unregisterCallback(mHelper);
+    }
+
+    @Test
+    public void setListening_shouldSendSummaryChange() {
+        mHelper.setListening(true);
+
+        verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_connected));
+    }
+
+    @Test
+    public void onBluetoothStateChanged_btDisabled_shouldSendDisabledSummary() {
+        mHelper.setListening(true);
+        mHelper.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF);
+
+        verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_disabled));
+    }
+
+    @Test
+    public void onBluetoothStateChanged_btEnabled_connected_shouldSendConnectedSummary() {
+        mHelper.setListening(true);
+        mHelper.onBluetoothStateChanged(BluetoothAdapter.STATE_ON);
+
+        verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_connected));
+    }
+
+    @Test
+    public void onBluetoothStateChanged_btEnabled_notConnected_shouldSendDisconnectedMessage() {
+        when(mBtAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_DISCONNECTED);
+        mHelper.setListening(true);
+        mHelper.onBluetoothStateChanged(BluetoothAdapter.STATE_TURNING_ON);
+
+        verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_disconnected));
+    }
+
+    @Test
+    public void onConnectionStateChanged_connected_shouldSendConnectedMessage() {
+        final List<CachedBluetoothDevice> devices = new ArrayList<>();
+        devices.add(mock(CachedBluetoothDevice.class));
+        when(devices.get(0).isConnected()).thenReturn(true);
+        when(mBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy())
+            .thenReturn(devices);
+        when(mBtAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_DISCONNECTED);
+        mHelper.setListening(true);
+
+        when(mBtAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTED);
+        mHelper.onConnectionStateChanged(null /* device */, BluetoothAdapter.STATE_CONNECTED);
+
+        verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_connected));
+    }
+
+    @Test
+    public void onConnectionStateChanged_inconsistentState_shouldSendDisconnectedMessage() {
+        mHelper.setListening(true);
+        mHelper.onConnectionStateChanged(null /* device */, BluetoothAdapter.STATE_CONNECTED);
+
+        verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_disconnected));
+    }
+
+    @Test
+    public void onConnectionStateChanged_connecting_shouldSendConnectingMessage() {
+        mHelper.setListening(true);
+        when(mBtAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTING);
+        mHelper.onConnectionStateChanged(null /* device */, BluetoothAdapter.STATE_CONNECTING);
+
+        verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_connecting));
+    }
+
+    @Test
+    public void onConnectionStateChanged_disconnecting_shouldSendDisconnectingMessage() {
+        mHelper.setListening(true);
+        when(mBtAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_DISCONNECTING);
+        mHelper.onConnectionStateChanged(null /* device */, BluetoothAdapter.STATE_DISCONNECTING);
+
+        verify(mListener).onSummaryChanged(mContext.getString(R.string.bluetooth_disconnecting));
+    }
+
+    private class SummaryListener implements BluetoothSummaryHelper.OnSummaryChangeListener {
+        String summary;
+
+        @Override
+        public void onSummaryChanged(String summary) {
+            this.summary = summary;
+        }
+    }
+
+}
diff --git a/tests/robotests/src/com/android/settings/display/AutoBrightnessPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/AutoBrightnessPreferenceControllerTest.java
new file mode 100644
index 0000000..10a8b0b
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/display/AutoBrightnessPreferenceControllerTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2017 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.display;
+
+import android.content.Context;
+import android.provider.Settings;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE;
+import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
+import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL;
+import static com.google.common.truth.Truth.assertThat;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class AutoBrightnessPreferenceControllerTest {
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+    private AutoBrightnessPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mController = new AutoBrightnessPreferenceController(mContext);
+    }
+
+    @Test
+    public void testOnPreferenceChange_TurnOnAuto_ReturnAuto() {
+        mController.onPreferenceChange(null, true);
+
+        final int mode = Settings.System.getInt(mContext.getContentResolver(),
+                SCREEN_BRIGHTNESS_MODE, SCREEN_BRIGHTNESS_MODE_MANUAL);
+        assertThat(mode).isEqualTo(SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+    }
+
+    @Test
+    public void testOnPreferenceChange_TurnOffAuto_ReturnManual() {
+        mController.onPreferenceChange(null, false);
+
+        final int mode = Settings.System.getInt(mContext.getContentResolver(),
+                SCREEN_BRIGHTNESS_MODE, SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        assertThat(mode).isEqualTo(SCREEN_BRIGHTNESS_MODE_MANUAL);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/display/TimeoutPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/TimeoutPreferenceControllerTest.java
new file mode 100644
index 0000000..ec142c2
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/display/TimeoutPreferenceControllerTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 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.display;
+
+import android.content.Context;
+import android.provider.Settings;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.TimeoutListPreference;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
+import static com.google.common.truth.Truth.assertThat;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class TimeoutPreferenceControllerTest {
+    private static final int TIMEOUT = 30;
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+    @Mock
+    private TimeoutListPreference mPreference;
+    private TimeoutPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mController = new TimeoutPreferenceController(mContext);
+    }
+
+    @Test
+    public void testOnPreferenceChange_SetTimeout_ReturnCorrectTimeout() {
+        mController.onPreferenceChange(mPreference, Integer.toString(TIMEOUT));
+
+        final int mode = Settings.System.getInt(mContext.getContentResolver(),
+                SCREEN_OFF_TIMEOUT, 0);
+        assertThat(mode).isEqualTo(TIMEOUT);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java
index 35df218..ea678b6 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java
@@ -36,6 +36,9 @@
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.annotation.Config;
 
+import java.util.ArrayList;
+import java.util.List;
+
 import static com.android.settings.fuelgauge.PowerUsageBase.MENU_STATS_REFRESH;
 import static com.android.settings.fuelgauge.PowerUsageSummary.MENU_ADDITIONAL_BATTERY_INFO;
 import static com.google.common.truth.Truth.assertThat;
@@ -53,7 +56,8 @@
     private static final String[] PACKAGE_NAMES = {"com.app1", "com.app2"};
     private static final int UID = 123;
     private static final int POWER_MAH = 100;
-
+    private static final double BATTERY_SCREEN_USAGE = 300;
+    private static final double PRECISION = 0.001;
     private static final Intent ADDITIONAL_BATTERY_INFO_INTENT =
             new Intent("com.example.app.ADDITIONAL_BATTERY_INFO");
 
@@ -68,7 +72,9 @@
     @Mock
     private MenuInflater mMenuInflater;
     @Mock
-    private BatterySipper mBatterySipper;
+    private BatterySipper mNormalBatterySipper;
+    @Mock
+    private BatterySipper mScreenBatterySipper;
 
     private TestFragment mFragment;
     private FakeFeatureFactory mFeatureFactory;
@@ -95,9 +101,12 @@
 
         mPowerUsageSummary = new PowerUsageSummary();
 
-        when(mBatterySipper.getPackages()).thenReturn(PACKAGE_NAMES);
-        when(mBatterySipper.getUid()).thenReturn(UID);
-        mBatterySipper.totalPowerMah = POWER_MAH;
+        when(mNormalBatterySipper.getPackages()).thenReturn(PACKAGE_NAMES);
+        when(mNormalBatterySipper.getUid()).thenReturn(UID);
+        mNormalBatterySipper.totalPowerMah = POWER_MAH;
+
+        mScreenBatterySipper.drainType = BatterySipper.DrainType.SCREEN;
+        mScreenBatterySipper.totalPowerMah = BATTERY_SCREEN_USAGE;
     }
 
     @Test
@@ -129,62 +138,73 @@
 
     @Test
     public void testExtractKeyFromSipper_TypeAPPUidObjectNull_ReturnPackageNames() {
-        mBatterySipper.uidObj = null;
-        mBatterySipper.drainType = BatterySipper.DrainType.APP;
+        mNormalBatterySipper.uidObj = null;
+        mNormalBatterySipper.drainType = BatterySipper.DrainType.APP;
 
-        final String key = mPowerUsageSummary.extractKeyFromSipper(mBatterySipper);
-        assertThat(key).isEqualTo(TextUtils.concat(mBatterySipper.getPackages()).toString());
+        final String key = mPowerUsageSummary.extractKeyFromSipper(mNormalBatterySipper);
+        assertThat(key).isEqualTo(TextUtils.concat(mNormalBatterySipper.getPackages()).toString());
     }
 
     @Test
     public void testExtractKeyFromSipper_TypeOther_ReturnDrainType() {
-        mBatterySipper.uidObj = null;
-        mBatterySipper.drainType = BatterySipper.DrainType.BLUETOOTH;
+        mNormalBatterySipper.uidObj = null;
+        mNormalBatterySipper.drainType = BatterySipper.DrainType.BLUETOOTH;
 
-        final String key = mPowerUsageSummary.extractKeyFromSipper(mBatterySipper);
-        assertThat(key).isEqualTo(mBatterySipper.drainType.toString());
+        final String key = mPowerUsageSummary.extractKeyFromSipper(mNormalBatterySipper);
+        assertThat(key).isEqualTo(mNormalBatterySipper.drainType.toString());
+    }
+
+    @Test
+    public void testRemoveScreenBatterySipper_ContainsScreenSipper_RemoveAndReturnValue() {
+        final List<BatterySipper> sippers = new ArrayList<>();
+        sippers.add(mNormalBatterySipper);
+        sippers.add(mScreenBatterySipper);
+
+        final double screenUsage = mPowerUsageSummary.removeScreenBatterySipper(sippers);
+        assertThat(sippers).containsExactly(mNormalBatterySipper);
+        assertThat(screenUsage).isWithin(PRECISION).of(BATTERY_SCREEN_USAGE);
     }
 
     @Test
     public void testExtractKeyFromSipper_TypeAPPUidObjectNotNull_ReturnUid() {
-        mBatterySipper.uidObj = new BatteryStatsImpl.Uid(new BatteryStatsImpl(), UID);
-        mBatterySipper.drainType = BatterySipper.DrainType.APP;
+        mNormalBatterySipper.uidObj = new BatteryStatsImpl.Uid(new BatteryStatsImpl(), UID);
+        mNormalBatterySipper.drainType = BatterySipper.DrainType.APP;
 
-        final String key = mPowerUsageSummary.extractKeyFromSipper(mBatterySipper);
-        assertThat(key).isEqualTo(Integer.toString(mBatterySipper.getUid()));
+        final String key = mPowerUsageSummary.extractKeyFromSipper(mNormalBatterySipper);
+        assertThat(key).isEqualTo(Integer.toString(mNormalBatterySipper.getUid()));
     }
 
     @Test
     public void testShouldHideSipper_TypeIdle_ReturnTrue() {
-        mBatterySipper.drainType = BatterySipper.DrainType.IDLE;
-        assertThat(mPowerUsageSummary.shouldHideSipper(mBatterySipper)).isTrue();
+        mNormalBatterySipper.drainType = BatterySipper.DrainType.IDLE;
+        assertThat(mPowerUsageSummary.shouldHideSipper(mNormalBatterySipper)).isTrue();
     }
 
     @Test
     public void testShouldHideSipper_TypeCell_ReturnTrue() {
-        mBatterySipper.drainType = BatterySipper.DrainType.CELL;
-        assertThat(mPowerUsageSummary.shouldHideSipper(mBatterySipper)).isTrue();
+        mNormalBatterySipper.drainType = BatterySipper.DrainType.CELL;
+        assertThat(mPowerUsageSummary.shouldHideSipper(mNormalBatterySipper)).isTrue();
     }
 
     @Test
     public void testShouldHideSipper_UidRoot_ReturnTrue() {
-        mBatterySipper.drainType = BatterySipper.DrainType.APP;
-        when(mBatterySipper.getUid()).thenReturn(Process.ROOT_UID);
-        assertThat(mPowerUsageSummary.shouldHideSipper(mBatterySipper)).isTrue();
+        mNormalBatterySipper.drainType = BatterySipper.DrainType.APP;
+        when(mNormalBatterySipper.getUid()).thenReturn(Process.ROOT_UID);
+        assertThat(mPowerUsageSummary.shouldHideSipper(mNormalBatterySipper)).isTrue();
     }
 
     @Test
     public void testShouldHideSipper_UidSystem_ReturnTrue() {
-        mBatterySipper.drainType = BatterySipper.DrainType.APP;
-        when(mBatterySipper.getUid()).thenReturn(Process.SYSTEM_UID);
-        assertThat(mPowerUsageSummary.shouldHideSipper(mBatterySipper)).isTrue();
+        mNormalBatterySipper.drainType = BatterySipper.DrainType.APP;
+        when(mNormalBatterySipper.getUid()).thenReturn(Process.SYSTEM_UID);
+        assertThat(mPowerUsageSummary.shouldHideSipper(mNormalBatterySipper)).isTrue();
     }
 
     @Test
     public void testShouldHideSipper_UidNormal_ReturnFalse() {
-        mBatterySipper.drainType = BatterySipper.DrainType.APP;
-        when(mBatterySipper.getUid()).thenReturn(UID);
-        assertThat(mPowerUsageSummary.shouldHideSipper(mBatterySipper)).isFalse();
+        mNormalBatterySipper.drainType = BatterySipper.DrainType.APP;
+        when(mNormalBatterySipper.getUid()).thenReturn(UID);
+        assertThat(mPowerUsageSummary.shouldHideSipper(mNormalBatterySipper)).isFalse();
     }
 
     public static class TestFragment extends PowerUsageSummary {
diff --git a/tests/robotests/src/com/android/settings/search/DatabaseIndexingManagerTest.java b/tests/robotests/src/com/android/settings/search/DatabaseIndexingManagerTest.java
index 8b363b0..1cf72ea 100644
--- a/tests/robotests/src/com/android/settings/search/DatabaseIndexingManagerTest.java
+++ b/tests/robotests/src/com/android/settings/search/DatabaseIndexingManagerTest.java
@@ -21,10 +21,13 @@
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.provider.SearchIndexableResource;
+
 import com.android.settings.R;
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
+import com.android.settings.search.IndexDatabaseHelper.SiteMapColumns;
 import com.android.settings.search2.DatabaseIndexingManager;
+import com.android.settings.testutils.DatabaseTestUtils;
 
 import org.junit.After;
 import org.junit.Before;
@@ -33,13 +36,13 @@
 import org.robolectric.annotation.Config;
 import org.robolectric.shadows.ShadowApplication;
 
-import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
 
+import static com.android.settings.dashboard.SiteMapManager.SITE_MAP_COLUMNS;
 import static com.google.common.truth.Truth.assertThat;
 import static org.mockito.Mockito.spy;
 
@@ -56,7 +59,7 @@
     private final String updatedSummaryOn = "summary-on";
     private final String normalizedSummaryOn = "summaryon";
     private final String summaryOff = "summary\u2011off";
-    private final String updatedSummaryOff ="summary-off";
+    private final String updatedSummaryOff = "summary-off";
     private final String normalizedSummaryOff = "summaryoff";
     private final String entries = "entries";
     private final String keywords = "keywords, keywordss, keywordsss";
@@ -85,15 +88,7 @@
 
     @After
     public void cleanUp() {
-        Field instance;
-        Class clazz = IndexDatabaseHelper.class;
-        try {
-            instance = clazz.getDeclaredField("sSingleton");
-            instance.setAccessible(true);
-            instance.set(null, null);
-        } catch (Exception e) {
-            throw new RuntimeException();
-        }
+        DatabaseTestUtils.clearDb();
     }
 
     @Test
@@ -101,7 +96,7 @@
         Cursor dbCursor = mDb.query("prefs_index", null, null, null, null, null, null);
         List<String> columnNames = new ArrayList<>(Arrays.asList(dbCursor.getColumnNames()));
         // Note that docid is not included.
-        List<String> expColumnNames = new ArrayList<>(Arrays.asList(new String[ ]{
+        List<String> expColumnNames = new ArrayList<>(Arrays.asList(new String[]{
                 "locale",
                 "data_rank",
                 "data_title",
@@ -203,7 +198,7 @@
     @Test
     public void testNullResource_NothingInserted() {
         mManager.indexOneSearchIndexableData(mDb, localeStr, null /* searchIndexableResource */,
-                new HashMap<String, List<String>>());
+                new HashMap<>());
         Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null);
         assertThat(cursor.getCount()).isEqualTo(0);
     }
@@ -212,7 +207,7 @@
     public void testAddResource_RowsInserted() {
         SearchIndexableResource resource = getFakeResource(R.xml.gesture_settings);
         mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
-                new HashMap<String, List<String>>());
+                new HashMap<>());
         Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null);
         assertThat(cursor.getCount()).isEqualTo(6);
     }
@@ -221,7 +216,7 @@
     public void testAddResourceHeader_RowsMatch() {
         SearchIndexableResource resource = getFakeResource(R.xml.application_settings);
         mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
-                new HashMap<String, List<String>>());
+                new HashMap<>());
 
         Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index ORDER BY data_title", null);
         cursor.moveToPosition(1);
@@ -271,28 +266,53 @@
     }
 
     @Test
+    public void testAddResourceWithChildFragment_shouldUpdateSiteMapDb() {
+        SearchIndexableResource resource = getFakeResource(R.xml.network_and_internet);
+        mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
+                new HashMap<>());
+        Cursor query = mDb.query(IndexDatabaseHelper.Tables.TABLE_SITE_MAP, SITE_MAP_COLUMNS,
+                null, null, null, null, null);
+        query.moveToPosition(-1);
+        int count = 0;
+        while (query.moveToNext()) {
+            count++;
+            assertThat(query.getString(query.getColumnIndex(SiteMapColumns.PARENT_CLASS)))
+                    .isEqualTo(className);
+            assertThat(query.getString(query.getColumnIndex(SiteMapColumns.PARENT_TITLE)))
+                    .isEqualTo(mContext.getString(R.string.network_dashboard_title));
+            assertThat(query.getString(query.getColumnIndex(SiteMapColumns.CHILD_CLASS)))
+                    .isNotEmpty();
+            assertThat(query.getString(query.getColumnIndex(SiteMapColumns.CHILD_TITLE)))
+                    .isNotEmpty();
+        }
+        assertThat(count).isEqualTo(5);
+    }
+
+    @Test
     public void testAddResourceCustomSetting_RowsMatch() {
         SearchIndexableResource resource = getFakeResource(R.xml.gesture_settings);
         mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
-                new HashMap<String, List<String>>());
-
-        Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null);
-        cursor.moveToPosition(0);
+                new HashMap<>());
+        final String prefTitle =
+                mContext.getString(R.string.fingerprint_swipe_for_notifications_title);
+        final String prefSummary =
+                mContext.getString(R.string.fingerprint_swipe_for_notifications_summary);
+        Cursor cursor = mDb.rawQuery(
+                "SELECT * FROM prefs_index where data_title='" + prefTitle + "'", null);
+        cursor.moveToFirst();
 
         // Locale
         assertThat(cursor.getString(0)).isEqualTo(localeStr);
         // Data Rank
         assertThat(cursor.getInt(1)).isEqualTo(rank);
         // Data Title
-        assertThat(cursor.getString(2)).isEqualTo("Swipe for notifications");
+        assertThat(cursor.getString(2)).isEqualTo(prefTitle);
         // Normalized Title
-        assertThat(cursor.getString(3)).isEqualTo("swipe for notifications");
+        assertThat(cursor.getString(3)).isEqualTo(prefTitle.toLowerCase());
         // Summary On
-        assertThat(cursor.getString(4)).isEqualTo("To check your notifications, " +
-                "swipe down on the fingerprint sensor on the back of your phone.");
+        assertThat(cursor.getString(4)).isEqualTo(prefSummary);
         // Summary On Normalized
-        assertThat(cursor.getString(5)).isEqualTo("to check your notifications, " +
-                "swipe down on the fingerprint sensor on the back of your phone.");
+        assertThat(cursor.getString(5)).isEqualTo(prefSummary.toLowerCase());
         // Summary Off - only on for checkbox preferences
         assertThat(cursor.getString(6)).isEmpty();
         // Summary off normalized - only on for checkbox preferences
@@ -302,7 +322,8 @@
         // Keywords
         assertThat(cursor.getString(9)).isEmpty();
         // Screen Title
-        assertThat(cursor.getString(10)).isEqualTo("Gestures");
+        assertThat(cursor.getString(10)).isEqualTo(
+                mContext.getString(R.string.gesture_preference_title));
         // Class Name
         assertThat(cursor.getString(11)).isEqualTo(className);
         // Icon
@@ -329,7 +350,7 @@
     public void testAddResourceCheckboxPreference_RowsMatch() {
         SearchIndexableResource resource = getFakeResource(R.xml.application_settings);
         mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
-                new HashMap<String, List<String>>());
+                new HashMap<>());
 
         /* Should return 6 results, with the following titles:
          * Advanced Settings, Apps, Manage Apps, Preferred install location, Running Services
@@ -384,7 +405,7 @@
     public void testAddResourceListPreference_RowsMatch() {
         SearchIndexableResource resource = getFakeResource(R.xml.application_settings);
         mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
-                new HashMap<String, List<String>>());
+                new HashMap<>());
 
         Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index ORDER BY data_title", null);
         cursor.moveToPosition(3);
@@ -397,16 +418,18 @@
         // Normalized Title
         assertThat(cursor.getString(3)).isEqualTo("preferred install location");
         // Summary On
-        assertThat(cursor.getString(4)).isEqualTo("Change the preferred installation location for new apps");
+        assertThat(cursor.getString(4)).isEqualTo(
+                "Change the preferred installation location for new apps");
         // Summary On Normalized
-        assertThat(cursor.getString(5)).isEqualTo("change the preferred installation location for new apps");
+        assertThat(cursor.getString(5)).isEqualTo(
+                "change the preferred installation location for new apps");
         // Summary Off - only on for checkbox preferences
         assertThat(cursor.getString(6)).isEmpty();
         // Summary off normalized - only on for checkbox preferences
         assertThat(cursor.getString(7)).isEmpty();
         // Entries - only on for list preferences
         assertThat(cursor.getString(8)).isEqualTo("Internal device storage|Removable SD card|" +
-                        "Let the system decide|");
+                "Let the system decide|");
         // Keywords
         assertThat(cursor.getString(9)).isEmpty();
         // Screen Title
@@ -443,7 +466,7 @@
         resource.className = "com.android.settings.display.ScreenZoomSettings";
 
         mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
-                new HashMap<String, List<String>>());
+                new HashMap<>());
         Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null);
         assertThat(cursor.getCount()).isEqualTo(1);
     }
@@ -455,7 +478,7 @@
         resource.className = "com.android.settings.display.ScreenZoomSettings";
 
         mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
-                new HashMap<String, List<String>>());
+                new HashMap<>());
         Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null);
         cursor.moveToPosition(0);
 
@@ -511,7 +534,7 @@
         resource.className = "com.android.settings.LegalSettings";
 
         mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
-                new HashMap<String, List<String>>());
+                new HashMap<>());
         Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null);
         assertThat(cursor.getCount()).isEqualTo(2);
     }
@@ -523,7 +546,7 @@
         resource.className = "com.android.settings.LegalSettings";
 
         mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
-                new HashMap<String, List<String>>());
+                new HashMap<>());
         Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index ORDER BY data_title", null);
         cursor.moveToPosition(0);
 
diff --git a/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java b/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java
index c592aef..c749a00 100644
--- a/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java
@@ -20,54 +20,68 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.sqlite.SQLiteDatabase;
+
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
+import com.android.settings.dashboard.SiteMapManager;
 import com.android.settings.search2.DatabaseIndexingUtils;
 import com.android.settings.search2.DatabaseResultLoader;
+import com.android.settings.testutils.DatabaseTestUtils;
+import com.android.settings.testutils.FakeFeatureFactory;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 
-import java.lang.reflect.Field;
-
 import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 @RunWith(SettingsRobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
 public class DatabaseResultLoaderTest {
-    private Context mContext;
 
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mMockContext;
+    @Mock
+    private SiteMapManager mSiteMapManager;
+    private Context mContext;
     private DatabaseResultLoader loader;
 
     SQLiteDatabase mDb;
 
     @Before
     public void setUp() {
+        MockitoAnnotations.initMocks(this);
         mContext = RuntimeEnvironment.application;
+        FakeFeatureFactory.setupForTest(mMockContext);
+        FakeFeatureFactory factory =
+                (FakeFeatureFactory) FakeFeatureFactory.getFactory(mMockContext);
+        when(factory.searchFeatureProvider.getSiteMapManager())
+                .thenReturn(mSiteMapManager);
         mDb = IndexDatabaseHelper.getInstance(mContext).getWritableDatabase();
         setUpDb();
     }
 
     @After
     public void cleanUp() {
-        Field instance;
-        Class clazz = IndexDatabaseHelper.class;
-        try {
-            instance = clazz.getDeclaredField("sSingleton");
-            instance.setAccessible(true);
-            instance.set(null, null);
-        } catch (Exception e) {
-            throw new RuntimeException();
-        }
+        DatabaseTestUtils.clearDb();
     }
 
     @Test
     public void testMatchTitle() {
         loader = new DatabaseResultLoader(mContext, "title");
         assertThat(loader.loadInBackground().size()).isEqualTo(3);
+        verify(mSiteMapManager, times(3)).buildBreadCrumb(eq(mContext), anyString(), anyString());
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/search/IntentSearchViewHolderTest.java b/tests/robotests/src/com/android/settings/search/IntentSearchViewHolderTest.java
index 3874479..7a2499b 100644
--- a/tests/robotests/src/com/android/settings/search/IntentSearchViewHolderTest.java
+++ b/tests/robotests/src/com/android/settings/search/IntentSearchViewHolderTest.java
@@ -41,6 +41,7 @@
 import org.robolectric.shadows.ShadowApplication;
 
 import java.util.ArrayList;
+import java.util.List;
 
 import static com.google.common.truth.Truth.assertThat;
 import static org.mockito.Matchers.any;
@@ -73,6 +74,7 @@
         assertThat(mHolder.titleView).isNotNull();
         assertThat(mHolder.summaryView).isNotNull();
         assertThat(mHolder.iconView).isNotNull();
+        assertThat(mHolder.breadcrumbView).isNotNull();
     }
 
     @Test
@@ -84,11 +86,43 @@
         assertThat(mHolder.titleView.getText()).isEqualTo(TITLE);
         assertThat(mHolder.summaryView.getText()).isEqualTo(SUMMARY);
         assertThat(mHolder.iconView.getDrawable()).isEqualTo(mIcon);
+        assertThat(mHolder.summaryView.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mHolder.breadcrumbView.getVisibility()).isEqualTo(View.GONE);
 
         verify(mFragment).onSearchResultClicked();
         verify(mFragment).startActivity(any(Intent.class));
     }
 
+    @Test
+    public void testBindViewElements_emptySummary_hideSummaryView() {
+        final SearchResult result = new Builder().addTitle(TITLE)
+                .addRank(1)
+                .addPayload(new IntentPayload(null))
+                .addIcon(mIcon)
+                .build();
+
+        mHolder.onBind(mFragment, result);
+        assertThat(mHolder.summaryView.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void testBindViewElements_withBreadcrumb_shouldFormatBreadcrumb() {
+        final List<String> breadcrumbs = new ArrayList<>();
+        breadcrumbs.add("a");
+        breadcrumbs.add("b");
+        breadcrumbs.add("c");
+        final SearchResult result = new Builder().addTitle(TITLE)
+                .addRank(1)
+                .addPayload(new IntentPayload(null))
+                .addBreadcrumbs(breadcrumbs)
+                .addIcon(mIcon)
+                .build();
+
+        mHolder.onBind(mFragment, result);
+        assertThat(mHolder.breadcrumbView.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mHolder.breadcrumbView.getText()).isEqualTo("a > b > c");
+    }
+
     private SearchResult getSearchResult() {
         Builder builder = new Builder();
         builder.addTitle(TITLE)
diff --git a/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java
index c0b1b3d..c314728 100644
--- a/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java
+++ b/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java
@@ -19,17 +19,13 @@
 
 import android.app.Activity;
 import android.content.Context;
-import android.content.res.Configuration;
 import android.view.Menu;
 
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
-import com.android.settings.overlay.FeatureFactory;
-import com.android.settings.search2.DatabaseIndexingManager;
+import com.android.settings.dashboard.SiteMapManager;
 import com.android.settings.search2.SearchFeatureProviderImpl;
 
-import com.android.settings.testutils.FakeFeatureFactory;
-import com.android.settingslib.drawer.DashboardCategory;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -77,6 +73,14 @@
     }
 
     @Test
+    public void getSiteMapManager_shouldCacheInstanec() {
+        final SiteMapManager manager1 = mProvider.getSiteMapManager();
+        final SiteMapManager manager2 = mProvider.getSiteMapManager();
+
+        assertThat(manager1).isSameAs(manager2);
+    }
+
+    @Test
     public void testUpdateIndexNewSearch_UsesDatabaseIndexingManager() {
         mProvider = spy(new SearchFeatureProviderImpl());
         when(mProvider.isEnabled(mActivity)).thenReturn(true);
diff --git a/tests/robotests/src/com/android/settings/search2/CursorToSearchResultConverterTest.java b/tests/robotests/src/com/android/settings/search2/CursorToSearchResultConverterTest.java
index 6ad7501..d69ba3e 100644
--- a/tests/robotests/src/com/android/settings/search2/CursorToSearchResultConverterTest.java
+++ b/tests/robotests/src/com/android/settings/search2/CursorToSearchResultConverterTest.java
@@ -28,12 +28,16 @@
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.SubSettings;
 import com.android.settings.TestConfig;
+import com.android.settings.dashboard.SiteMapManager;
 import com.android.settings.gestures.GestureSettings;
 import com.android.settings.search2.ResultPayload.PayloadType;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 import org.robolectric.Robolectric;
 import org.robolectric.annotation.Config;
 
@@ -57,11 +61,14 @@
     private static final int BASE_RANK = 1;
     private static final int EXAMPLES = 3;
 
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private SiteMapManager mSiteMapManager;
     private Drawable mDrawable;
     private CursorToSearchResultConverter mConverter;
 
     @Before
     public void setUp() {
+        MockitoAnnotations.initMocks(this);
         Context context = Robolectric.buildActivity(Activity.class).get();
         mDrawable = context.getDrawable(ICON);
         mConverter = new CursorToSearchResultConverter(context, QUERY);
@@ -69,19 +76,21 @@
 
     @Test
     public void testParseNullResults_ReturnsNull() {
-        List<SearchResult> results = mConverter.convertCursor(null, BASE_RANK);
+        List<SearchResult> results = mConverter.convertCursor(mSiteMapManager, null, BASE_RANK);
         assertThat(results).isNull();
     }
 
     @Test
     public void testParseCursor_NotNull() {
-        List<SearchResult> results = mConverter.convertCursor(getDummyCursor(), BASE_RANK);
+        List<SearchResult> results = mConverter.convertCursor(
+                mSiteMapManager, getDummyCursor(), BASE_RANK);
         assertThat(results).isNotNull();
     }
 
     @Test
     public void testParseCursor_MatchesRank() {
-        List<SearchResult> results = mConverter.convertCursor(getDummyCursor(), BASE_RANK);
+        List<SearchResult> results = mConverter.convertCursor(
+                mSiteMapManager, getDummyCursor(), BASE_RANK);
         for (int i = 0; i < EXAMPLES; i++) {
             assertThat(results.get(i).rank).isEqualTo(BASE_RANK);
         }
@@ -89,7 +98,8 @@
 
     @Test
     public void testParseCursor_MatchesTitle() {
-        List<SearchResult> results = mConverter.convertCursor(getDummyCursor(), BASE_RANK);
+        List<SearchResult> results = mConverter.convertCursor(
+                mSiteMapManager, getDummyCursor(), BASE_RANK);
         for (int i = 0; i < EXAMPLES; i++) {
             assertThat(results.get(i).title).isEqualTo(TITLES[i]);
         }
@@ -97,7 +107,8 @@
 
     @Test
     public void testParseCursor_MatchesSummary() {
-        List<SearchResult> results = mConverter.convertCursor(getDummyCursor(), BASE_RANK);
+        List<SearchResult> results = mConverter.convertCursor(
+                mSiteMapManager, getDummyCursor(), BASE_RANK);
         for (int i = 0; i < EXAMPLES; i++) {
             assertThat(results.get(i).summary).isEqualTo(SUMMARY);
         }
@@ -105,7 +116,8 @@
 
     @Test
     public void testParseCursor_MatchesIcon() {
-        List<SearchResult> results = mConverter.convertCursor(getDummyCursor(), BASE_RANK);
+        List<SearchResult> results = mConverter.convertCursor(
+                mSiteMapManager, getDummyCursor(), BASE_RANK);
         for (int i = 0; i < EXAMPLES; i++) {
             Drawable resultDrawable = results.get(i).icon;
             assertThat(resultDrawable).isNotNull();
@@ -116,7 +128,7 @@
     @Test
     public void testParseCursor_NoIcon() {
         List<SearchResult> results = mConverter.convertCursor(
-                getDummyCursor(false /* hasIcon */), BASE_RANK);
+                mSiteMapManager, getDummyCursor(false /* hasIcon */), BASE_RANK);
         for (int i = 0; i < EXAMPLES; i++) {
             Drawable resultDrawable = results.get(i).icon;
             assertThat(resultDrawable).isNull();
@@ -125,7 +137,8 @@
 
     @Test
     public void testParseCursor_MatchesPayloadType() {
-        List<SearchResult> results = mConverter.convertCursor(getDummyCursor(), BASE_RANK);
+        List<SearchResult> results = mConverter.convertCursor(
+                mSiteMapManager, getDummyCursor(), BASE_RANK);
         ResultPayload payload;
         for (int i = 0; i < EXAMPLES; i++) {
             payload = results.get(i).payload;
@@ -152,7 +165,7 @@
                 0,       // Payload Type
                 null     // Payload
         });
-        List<SearchResult> results = mConverter.convertCursor(cursor, BASE_RANK);
+        List<SearchResult> results = mConverter.convertCursor(mSiteMapManager, cursor, BASE_RANK);
         IntentPayload payload = (IntentPayload) results.get(0).payload;
         Intent intent = payload.intent;
         assertThat(intent.getComponent().getClassName()).isEqualTo(SubSettings.class.getName());
@@ -160,7 +173,8 @@
 
     @Test
     public void testParseCursor_MatchesIntentPayload() {
-        List<SearchResult> results = mConverter.convertCursor(getDummyCursor(), BASE_RANK);
+        List<SearchResult> results = mConverter.convertCursor(
+                mSiteMapManager, getDummyCursor(), BASE_RANK);
         IntentPayload payload;
         for (int i = 0; i < EXAMPLES; i++) {
             payload = (IntentPayload) results.get(i).payload;
@@ -187,7 +201,7 @@
                 PayloadType.INTENT,    // Payload Type
                 null // Payload
         });
-        List<SearchResult> results = mConverter.convertCursor(cursor, BASE_RANK);
+        List<SearchResult> results = mConverter.convertCursor(mSiteMapManager, cursor, BASE_RANK);
         IntentPayload payload = (IntentPayload) results.get(0).payload;
         Intent intent = payload.intent;
 
@@ -222,7 +236,7 @@
                 type,    // Payload Type
                 ResultPayloadUtils.marshall(payload) // Payload
         });
-        List<SearchResult> results = mConverter.convertCursor(cursor, BASE_RANK);
+        List<SearchResult> results = mConverter.convertCursor(mSiteMapManager, cursor, BASE_RANK);
         InlineSwitchPayload newPayload = (InlineSwitchPayload) results.get(0).payload;
 
         assertThat(newPayload.settingsUri).isEqualTo(uri);
diff --git a/tests/robotests/src/com/android/settings/search2/InstalledAppResultLoaderTest.java b/tests/robotests/src/com/android/settings/search2/InstalledAppResultLoaderTest.java
index 8fde73d..e6397e1 100644
--- a/tests/robotests/src/com/android/settings/search2/InstalledAppResultLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/search2/InstalledAppResultLoaderTest.java
@@ -17,13 +17,18 @@
 package com.android.settings.search2;
 
 import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
 import android.os.UserManager;
 
+import com.android.settings.R;
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
 import com.android.settings.applications.PackageManagerWrapper;
+import com.android.settings.dashboard.SiteMapManager;
 import com.android.settings.testutils.ApplicationTestUtils;
+import com.android.settings.testutils.FakeFeatureFactory;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -38,8 +43,16 @@
 import java.util.List;
 
 import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
+import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
 import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 @RunWith(SettingsRobolectricTestRunner.class)
@@ -52,16 +65,24 @@
     private PackageManagerWrapper mPackageManagerWrapper;
     @Mock
     private UserManager mUserManager;
+    @Mock
+    private SiteMapManager mSiteMapManager;
 
     private InstalledAppResultLoader mLoader;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        FakeFeatureFactory.setupForTest(mContext);
+        FakeFeatureFactory factory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
+        when(factory.searchFeatureProvider.getSiteMapManager())
+                .thenReturn(mSiteMapManager);
         final List<UserInfo> infos = new ArrayList<>();
         infos.add(new UserInfo(1, "user 1", 0));
         when(mUserManager.getProfiles(anyInt())).thenReturn(infos);
         when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+        when(mContext.getString(R.string.applications_settings))
+                .thenReturn("app");
         when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
                 .thenReturn(Arrays.asList(
                         ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_SYSTEM,
@@ -89,9 +110,68 @@
     public void query_matchingQuery_shouldReturnNonSystemApps() {
         final String query = "app";
 
-        mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query);
+        mLoader = spy(new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query));
+        when(mLoader.getContext()).thenReturn(mContext);
+        when(mSiteMapManager.buildBreadCrumb(eq(mContext), anyString(), anyString()))
+                .thenReturn(Arrays.asList(new String[]{"123"}));
 
         assertThat(mLoader.loadInBackground().size()).isEqualTo(2);
+        verify(mSiteMapManager)
+                .buildBreadCrumb(eq(mContext), anyString(), anyString());
+    }
+
+    @Test
+    public void query_matchingQuery_shouldReturnSystemAppUpdates() {
+        when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
+                .thenReturn(Arrays.asList(
+                        ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_UPDATED_SYSTEM_APP,
+                                0 /* targetSdkVersion */)));
+        final String query = "app";
+
+        mLoader = spy(new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query));
+        when(mLoader.getContext()).thenReturn(mContext);
+
+        assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
+        verify(mSiteMapManager)
+                .buildBreadCrumb(eq(mContext), anyString(), anyString());
+    }
+
+    @Test
+    public void query_matchingQuery_shouldReturnSystemAppIfLaunchable() {
+        when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
+                .thenReturn(Arrays.asList(
+                        ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_SYSTEM,
+                                0 /* targetSdkVersion */)));
+        final List<ResolveInfo> list = mock(List.class);
+        when(list.size()).thenReturn(1);
+        when(mPackageManagerWrapper.queryIntentActivitiesAsUser(
+                any(Intent.class), anyInt(), anyInt()))
+                .thenReturn(list);
+
+        final String query = "app";
+
+        mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query);
+
+        assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
+    }
+
+    @Test
+    public void query_matchingQuery_shouldNotReturnSystemAppIfNotLaunchable() {
+        when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
+                .thenReturn(Arrays.asList(
+                        ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_SYSTEM,
+                                0 /* targetSdkVersion */)));
+        when(mPackageManagerWrapper.queryIntentActivitiesAsUser(
+                any(Intent.class), anyInt(), anyInt()))
+                .thenReturn(null);
+
+        final String query = "app";
+
+        mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query);
+
+        assertThat(mLoader.loadInBackground()).isEmpty();
+        verify(mSiteMapManager, never())
+                .buildBreadCrumb(eq(mContext), anyString(), anyString());
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/search2/SavedQueryLoaderTest.java b/tests/robotests/src/com/android/settings/search2/SavedQueryLoaderTest.java
new file mode 100644
index 0000000..d975f0c
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/search2/SavedQueryLoaderTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 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.search2;
+
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.search.IndexDatabaseHelper;
+import com.android.settings.testutils.DatabaseTestUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.List;
+
+import static com.google.common.truth.Truth.assertThat;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SavedQueryLoaderTest {
+
+    private Context mContext;
+    private SQLiteDatabase mDb;
+    private SavedQueryLoader mLoader;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mDb = IndexDatabaseHelper.getInstance(mContext).getWritableDatabase();
+        mLoader = new SavedQueryLoader(mContext);
+        setUpDb();
+    }
+
+    @After
+    public void cleanUp() {
+        DatabaseTestUtils.clearDb();
+    }
+
+    @Test
+    public void loadInBackground_shouldReturnSavedQueries() {
+        final List<SearchResult> results = mLoader.loadInBackground();
+        assertThat(results.size()).isEqualTo(SavedQueryLoader.MAX_PROPOSED_SUGGESTIONS);
+        for (SearchResult result : results) {
+            assertThat(result.viewType).isEqualTo(ResultPayload.PayloadType.SAVED_QUERY);
+        }
+    }
+
+    private void setUpDb() {
+        final long now = System.currentTimeMillis();
+        for (int i = 0; i < SavedQueryLoader.MAX_PROPOSED_SUGGESTIONS + 2; i++) {
+            ContentValues values = new ContentValues();
+            values.put(IndexDatabaseHelper.SavedQueriesColumns.QUERY, String.valueOf(i));
+            values.put(IndexDatabaseHelper.SavedQueriesColumns.TIME_STAMP, now);
+            mDb.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_SAVED_QUERIES, null, values);
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/search2/SavedQueryPayloadTest.java b/tests/robotests/src/com/android/settings/search2/SavedQueryPayloadTest.java
new file mode 100644
index 0000000..daa6d5e
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/search2/SavedQueryPayloadTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 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.search2;
+
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import static com.google.common.truth.Truth.assertThat;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SavedQueryPayloadTest {
+
+    private SavedQueryPayload mPayload;
+
+    @Test
+    public void getType_shouldBeSavedQueryType() {
+        mPayload = new SavedQueryPayload("Test");
+        assertThat(mPayload.getType()).isEqualTo(ResultPayload.PayloadType.SAVED_QUERY);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/search2/SavedQueryRecorderTest.java b/tests/robotests/src/com/android/settings/search2/SavedQueryRecorderTest.java
new file mode 100644
index 0000000..c56ecce
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/search2/SavedQueryRecorderTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2017 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.search2;
+
+
+import android.content.Context;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.DatabaseTestUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.List;
+
+import static com.google.common.truth.Truth.assertThat;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SavedQueryRecorderTest {
+
+    private Context mContext;
+    private SavedQueryRecorder mRecorder;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+    }
+
+    @After
+    public void cleanUp() {
+        DatabaseTestUtils.clearDb();
+    }
+
+    @Test
+    public void canSaveQueryToDb() {
+        final String query = "test";
+        mRecorder = new SavedQueryRecorder(mContext, query);
+
+        mRecorder.loadInBackground();
+
+        final SavedQueryLoader loader = new SavedQueryLoader(mContext);
+        List<SearchResult> results = loader.loadInBackground();
+
+        assertThat(results.size()).isEqualTo(1);
+        assertThat(results.get(0).title).isEqualTo(query);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/search2/SearchFragmentTest.java b/tests/robotests/src/com/android/settings/search2/SearchFragmentTest.java
index d97360d..7a0bb54 100644
--- a/tests/robotests/src/com/android/settings/search2/SearchFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/search2/SearchFragmentTest.java
@@ -39,6 +39,7 @@
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -52,6 +53,9 @@
     private DatabaseResultLoader mDatabaseResultLoader;
     @Mock
     private InstalledAppResultLoader mInstalledAppResultLoader;
+    @Mock
+    private SavedQueryLoader mSavedQueryLoader;
+
     private FakeFeatureFactory mFeatureFactory;
 
     @Before
@@ -65,6 +69,8 @@
         when(mFeatureFactory.searchFeatureProvider
                 .getInstalledAppSearchLoader(any(Context.class), anyString()))
                 .thenReturn(mInstalledAppResultLoader);
+        when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
+                .thenReturn(mSavedQueryLoader);
     }
 
     @Test
@@ -115,6 +121,27 @@
     }
 
     @Test
+    public void queryTextChangeToEmpty_shouldTriggerSavedQueryLoader() {
+        ActivityController<SearchActivity> activityController =
+                Robolectric.buildActivity(SearchActivity.class);
+        activityController.setup();
+        SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
+                .findFragmentById(R.id.main_content);
+
+        fragment.onQueryTextChange("");
+        activityController.get().onBackPressed();
+        activityController.pause().stop().destroy();
+
+        verify(mFeatureFactory.searchFeatureProvider, never())
+                .getDatabaseSearchLoader(any(Context.class), anyString());
+        verify(mFeatureFactory.searchFeatureProvider, never())
+                .getInstalledAppSearchLoader(any(Context.class), anyString());
+        // Saved query loaded 2 times: fragment start, and query change to empty.
+        verify(mFeatureFactory.searchFeatureProvider, times(2))
+                .getSavedQueryLoader(any(Context.class));
+    }
+
+    @Test
     public void updateIndex_TriggerOnCreate() {
         ActivityController<SearchActivity> activityController =
                 Robolectric.buildActivity(SearchActivity.class);
diff --git a/tests/robotests/src/com/android/settings/search2/SiteMapManagerTest.java b/tests/robotests/src/com/android/settings/search2/SiteMapManagerTest.java
new file mode 100644
index 0000000..b8ac8fe
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/search2/SiteMapManagerTest.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2017 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.search2;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.os.Bundle;
+
+import com.android.settings.SettingsActivity;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.dashboard.SiteMapManager;
+import com.android.settings.search.IndexDatabaseHelper;
+import com.android.settings.search.IndexDatabaseHelper.SiteMapColumns;
+import com.android.settings.system.SystemDashboardFragment;
+import com.android.settings.testutils.DatabaseTestUtils;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settingslib.drawer.CategoryKey;
+import com.android.settingslib.drawer.DashboardCategory;
+import com.android.settingslib.drawer.Tile;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SiteMapManagerTest {
+
+    private static final int STATIC_DB_DEPTH = 4;
+    private static final String CLASS_PREFIX = "class_";
+    private static final String TITLE_PREFIX = "title_";
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mMockContext;
+    private Context mContext;
+    private SQLiteDatabase mDb;
+    private SiteMapManager mSiteMapManager;
+    private FakeFeatureFactory mFeatureFactory;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        FakeFeatureFactory.setupForTest(mMockContext);
+        mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mMockContext);
+
+        mContext = RuntimeEnvironment.application;
+        mDb = IndexDatabaseHelper.getInstance(mContext).getWritableDatabase();
+        buildDb();
+        mSiteMapManager = new SiteMapManager();
+    }
+
+    @After
+    public void cleanUp() {
+        DatabaseTestUtils.clearDb();
+    }
+
+    @Test
+    public void buildBreadCrumb_onlyFromSiteMapDb_breadcrumbShouldLinkUp() {
+        List<String> breadcrumb = mSiteMapManager.buildBreadCrumb(mContext,
+                CLASS_PREFIX + 0, TITLE_PREFIX + 0);
+        assertThat(breadcrumb.size()).isEqualTo(STATIC_DB_DEPTH + 1);
+        for (int i = 0; i < STATIC_DB_DEPTH; i++) {
+            assertThat(breadcrumb.get(i)).isEqualTo(TITLE_PREFIX + (STATIC_DB_DEPTH - i));
+        }
+    }
+
+    @Test
+    public void buildBreadCrumb_fromSiteMapDbAndDashboardProvider_breadcrumbShouldLinkUp() {
+        final String iaClass = SystemDashboardFragment.class.getName();
+        final String iaTitle = "ia_title";
+
+        ContentValues index = new ContentValues();
+        index.put(IndexDatabaseHelper.IndexColumns.CLASS_NAME, iaClass);
+        index.put(IndexDatabaseHelper.IndexColumns.SCREEN_TITLE, iaTitle);
+        mDb.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX, null, index);
+
+        final DashboardCategory category = new DashboardCategory();
+        category.key = CategoryKey.CATEGORY_SYSTEM;
+        category.tiles.add(new Tile());
+        category.tiles.get(0).title = TITLE_PREFIX + STATIC_DB_DEPTH;
+        category.tiles.get(0).metaData = new Bundle();
+        category.tiles.get(0).metaData.putString(SettingsActivity.META_DATA_KEY_FRAGMENT_CLASS,
+                CLASS_PREFIX + STATIC_DB_DEPTH);
+        when(mFeatureFactory.dashboardFeatureProvider.getAllCategories())
+                .thenReturn(Arrays.asList(category));
+
+        final List<String> breadcrumb = mSiteMapManager.buildBreadCrumb(mContext,
+                CLASS_PREFIX + 0, TITLE_PREFIX + 0);
+
+        assertThat(breadcrumb.size()).isEqualTo(STATIC_DB_DEPTH + 2);
+        assertThat(breadcrumb.get(0))
+                .isEqualTo(iaTitle);
+    }
+
+    @Test
+    public void buildBreadCrumb_classNotIndexed_shouldNotHaveBreadCrumb() {
+        final String title = "wrong_title";
+
+        final List<String> breadcrumb = mSiteMapManager.buildBreadCrumb(mContext,
+                "wrong_class", title);
+
+        assertThat(breadcrumb.size()).isEqualTo(1);
+        assertThat(breadcrumb.get(0)).isEqualTo(title);
+    }
+
+    private void buildDb() {
+        for (int i = 0; i < STATIC_DB_DEPTH; i++) {
+            final ContentValues siteMapPair = new ContentValues();
+            siteMapPair.put(SiteMapColumns.DOCID, i);
+            siteMapPair.put(SiteMapColumns.PARENT_CLASS, CLASS_PREFIX + (i + 1));
+            siteMapPair.put(SiteMapColumns.PARENT_TITLE, TITLE_PREFIX + (i + 1));
+            siteMapPair.put(SiteMapColumns.CHILD_CLASS, CLASS_PREFIX + i);
+            siteMapPair.put(SiteMapColumns.CHILD_TITLE, TITLE_PREFIX + i);
+            mDb.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_SITE_MAP, null, siteMapPair);
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/testutils/DatabaseTestUtils.java b/tests/robotests/src/com/android/settings/testutils/DatabaseTestUtils.java
new file mode 100644
index 0000000..8fbe1c9
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/testutils/DatabaseTestUtils.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 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.testutils;
+
+import com.android.settings.search.IndexDatabaseHelper;
+
+import java.lang.reflect.Field;
+
+public class DatabaseTestUtils {
+
+    public static void clearDb() {
+        Field instance;
+        Class clazz = IndexDatabaseHelper.class;
+        try {
+            instance = clazz.getDeclaredField("sSingleton");
+            instance.setAccessible(true);
+            instance.set(null, null);
+        } catch (Exception e) {
+            throw new RuntimeException();
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/webview/WebViewAppListAdapterTest.java b/tests/robotests/src/com/android/settings/webview/WebViewAppListAdapterTest.java
new file mode 100644
index 0000000..a8ab5d4
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/webview/WebViewAppListAdapterTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2017 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.webview;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.UserInfo;
+
+import com.android.settings.R;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+
+import java.util.Arrays;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class WebViewAppListAdapterTest {
+    private Context mContext = RuntimeEnvironment.application;
+
+    private final static UserInfo FIRST_USER = new UserInfo(0, "FIRST_USER", 0);
+    private final static UserInfo SECOND_USER = new UserInfo(0, "SECOND_USER", 0);
+
+    private final static String DEFAULT_PACKAGE_NAME = "DEFAULT_PACKAGE_NAME";
+
+    @Test
+    public void testDisabledReasonNullIfPackagesOk() {
+        UserPackageWrapper packageForFirstUser = mock(UserPackageWrapper.class);
+        when(packageForFirstUser.isEnabledPackage()).thenReturn(true);
+        when(packageForFirstUser.isInstalledPackage()).thenReturn(true);
+
+        UserPackageWrapper packageForSecondUser = mock(UserPackageWrapper.class);
+        when(packageForSecondUser.isEnabledPackage()).thenReturn(true);
+        when(packageForSecondUser.isInstalledPackage()).thenReturn(true);
+
+        WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class);
+        when(wvusWrapper.getPackageInfosAllUsers(
+                any(), eq(DEFAULT_PACKAGE_NAME))).thenReturn(
+                        Arrays.asList(packageForFirstUser, packageForSecondUser));
+
+        assertThat(WebViewAppListAdapter.getDisabledReason(
+                wvusWrapper, mContext, DEFAULT_PACKAGE_NAME)).isNull();
+    }
+
+    @Test
+    public void testDisabledReasonForSingleUserDisabledPackage() {
+        UserPackageWrapper packageForFirstUser = mock(UserPackageWrapper.class);
+        when(packageForFirstUser.isEnabledPackage()).thenReturn(false);
+        when(packageForFirstUser.isInstalledPackage()).thenReturn(true);
+        when(packageForFirstUser.getUserInfo()).thenReturn(FIRST_USER);
+
+        WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class);
+        when(wvusWrapper.getPackageInfosAllUsers(any(), eq(DEFAULT_PACKAGE_NAME)
+                )).thenReturn(Arrays.asList(packageForFirstUser));
+
+        assertThat(WebViewAppListAdapter.getDisabledReason(wvusWrapper, mContext,
+                DEFAULT_PACKAGE_NAME)).isEqualTo("Disabled for user " + FIRST_USER.name + "\n");
+    }
+
+    @Test
+    public void testDisabledReasonForSingleUserUninstalledPackage() {
+        UserPackageWrapper packageForFirstUser = mock(UserPackageWrapper.class);
+        when(packageForFirstUser.isEnabledPackage()).thenReturn(true);
+        when(packageForFirstUser.isInstalledPackage()).thenReturn(false);
+        when(packageForFirstUser.getUserInfo()).thenReturn(FIRST_USER);
+
+        WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class);
+        when(wvusWrapper.getPackageInfosAllUsers(any(), eq(DEFAULT_PACKAGE_NAME)
+                )).thenReturn(Arrays.asList(packageForFirstUser));
+
+        assertThat(WebViewAppListAdapter.getDisabledReason(wvusWrapper, mContext,
+                DEFAULT_PACKAGE_NAME)).isEqualTo("Uninstalled for user " + FIRST_USER.name + "\n");
+    }
+
+    @Test
+    public void testDisabledReasonSeveralUsers() {
+        UserPackageWrapper packageForFirstUser = mock(UserPackageWrapper.class);
+        when(packageForFirstUser.isEnabledPackage()).thenReturn(false);
+        when(packageForFirstUser.isInstalledPackage()).thenReturn(true);
+        when(packageForFirstUser.getUserInfo()).thenReturn(FIRST_USER);
+
+        UserPackageWrapper packageForSecondUser = mock(UserPackageWrapper.class);
+        when(packageForSecondUser.isEnabledPackage()).thenReturn(true);
+        when(packageForSecondUser.isInstalledPackage()).thenReturn(false);
+        when(packageForSecondUser.getUserInfo()).thenReturn(SECOND_USER);
+
+        WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class);
+        when(wvusWrapper.getPackageInfosAllUsers(any(), eq(DEFAULT_PACKAGE_NAME)
+                )).thenReturn(Arrays.asList(packageForFirstUser, packageForSecondUser));
+
+        final String EXPECTED_DISABLED_REASON = String.format(
+                "Disabled for user %s\nUninstalled for user %s\n",
+                FIRST_USER.name, SECOND_USER.name);
+        assertThat(WebViewAppListAdapter.getDisabledReason(
+                wvusWrapper, mContext,DEFAULT_PACKAGE_NAME)).isEqualTo(EXPECTED_DISABLED_REASON);
+    }
+
+    /**
+     * Ensure we only proclaim a package as uninstalled for a certain user if it's both uninstalled
+     * and disabled.
+     */
+    @Test
+    public void testDisabledReasonUninstalledAndDisabled() {
+        UserPackageWrapper packageForFirstUser = mock(UserPackageWrapper.class);
+        when(packageForFirstUser.isEnabledPackage()).thenReturn(false);
+        when(packageForFirstUser.isInstalledPackage()).thenReturn(false);
+        when(packageForFirstUser.getUserInfo()).thenReturn(FIRST_USER);
+
+        UserPackageWrapper packageForSecondUser = mock(UserPackageWrapper.class);
+        when(packageForSecondUser.isEnabledPackage()).thenReturn(true);
+        when(packageForSecondUser.isInstalledPackage()).thenReturn(true);
+        when(packageForSecondUser.getUserInfo()).thenReturn(SECOND_USER);
+
+        WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class);
+        when(wvusWrapper.getPackageInfosAllUsers(any(), eq(DEFAULT_PACKAGE_NAME)
+                )).thenReturn(Arrays.asList(packageForFirstUser, packageForSecondUser));
+
+        final String EXPECTED_DISABLED_REASON = String.format(
+                "Uninstalled for user %s\n", FIRST_USER.name);
+        assertThat(WebViewAppListAdapter.getDisabledReason(wvusWrapper, mContext,
+                DEFAULT_PACKAGE_NAME)).isEqualTo(EXPECTED_DISABLED_REASON);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/webview/WebViewAppPickerTest.java b/tests/robotests/src/com/android/settings/webview/WebViewAppPickerTest.java
new file mode 100644
index 0000000..8ace8aa
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/webview/WebViewAppPickerTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2017 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.webview;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.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;
+import static org.mockito.Mockito.when;
+import static org.robolectric.shadows.ShadowView.clickOn;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.app.Activity;
+import android.content.pm.ApplicationInfo;
+import android.view.View;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+
+import java.util.Arrays;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ActivityController;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class WebViewAppPickerTest {
+
+  private static final String DEFAULT_PACKAGE_NAME = "DEFAULT_PACKAGE_NAME";
+
+  private static ApplicationInfo createApplicationInfo(String packageName) {
+      ApplicationInfo ai = new ApplicationInfo();
+      ai.packageName = packageName;
+      return ai;
+  }
+
+  @Test
+  public void testClickingItemChangesProvider() {
+      ActivityController<WebViewAppPicker> controller =
+              Robolectric.buildActivity(WebViewAppPicker.class);
+      WebViewAppPicker webviewAppPicker = controller.get();
+
+      WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class);
+      when(wvusWrapper.getValidWebViewApplicationInfos(any())).thenReturn(
+              Arrays.asList(createApplicationInfo(DEFAULT_PACKAGE_NAME)));
+      when(wvusWrapper.setWebViewProvider(eq(DEFAULT_PACKAGE_NAME))).thenReturn(true);
+
+      webviewAppPicker.setWebViewUpdateServiceWrapper(wvusWrapper);
+
+      controller.create().start().postCreate(null).resume().visible();
+      WebViewApplicationInfo firstItem =
+              (WebViewApplicationInfo) webviewAppPicker.getListView().getItemAtPosition(0);
+      assertThat(firstItem.info.packageName).isEqualTo(DEFAULT_PACKAGE_NAME);
+
+      webviewAppPicker.onListItemClick(webviewAppPicker.getListView(), null, 0, 0);
+
+      verify(wvusWrapper, times(1)).setWebViewProvider(eq(DEFAULT_PACKAGE_NAME));
+      assertThat(shadowOf(webviewAppPicker).getResultCode()).isEqualTo(Activity.RESULT_OK);
+      verify(wvusWrapper, never()).showInvalidChoiceToast(any());
+  }
+
+  @Test
+  public void testFailingPackageChangeReturnsCancelled() {
+      ActivityController<WebViewAppPicker> controller =
+              Robolectric.buildActivity(WebViewAppPicker.class);
+      WebViewAppPicker webviewAppPicker = controller.get();
+
+      WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class);
+      when(wvusWrapper.getValidWebViewApplicationInfos(any())).thenReturn(
+              Arrays.asList(createApplicationInfo(DEFAULT_PACKAGE_NAME)));
+      when(wvusWrapper.setWebViewProvider(eq(DEFAULT_PACKAGE_NAME))).thenReturn(false);
+
+      webviewAppPicker.setWebViewUpdateServiceWrapper(wvusWrapper);
+
+      controller.create().start().postCreate(null).resume().visible();
+      WebViewApplicationInfo firstItem =
+              (WebViewApplicationInfo) webviewAppPicker.getListView().getItemAtPosition(0);
+      assertThat(firstItem.info.packageName).isEqualTo(DEFAULT_PACKAGE_NAME);
+
+      webviewAppPicker.onListItemClick(webviewAppPicker.getListView(), null, 0, 0);
+
+      verify(wvusWrapper, times(1)).setWebViewProvider(eq(DEFAULT_PACKAGE_NAME));
+      assertThat(shadowOf(webviewAppPicker).getResultCode()).isEqualTo(Activity.RESULT_CANCELED);
+      verify(wvusWrapper, times(1)).showInvalidChoiceToast(any());
+  }
+}
diff --git a/tests/robotests/src/com/android/settings/webview/WebViewAppPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/webview/WebViewAppPreferenceControllerTest.java
new file mode 100644
index 0000000..c16bd1f
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/webview/WebViewAppPreferenceControllerTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 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.webview;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.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;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class WebViewAppPreferenceControllerTest {
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+
+    @Mock
+    private PreferenceScreen mPreferenceScreen;
+    @Mock
+    private Preference mPreference;
+
+    private static final String DEFAULT_PACKAGE_NAME = "DEFAULT_PACKAGE_NAME";
+
+    @Before public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mPreferenceScreen.findPreference(any())).thenReturn(mPreference);
+    }
+
+    @Test public void testOnActivityResultUpdatesStateOnSuccess() {
+        WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class);
+        WebViewAppPreferenceController controller =
+                spy(new WebViewAppPreferenceController(mContext, wvusWrapper));
+
+        controller.displayPreference(mPreferenceScreen); // Makes sure Preference is non-null
+        controller.onActivityResult(Activity.RESULT_OK, new Intent(DEFAULT_PACKAGE_NAME));
+        verify(controller, times(1)).updateState(any());
+    }
+
+    @Test public void testOnActivityResultWithFailureDoesNothing() {
+        WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class);
+
+        WebViewAppPreferenceController controller =
+                spy(new WebViewAppPreferenceController(mContext, wvusWrapper));
+
+        controller.displayPreference(mPreferenceScreen); // Makes sure Preference is non-null
+        controller.onActivityResult(Activity.RESULT_CANCELED, new Intent(DEFAULT_PACKAGE_NAME));
+        verify(controller, never()).updateState(any());
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/widget/MasterSwitchPreferenceTest.java b/tests/robotests/src/com/android/settings/widget/MasterSwitchPreferenceTest.java
new file mode 100644
index 0000000..1dce599
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/widget/MasterSwitchPreferenceTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2017 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.widget;
+
+import android.content.Context;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceViewHolder;
+import android.support.v7.preference.Preference.OnPreferenceChangeListener;
+import android.view.LayoutInflater;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.Switch;
+
+import com.android.settings.R;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class MasterSwitchPreferenceTest {
+
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = ShadowApplication.getInstance().getApplicationContext();
+    }
+
+    @Test
+    public void createNewPreference_shouldSetLayout() {
+        final MasterSwitchPreference preference = new MasterSwitchPreference(mContext);
+
+        assertThat(preference.getWidgetLayoutResource()).isEqualTo(
+            R.layout.preference_widget_master_switch);
+    }
+
+    @Test
+    public void setChecked_shouldUpdateButtonCheckedState() {
+        final MasterSwitchPreference preference = new MasterSwitchPreference(mContext);
+        final PreferenceViewHolder holder = new PreferenceViewHolder(LayoutInflater.from(mContext)
+            .inflate(R.layout.preference_widget_master_switch, null));
+        final Switch toggle = (Switch) holder.itemView.findViewById(R.id.switchWidget);
+        preference.onBindViewHolder(holder);
+
+        preference.setChecked(true);
+        assertThat(toggle.isChecked()).isTrue();
+
+        preference.setChecked(false);
+        assertThat(toggle.isChecked()).isFalse();
+    }
+
+    @Test
+    public void setSwitchEnabled_shouldUpdateButtonEnabledState() {
+        final MasterSwitchPreference preference = new MasterSwitchPreference(mContext);
+        final PreferenceViewHolder holder = new PreferenceViewHolder(
+            LayoutInflater.from(mContext).inflate(R.layout.preference_widget_master_switch, null));
+        final Switch toggle = (Switch) holder.itemView.findViewById(R.id.switchWidget);
+        preference.onBindViewHolder(holder);
+
+        preference.setSwitchEnabled(true);
+        assertThat(toggle.isEnabled()).isTrue();
+
+        preference.setSwitchEnabled(false);
+        assertThat(toggle.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void toggleButtonOn_shouldNotifyChecked() {
+        final MasterSwitchPreference preference = new MasterSwitchPreference(mContext);
+        final PreferenceViewHolder holder = new PreferenceViewHolder(
+            LayoutInflater.from(mContext).inflate(R.layout.preference_widget_master_switch, null));
+        final Switch toggle = (Switch) holder.itemView.findViewById(R.id.switchWidget);
+        final OnPreferenceChangeListener listener = mock(OnPreferenceChangeListener.class);
+        preference.setOnPreferenceChangeListener(listener);
+        preference.onBindViewHolder(holder);
+
+        toggle.setChecked(true);
+        verify(listener).onPreferenceChange(preference, true);
+    }
+
+    @Test
+    public void toggleButtonOff_shouldNotifyUnchecked() {
+        final MasterSwitchPreference preference = new MasterSwitchPreference(mContext);
+        final PreferenceViewHolder holder = new PreferenceViewHolder(
+            LayoutInflater.from(mContext).inflate(R.layout.preference_widget_master_switch, null));
+        final Switch toggle = (Switch) holder.itemView.findViewById(R.id.switchWidget);
+        final OnPreferenceChangeListener listener = mock(OnPreferenceChangeListener.class);
+        preference.setChecked(true);
+        preference.setOnPreferenceChangeListener(listener);
+        preference.onBindViewHolder(holder);
+
+        toggle.setChecked(false);
+        verify(listener).onPreferenceChange(preference, false);
+    }
+}