Merge "Change the text "Network & Internet""
diff --git a/res/drawable/ic_bt_laptop.xml b/res/drawable/ic_bt_laptop.xml
deleted file mode 100644
index 029e4d9..0000000
--- a/res/drawable/ic_bt_laptop.xml
+++ /dev/null
@@ -1,26 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24.0"
-    android:viewportHeight="24.0"
-    android:tint="?android:attr/colorControlNormal">
-    <path
-        android:fillColor="#FF000000"
-        android:pathData="M20,18c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2H4c-1.1,0
-            -2,0.9 -2,2v10c0,1.1 0.9,2 2,2H0v2h24v-2h-4zM4,6h16v10H4V6z"/>
-</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_settings_bluetooth.xml b/res/drawable/ic_settings_bluetooth.xml
deleted file mode 100644
index 6e32e1a..0000000
--- a/res/drawable/ic_settings_bluetooth.xml
+++ /dev/null
@@ -1,25 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24.0dp"
-        android:height="24.0dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"
-        android:tint="?android:attr/colorControlNormal">
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M13.5,12l3.8,-3.7c0.4,-0.4 0.4,-1.1 0,-1.5l-4.5,-4.5c-0.4,-0.4 -1.1,-0.4 -1.5,0.1C11.1,2.5 11,2.8 11,3v6.4L6.9,5.4C6.5,5 5.9,5 5.5,5.4s-0.4,1.1 0,1.5l5.1,5.1l-5.1,5.1c-0.4,0.4 -0.4,1.1 0,1.5s1.1,0.4 1.5,0l4.1,-4V21c0,0.6 0.5,1 1,1c0.3,0 0.5,-0.1 0.7,-0.3l0.1,0l4.5,-4.5c0.4,-0.4 0.4,-1.1 0,-1.5L13.5,12zM13,9.7V5.4l2.1,2.2L13,9.7zM13,18.6v-4.3l2.1,2.2L13,18.6z"/>
-</vector>
diff --git a/res/drawable/ic_settings_print.xml b/res/drawable/ic_settings_print.xml
deleted file mode 100644
index 0eab402..0000000
--- a/res/drawable/ic_settings_print.xml
+++ /dev/null
@@ -1,28 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24.0dp"
-        android:height="24.0dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"
-        android:tint="?android:attr/colorControlNormal">
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M19,8H5c-1.66,0-3,1.34-3,3v5c0,0.55,0.45,1,1,1h3v3c0,0.55,0.45,1,1,1h10c0.55,0,1-0.45,1-1v-3h3c0.55,0,1-0.45,1-1v-5
-C22,9.34,20.66,8,19,8z M16,19H8v-5h8V19z
-M19,12c-0.55,0-1-0.45-1-1s0.45-1,1-1s1,0.45,1,1S19.55,12,19,12z M17,3H7
-C6.45,3,6,3.45,6,4v3h12V4C18,3.45,17.55,3,17,3z"/>
-</vector>
diff --git a/res/layout/app_header.xml b/res/layout/app_header.xml
deleted file mode 100644
index 2d8a099..0000000
--- a/res/layout/app_header.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-<!--
-     Copyright (C) 2014 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.
--->
-
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="?android:attr/actionBarSize"
-    android:background="@drawable/switchbar_background"
-    android:gravity="center_vertical"
-    android:theme="?attr/switchBarTheme" >
-
-    <ImageView android:id="@+id/app_icon"
-        android:layout_width="@dimen/switchbar_subsettings_margin_start"
-        android:layout_height="40dp"
-        android:gravity="end"
-        android:layout_centerVertical="true" />
-
-    <TextView
-        android:id="@+id/app_name"
-        android:layout_height="wrap_content"
-        android:layout_width="match_parent"
-        android:layout_toStartOf="@+id/app_settings"
-        android:layout_marginStart="@dimen/switchbar_subsettings_margin_start"
-        android:layout_alignWithParentIfMissing="true"
-        android:layout_centerVertical="true"
-        android:textAppearance="@style/TextAppearance.Switch"
-        android:textColor="@android:color/white"
-        android:textAlignment="viewStart" />
-
-    <ImageView
-        android:id="@id/app_settings"
-        android:layout_width="56dp"
-        android:layout_height="56dp"
-        android:layout_alignParentEnd="true"
-        android:layout_marginEnd="@dimen/switchbar_subsettings_margin_end"
-        android:layout_centerVertical="true"
-        android:minHeight="0dp"
-        android:minWidth="0dp"
-        android:contentDescription="@string/notification_app_settings_button"
-        android:scaleType="center"
-        android:src="@drawable/ic_settings_24dp"
-        android:visibility="gone"
-        style="?android:attr/borderlessButtonStyle" />
-
-</RelativeLayout>
-
diff --git a/res/layout/app_item.xml b/res/layout/app_item.xml
deleted file mode 100644
index d53afc9..0000000
--- a/res/layout/app_item.xml
+++ /dev/null
@@ -1,81 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 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.
--->
-
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:minHeight="72dp"
-    android:paddingTop="12dp"
-    android:paddingBottom="12dp"
-    android:gravity="top"
-    android:columnCount="3"
-    android:duplicateParentState="true">
-
-    <ImageView
-        android:id="@android:id/icon"
-        android:layout_width="48dp"
-        android:layout_height="48dp"
-        android:layout_gravity="center"
-        android:scaleType="fitXY"
-        android:layout_marginEnd="16dip"
-        android:contentDescription="@null"
-        android:duplicateParentState="true" />
-
-    <TextView
-        android:id="@android:id/title"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_toEndOf="@android:id/icon"
-        android:singleLine="true"
-        android:ellipsize="marquee"
-        android:textAppearance="@android:style/TextAppearance.Material.Subhead"
-        android:textColor="?android:attr/textColorPrimary"
-        android:textAlignment="viewStart"
-        android:duplicateParentState="true" />
-
-    <TextView
-        android:id="@android:id/summary"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_toEndOf="@android:id/title"
-        android:layout_alignParentEnd="true"
-        android:layout_marginStart="6dip"
-        android:gravity="end"
-        android:singleLine="true"
-        android:ellipsize="marquee"
-        android:textAppearance="@android:style/TextAppearance.Material.Subhead"
-        android:textColor="?android:attr/textColorSecondary"
-        android:duplicateParentState="true" />
-
-    <Switch
-            android:id="@android:id/switch_widget"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_alignParentRight="true"
-            android:background="@null"
-            android:visibility="gone"/>
-
-    <FrameLayout
-        android:id="@android:id/widget_frame"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_toEndOf="@android:id/icon"
-        android:layout_below="@android:id/title"
-        android:layout_alignParentEnd="true"
-        android:layout_gravity="fill_horizontal|top"
-        android:duplicateParentState="true" />
-
-</RelativeLayout>
diff --git a/res/layout/data_usage_item.xml b/res/layout/data_usage_item.xml
deleted file mode 100644
index 5e931f9..0000000
--- a/res/layout/data_usage_item.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 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.
--->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
-    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
-
-    <include layout="@layout/app_item" />
-
-</FrameLayout>
diff --git a/res/layout/device_admin_item.xml b/res/layout/device_admin_item.xml
index 0420bb5..875c734 100644
--- a/res/layout/device_admin_item.xml
+++ b/res/layout/device_admin_item.xml
@@ -29,13 +29,17 @@
     <LinearLayout
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:gravity="center"
-        android:orientation="horizontal">
+        android:gravity="start|center_vertical"
+        android:minWidth="60dp"
+        android:orientation="horizontal"
+        android:paddingEnd="12dp"
+        android:paddingTop="4dp"
+        android:paddingBottom="4dp">
 
         <ImageView
             android:id="@+id/icon"
-            android:layout_width="@android:dimen/app_icon_size"
-            android:layout_height="@android:dimen/app_icon_size"
+            android:layout_width="@dimen/secondary_app_icon_size"
+            android:layout_height="@dimen/secondary_app_icon_size"
             android:layout_gravity="center_vertical"
             android:layout_marginEnd="8dip"
             android:contentDescription="@null" />
@@ -70,7 +74,7 @@
 
     </RelativeLayout>
 
-    <CheckBox
+    <Switch
         android:id="@+id/checkbox"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
diff --git a/res/layout/location_list_no_item.xml b/res/layout/location_list_no_item.xml
deleted file mode 100644
index c0465cf..0000000
--- a/res/layout/location_list_no_item.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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.
--->
-
-<!-- text that appears when the recent app list is empty -->
-<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:paddingEnd="?android:attr/scrollbarSize"
-    android:background="?android:attr/selectableItemBackground" >
-
-    <RelativeLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="15dip"
-        android:layout_marginEnd="6dip"
-        android:layout_marginTop="6dip"
-        android:layout_marginBottom="6dip"
-        android:layout_weight="1">
-
-        <TextView android:id="@android:id/title"
-            android:gravity="center"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:textAppearance="?android:attr/textAppearanceSmall"
-            android:textColor="?android:attr/textColorSecondary" />
-
-    </RelativeLayout>
-
-</LinearLayout>
-
diff --git a/res/layout/preference_app.xml b/res/layout/preference_app.xml
index 526d5af..8cbb6f2 100644
--- a/res/layout/preference_app.xml
+++ b/res/layout/preference_app.xml
@@ -13,13 +13,87 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:background="?android:attr/selectableItemBackground"
+    android:gravity="center_vertical"
+    android:minHeight="?android:attr/listPreferredItemHeightSmall"
     android:paddingStart="?android:attr/listPreferredItemPaddingStart"
     android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
 
-    <include layout="@layout/app_item" />
+    <LinearLayout
+        android:id="@+id/icon_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="start|center_vertical"
+        android:minWidth="60dp"
+        android:orientation="horizontal"
+        android:paddingEnd="12dp"
+        android:paddingTop="4dp"
+        android:paddingBottom="4dp">
+        <android.support.v7.internal.widget.PreferenceImageView
+            android:id="@android:id/icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            settings:maxWidth="@dimen/secondary_app_icon_size"
+            settings:maxHeight="@dimen/secondary_app_icon_size" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:orientation="vertical"
+        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="@style/TextAppearance.TileTitle"
+                  android:ellipsize="marquee"
+                  android:fadingEdge="horizontal" />
+
+        <LinearLayout
+            android:id="@+id/summary_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:visibility="gone">
+            <TextView android:id="@android:id/summary"
+                      android:layout_width="0dp"
+                      android:layout_height="wrap_content"
+                      android:layout_weight="1"
+                      android:textAppearance="@style/TextAppearance.Small"
+                      android:textColor="?android:attr/textColorSecondary" />
+
+            <TextView android:id="@+id/appendix"
+                      android:layout_width="wrap_content"
+                      android:layout_height="wrap_content"
+                      android:textAppearance="@style/TextAppearance.Small"
+                      android:textColor="?android:attr/textColorSecondary"
+                      android:maxLines="1"
+                      android:ellipsize="end" />
+        </LinearLayout>
+        <ProgressBar
+            android:id="@android:id/progress"
+            style="?android:attr/progressBarStyleHorizontal"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="4dp"
+            android:max="100"
+            android:visibility="gone" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@android:id/widget_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:gravity="end|center_vertical"
+        android:paddingStart="16dp"
+        android:orientation="vertical" />
 
 </LinearLayout>
diff --git a/res/layout/widget_progress_bar.xml b/res/layout/widget_progress_bar.xml
deleted file mode 100644
index a2a5154..0000000
--- a/res/layout/widget_progress_bar.xml
+++ /dev/null
@@ -1,23 +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.
--->
-
-<ProgressBar xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@android:id/progress"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:layout_marginTop="4dp"
-    android:max="100"
-    style="?android:attr/progressBarStyleHorizontal" />
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index c0d92c1..2ba7919 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -133,11 +133,6 @@
         <attr name="forWork" format="boolean" />
     </declare-styleable>
 
-    <declare-styleable name="DividerPreference">
-        <attr name="allowDividerAbove" format="boolean" />
-        <attr name="allowDividerBelow" format="boolean" />
-    </declare-styleable>
-
     <declare-styleable name="VideoPreference">
         <attr name="animation" format="reference" />
         <attr name="preview" format="reference" />
diff --git a/res/values/config.xml b/res/values/config.xml
index 8c59999..4ba73cc 100755
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -73,6 +73,9 @@
     <!-- If the support features are enabled. -->
     <bool name="config_support_enabled">false</bool>
 
+    <!-- Whether to enable "show operator name in the status bar" setting -->
+    <bool name="config_showOperatorNameInStatusBar">false</bool>
+
     <!-- List containing the component names of pre-installed screen reader services. -->
     <string-array name="config_preinstalled_screen_reader_services" translatable="false">
         <!--
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index fdb9c32..0d4289a 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -22,6 +22,7 @@
     <dimen name="action_bar_switch_padding">16dip</dimen>
 
     <dimen name="app_icon_size">40dip</dimen>
+    <dimen name="secondary_app_icon_size">24dp</dimen>
     <dimen name="min_tap_target_size">48dp</dimen>
     <dimen name="screen_margin_sides">64dip</dimen>
     <dimen name="screen_margin_top">72dip</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 4c42905..8b84849 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -8658,27 +8658,6 @@
     <!-- Estimated wait time range for real time supports -->
     <string name="support_estimated_wait_time">~<xliff:g id="ESTIMATE" example="2 minutes">%1$s</xliff:g> wait</string>
 
-    <!-- Message for telling the user the kind of BT device being displayed in list. -->
-    <string name="bluetooth_talkback_computer">Computer</string>
-
-    <!-- Message for telling the user the kind of BT device being displayed in list. -->
-    <string name="bluetooth_talkback_headset">Headset</string>
-
-    <!-- Message for telling the user the kind of BT device being displayed in list. -->
-    <string name="bluetooth_talkback_phone">Phone</string>
-
-    <!-- Message for telling the user the kind of BT device being displayed in list. -->
-    <string name="bluetooth_talkback_imaging">Imaging</string>
-
-    <!-- Message for telling the user the kind of BT device being displayed in list. -->
-    <string name="bluetooth_talkback_headphone">Headphone</string>
-
-    <!-- Message for telling the user the kind of BT device being displayed in list. -->
-    <string name="bluetooth_talkback_input_peripheral">Input Peripheral</string>
-
-    <!-- Message for telling the user the kind of BT device being displayed in list. -->
-    <string name="bluetooth_talkback_bluetooth">Bluetooth</string>
-
     <!-- Used as title on the automatic storage manager settings. [CHAR LIMIT=60] -->
     <string name="automatic_storage_manager_settings">Manage storage</string>
 
@@ -8801,7 +8780,8 @@
     <string name="oem_unlock_enable_disabled_summary_connectivity_or_locked">Connect to the Internet or contact your carrier</string>
     <!-- setting enable OEM unlock Checkbox's summary to explain this Checkbox is disabled because this setting is unavailable on sim-locked devices. [CHAR_LIMIT=60] -->
     <string name="oem_unlock_enable_disabled_summary_sim_locked_device">Unavailable on carrier-locked devices</string>
-
+    <!-- Information displayed after user locks OEM lock [Char Limit=None]-->
+    <string name="oem_lock_info_message">Please restart the device to enable device protection feature.</string>
     <string name="automatic_storage_manager_freed_bytes"><xliff:g id="size" example="3.25MB">%1$s</xliff:g> total made available\n\nLast ran on <xliff:g id="date" example="Jan 12">%2$s</xliff:g></string>
 
     <!-- Title text for enabling web actions. [CHAR_LIMIT=60] -->
@@ -9036,6 +9016,11 @@
     <!-- Temporary reboot string, will be removed -->
     <string name="change_theme_reboot" translatable="false">Changing the theme requires a restart.</string>
 
+    <!-- Switch label to show operator name in the status bar [CHAR LIMIT=60] -->
+    <string name="show_operator_name_title">Network name</string>
+    <!-- Switch summary to show operator name in the status bar [CHAR LIMIT=NONE] -->
+    <string name="show_operator_name_summary">Display network name in status bar</string>
+
     <!-- Indicates if the automatic storage manager is enabled or not. [CHAR_LIMIT=40] -->
     <string name="storage_manager_indicator">Storage Manager: <xliff:g id="status" example="on">^1</xliff:g></string>
 
diff --git a/res/xml/app_default_settings.xml b/res/xml/app_default_settings.xml
index 3a8c997..0204c64 100644
--- a/res/xml/app_default_settings.xml
+++ b/res/xml/app_default_settings.xml
@@ -21,56 +21,56 @@
     android:key="app_default_settings_screen"
     android:title="@string/app_default_dashboard_title">
 
-    <Preference
+    <com.android.settings.widget.AppPreference
         android:key="assist_and_voice_input"
         android:title="@string/assist_and_voice_input_title"
         android:fragment="com.android.settings.applications.assist.ManageAssist" />
 
-    <Preference
+    <com.android.settings.widget.AppPreference
         android:key="default_browser"
         android:title="@string/default_browser_title"
         android:fragment="com.android.settings.applications.defaultapps.DefaultBrowserPicker">
         <extra android:name="for_work" android:value="false" />
-    </Preference>
+    </com.android.settings.widget.AppPreference>
 
-    <Preference
+    <com.android.settings.widget.AppPreference
         android:key="default_home"
         android:title="@string/home_app"
         android:fragment="com.android.settings.applications.defaultapps.DefaultHomePicker"
         settings:keywords="@string/keywords_home" />
 
-    <Preference
+    <com.android.settings.widget.AppPreference
         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" />
 
-    <Preference
+    <com.android.settings.widget.AppPreference
         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" />
 
-    <Preference
+    <com.android.settings.widget.AppPreference
         android:key="default_payment_app"
         android:title="@string/nfc_payment_settings_title"
         android:summary="@string/summary_placeholder"
         android:fragment="com.android.settings.nfc.PaymentSettings" />
 
-    <Preference
+    <com.android.settings.widget.AppPreference
         android:key="default_emergency_app"
         android:title="@string/default_emergency_app"
         settings:keywords="@string/keywords_emergency_app" />
 
     <!--
-    <Preference
+    <com.android.settings.widget.AppPreference
         android:key="default_notification_asst_app"
         android:title="@string/default_notification_assistant"
         android:fragment="com.android.settings.applications.defaultapps.DefaultNotificationAssistantPicker"
         />
      -->
 
-    <Preference
+    <com.android.settings.widget.AppPreference
         android:key="domain_urls"
         android:title="@string/domain_urls_title"
         android:fragment="com.android.settings.applications.ManageDomainUrls" />
@@ -79,20 +79,20 @@
         android:key="work_app_defaults"
         android:title="@string/default_for_work">
 
-        <Preference
+        <com.android.settings.widget.AppPreference
             android:key="work_default_browser"
             android:title="@string/default_browser_title"
             android:fragment="com.android.settings.applications.defaultapps.DefaultBrowserPicker">
             <extra android:name="for_work" android:value="true" />
-        </Preference>
+        </com.android.settings.widget.AppPreference>
 
-        <Preference
+        <com.android.settings.widget.AppPreference
             android:key="work_default_phone_app"
             android:title="@string/default_phone_title"
             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.widget.AppPreference>
 
     </com.android.settings.widget.WorkOnlyCategory>
 
diff --git a/res/xml/data_usage.xml b/res/xml/data_usage.xml
index 5bc8087..958459c 100644
--- a/res/xml/data_usage.xml
+++ b/res/xml/data_usage.xml
@@ -28,7 +28,7 @@
             android:key="status_header"
             android:selectable="false" />
 
-        <com.android.settings.DividerPreference
+        <Preference
             android:key="limit_summary"
             android:selectable="false"
             settings:allowDividerBelow="true" />
diff --git a/res/xml/display_settings.xml b/res/xml/display_settings.xml
index 18bfe57..a6efb5c 100644
--- a/res/xml/display_settings.xml
+++ b/res/xml/display_settings.xml
@@ -84,6 +84,11 @@
         android:fragment="com.android.settings.display.ScreenZoomSettings"
         settings:keywords="@string/screen_zoom_keywords" />
 
+    <SwitchPreference
+        android:key="show_operator_name"
+        android:title="@string/show_operator_name_title"
+        android:summary="@string/show_operator_name_summary" />
+
     <Preference
         android:key="screensaver"
         android:title="@string/screensaver_settings_title"
diff --git a/res/xml/security_settings_misc.xml b/res/xml/security_settings_misc.xml
index ee2c534..6a71033 100644
--- a/res/xml/security_settings_misc.xml
+++ b/res/xml/security_settings_misc.xml
@@ -64,7 +64,7 @@
         android:key="encryption_and_credential"
         android:title="@string/encryption_and_credential_settings_title"
         android:summary="@string/encryption_and_credential_settings_summary"
-        android:fragment="com.android.settings.EncryptionAndCredential"/>
+        android:fragment="com.android.settings.security.EncryptionAndCredential"/>
 
     <Preference android:key="manage_trust_agents"
         android:title="@string/manage_trust_agents"
diff --git a/src/com/android/settings/AppProgressPreference.java b/src/com/android/settings/AppProgressPreference.java
deleted file mode 100644
index 5822d8d..0000000
--- a/src/com/android/settings/AppProgressPreference.java
+++ /dev/null
@@ -1,46 +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;
-
-import android.content.Context;
-import android.support.v7.preference.Preference;
-import android.support.v7.preference.PreferenceViewHolder;
-import android.util.AttributeSet;
-import android.widget.ProgressBar;
-
-public class AppProgressPreference extends Preference {
-
-    private int mProgress;
-
-    public AppProgressPreference(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        setLayoutResource(R.layout.preference_app);
-        setWidgetLayoutResource(R.layout.widget_progress_bar);
-    }
-
-    public void setProgress(int amount) {
-        mProgress = amount;
-        notifyChanged();
-    }
-
-    @Override
-    public void onBindViewHolder(PreferenceViewHolder view) {
-        super.onBindViewHolder(view);
-
-        final ProgressBar progress = (ProgressBar) view.findViewById(android.R.id.progress);
-        progress.setProgress(mProgress);
-    }
-}
diff --git a/src/com/android/settings/DeviceAdminSettings.java b/src/com/android/settings/DeviceAdminSettings.java
index 00f4d9c..350d731 100644
--- a/src/com/android/settings/DeviceAdminSettings.java
+++ b/src/com/android/settings/DeviceAdminSettings.java
@@ -43,9 +43,9 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.BaseAdapter;
-import android.widget.CheckBox;
 import android.widget.ImageView;
 import android.widget.ListView;
+import android.widget.Switch;
 import android.widget.TextView;
 
 import com.android.internal.logging.nano.MetricsProto;
@@ -205,7 +205,7 @@
     static class ViewHolder {
         ImageView icon;
         TextView name;
-        CheckBox checkbox;
+        Switch checkbox;
         TextView description;
     }
 
@@ -291,10 +291,10 @@
         private View newDeviceAdminView(ViewGroup parent) {
             View v = mInflater.inflate(R.layout.device_admin_item, parent, false);
             ViewHolder h = new ViewHolder();
-            h.icon = (ImageView) v.findViewById(R.id.icon);
-            h.name = (TextView) v.findViewById(R.id.name);
-            h.checkbox = (CheckBox) v.findViewById(R.id.checkbox);
-            h.description = (TextView) v.findViewById(R.id.description);
+            h.icon = v.findViewById(R.id.icon);
+            h.name = v.findViewById(R.id.name);
+            h.checkbox =  v.findViewById(R.id.checkbox);
+            h.description = v.findViewById(R.id.description);
             v.setTag(h);
             return v;
         }
diff --git a/src/com/android/settings/DisplaySettings.java b/src/com/android/settings/DisplaySettings.java
index 9a3f047..d67758a 100644
--- a/src/com/android/settings/DisplaySettings.java
+++ b/src/com/android/settings/DisplaySettings.java
@@ -33,6 +33,7 @@
 import com.android.settings.display.NightDisplayPreferenceController;
 import com.android.settings.display.NightModePreferenceController;
 import com.android.settings.display.ScreenSaverPreferenceController;
+import com.android.settings.display.ShowOperatorNamePreferenceController;
 import com.android.settings.display.TapToWakePreferenceController;
 import com.android.settings.display.ThemePreferenceController;
 import com.android.settings.display.TimeoutPreferenceController;
@@ -98,6 +99,7 @@
         controllers.add(new TapToWakePreferenceController(context));
         controllers.add(new TimeoutPreferenceController(context, KEY_SCREEN_TIMEOUT));
         controllers.add(new VrDisplayPreferenceController(context));
+        controllers.add(new ShowOperatorNamePreferenceController(context));
         controllers.add(new WallpaperPreferenceController(context));
         controllers.add(new ThemePreferenceController(context));
         controllers.add(new BrightnessLevelPreferenceController(context, lifecycle));
diff --git a/src/com/android/settings/DividerPreference.java b/src/com/android/settings/DividerPreference.java
deleted file mode 100644
index aec7d44..0000000
--- a/src/com/android/settings/DividerPreference.java
+++ /dev/null
@@ -1,65 +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.content.Context;
-import android.content.res.TypedArray;
-import android.support.v7.preference.Preference;
-import android.support.v7.preference.PreferenceViewHolder;
-import android.util.AttributeSet;
-
-@Deprecated
-public class DividerPreference extends Preference {
-
-    private Boolean mAllowAbove;
-    private Boolean mAllowBelow;
-
-    public DividerPreference(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DividerPreference, 0, 0);
-        if (a.hasValue(R.styleable.DividerPreference_allowDividerAbove)) {
-            mAllowAbove = a.getBoolean(R.styleable.DividerPreference_allowDividerAbove, false);
-        }
-        if (a.hasValue(R.styleable.DividerPreference_allowDividerBelow)) {
-            mAllowBelow = a.getBoolean(R.styleable.DividerPreference_allowDividerBelow, false);
-        }
-        a.recycle();
-    }
-
-    public DividerPreference(Context context) {
-        this(context, null /* attrs */);
-    }
-
-    public void setDividerAllowedAbove(boolean allowed) {
-        mAllowAbove = allowed;
-        notifyChanged();
-    }
-
-    public void setDividerAllowedBelow(boolean allowed) {
-        mAllowBelow = allowed;
-        notifyChanged();
-    }
-
-    @Override
-    public void onBindViewHolder(PreferenceViewHolder holder) {
-        super.onBindViewHolder(holder);
-        if (mAllowAbove != null) {
-            holder.setDividerAllowedAbove(mAllowAbove);
-        }
-        if (mAllowBelow != null) {
-            holder.setDividerAllowedBelow(mAllowBelow);
-        }
-    }
-}
diff --git a/src/com/android/settings/accounts/AccountPreferenceBase.java b/src/com/android/settings/accounts/AccountPreferenceBase.java
index 7c3b54c..54ddf64 100644
--- a/src/com/android/settings/accounts/AccountPreferenceBase.java
+++ b/src/com/android/settings/accounts/AccountPreferenceBase.java
@@ -23,18 +23,16 @@
 import android.content.SyncStatusObserver;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
-import android.os.Handler;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.support.v7.preference.PreferenceScreen;
 import android.text.format.DateFormat;
 import android.util.Log;
 
 import com.android.settings.SettingsPreferenceFragment;
 import com.android.settings.Utils;
 import com.android.settingslib.accounts.AuthenticatorHelper;
+import com.android.settingslib.utils.ThreadUtils;
 
-import java.util.ArrayList;
 import java.util.Date;
 
 abstract class AccountPreferenceBase extends SettingsPreferenceFragment
@@ -46,8 +44,6 @@
     public static final String AUTHORITIES_FILTER_KEY = "authorities";
     public static final String ACCOUNT_TYPES_FILTER_KEY = "account_types";
 
-    private final Handler mHandler = new Handler();
-
     private UserManager mUm;
     private Object mStatusChangeListenerHandle;
     protected AuthenticatorHelper mAuthenticatorHelper;
@@ -118,29 +114,8 @@
         ContentResolver.removeStatusChangeListener(mStatusChangeListenerHandle);
     }
 
-    private SyncStatusObserver mSyncStatusObserver = new SyncStatusObserver() {
-        public void onStatusChanged(int which) {
-            mHandler.post(new Runnable() {
-                public void run() {
-                    onSyncStateUpdated();
-                }
-            });
-        }
-    };
-
-    public ArrayList<String> getAuthoritiesForAccountType(String type) {
-        return mAuthenticatorHelper.getAuthoritiesForAccountType(type);
-    }
-
-    /**
-     * Gets the preferences.xml file associated with a particular account type.
-     * @param accountType the type of account
-     * @return a PreferenceScreen inflated from accountPreferenceId.
-     */
-    public PreferenceScreen addPreferencesForType(final String accountType,
-            PreferenceScreen parent) {
-        return mAccountTypePreferenceLoader.addPreferencesForType(accountType, parent);
-    }
+    private SyncStatusObserver mSyncStatusObserver =
+            which -> ThreadUtils.postOnMainThread(() -> onSyncStateUpdated());
 
     public void updateAuthDescriptions() {
         mAuthenticatorHelper.updateAuthDescriptions(getActivity());
diff --git a/src/com/android/settings/accounts/AccountTypePreference.java b/src/com/android/settings/accounts/AccountTypePreference.java
index 3e869fe..1d39473 100644
--- a/src/com/android/settings/accounts/AccountTypePreference.java
+++ b/src/com/android/settings/accounts/AccountTypePreference.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.accounts;
 
+import static android.content.Intent.EXTRA_USER;
+
 import android.accounts.Account;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
@@ -25,12 +27,10 @@
 import android.support.v7.preference.Preference;
 import android.support.v7.preference.Preference.OnPreferenceClickListener;
 
-import com.android.settings.R;
 import com.android.settings.Utils;
+import com.android.settings.widget.AppPreference;
 
-import static android.content.Intent.EXTRA_USER;
-
-public class AccountTypePreference extends Preference implements OnPreferenceClickListener {
+public class AccountTypePreference extends AppPreference implements OnPreferenceClickListener {
     /**
      * Title of the tile that is shown to the user.
      * @attr ref android.R.styleable#PreferenceHeader_title
diff --git a/src/com/android/settings/accounts/ProviderPreference.java b/src/com/android/settings/accounts/ProviderPreference.java
index 817c3b6..81e0221 100644
--- a/src/com/android/settings/accounts/ProviderPreference.java
+++ b/src/com/android/settings/accounts/ProviderPreference.java
@@ -16,15 +16,14 @@
 
 package com.android.settings.accounts;
 
+import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+
 import android.content.Context;
 import android.graphics.drawable.Drawable;
-import android.support.v7.preference.Preference;
 
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.settingslib.RestrictedPreference;
 
-import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
-
 /**
  * ProviderPreference is used to display an image to the left of a provider name.
  * The preference ultimately calls AccountManager.addAccount() for the account type.
@@ -35,6 +34,7 @@
     public ProviderPreference(
             Context context, String accountType, Drawable icon, CharSequence providerName) {
         super(context);
+        setUseSmallIcon(true);
         mAccountType = accountType;
         setIcon(icon);
         setPersistent(false);
diff --git a/src/com/android/settings/applications/PictureInPictureSettings.java b/src/com/android/settings/applications/PictureInPictureSettings.java
index 9b8a897..3dc8ab3 100644
--- a/src/com/android/settings/applications/PictureInPictureSettings.java
+++ b/src/com/android/settings/applications/PictureInPictureSettings.java
@@ -38,6 +38,7 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settings.R;
 import com.android.settings.notification.EmptyTextSettings;
+import com.android.settings.widget.AppPreference;
 import com.android.settings.wrapper.ActivityInfoWrapper;
 import com.android.settings.wrapper.UserManagerWrapper;
 import com.android.settingslib.wrapper.PackageManagerWrapper;
@@ -176,7 +177,7 @@
             final String packageName = appInfo.packageName;
             final CharSequence label = appInfo.loadLabel(pm);
 
-            final Preference pref = new Preference(prefContext);
+            final Preference pref = new AppPreference(prefContext);
             pref.setIcon(mIconDrawableFactory.getBadgedIcon(appInfo, userId));
             pref.setTitle(pm.getUserBadgedLabel(label, user));
             pref.setSummary(PictureInPictureDetails.getPreferenceSummary(prefContext,
diff --git a/src/com/android/settings/applications/PremiumSmsAccess.java b/src/com/android/settings/applications/PremiumSmsAccess.java
index 94945a9..b6613f2 100644
--- a/src/com/android/settings/applications/PremiumSmsAccess.java
+++ b/src/com/android/settings/applications/PremiumSmsAccess.java
@@ -28,7 +28,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.internal.telephony.SmsUsageMonitor;
-import com.android.settings.DividerPreference;
 import com.android.settings.R;
 import com.android.settings.applications.AppStateBaseBridge.Callback;
 import com.android.settings.applications.AppStateSmsPremBridge.SmsState;
@@ -38,6 +37,7 @@
 import com.android.settingslib.applications.ApplicationsState.AppEntry;
 import com.android.settingslib.applications.ApplicationsState.Callbacks;
 import com.android.settingslib.applications.ApplicationsState.Session;
+import com.android.settingslib.widget.FooterPreference;
 
 import java.util.ArrayList;
 
@@ -141,11 +141,9 @@
             screen.addPreference(smsPreference);
         }
         if (apps.size() != 0) {
-            DividerPreference summary = new DividerPreference(getPrefContext());
-            summary.setSelectable(false);
-            summary.setSummary(R.string.premium_sms_warning);
-            summary.setDividerAllowedAbove(true);
-            screen.addPreference(summary);
+            FooterPreference footer = new FooterPreference(getPrefContext());
+            footer.setTitle(R.string.premium_sms_warning);
+            screen.addPreference(footer);
         }
 
         if (!usePreferenceScreenTitle()) {
diff --git a/src/com/android/settings/applications/ProcessStatsPreference.java b/src/com/android/settings/applications/ProcessStatsPreference.java
index 4ee0a04..034a68d 100644
--- a/src/com/android/settings/applications/ProcessStatsPreference.java
+++ b/src/com/android/settings/applications/ProcessStatsPreference.java
@@ -18,14 +18,13 @@
 
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.graphics.drawable.ColorDrawable;
 import android.text.TextUtils;
 import android.text.format.Formatter;
 
 import android.util.Log;
-import com.android.settings.AppProgressPreference;
+import com.android.settings.widget.AppPreference;
 
-public class ProcessStatsPreference extends AppProgressPreference {
+public class ProcessStatsPreference extends AppPreference {
     static final String TAG = "ProcessStatsPreference";
 
     private ProcStatsPackageEntry mEntry;
diff --git a/src/com/android/settings/applications/RecentAppsPreferenceController.java b/src/com/android/settings/applications/RecentAppsPreferenceController.java
index b6ae1ee..090a5a7 100644
--- a/src/com/android/settings/applications/RecentAppsPreferenceController.java
+++ b/src/com/android/settings/applications/RecentAppsPreferenceController.java
@@ -40,6 +40,7 @@
 import com.android.settings.R;
 import com.android.settings.Utils;
 import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settings.widget.AppPreference;
 import com.android.settingslib.applications.ApplicationsState;
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.wrapper.PackageManagerWrapper;
@@ -230,7 +231,7 @@
             boolean rebindPref = true;
             Preference pref = appPreferences.remove(pkgName);
             if (pref == null) {
-                pref = new Preference(prefContext);
+                pref = new AppPreference(prefContext);
                 rebindPref = false;
             }
             pref.setKey(pkgName);
diff --git a/src/com/android/settings/applications/RunningServiceDetails.java b/src/com/android/settings/applications/RunningServiceDetails.java
index 84c2ee5..7e73a9b 100644
--- a/src/com/android/settings/applications/RunningServiceDetails.java
+++ b/src/com/android/settings/applications/RunningServiceDetails.java
@@ -21,7 +21,6 @@
 import android.content.res.Resources;
 import android.os.Bundle;
 import android.os.Debug;
-import android.os.Handler;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -37,6 +36,7 @@
 import com.android.settings.Utils;
 import com.android.settings.core.InstrumentedPreferenceFragment;
 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settingslib.utils.ThreadUtils;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -481,15 +481,12 @@
             addDetailViews();
         }
     }
-    
+
     private void finish() {
-        (new Handler()).post(new Runnable() {
-            @Override
-            public void run() {
-                Activity a = getActivity();
-                if (a != null) {
-                    a.onBackPressed();
-                }
+        ThreadUtils.postOnMainThread(() -> {
+            final Activity a = getActivity();
+            if (a != null) {
+                a.onBackPressed();
             }
         });
     }
diff --git a/src/com/android/settings/applications/defaultapps/DefaultAppPickerFragment.java b/src/com/android/settings/applications/defaultapps/DefaultAppPickerFragment.java
index 6eb1ad8..7b27516 100644
--- a/src/com/android/settings/applications/defaultapps/DefaultAppPickerFragment.java
+++ b/src/com/android/settings/applications/defaultapps/DefaultAppPickerFragment.java
@@ -84,6 +84,11 @@
         }
     }
 
+    @Override
+    protected int getRadioButtonPreferenceCustomLayoutResId() {
+        return R.layout.preference_app;
+    }
+
     protected ConfirmationDialogFragment newConfirmationDialogFragment(String selectedKey,
             CharSequence confirmationMessage) {
         final ConfirmationDialogFragment fragment = new ConfirmationDialogFragment();
diff --git a/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java
index 91143b1..d016567 100644
--- a/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java
+++ b/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java
@@ -29,6 +29,7 @@
 import com.android.settings.Utils;
 import com.android.settings.core.PreferenceControllerMixin;
 import com.android.settings.widget.GearPreference;
+import com.android.settingslib.TwoTargetPreference;
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.wrapper.PackageManagerWrapper;
 
@@ -53,6 +54,12 @@
     public void updateState(Preference preference) {
         final DefaultAppInfo app = getDefaultAppInfo();
         CharSequence defaultAppLabel = getDefaultAppLabel();
+        if (preference instanceof TwoTargetPreference) {
+            // For use small icon because we are displaying an app preference.
+            // We only need to do this for TwoTargetPreference because the other prefs are
+            // already using AppPreference so their icon is already normalized.
+            ((TwoTargetPreference) preference).setUseSmallIcon(true);
+        }
         if (!TextUtils.isEmpty(defaultAppLabel)) {
             preference.setSummary(defaultAppLabel);
             Utils.setSafeIcon(preference, getDefaultAppIcon());
diff --git a/src/com/android/settings/applications/defaultapps/DefaultAutofillPicker.java b/src/com/android/settings/applications/defaultapps/DefaultAutofillPicker.java
index 00dedf9..97f6d4b 100644
--- a/src/com/android/settings/applications/defaultapps/DefaultAutofillPicker.java
+++ b/src/com/android/settings/applications/defaultapps/DefaultAutofillPicker.java
@@ -27,7 +27,6 @@
 import android.content.pm.ServiceInfo;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.Handler;
 import android.provider.Settings;
 import android.service.autofill.AutofillService;
 import android.service.autofill.AutofillServiceInfo;
@@ -39,6 +38,7 @@
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.settings.R;
+import com.android.settingslib.utils.ThreadUtils;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -59,7 +59,6 @@
      * Set when the fragment is implementing ACTION_REQUEST_SET_AUTOFILL_SERVICE.
      */
     private DialogInterface.OnClickListener mCancelListener;
-    private final Handler mHandler = new Handler();
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -123,17 +122,17 @@
     private final PackageMonitor mSettingsPackageMonitor = new PackageMonitor() {
         @Override
         public void onPackageAdded(String packageName, int uid) {
-            mHandler.post(() -> update());
+            ThreadUtils.postOnMainThread(() -> update());
         }
 
         @Override
         public void onPackageModified(String packageName) {
-            mHandler.post(() -> update());
+            ThreadUtils.postOnMainThread(() -> update());
         }
 
         @Override
         public void onPackageRemoved(String packageName, int uid) {
-            mHandler.post(() -> update());
+            ThreadUtils.postOnMainThread(() -> update());
         }
     };
 
diff --git a/src/com/android/settings/applications/manageapplications/ApplicationViewHolder.java b/src/com/android/settings/applications/manageapplications/ApplicationViewHolder.java
index 9ac2167..e968b1c 100644
--- a/src/com/android/settings/applications/manageapplications/ApplicationViewHolder.java
+++ b/src/com/android/settings/applications/manageapplications/ApplicationViewHolder.java
@@ -22,6 +22,7 @@
 import android.support.annotation.StringRes;
 import android.support.annotation.VisibleForTesting;
 import android.support.v7.widget.RecyclerView;
+import android.text.TextUtils;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -38,33 +39,39 @@
     private final TextView mAppName;
     private final ImageView mAppIcon;
 
+    private final boolean mKeepStableHeight;
+
+    @VisibleForTesting
+    View mSummaryContainer;
     @VisibleForTesting
     final TextView mSummary;
     @VisibleForTesting
     final TextView mDisabled;
 
-    ApplicationViewHolder(View itemView) {
+
+    ApplicationViewHolder(View itemView, boolean keepStableHeight) {
         super(itemView);
         mAppName = itemView.findViewById(android.R.id.title);
         mAppIcon = itemView.findViewById(android.R.id.icon);
-        mSummary = itemView.findViewById(R.id.widget_text1);
-        mDisabled = itemView.findViewById(R.id.widget_text2);
+        mSummaryContainer = itemView.findViewById(R.id.summary_container);
+        mSummary = itemView.findViewById(android.R.id.summary);
+        mDisabled = itemView.findViewById(R.id.appendix);
+        mKeepStableHeight = keepStableHeight;
     }
 
-    static View newView(LayoutInflater inflater, ViewGroup parent) {
-        final View root = LayoutInflater.from(parent.getContext())
+    static View newView(ViewGroup parent) {
+        return LayoutInflater.from(parent.getContext())
                 .inflate(R.layout.preference_app, parent, false);
-        inflater.inflate(R.layout.widget_text_views,
-                root.findViewById(android.R.id.widget_frame));
-        return root;
     }
 
     void setSummary(CharSequence summary) {
         mSummary.setText(summary);
+        updateSummaryContainer();
     }
 
     void setSummary(@StringRes int summary) {
         mSummary.setText(summary);
+        updateSummaryContainer();
     }
 
     void setEnabled(boolean isEnabled) {
@@ -78,6 +85,10 @@
         mAppName.setText(title);
     }
 
+    void setIcon(int drawableRes) {
+        mAppIcon.setImageResource(drawableRes);
+    }
+
     void setIcon(Drawable icon) {
         if (icon == null) {
             return;
@@ -96,6 +107,17 @@
         } else {
             mDisabled.setVisibility(View.GONE);
         }
+        updateSummaryContainer();
+    }
+
+    void updateSummaryContainer() {
+        if (mKeepStableHeight) {
+            mSummaryContainer.setVisibility(View.VISIBLE);
+            return;
+        }
+        final boolean hasContent =
+                !TextUtils.isEmpty(mDisabled.getText()) || !TextUtils.isEmpty(mSummary.getText());
+        mSummaryContainer.setVisibility(hasContent ? View.VISIBLE : View.GONE);
     }
 
     void updateSizeText(AppEntry entry, CharSequence invalidSizeStr, int whichSize) {
diff --git a/src/com/android/settings/applications/manageapplications/ManageApplications.java b/src/com/android/settings/applications/manageapplications/ManageApplications.java
index 33762e4..7dc8951 100644
--- a/src/com/android/settings/applications/manageapplications/ManageApplications.java
+++ b/src/com/android/settings/applications/manageapplications/ManageApplications.java
@@ -412,20 +412,6 @@
         return null;
     }
 
-    private boolean isFastScrollEnabled() {
-        switch (mListType) {
-            case LIST_TYPE_MAIN:
-            case LIST_TYPE_NOTIFICATION:
-            case LIST_TYPE_STORAGE:
-            case LIST_TYPE_GAMES:
-            case LIST_TYPE_MOVIES:
-            case LIST_TYPE_PHOTOGRAPHY:
-                return mSortOrder == R.id.sort_order_alpha;
-            default:
-                return false;
-        }
-    }
-
     @Override
     public int getMetricsCategory() {
         switch (mListType) {
@@ -924,9 +910,9 @@
 
         @Override
         public ApplicationViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-            final View view = ApplicationViewHolder.newView(
-                    LayoutInflater.from(parent.getContext()), parent);
-            return new ApplicationViewHolder(view);
+            final View view = ApplicationViewHolder.newView(parent);
+            return new ApplicationViewHolder(view,
+                    shouldUseStableItemHeight(mManageApplications.mListType));
         }
 
         @Override
@@ -994,7 +980,21 @@
             });
         }
 
-        static private boolean packageNameEquals(PackageItemInfo info1, PackageItemInfo info2) {
+        @VisibleForTesting
+        static boolean shouldUseStableItemHeight(int listType) {
+            switch (listType) {
+                case LIST_TYPE_NOTIFICATION:
+                    // Most entries in notification type has no summary. Don't use stable height
+                    // so height is short for most entries.
+                    return false;
+                default:
+                    // Other types have non-empty summary, so keep the height as we expect summary
+                    // to fill in.
+                    return true;
+            }
+        }
+
+        private static boolean packageNameEquals(PackageItemInfo info1, PackageItemInfo info2) {
             if (info1 == null || info2 == null) {
                 return false;
             }
diff --git a/src/com/android/settings/applications/manageapplications/MusicViewHolderController.java b/src/com/android/settings/applications/manageapplications/MusicViewHolderController.java
index 2a2ac3b..14d08aa 100644
--- a/src/com/android/settings/applications/manageapplications/MusicViewHolderController.java
+++ b/src/com/android/settings/applications/manageapplications/MusicViewHolderController.java
@@ -19,7 +19,6 @@
 import android.app.Fragment;
 import android.content.Context;
 import android.content.Intent;
-import android.graphics.drawable.InsetDrawable;
 import android.os.UserHandle;
 import android.provider.DocumentsContract;
 import android.support.annotation.WorkerThread;
@@ -39,7 +38,6 @@
     private static final String TAG = "MusicViewHolderCtrl";
 
     private static final String AUTHORITY_MEDIA = "com.android.providers.media.documents";
-    private static final int INSET_SIZE = 24; // dp
 
     private Context mContext;
     private StorageStatsSource mSource;
@@ -73,8 +71,7 @@
 
     @Override
     public void setupView(ApplicationViewHolder holder) {
-        holder.setIcon(
-                new InsetDrawable(mContext.getDrawable(R.drawable.ic_headset_24dp), INSET_SIZE));
+        holder.setIcon(R.drawable.ic_headset_24dp);
         holder.setTitle(mContext.getText(R.string.audio_files_title));
         holder.setSummary(Formatter.formatFileSize(mContext, mMusicSize));
     }
diff --git a/src/com/android/settings/applications/manageapplications/PhotosViewHolderController.java b/src/com/android/settings/applications/manageapplications/PhotosViewHolderController.java
index fafa875..73330e9 100644
--- a/src/com/android/settings/applications/manageapplications/PhotosViewHolderController.java
+++ b/src/com/android/settings/applications/manageapplications/PhotosViewHolderController.java
@@ -19,7 +19,6 @@
 import android.app.Fragment;
 import android.content.Context;
 import android.content.Intent;
-import android.graphics.drawable.InsetDrawable;
 import android.os.UserHandle;
 import android.support.annotation.WorkerThread;
 import android.text.format.Formatter;
@@ -36,7 +35,6 @@
     private static final String TAG = "PhotosViewHolderCtrl";
 
     private static final String IMAGE_MIME_TYPE = "image/*";
-    private static final int INSET_SIZE = 24; // dp
 
     private Context mContext;
     private StorageStatsSource mSource;
@@ -72,8 +70,7 @@
 
     @Override
     public void setupView(ApplicationViewHolder holder) {
-        holder.setIcon(
-                new InsetDrawable(mContext.getDrawable(R.drawable.ic_photo_library), INSET_SIZE));
+        holder.setIcon(R.drawable.ic_photo_library);
         holder.setTitle(mContext.getText(R.string.storage_detail_images));
         holder.setSummary(Formatter.formatFileSize(mContext, mFilesSize));
     }
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsHeaderController.java b/src/com/android/settings/bluetooth/BluetoothDetailsHeaderController.java
index 04e9f5a..df7b601 100644
--- a/src/com/android/settings/bluetooth/BluetoothDetailsHeaderController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsHeaderController.java
@@ -51,8 +51,8 @@
     }
 
     protected void setHeaderProperties() {
-        final Pair<Drawable, String> pair = Utils.getBtClassDrawableWithDescription(
-                mContext, mCachedDevice,
+        final Pair<Drawable, String> pair = com.android.settingslib.bluetooth.Utils
+                .getBtClassDrawableWithDescription(mContext, mCachedDevice,
                 mContext.getResources().getFraction(R.fraction.bt_battery_scale_fraction, 1, 1));
         String summaryText = mCachedDevice.getConnectionSummary();
         mHeaderController.setLabel(mCachedDevice.getName());
diff --git a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
index 94ba478..043cb95 100644
--- a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
+++ b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
@@ -122,8 +122,8 @@
         // Null check is done at the framework
         setSummary(mCachedDevice.getConnectionSummary());
 
-        final Pair<Drawable, String> pair = Utils.getBtClassDrawableWithDescription(getContext(),
-                mCachedDevice);
+        final Pair<Drawable, String> pair = com.android.settingslib.bluetooth.Utils
+                .getBtClassDrawableWithDescription(getContext(), mCachedDevice);
         if (pair.first != null) {
             setIcon(pair.first);
             contentDescription = pair.second;
diff --git a/src/com/android/settings/bluetooth/Utils.java b/src/com/android/settings/bluetooth/Utils.java
index 0ecf62d..d5d4e50 100755
--- a/src/com/android/settings/bluetooth/Utils.java
+++ b/src/com/android/settings/bluetooth/Utils.java
@@ -17,31 +17,19 @@
 package com.android.settings.bluetooth;
 
 import android.app.AlertDialog;
-import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.content.DialogInterface;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-import android.support.annotation.DrawableRes;
-import android.support.annotation.IdRes;
 import android.support.annotation.VisibleForTesting;
-import android.util.Pair;
 import android.widget.Toast;
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settings.R;
 import com.android.settings.overlay.FeatureFactory;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.HidProfile;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.bluetooth.LocalBluetoothManager.BluetoothManagerCallback;
-import com.android.settingslib.bluetooth.LocalBluetoothProfile;
 import com.android.settingslib.bluetooth.Utils.ErrorListener;
-import com.android.settingslib.graph.BluetoothDeviceLayerDrawable;
-
-import java.util.List;
 
 /**
  * Utils is a helper class that contains constants for various
@@ -57,16 +45,16 @@
 
     public static int getConnectionStateSummary(int connectionState) {
         switch (connectionState) {
-        case BluetoothProfile.STATE_CONNECTED:
-            return R.string.bluetooth_connected;
-        case BluetoothProfile.STATE_CONNECTING:
-            return R.string.bluetooth_connecting;
-        case BluetoothProfile.STATE_DISCONNECTED:
-            return R.string.bluetooth_disconnected;
-        case BluetoothProfile.STATE_DISCONNECTING:
-            return R.string.bluetooth_disconnecting;
-        default:
-            return 0;
+            case BluetoothProfile.STATE_CONNECTED:
+                return R.string.bluetooth_connected;
+            case BluetoothProfile.STATE_CONNECTING:
+                return R.string.bluetooth_connecting;
+            case BluetoothProfile.STATE_DISCONNECTED:
+                return R.string.bluetooth_disconnected;
+            case BluetoothProfile.STATE_DISCONNECTING:
+                return R.string.bluetooth_disconnecting;
+            default:
+                return 0;
         }
     }
 
@@ -154,81 +142,4 @@
         }
     };
 
-    static Pair<Drawable, String> getBtClassDrawableWithDescription(Context context,
-            CachedBluetoothDevice cachedDevice) {
-        return getBtClassDrawableWithDescription(context, cachedDevice, 1 /* iconScale */);
-    }
-
-    static Pair<Drawable, String> getBtClassDrawableWithDescription(Context context,
-            CachedBluetoothDevice cachedDevice, float iconScale) {
-        BluetoothClass btClass = cachedDevice.getBtClass();
-        final int level = cachedDevice.getBatteryLevel();
-        if (btClass != null) {
-            switch (btClass.getMajorDeviceClass()) {
-                case BluetoothClass.Device.Major.COMPUTER:
-                    return new Pair<>(getBluetoothDrawable(context, R.drawable.ic_bt_laptop, level,
-                            iconScale),
-                            context.getString(R.string.bluetooth_talkback_computer));
-
-                case BluetoothClass.Device.Major.PHONE:
-                    return new Pair<>(
-                            getBluetoothDrawable(context, R.drawable.ic_bt_cellphone, level,
-                                    iconScale),
-                            context.getString(R.string.bluetooth_talkback_phone));
-
-                case BluetoothClass.Device.Major.PERIPHERAL:
-                    return new Pair<>(
-                            getBluetoothDrawable(context, HidProfile.getHidClassDrawable(btClass),
-                                    level, iconScale),
-                            context.getString(R.string.bluetooth_talkback_input_peripheral));
-
-                case BluetoothClass.Device.Major.IMAGING:
-                    return new Pair<>(
-                            getBluetoothDrawable(context, R.drawable.ic_settings_print, level,
-                                    iconScale),
-                            context.getString(R.string.bluetooth_talkback_imaging));
-
-                default:
-                    // unrecognized device class; continue
-            }
-        }
-
-        List<LocalBluetoothProfile> profiles = cachedDevice.getProfiles();
-        for (LocalBluetoothProfile profile : profiles) {
-            int resId = profile.getDrawableResource(btClass);
-            if (resId != 0) {
-                return new Pair<>(getBluetoothDrawable(context, resId, level, iconScale), null);
-            }
-        }
-        if (btClass != null) {
-            if (btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) {
-                return new Pair<>(
-                        getBluetoothDrawable(context, R.drawable.ic_bt_headset_hfp, level,
-                                iconScale),
-                        context.getString(R.string.bluetooth_talkback_headset));
-            }
-            if (btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) {
-                return new Pair<>(
-                        getBluetoothDrawable(context, R.drawable.ic_bt_headphones_a2dp, level,
-                                iconScale),
-                        context.getString(R.string.bluetooth_talkback_headphone));
-            }
-        }
-        return new Pair<>(
-                getBluetoothDrawable(context, R.drawable.ic_settings_bluetooth, level, iconScale),
-                context.getString(R.string.bluetooth_talkback_bluetooth));
-    }
-
-    @VisibleForTesting
-    static Drawable getBluetoothDrawable(Context context, @DrawableRes int resId,
-            int batteryLevel, float iconScale) {
-        if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
-            return BluetoothDeviceLayerDrawable.createLayerDrawable(context, resId, batteryLevel,
-                    iconScale);
-        } else if (resId != 0) {
-            return context.getDrawable(resId);
-        } else {
-            return null;
-        }
-    }
 }
diff --git a/src/com/android/settings/core/InstrumentedPreferenceFragment.java b/src/com/android/settings/core/InstrumentedPreferenceFragment.java
index 8ef8c0e..5b95d66 100644
--- a/src/com/android/settings/core/InstrumentedPreferenceFragment.java
+++ b/src/com/android/settings/core/InstrumentedPreferenceFragment.java
@@ -85,7 +85,7 @@
     }
 
     public static boolean usePreferenceScreenTitle() {
-        return FeatureFlagUtils.isEnabled(FEATURE_FLAG_USE_PREFERENCE_SCREEN_TITLE);
+        return FeatureFlagUtils.isEnabled(FEATURE_FLAG_USE_PREFERENCE_SCREEN_TITLE) || true;
     }
 
     protected final Context getPrefContext() {
diff --git a/src/com/android/settings/dashboard/DashboardFragment.java b/src/com/android/settings/dashboard/DashboardFragment.java
index e7f36e1..6796c26 100644
--- a/src/com/android/settings/dashboard/DashboardFragment.java
+++ b/src/com/android/settings/dashboard/DashboardFragment.java
@@ -183,6 +183,9 @@
         }
     }
 
+    @Override
+    protected abstract int getPreferenceScreenResId();
+
     protected <T extends AbstractPreferenceController> T getPreferenceController(Class<T> clazz) {
         AbstractPreferenceController controller = mPreferenceControllers.get(clazz);
         return (T) controller;
diff --git a/src/com/android/settings/dashboard/SummaryLoader.java b/src/com/android/settings/dashboard/SummaryLoader.java
index 4586a55..5816bba8 100644
--- a/src/com/android/settings/dashboard/SummaryLoader.java
+++ b/src/com/android/settings/dashboard/SummaryLoader.java
@@ -35,6 +35,7 @@
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settingslib.drawer.DashboardCategory;
 import com.android.settingslib.drawer.Tile;
+import com.android.settingslib.utils.ThreadUtils;
 
 import java.lang.reflect.Field;
 import java.util.List;
@@ -52,7 +53,6 @@
     private final String mCategoryKey;
 
     private final Worker mWorker;
-    private final Handler mHandler;
     private final HandlerThread mWorkerThread;
 
     private SummaryConsumer mSummaryConsumer;
@@ -64,7 +64,6 @@
         mDashboardFeatureProvider = FeatureFactory.getFactory(activity)
                 .getDashboardFeatureProvider(activity);
         mCategoryKey = null;
-        mHandler = new Handler();
         mWorkerThread = new HandlerThread("SummaryLoader", Process.THREAD_PRIORITY_BACKGROUND);
         mWorkerThread.start();
         mWorker = new Worker(mWorkerThread.getLooper());
@@ -82,7 +81,6 @@
         mDashboardFeatureProvider = FeatureFactory.getFactory(activity)
                 .getDashboardFeatureProvider(activity);
         mCategoryKey = categoryKey;
-        mHandler = new Handler();
         mWorkerThread = new HandlerThread("SummaryLoader", Process.THREAD_PRIORITY_BACKGROUND);
         mWorkerThread.start();
         mWorker = new Worker(mWorkerThread.getLooper());
@@ -112,25 +110,22 @@
 
     public void setSummary(SummaryProvider provider, final CharSequence summary) {
         final ComponentName component = mSummaryProviderMap.get(provider);
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
+        ThreadUtils.postOnMainThread(() -> {
 
-                final Tile tile = getTileFromCategory(
-                        mDashboardFeatureProvider.getTilesForCategory(mCategoryKey), component);
+            final Tile tile = getTileFromCategory(
+                    mDashboardFeatureProvider.getTilesForCategory(mCategoryKey), component);
 
-                if (tile == null) {
-                    if (DEBUG) {
-                        Log.d(TAG, "Can't find tile for " + component);
-                    }
-                    return;
-                }
+            if (tile == null) {
                 if (DEBUG) {
-                    Log.d(TAG, "setSummary " + tile.title + " - " + summary);
+                    Log.d(TAG, "Can't find tile for " + component);
                 }
-
-                updateSummaryIfNeeded(tile, summary);
+                return;
             }
+            if (DEBUG) {
+                Log.d(TAG, "setSummary " + tile.title + " - " + summary);
+            }
+
+            updateSummaryIfNeeded(tile, summary);
         });
     }
 
diff --git a/src/com/android/settings/dashboard/SupportFragment.java b/src/com/android/settings/dashboard/SupportFragment.java
index 8a1a79b..fcc9f78 100644
--- a/src/com/android/settings/dashboard/SupportFragment.java
+++ b/src/com/android/settings/dashboard/SupportFragment.java
@@ -27,8 +27,6 @@
 import android.net.NetworkInfo;
 import android.net.NetworkRequest;
 import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
 import android.view.LayoutInflater;
@@ -40,6 +38,7 @@
 import com.android.settings.core.InstrumentedFragment;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.overlay.SupportFeatureProvider;
+import com.android.settingslib.utils.ThreadUtils;
 
 /**
  * Fragment for support tab in SettingsGoogle.
@@ -47,7 +46,6 @@
 public final class SupportFragment extends InstrumentedFragment implements View.OnClickListener,
         OnAccountsUpdateListener {
 
-    private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final ConnectivityManager.NetworkCallback mNetworkCallback =
             new ConnectivityManager.NetworkCallback() {
 
@@ -152,12 +150,9 @@
     }
 
     private void postConnectivityChanged() {
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                if (mSupportItemAdapter != null) {
-                    mSupportItemAdapter.setHasInternet(hasInternet());
-                }
+        ThreadUtils.postOnMainThread(() -> {
+            if (mSupportItemAdapter != null) {
+                mSupportItemAdapter.setHasInternet(hasInternet());
             }
         });
     }
diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java b/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java
index 783987d..1be31b4 100644
--- a/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java
+++ b/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java
@@ -92,7 +92,7 @@
     }
 
     private static boolean isV2Enabled() {
-        return FeatureFlagUtils.isEnabled(FEATURE_FLAG_SUGGESTIONS_V2);
+        return FeatureFlagUtils.isEnabled(FEATURE_FLAG_SUGGESTIONS_V2) || true;
     }
 
     @Override
diff --git a/src/com/android/settings/datausage/AppDataUsagePreference.java b/src/com/android/settings/datausage/AppDataUsagePreference.java
index 35f1fe2..f0fd755 100644
--- a/src/com/android/settings/datausage/AppDataUsagePreference.java
+++ b/src/com/android/settings/datausage/AppDataUsagePreference.java
@@ -15,19 +15,18 @@
 package com.android.settings.datausage;
 
 import android.content.Context;
-import android.support.v7.preference.Preference;
 import android.support.v7.preference.PreferenceViewHolder;
 import android.text.format.Formatter;
 import android.view.View;
 import android.widget.ProgressBar;
 
-import com.android.settings.R;
+import com.android.settings.widget.AppPreference;
 import com.android.settingslib.AppItem;
 import com.android.settingslib.net.UidDetail;
 import com.android.settingslib.net.UidDetailProvider;
 import com.android.settingslib.utils.ThreadUtils;
 
-public class AppDataUsagePreference extends Preference {
+public class AppDataUsagePreference extends AppPreference {
 
     private final AppItem mItem;
     private final int mPercent;
@@ -38,8 +37,6 @@
         super(context);
         mItem = item;
         mPercent = percent;
-        setLayoutResource(R.layout.data_usage_item);
-        setWidgetLayoutResource(R.layout.widget_progress_bar);
 
         if (item.restricted && item.total <= 0) {
             setSummary(com.android.settings.R.string.data_usage_app_restricted);
@@ -60,7 +57,6 @@
     @Override
     public void onBindViewHolder(PreferenceViewHolder holder) {
         super.onBindViewHolder(holder);
-
         final ProgressBar progress = (ProgressBar) holder.findViewById(
                 android.R.id.progress);
 
diff --git a/src/com/android/settings/datausage/DataSaverBackend.java b/src/com/android/settings/datausage/DataSaverBackend.java
index f37a3c3..041a81f 100644
--- a/src/com/android/settings/datausage/DataSaverBackend.java
+++ b/src/com/android/settings/datausage/DataSaverBackend.java
@@ -14,24 +14,23 @@
 
 package com.android.settings.datausage;
 
+import static android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND;
+import static android.net.NetworkPolicyManager.POLICY_NONE;
+import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
+
 import android.content.Context;
 import android.net.INetworkPolicyListener;
 import android.net.NetworkPolicyManager;
-import android.os.Handler;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.util.SparseIntArray;
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settings.core.instrumentation.MetricsFeatureProvider;
 import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.utils.ThreadUtils;
 
 import java.util.ArrayList;
 
-import static android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND;
-import static android.net.NetworkPolicyManager.POLICY_NONE;
-import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
-
 public class DataSaverBackend {
 
     private static final String TAG = "DataSaverBackend";
@@ -39,7 +38,6 @@
     private final Context mContext;
     private final MetricsFeatureProvider mMetricsFeatureProvider;
 
-    private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final NetworkPolicyManager mPolicyManager;
     private final ArrayList<Listener> mListeners = new ArrayList<>();
     private SparseIntArray mUidPolicies = new SparseIntArray();
@@ -195,7 +193,7 @@
 
         @Override
         public void onUidPoliciesChanged(final int uid, final int uidPolicies) {
-            mHandler.post(() -> handleUidPoliciesChanged(uid, uidPolicies));
+            ThreadUtils.postOnMainThread(() -> handleUidPoliciesChanged(uid, uidPolicies));
         }
 
         @Override
@@ -204,7 +202,7 @@
 
         @Override
         public void onRestrictBackgroundChanged(final boolean isDataSaving) throws RemoteException {
-            mHandler.post(() -> handleRestrictBackgroundChanged(isDataSaving));
+            ThreadUtils.postOnMainThread(() -> handleRestrictBackgroundChanged(isDataSaving));
         }
     };
 
diff --git a/src/com/android/settings/datausage/UnrestrictedDataAccess.java b/src/com/android/settings/datausage/UnrestrictedDataAccess.java
index 8fad986..6771abc 100644
--- a/src/com/android/settings/datausage/UnrestrictedDataAccess.java
+++ b/src/com/android/settings/datausage/UnrestrictedDataAccess.java
@@ -18,7 +18,6 @@
 import android.content.Context;
 import android.os.Bundle;
 import android.os.UserHandle;
-import android.support.v14.preference.SwitchPreference;
 import android.support.v7.preference.Preference;
 import android.support.v7.preference.PreferenceViewHolder;
 import android.view.Menu;
@@ -34,6 +33,7 @@
 import com.android.settings.applications.InstalledAppDetails;
 import com.android.settings.datausage.AppStateDataUsageBridge.DataUsageState;
 import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.widget.AppSwitchPreference;
 import com.android.settingslib.applications.ApplicationsState;
 import com.android.settingslib.applications.ApplicationsState.AppEntry;
 import com.android.settingslib.applications.ApplicationsState.AppFilter;
@@ -59,11 +59,7 @@
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
         setAnimationAllowed(true);
-        if (usePreferenceScreenTitle()) {
-            addPreferencesFromResource(R.xml.unrestricted_data_access_settings);
-        } else {
-            setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getContext()));
-        }
+        addPreferencesFromResource(R.xml.unrestricted_data_access_settings);
         mApplicationsState = ApplicationsState.getInstance(
                 (Application) getContext().getApplicationContext());
         mDataSaverBackend = new DataSaverBackend(getContext());
@@ -239,7 +235,8 @@
         return app != null && UserHandle.isApp(app.info.uid);
     }
 
-    private class AccessPreference extends SwitchPreference implements DataSaverBackend.Listener {
+    private class AccessPreference extends AppSwitchPreference
+            implements DataSaverBackend.Listener {
         private final AppEntry mEntry;
         private final DataUsageState mState;
 
diff --git a/src/com/android/settings/development/AppViewHolder.java b/src/com/android/settings/development/AppViewHolder.java
index 7a90bcf..1082efe 100644
--- a/src/com/android/settings/development/AppViewHolder.java
+++ b/src/com/android/settings/development/AppViewHolder.java
@@ -18,7 +18,6 @@
 
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
 import android.widget.ImageView;
 import android.widget.TextView;
 
@@ -37,23 +36,21 @@
     static public AppViewHolder createOrRecycle(LayoutInflater inflater, View convertView) {
         if (convertView == null) {
             convertView = inflater.inflate(R.layout.preference_app, null);
-            inflater.inflate(R.layout.widget_text_views,
-                    (ViewGroup) convertView.findViewById(android.R.id.widget_frame));
 
             // Creates a ViewHolder and store references to the two children views
             // we want to bind data to.
             AppViewHolder holder = new AppViewHolder();
             holder.rootView = convertView;
-            holder.appName = (TextView) convertView.findViewById(android.R.id.title);
-            holder.appIcon = (ImageView) convertView.findViewById(android.R.id.icon);
-            holder.summary = (TextView) convertView.findViewById(R.id.widget_text1);
-            holder.disabled = (TextView) convertView.findViewById(R.id.widget_text2);
+            holder.appName = convertView.findViewById(android.R.id.title);
+            holder.appIcon = convertView.findViewById(android.R.id.icon);
+            holder.summary = convertView.findViewById(android.R.id.summary);
+            holder.disabled = convertView.findViewById(R.id.appendix);
             convertView.setTag(holder);
             return holder;
         } else {
             // Get the ViewHolder back to get fast access to the TextView
             // and the ImageView.
-            return (AppViewHolder)convertView.getTag();
+            return (AppViewHolder) convertView.getTag();
         }
     }
 }
\ No newline at end of file
diff --git a/src/com/android/settings/development/OemLockInfoDialog.java b/src/com/android/settings/development/OemLockInfoDialog.java
new file mode 100644
index 0000000..6d75812
--- /dev/null
+++ b/src/com/android/settings/development/OemLockInfoDialog.java
@@ -0,0 +1,53 @@
+/*
+ * 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.development;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.os.Bundle;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.R;
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+
+public class OemLockInfoDialog extends InstrumentedDialogFragment {
+
+    private static final String TAG = "OemLockInfoDialog";
+
+    public static void show(Fragment host) {
+        final FragmentManager manager = host.getChildFragmentManager();
+        if (manager.findFragmentByTag(TAG) == null) {
+            final OemLockInfoDialog dialog = new OemLockInfoDialog();
+            dialog.show(manager, TAG);
+        }
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        return MetricsProto.MetricsEvent.DIALOG_OEM_LOCK_INFO;
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
+                .setMessage(R.string.oem_lock_info_message);
+
+        return builder.create();
+    }
+}
diff --git a/src/com/android/settings/development/OemUnlockPreferenceController.java b/src/com/android/settings/development/OemUnlockPreferenceController.java
index 91994c2..f486b3d 100644
--- a/src/com/android/settings/development/OemUnlockPreferenceController.java
+++ b/src/com/android/settings/development/OemUnlockPreferenceController.java
@@ -90,6 +90,7 @@
             }
         } else {
             mOemLockManager.setOemUnlockAllowedByUser(false);
+            OemLockInfoDialog.show(mFragment);
         }
         return true;
     }
diff --git a/src/com/android/settings/display/ShowOperatorNamePreferenceController.java b/src/com/android/settings/display/ShowOperatorNamePreferenceController.java
new file mode 100644
index 0000000..4573c6d
--- /dev/null
+++ b/src/com/android/settings/display/ShowOperatorNamePreferenceController.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.display;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settingslib.core.AbstractPreferenceController;
+
+public class ShowOperatorNamePreferenceController extends AbstractPreferenceController
+        implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener {
+
+    private static final String KEY_SHOW_OPERATOR_NAME = "show_operator_name";
+
+    public ShowOperatorNamePreferenceController(Context context) {
+        super(context);
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return mContext.getResources().getBoolean(R.bool.config_showOperatorNameInStatusBar);
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_SHOW_OPERATOR_NAME;
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        boolean value = (Boolean) newValue;
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                KEY_SHOW_OPERATOR_NAME, value ? 1 : 0);
+        return true;
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        int value = Settings.Secure.getInt(mContext.getContentResolver(),
+                KEY_SHOW_OPERATOR_NAME, 1);
+        ((SwitchPreference) preference).setChecked(value != 0);
+    }
+}
diff --git a/src/com/android/settings/enterprise/ApplicationListPreferenceController.java b/src/com/android/settings/enterprise/ApplicationListPreferenceController.java
index dce6f57..8be7944 100644
--- a/src/com/android/settings/enterprise/ApplicationListPreferenceController.java
+++ b/src/com/android/settings/enterprise/ApplicationListPreferenceController.java
@@ -22,11 +22,11 @@
 import android.support.v7.preference.PreferenceScreen;
 import android.util.IconDrawableFactory;
 
-import com.android.settings.R;
 import com.android.settings.SettingsPreferenceFragment;
 import com.android.settings.applications.ApplicationFeatureProvider;
 import com.android.settings.applications.UserAppInfo;
 import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settings.widget.AppPreference;
 import com.android.settingslib.core.AbstractPreferenceController;
 
 import java.util.List;
@@ -68,8 +68,7 @@
         final Context prefContext = mParent.getPreferenceManager().getContext();
         for (int position = 0; position < result.size(); position++) {
             final UserAppInfo item = result.get(position);
-            final Preference preference = new Preference(prefContext);
-            preference.setLayoutResource(R.layout.preference_app);
+            final Preference preference = new AppPreference(prefContext);
             preference.setTitle(item.appInfo.loadLabel(mPm));
             preference.setIcon(iconDrawableFactory.getBadgedIcon(item.appInfo));
             preference.setOrder(position);
diff --git a/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsListPreferenceController.java b/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsListPreferenceController.java
index 03a78ea..0927299 100644
--- a/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsListPreferenceController.java
+++ b/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsListPreferenceController.java
@@ -20,7 +20,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
-import android.os.Handler;
 import android.os.UserHandle;
 import android.support.v7.preference.Preference;
 import android.support.v7.preference.PreferenceCategory;
@@ -36,6 +35,7 @@
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.users.UserFeatureProvider;
 import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.utils.ThreadUtils;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -100,7 +100,7 @@
                 userMap.put(typeOfDefault, applicationInfos);
             }
         }
-        new Handler(mContext.getMainLooper()).post(() -> { updateUi(); });
+        ThreadUtils.postOnMainThread(() -> updateUi());
     }
 
     @Override
diff --git a/src/com/android/settings/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/fingerprint/FingerprintEnrollEnrolling.java
index 10bc9e3..756f826 100644
--- a/src/com/android/settings/fingerprint/FingerprintEnrollEnrolling.java
+++ b/src/com/android/settings/fingerprint/FingerprintEnrollEnrolling.java
@@ -32,6 +32,8 @@
 import android.hardware.fingerprint.FingerprintManager;
 import android.os.Bundle;
 import android.os.UserHandle;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
 import android.text.TextUtils;
 import android.view.MotionEvent;
 import android.view.View;
@@ -74,6 +76,9 @@
      */
     private static final int ICON_TOUCH_COUNT_SHOW_UNTIL_DIALOG_SHOWN = 3;
 
+    private static final VibrationEffect VIBRATE_EFFECT_ERROR =
+            VibrationEffect.createWaveform(new long[] {0, 5, 55, 60}, -1);
+
     private ProgressBar mProgressBar;
     private ObjectAnimator mProgressAnim;
     private TextView mStartMessage;
@@ -90,6 +95,7 @@
     private int mIndicatorBackgroundRestingColor;
     private int mIndicatorBackgroundActivatedColor;
     private boolean mRestoring;
+    private Vibrator mVibrator;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -100,6 +106,7 @@
         mRepeatMessage = (TextView) findViewById(R.id.repeat_message);
         mErrorText = (TextView) findViewById(R.id.error_text);
         mProgressBar = (ProgressBar) findViewById(R.id.fingerprint_progress_bar);
+        mVibrator = getSystemService(Vibrator.class);
 
         Button skipButton = findViewById(R.id.skip_button);
         skipButton.setOnClickListener(this);
@@ -368,6 +375,9 @@
             mErrorText.setAlpha(1f);
             mErrorText.setTranslationY(0f);
         }
+        if (isResumed()) {
+            mVibrator.vibrate(VIBRATE_EFFECT_ERROR);
+        }
     }
 
     private void clearError() {
@@ -378,12 +388,7 @@
                             R.dimen.fingerprint_error_text_disappear_distance))
                     .setDuration(100)
                     .setInterpolator(mFastOutLinearInInterpolator)
-                    .withEndAction(new Runnable() {
-                        @Override
-                        public void run() {
-                            mErrorText.setVisibility(View.INVISIBLE);
-                        }
-                    })
+                    .withEndAction(() -> mErrorText.setVisibility(View.INVISIBLE))
                     .start();
         }
     }
diff --git a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java
index d96db68..819846a 100644
--- a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java
+++ b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java
@@ -45,6 +45,7 @@
 import com.android.settings.Utils;
 import com.android.settings.applications.LayoutPreference;
 import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.fuelgauge.anomaly.AnomalyUtils;
 import com.android.settings.wrapper.DevicePolicyManagerWrapper;
 import com.android.settings.fuelgauge.anomaly.Anomaly;
 import com.android.settings.fuelgauge.anomaly.AnomalyDialogFragment;
@@ -360,6 +361,9 @@
 
     @Override
     public void onLoadFinished(Loader<List<Anomaly>> loader, List<Anomaly> data) {
+        final AnomalyUtils anomalyUtils = AnomalyUtils.getInstance(getContext());
+        anomalyUtils.logAnomalies(mMetricsFeatureProvider, data,
+                MetricsEvent.FUELGAUGE_POWER_USAGE_DETAIL);
         mAnomalySummaryPreferenceController.updateAnomalySummaryPreference(data);
     }
 
diff --git a/src/com/android/settings/fuelgauge/PowerGaugePreference.java b/src/com/android/settings/fuelgauge/PowerGaugePreference.java
index 05dad27..93e33cc 100644
--- a/src/com/android/settings/fuelgauge/PowerGaugePreference.java
+++ b/src/com/android/settings/fuelgauge/PowerGaugePreference.java
@@ -19,15 +19,13 @@
 import android.content.Context;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
-import android.support.v7.preference.Preference;
 import android.support.v7.preference.PreferenceViewHolder;
 import android.util.AttributeSet;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import com.android.settings.R;
 import com.android.settings.Utils;
+import com.android.settings.widget.AppPreference;
 
 /**
  * Custom preference for displaying battery usage info as a bar and an icon on
@@ -36,8 +34,7 @@
  * The battery usage info could be usage percentage or usage time. The preference
  * won't show any icon if it is null.
  */
-public class PowerGaugePreference extends Preference {
-    private final int mIconSize;
+public class PowerGaugePreference extends AppPreference {
 
     private BatteryEntry mInfo;
     private CharSequence mContentDescription;
@@ -64,7 +61,6 @@
         setWidgetLayoutResource(R.layout.preference_widget_summary);
         mInfo = info;
         mContentDescription = contentDescription;
-        mIconSize = context.getResources().getDimensionPixelSize(R.dimen.app_icon_size);
         mShowAnomalyIcon = false;
     }
 
@@ -107,8 +103,6 @@
     @Override
     public void onBindViewHolder(PreferenceViewHolder view) {
         super.onBindViewHolder(view);
-        ImageView icon = (ImageView) view.findViewById(android.R.id.icon);
-        icon.setLayoutParams(new LinearLayout.LayoutParams(mIconSize, mIconSize));
 
         final TextView subtitle = (TextView) view.findViewById(R.id.widget_summary);
         subtitle.setText(mProgress);
diff --git a/src/com/android/settings/fuelgauge/PowerUsageAdvanced.java b/src/com/android/settings/fuelgauge/PowerUsageAdvanced.java
index adadb78..6ed92a7 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageAdvanced.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageAdvanced.java
@@ -18,6 +18,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
 import android.os.BatteryManager;
 import android.os.BatteryStats;
 import android.os.Bundle;
@@ -250,7 +251,7 @@
     boolean shouldHideCategory(PowerUsageData powerUsageData) {
         return powerUsageData.usageType == UsageType.UNACCOUNTED
                 || powerUsageData.usageType == UsageType.OVERCOUNTED
-                || (powerUsageData.usageType == UsageType.USER && mUserManager.getUserCount() == 1)
+                || (powerUsageData.usageType == UsageType.USER && isSingleNormalUser())
                 || (powerUsageData.usageType == UsageType.CELL
                 && !DataUsageUtils.hasMobileData(getContext()));
     }
@@ -373,6 +374,18 @@
         mBatteryUtils = batteryUtils;
     }
 
+    @VisibleForTesting
+    boolean isSingleNormalUser() {
+        int count = 0;
+        for (UserInfo userInfo : mUserManager.getUsers()) {
+            if (userInfo.isEnabled() && !userInfo.isManagedProfile()) {
+                count++;
+            }
+        }
+
+        return count == 1;
+    }
+
     /**
      * Class that contains data used in {@link PowerGaugePreference}.
      */
diff --git a/src/com/android/settings/fuelgauge/PowerUsageAnomalyDetails.java b/src/com/android/settings/fuelgauge/PowerUsageAnomalyDetails.java
index bf4c2af..0d73511 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageAnomalyDetails.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageAnomalyDetails.java
@@ -125,8 +125,7 @@
 
     @Override
     public int getMetricsCategory() {
-        //TODO(b/37681923): add correct metrics category
-        return 0;
+        return MetricsProto.MetricsEvent.FUELGAUGE_ANOMALY_DETAIL;
     }
 
     void refreshUi() {
diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java
index ac50e66..dd8c169 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java
@@ -67,6 +67,7 @@
 import com.android.settings.fuelgauge.anomaly.AnomalyDialogFragment.AnomalyDialogListener;
 import com.android.settings.fuelgauge.anomaly.AnomalyLoader;
 import com.android.settings.fuelgauge.anomaly.AnomalySummaryPreferenceController;
+import com.android.settings.fuelgauge.anomaly.AnomalyUtils;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settingslib.core.AbstractPreferenceController;
@@ -148,6 +149,10 @@
 
                 @Override
                 public void onLoadFinished(Loader<List<Anomaly>> loader, List<Anomaly> data) {
+                    final AnomalyUtils anomalyUtils = AnomalyUtils.getInstance(getContext());
+                    anomalyUtils.logAnomalies(mMetricsFeatureProvider, data,
+                            MetricsEvent.FUELGAUGE_POWER_USAGE_SUMMARY);
+
                     // show high usage preference if possible
                     mAnomalySummaryPreferenceController.updateAnomalySummaryPreference(data);
 
diff --git a/src/com/android/settings/fuelgauge/anomaly/AnomalyDialogFragment.java b/src/com/android/settings/fuelgauge/anomaly/AnomalyDialogFragment.java
index 69d03b9..538f5b1 100644
--- a/src/com/android/settings/fuelgauge/anomaly/AnomalyDialogFragment.java
+++ b/src/com/android/settings/fuelgauge/anomaly/AnomalyDialogFragment.java
@@ -23,6 +23,7 @@
 import android.os.Bundle;
 import android.support.annotation.VisibleForTesting;
 
+import com.android.internal.logging.nano.MetricsProto;
 import com.android.settings.R;
 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
 import com.android.settings.fuelgauge.anomaly.action.AnomalyAction;
@@ -79,8 +80,7 @@
 
     @Override
     public int getMetricsCategory() {
-        // TODO(b/37681923): add anomaly metric id
-        return 0;
+        return MetricsProto.MetricsEvent.DIALOG_HANDLE_ANOMALY;
     }
 
     @Override
@@ -100,9 +100,13 @@
     @Override
     public Dialog onCreateDialog(Bundle savedInstanceState) {
         final Bundle bundle = getArguments();
-        mAnomaly = bundle.getParcelable(ARG_ANOMALY);
-
         final Context context = getContext();
+        final AnomalyUtils anomalyUtils = AnomalyUtils.getInstance(context);
+
+        mAnomaly = bundle.getParcelable(ARG_ANOMALY);
+        anomalyUtils.logAnomaly(mMetricsFeatureProvider, mAnomaly,
+                MetricsProto.MetricsEvent.DIALOG_HANDLE_ANOMALY);
+
         final AnomalyAction anomalyAction = mAnomalyUtils.getAnomalyAction(mAnomaly);
         switch (anomalyAction.getActionType()) {
             case Anomaly.AnomalyActionType.FORCE_STOP:
diff --git a/src/com/android/settings/fuelgauge/anomaly/AnomalyUtils.java b/src/com/android/settings/fuelgauge/anomaly/AnomalyUtils.java
index 491a3b4..39d51dc0 100644
--- a/src/com/android/settings/fuelgauge/anomaly/AnomalyUtils.java
+++ b/src/com/android/settings/fuelgauge/anomaly/AnomalyUtils.java
@@ -19,8 +19,12 @@
 import android.content.Context;
 import android.os.Build;
 import android.support.annotation.VisibleForTesting;
+import android.util.Pair;
+import android.util.SparseIntArray;
 
+import com.android.internal.logging.nano.MetricsProto;
 import com.android.internal.os.BatteryStatsHelper;
+import com.android.settings.core.instrumentation.MetricsFeatureProvider;
 import com.android.settings.fuelgauge.anomaly.action.AnomalyAction;
 import com.android.settings.fuelgauge.anomaly.action.ForceStopAction;
 import com.android.settings.fuelgauge.anomaly.action.LocationCheckAction;
@@ -40,6 +44,17 @@
     private Context mContext;
     private static AnomalyUtils sInstance;
 
+    private static final SparseIntArray mMetricArray;
+    static {
+        mMetricArray = new SparseIntArray();
+        mMetricArray.append(Anomaly.AnomalyType.WAKE_LOCK,
+                MetricsProto.MetricsEvent.ANOMALY_TYPE_WAKELOCK);
+        mMetricArray.append(Anomaly.AnomalyType.WAKEUP_ALARM,
+                MetricsProto.MetricsEvent.ANOMALY_TYPE_WAKEUP_ALARM);
+        mMetricArray.append(Anomaly.AnomalyType.BLUETOOTH_SCAN,
+                MetricsProto.MetricsEvent.ANOMALY_TYPE_UNOPTIMIZED_BT);
+    }
+
     @VisibleForTesting
     AnomalyUtils(Context context) {
         mContext = context.getApplicationContext();
@@ -118,4 +133,39 @@
         return anomalies;
     }
 
+    /**
+     * Log the list of {@link Anomaly} using {@link MetricsFeatureProvider}, which contains
+     * anomaly type, package name, field_context, field_action_type
+     *
+     * @param provider  provider to do the logging
+     * @param anomalies contains the data to log
+     * @param contextId which page invoke this logging
+     * @see #logAnomaly(MetricsFeatureProvider, Anomaly, int)
+     */
+    public void logAnomalies(MetricsFeatureProvider provider, List<Anomaly> anomalies,
+            int contextId) {
+        for (int i = 0, size = anomalies.size(); i < size; i++) {
+            logAnomaly(provider, anomalies.get(i), contextId);
+        }
+    }
+
+    /**
+     * Log the {@link Anomaly} using {@link MetricsFeatureProvider}, which contains
+     * anomaly type, package name, field_context, field_action_type
+     *
+     * @param provider  provider to do the logging
+     * @param anomaly   contains the data to log
+     * @param contextId which page invoke this logging
+     * @see #logAnomalies(MetricsFeatureProvider, List, int)
+     */
+    public void logAnomaly(MetricsFeatureProvider provider, Anomaly anomaly, int contextId) {
+        provider.action(
+                mContext,
+                mMetricArray.get(anomaly.type, MetricsProto.MetricsEvent.VIEW_UNKNOWN),
+                anomaly.packageName,
+                Pair.create(MetricsProto.MetricsEvent.FIELD_CONTEXT, contextId),
+                Pair.create(MetricsProto.MetricsEvent.FIELD_ANOMALY_ACTION_TYPE,
+                        getAnomalyAction(anomaly).getActionType()));
+    }
+
 }
diff --git a/src/com/android/settings/fuelgauge/anomaly/action/AnomalyAction.java b/src/com/android/settings/fuelgauge/anomaly/action/AnomalyAction.java
index 87212e7..3ee89d1 100644
--- a/src/com/android/settings/fuelgauge/anomaly/action/AnomalyAction.java
+++ b/src/com/android/settings/fuelgauge/anomaly/action/AnomalyAction.java
@@ -58,5 +58,6 @@
      */
     public abstract boolean isActionActive(Anomaly anomaly);
 
+    @Anomaly.AnomalyActionType
     public abstract int getActionType();
 }
diff --git a/src/com/android/settings/fuelgauge/anomaly/action/StopAndBackgroundCheckAction.java b/src/com/android/settings/fuelgauge/anomaly/action/StopAndBackgroundCheckAction.java
index dba221a..2c799ee 100644
--- a/src/com/android/settings/fuelgauge/anomaly/action/StopAndBackgroundCheckAction.java
+++ b/src/com/android/settings/fuelgauge/anomaly/action/StopAndBackgroundCheckAction.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.support.annotation.VisibleForTesting;
 
+import com.android.internal.logging.nano.MetricsProto;
 import com.android.settings.fuelgauge.anomaly.Anomaly;
 
 /**
@@ -34,6 +35,7 @@
 
     public StopAndBackgroundCheckAction(Context context) {
         this(context, new ForceStopAction(context), new BackgroundCheckAction(context));
+        mActionMetricKey = MetricsProto.MetricsEvent.ACTION_APP_STOP_AND_BACKGROUND_CHECK;
     }
 
     @VisibleForTesting
@@ -46,6 +48,7 @@
 
     @Override
     public void handlePositiveAction(Anomaly anomaly, int metricsKey) {
+        super.handlePositiveAction(anomaly, metricsKey);
         mForceStopAction.handlePositiveAction(anomaly, metricsKey);
         mBackgroundCheckAction.handlePositiveAction(anomaly, metricsKey);
     }
diff --git a/src/com/android/settings/location/LocationSettings.java b/src/com/android/settings/location/LocationSettings.java
index 39d5f6d..7d61f7f 100644
--- a/src/com/android/settings/location/LocationSettings.java
+++ b/src/com/android/settings/location/LocationSettings.java
@@ -43,9 +43,9 @@
 import com.android.settings.dashboard.SummaryLoader;
 import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settings.search.Indexable;
+import com.android.settings.widget.AppPreference;
 import com.android.settings.widget.SwitchBar;
 import com.android.settingslib.RestrictedLockUtils;
-import com.android.settingslib.RestrictedPreference;
 import com.android.settingslib.RestrictedSwitchPreference;
 import com.android.settingslib.location.RecentLocationApps;
 
@@ -211,21 +211,19 @@
 
         List<Preference> recentLocationPrefs = new ArrayList<>(recentLocationRequests.size());
         for (final RecentLocationApps.Request request : recentLocationRequests) {
-            RestrictedPreference pref = new RestrictedPreference(getPrefContext());
+            final AppPreference pref = new AppPreference(getPrefContext());
             pref.setSummary(request.contentDescription);
             pref.setIcon(request.icon);
             pref.setTitle(request.label);
             pref.setOnPreferenceClickListener(
                     new PackageEntryClickedListener(request.packageName, request.userHandle));
             recentLocationPrefs.add(pref);
-
         }
         if (recentLocationRequests.size() > 0) {
             addPreferencesSorted(recentLocationPrefs, mCategoryRecentLocationRequests);
         } else {
             // If there's no item to display, add a "No recent apps" item.
-            Preference banner = new Preference(getPrefContext());
-            banner.setLayoutResource(R.layout.location_list_no_item);
+            Preference banner = new AppPreference(getPrefContext());
             banner.setTitle(R.string.location_no_recent_apps);
             banner.setSelectable(false);
             mCategoryRecentLocationRequests.addPreference(banner);
diff --git a/src/com/android/settings/location/SettingsInjector.java b/src/com/android/settings/location/SettingsInjector.java
index 428745b..c6d1f37 100644
--- a/src/com/android/settings/location/SettingsInjector.java
+++ b/src/com/android/settings/location/SettingsInjector.java
@@ -40,6 +40,8 @@
 import android.util.Log;
 import android.util.Xml;
 
+import com.android.settings.widget.AppPreference;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -268,7 +270,7 @@
         PackageManager pm = mContext.getPackageManager();
         Drawable appIcon = pm.getDrawable(info.packageName, info.iconId, null);
         Drawable icon = pm.getUserBadgedIcon(appIcon, info.mUserHandle);
-        Preference pref = new Preference(prefContext);
+        Preference pref = new AppPreference(prefContext);
         pref.setTitle(info.title);
         pref.setSummary(null);
         pref.setIcon(icon);
diff --git a/src/com/android/settings/network/VpnPreferenceController.java b/src/com/android/settings/network/VpnPreferenceController.java
index 763fb72..95513e0 100644
--- a/src/com/android/settings/network/VpnPreferenceController.java
+++ b/src/com/android/settings/network/VpnPreferenceController.java
@@ -23,8 +23,6 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
-import android.os.Handler;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
@@ -45,6 +43,7 @@
 import com.android.settingslib.core.lifecycle.LifecycleObserver;
 import com.android.settingslib.core.lifecycle.events.OnPause;
 import com.android.settingslib.core.lifecycle.events.OnResume;
+import com.android.settingslib.utils.ThreadUtils;
 
 import java.util.List;
 
@@ -157,7 +156,7 @@
         } else {
             summary = getNameForVpnConfig(vpn, UserHandle.of(uid));
         }
-        new Handler(Looper.getMainLooper()).post(() -> mPreference.setSummary(summary));
+        ThreadUtils.postOnMainThread(() -> mPreference.setSummary(summary));
     }
 
     private String getNameForVpnConfig(VpnConfig cfg, UserHandle user) {
diff --git a/src/com/android/settings/notification/ZenAccessSettings.java b/src/com/android/settings/notification/ZenAccessSettings.java
index 44052b1..0a4915a 100644
--- a/src/com/android/settings/notification/ZenAccessSettings.java
+++ b/src/com/android/settings/notification/ZenAccessSettings.java
@@ -51,6 +51,7 @@
 import com.android.settings.R;
 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
 import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.widget.AppSwitchPreference;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -138,7 +139,7 @@
         for (ApplicationInfo app : apps) {
             final String pkg = app.packageName;
             final CharSequence label = app.loadLabel(mPkgMan);
-            final SwitchPreference pref = new SwitchPreference(getPrefContext());
+            final SwitchPreference pref = new AppSwitchPreference(getPrefContext());
             pref.setPersistent(false);
             pref.setIcon(app.loadIcon(mPkgMan));
             pref.setTitle(label);
diff --git a/src/com/android/settings/search/AccessibilityServiceResultLoader.java b/src/com/android/settings/search/AccessibilityServiceResultLoader.java
index 345ab30..327aef2 100644
--- a/src/com/android/settings/search/AccessibilityServiceResultLoader.java
+++ b/src/com/android/settings/search/AccessibilityServiceResultLoader.java
@@ -30,105 +30,112 @@
 import android.support.annotation.VisibleForTesting;
 import android.support.v4.content.ContextCompat;
 import android.util.IconDrawableFactory;
+import android.util.Log;
 import android.view.accessibility.AccessibilityManager;
 
 import com.android.settings.R;
 import com.android.settings.accessibility.AccessibilitySettings;
 import com.android.settings.dashboard.SiteMapManager;
-import com.android.settings.utils.AsyncLoader;
 
-import java.util.HashSet;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
-import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.FutureTask;
 
-public class AccessibilityServiceResultLoader extends AsyncLoader<Set<? extends SearchResult>> {
+public class AccessibilityServiceResultLoader extends
+        FutureTask<List<? extends SearchResult>> {
 
-    private static final int NAME_NO_MATCH = -1;
-
-    private final Context mContext;
-
-    private List<String> mBreadcrumb;
-    private SiteMapManager mSiteMapManager;
-    @VisibleForTesting
-    final String mQuery;
-    private final AccessibilityManager mAccessibilityManager;
-    private final PackageManager mPackageManager;
-    private final int mUserId;
-
+    private static final String TAG = "A11yResultFutureTask";
 
     public AccessibilityServiceResultLoader(Context context, String query,
-            SiteMapManager mapManager) {
-        super(context);
-        mContext = context;
-        mUserId = UserHandle.myUserId();
-        mSiteMapManager = mapManager;
-        mPackageManager = context.getPackageManager();
-        mAccessibilityManager =
-                (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
-        mQuery = query;
+            SiteMapManager manager) {
+        super(new AccessibilityServiceResultCallable(context, query, manager));
     }
 
-    @Override
-    public Set<? extends SearchResult> loadInBackground() {
-        final Set<SearchResult> results = new HashSet<>();
-        final Context context = getContext();
-        final List<AccessibilityServiceInfo> services = mAccessibilityManager
-                .getInstalledAccessibilityServiceList();
-        final IconDrawableFactory iconFactory = IconDrawableFactory.newInstance(mContext);
-        final String screenTitle = context.getString(R.string.accessibility_settings);
-        for (AccessibilityServiceInfo service : services) {
-            if (service == null) {
-                continue;
-            }
-            final ResolveInfo resolveInfo = service.getResolveInfo();
-            if (service.getResolveInfo() == null) {
-                continue;
-            }
-            final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
-            final CharSequence title = resolveInfo.loadLabel(mPackageManager);
-            final int wordDiff = getWordDifference(title.toString(), mQuery);
-            if (wordDiff == NAME_NO_MATCH) {
-                continue;
-            }
-            final Drawable icon;
-            if (resolveInfo.getIconResource() == 0) {
-                icon = ContextCompat.getDrawable(context, R.mipmap.ic_accessibility_generic);
-            } else {
-                icon = iconFactory.getBadgedIcon(
-                        resolveInfo.serviceInfo,
-                        resolveInfo.serviceInfo.applicationInfo,
-                        mUserId);
-            }
-            final String componentName = new ComponentName(serviceInfo.packageName,
-                    serviceInfo.name).flattenToString();
-            final Intent intent = DatabaseIndexingUtils.buildSearchResultPageIntent(context,
-                    AccessibilitySettings.class.getName(), componentName, screenTitle);
+    static class AccessibilityServiceResultCallable implements
+            Callable<List<? extends SearchResult>> {
 
-            results.add(new SearchResult.Builder()
-                    .setTitle(title)
-                    .addBreadcrumbs(getBreadCrumb())
-                    .setPayload(new ResultPayload(intent))
-                    .setRank(wordDiff)
-                    .setIcon(icon)
-                    .setStableId(Objects.hash(screenTitle, componentName))
-                    .build());
+        private static final int NAME_NO_MATCH = -1;
+
+        private final Context mContext;
+        private List<String> mBreadcrumb;
+        private SiteMapManager mSiteMapManager;
+        @VisibleForTesting
+        final String mQuery;
+        private final AccessibilityManager mAccessibilityManager;
+        private final PackageManager mPackageManager;
+        private final int mUserId;
+
+        public AccessibilityServiceResultCallable(Context context, String query,
+                SiteMapManager mapManager) {
+            mUserId = UserHandle.myUserId();
+            mContext = context;
+            mSiteMapManager = mapManager;
+            mPackageManager = context.getPackageManager();
+            mAccessibilityManager =
+                    (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
+            mQuery = query;
         }
-        return results;
-    }
 
-    private List<String> getBreadCrumb() {
-        if (mBreadcrumb == null || mBreadcrumb.isEmpty()) {
-            final Context context = getContext();
-            mBreadcrumb = mSiteMapManager.buildBreadCrumb(
-                    context, AccessibilitySettings.class.getName(),
-                    context.getString(R.string.accessibility_settings));
+        @Override
+        public List<? extends SearchResult> call() throws Exception {
+            long startTime = System.currentTimeMillis();
+            final List<SearchResult> results = new ArrayList<>();
+            final List<AccessibilityServiceInfo> services = mAccessibilityManager
+                    .getInstalledAccessibilityServiceList();
+            final IconDrawableFactory iconFactory = IconDrawableFactory.newInstance(mContext);
+            final String screenTitle = mContext.getString(R.string.accessibility_settings);
+            for (AccessibilityServiceInfo service : services) {
+                if (service == null) {
+                    continue;
+                }
+                final ResolveInfo resolveInfo = service.getResolveInfo();
+                if (service.getResolveInfo() == null) {
+                    continue;
+                }
+                final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+                final CharSequence title = resolveInfo.loadLabel(mPackageManager);
+                final int wordDiff = getWordDifference(title.toString(), mQuery);
+                if (wordDiff == NAME_NO_MATCH) {
+                    continue;
+                }
+                final Drawable icon;
+                if (resolveInfo.getIconResource() == 0) {
+                    icon = ContextCompat.getDrawable(mContext, R.mipmap.ic_accessibility_generic);
+                } else {
+                    icon = iconFactory.getBadgedIcon(
+                            resolveInfo.serviceInfo,
+                            resolveInfo.serviceInfo.applicationInfo,
+                            mUserId);
+                }
+                final String componentName = new ComponentName(serviceInfo.packageName,
+                        serviceInfo.name).flattenToString();
+                final Intent intent = DatabaseIndexingUtils.buildSearchResultPageIntent(mContext,
+                        AccessibilitySettings.class.getName(), componentName, screenTitle);
+
+                results.add(new SearchResult.Builder()
+                        .setTitle(title)
+                        .addBreadcrumbs(getBreadCrumb())
+                        .setPayload(new ResultPayload(intent))
+                        .setRank(wordDiff)
+                        .setIcon(icon)
+                        .setStableId(Objects.hash(screenTitle, componentName))
+                        .build());
+            }
+            Collections.sort(results);
+            Log.i(TAG, "A11y search loading took:" + (System.currentTimeMillis() - startTime));
+            return results;
         }
-        return mBreadcrumb;
-    }
 
-    @Override
-    protected void onDiscardResult(Set<? extends SearchResult> result) {
-
+        private List<String> getBreadCrumb() {
+            if (mBreadcrumb == null || mBreadcrumb.isEmpty()) {
+                mBreadcrumb = mSiteMapManager.buildBreadCrumb(
+                        mContext, AccessibilitySettings.class.getName(),
+                        mContext.getString(R.string.accessibility_settings));
+            }
+            return mBreadcrumb;
+        }
     }
 }
diff --git a/src/com/android/settings/search/CursorToSearchResultConverter.java b/src/com/android/settings/search/CursorToSearchResultConverter.java
index ce64de9..8528c56 100644
--- a/src/com/android/settings/search/CursorToSearchResultConverter.java
+++ b/src/com/android/settings/search/CursorToSearchResultConverter.java
@@ -36,16 +36,6 @@
 import java.util.Set;
 
 import static com.android.settings.search.DatabaseResultLoader.BASE_RANKS;
-import static com.android.settings.search.DatabaseResultLoader.COLUMN_INDEX_CLASS_NAME;
-import static com.android.settings.search.DatabaseResultLoader.COLUMN_INDEX_ICON;
-import static com.android.settings.search.DatabaseResultLoader.COLUMN_INDEX_ID;
-import static com.android.settings.search.DatabaseResultLoader.COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE;
-import static com.android.settings.search.DatabaseResultLoader.COLUMN_INDEX_KEY;
-import static com.android.settings.search.DatabaseResultLoader.COLUMN_INDEX_PAYLOAD;
-import static com.android.settings.search.DatabaseResultLoader.COLUMN_INDEX_PAYLOAD_TYPE;
-import static com.android.settings.search.DatabaseResultLoader.COLUMN_INDEX_SCREEN_TITLE;
-import static com.android.settings.search.DatabaseResultLoader.COLUMN_INDEX_SUMMARY_ON;
-import static com.android.settings.search.DatabaseResultLoader.COLUMN_INDEX_TITLE;
 import static com.android.settings.search.SearchResult.TOP_RANK;
 
 /**
@@ -62,6 +52,25 @@
 
     private static final String TAG = "CursorConverter";
 
+    /**
+     * 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
+     * IndexDatabaseHelper
+     */
+    public static final int COLUMN_INDEX_ID = 0;
+    public static final int COLUMN_INDEX_TITLE = 1;
+    public static final int COLUMN_INDEX_SUMMARY_ON = 2;
+    public static final int COLUMN_INDEX_SUMMARY_OFF = 3;
+    public static final int COLUMN_INDEX_CLASS_NAME = 4;
+    public static final int COLUMN_INDEX_SCREEN_TITLE = 5;
+    public static final int COLUMN_INDEX_ICON = 6;
+    public static final int COLUMN_INDEX_INTENT_ACTION = 7;
+    public static final int COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE = 8;
+    public static final int COLUMN_INDEX_INTENT_ACTION_TARGET_CLASS = 9;
+    public static final int COLUMN_INDEX_KEY = 10;
+    public static final int COLUMN_INDEX_PAYLOAD_TYPE = 11;
+    public static final int COLUMN_INDEX_PAYLOAD = 12;
+
     private final Context mContext;
 
     private final int LONG_TITLE_LENGTH = 20;
diff --git a/src/com/android/settings/search/DatabaseIndexingManager.java b/src/com/android/settings/search/DatabaseIndexingManager.java
index e94befb..970b50f 100644
--- a/src/com/android/settings/search/DatabaseIndexingManager.java
+++ b/src/com/android/settings/search/DatabaseIndexingManager.java
@@ -17,11 +17,13 @@
 
 package com.android.settings.search;
 
-import static com.android.settings.search.DatabaseResultLoader.COLUMN_INDEX_ID;
-import static com.android.settings.search.DatabaseResultLoader
+
+import static com.android.settings.search.CursorToSearchResultConverter.COLUMN_INDEX_ID;
+import static com.android.settings.search.CursorToSearchResultConverter
         .COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE;
-import static com.android.settings.search.DatabaseResultLoader.COLUMN_INDEX_KEY;
+import static com.android.settings.search.CursorToSearchResultConverter.COLUMN_INDEX_KEY;
 import static com.android.settings.search.DatabaseResultLoader.SELECT_COLUMNS;
+import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DOCID;
 import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.CLASS_NAME;
 import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DATA_ENTRIES;
 import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DATA_KEYWORDS;
@@ -31,7 +33,6 @@
         .DATA_SUMMARY_ON_NORMALIZED;
 import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DATA_TITLE;
 import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DATA_TITLE_NORMALIZED;
-import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DOCID;
 import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.ENABLED;
 import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.ICON;
 import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.INTENT_ACTION;
diff --git a/src/com/android/settings/search/DatabaseResultLoader.java b/src/com/android/settings/search/DatabaseResultLoader.java
index c1663ab..66548a4 100644
--- a/src/com/android/settings/search/DatabaseResultLoader.java
+++ b/src/com/android/settings/search/DatabaseResultLoader.java
@@ -24,35 +24,31 @@
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.support.annotation.VisibleForTesting;
+import android.util.Log;
+import android.util.Pair;
 
 import com.android.settings.dashboard.SiteMapManager;
-import com.android.settings.utils.AsyncLoader;
+import com.android.settings.overlay.FeatureFactory;
 
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 /**
- * AsyncTask to retrieve Settings, First party app and any intent based results.
+ * AsyncTask to retrieve Settings, first party app and any intent based results.
  */
-public class DatabaseResultLoader extends AsyncLoader<Set<? extends SearchResult>> {
-    private static final String LOG = "DatabaseResultLoader";
+public class DatabaseResultLoader extends FutureTask<List<? extends SearchResult>> {
 
-    /* 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
-     IndexDatabaseHelper */
-    public static final int COLUMN_INDEX_ID = 0;
-    public static final int COLUMN_INDEX_TITLE = 1;
-    public static final int COLUMN_INDEX_SUMMARY_ON = 2;
-    public static final int COLUMN_INDEX_SUMMARY_OFF = 3;
-    public static final int COLUMN_INDEX_CLASS_NAME = 4;
-    public static final int COLUMN_INDEX_SCREEN_TITLE = 5;
-    public static final int COLUMN_INDEX_ICON = 6;
-    public static final int COLUMN_INDEX_INTENT_ACTION = 7;
-    public static final int COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE = 8;
-    public static final int COLUMN_INDEX_INTENT_ACTION_TARGET_CLASS = 9;
-    public static final int COLUMN_INDEX_KEY = 10;
-    public static final int COLUMN_INDEX_PAYLOAD_TYPE = 11;
-    public static final int COLUMN_INDEX_PAYLOAD = 12;
+    private static final String TAG = "DatabaseResultLoader";
 
     public static final String[] SELECT_COLUMNS = {
             IndexColumns.DOCID,
@@ -82,194 +78,267 @@
             IndexColumns.DATA_SUMMARY_OFF_NORMALIZED,
     };
 
-    public static final String[] MATCH_COLUMNS_TERTIARY = {
-            IndexColumns.DATA_KEYWORDS,
-            IndexColumns.DATA_ENTRIES
-    };
-
     /**
      * Base ranks defines the best possible rank based on what the query matches.
-     * If the query matches the prefix of the first word in the title, the best rank it can be is 1
-     * If the query matches the prefix of the other words in the title, the best rank it can be is 3
+     * If the query matches the prefix of the first word in the title, the best rank it can be
+     * is 1
+     * If the query matches the prefix of the other words in the title, the best rank it can be
+     * is 3
      * If the query only matches the summary, the best rank it can be is 7
      * If the query only matches keywords or entries, the best rank it can be is 9
      */
     public static final int[] BASE_RANKS = {1, 3, 7, 9};
 
-    @VisibleForTesting
-    final String mQueryText;
-    private final Context mContext;
-    private final CursorToSearchResultConverter mConverter;
-    private final SiteMapManager mSiteMapManager;
-
-    public DatabaseResultLoader(Context context, String queryText, SiteMapManager mapManager) {
-        super(context);
-        mSiteMapManager = mapManager;
-        mContext = context;
-        mQueryText = queryText;
-        mConverter = new CursorToSearchResultConverter(context);
+    public DatabaseResultLoader(Context context, String query, SiteMapManager manager) {
+        super(new StaticSearchResultCallable(context, query, manager));
     }
 
-    @Override
-    protected void onDiscardResult(Set<? extends SearchResult> result) {
-        // TODO Search
-    }
+    static class StaticSearchResultCallable implements
+            Callable<List<? extends SearchResult>> {
 
-    @Override
-    public Set<? extends SearchResult> loadInBackground() {
-        if (mQueryText == null || mQueryText.isEmpty()) {
-            return null;
+        public final String[] MATCH_COLUMNS_TERTIARY = {
+                IndexColumns.DATA_KEYWORDS,
+                IndexColumns.DATA_ENTRIES
+        };
+
+        @VisibleForTesting
+        final String mQueryText;
+        private final Context mContext;
+        private final CursorToSearchResultConverter mConverter;
+        private final SiteMapManager mSiteMapManager;
+        private final SearchFeatureProvider mFeatureProvider;
+
+        public StaticSearchResultCallable(Context context, String queryText,
+                SiteMapManager mapManager) {
+            mContext = context;
+            mSiteMapManager = mapManager;
+            mQueryText = queryText;
+            mConverter = new CursorToSearchResultConverter(context);
+            mFeatureProvider = FeatureFactory.getFactory(context).getSearchFeatureProvider();
         }
 
-        final Set<SearchResult> results = new HashSet<>();
+        @Override
+        public List<? extends SearchResult> call() {
+            if (mQueryText == null || mQueryText.isEmpty()) {
+                return new ArrayList<>();
+            }
 
-        results.addAll(firstWordQuery(MATCH_COLUMNS_PRIMARY, BASE_RANKS[0]));
-        results.addAll(secondaryWordQuery(MATCH_COLUMNS_PRIMARY, BASE_RANKS[1]));
-        results.addAll(anyWordQuery(MATCH_COLUMNS_SECONDARY, BASE_RANKS[2]));
-        results.addAll(anyWordQuery(MATCH_COLUMNS_TERTIARY, BASE_RANKS[3]));
-        return results;
-    }
+            // TODO (b/68656233) Consolidate timing metrics
+            long startTime = System.currentTimeMillis();
+            // Start a Future to get search result scores.
+            FutureTask<List<Pair<String, Float>>> rankerTask = mFeatureProvider.getRankerTask(
+                    mContext, mQueryText);
 
-    @Override
-    protected boolean onCancelLoad() {
-        // TODO
-        return super.onCancelLoad();
-    }
+            if (rankerTask != null) {
+                ExecutorService executorService = mFeatureProvider.getExecutorService();
+                executorService.execute(rankerTask);
+            }
 
-    /**
-     * Creates and executes the query which matches prefixes of the first word of the given columns.
-     *
-     * @param matchColumns The columns to match on
-     * @param baseRank     The highest rank achievable by these results
-     * @return A set of the matching results.
-     */
-    private Set<SearchResult> firstWordQuery(String[] matchColumns, int baseRank) {
-        final String whereClause = buildSingleWordWhereClause(matchColumns);
-        final String query = mQueryText + "%";
-        final String[] selection = buildSingleWordSelection(query, matchColumns.length);
+            final Set<SearchResult> resultSet = new HashSet<>();
 
-        return query(whereClause, selection, baseRank);
-    }
+            resultSet.addAll(firstWordQuery(MATCH_COLUMNS_PRIMARY, BASE_RANKS[0]));
+            resultSet.addAll(secondaryWordQuery(MATCH_COLUMNS_PRIMARY, BASE_RANKS[1]));
+            resultSet.addAll(anyWordQuery(MATCH_COLUMNS_SECONDARY, BASE_RANKS[2]));
+            resultSet.addAll(anyWordQuery(MATCH_COLUMNS_TERTIARY, BASE_RANKS[3]));
 
-    /**
-     * Creates and executes the query which matches prefixes of the non-first words of the
-     * given columns.
-     *
-     * @param matchColumns The columns to match on
-     * @param baseRank     The highest rank achievable by these results
-     * @return A set of the matching results.
-     */
-    private Set<SearchResult> secondaryWordQuery(String[] matchColumns, int baseRank) {
-        final String whereClause = buildSingleWordWhereClause(matchColumns);
-        final String query = "% " + mQueryText + "%";
-        final String[] selection = buildSingleWordSelection(query, matchColumns.length);
+            // Try to retrieve the scores in time. Otherwise use static ranking.
+            if (rankerTask != null) {
+                try {
+                    final long timeoutMs = mFeatureProvider.smartSearchRankingTimeoutMs(mContext);
+                    List<Pair<String, Float>> searchRankScores = rankerTask.get(timeoutMs,
+                            TimeUnit.MILLISECONDS);
+                    return getDynamicRankedResults(resultSet, searchRankScores);
+                } catch (TimeoutException | InterruptedException | ExecutionException e) {
+                    Log.d(TAG, "Error waiting for result scores: " + e);
+                }
+            }
 
-        return query(whereClause, selection, baseRank);
-    }
-
-    /**
-     * Creates and executes the query which matches prefixes of the any word of the given columns.
-     *
-     * @param matchColumns The columns to match on
-     * @param baseRank     The highest rank achievable by these results
-     * @return A set of the matching results.
-     */
-    private Set<SearchResult> anyWordQuery(String[] matchColumns, int baseRank) {
-        final String whereClause = buildTwoWordWhereClause(matchColumns);
-        final String[] selection = buildAnyWordSelection(matchColumns.length * 2);
-
-        return query(whereClause, selection, baseRank);
-    }
-
-    /**
-     * Generic method used by all of the query methods above to execute a query.
-     *
-     * @param whereClause Where clause for the SQL query which uses bindings.
-     * @param selection   List of the transformed query to match each bind in the whereClause
-     * @param baseRank    The highest rank achievable by these results.
-     * @return A set of the matching results.
-     */
-    private Set<SearchResult> query(String whereClause, String[] selection, int baseRank) {
-        final SQLiteDatabase database =
-                IndexDatabaseHelper.getInstance(mContext).getReadableDatabase();
-        try (Cursor resultCursor = database.query(TABLE_PREFS_INDEX, SELECT_COLUMNS, whereClause,
-                selection, null, null, null)) {
-            return mConverter.convertCursor(mSiteMapManager, resultCursor, baseRank);
+            List<SearchResult> resultList = new ArrayList<>(resultSet);
+            Collections.sort(resultList);
+            Log.i(TAG, "Static search loading took:" + (System.currentTimeMillis() - startTime));
+            return resultList;
         }
-    }
 
-    /**
-     * Builds the SQLite WHERE clause that matches all matchColumns for a single query.
-     *
-     * @param matchColumns List of columns that will be used for matching.
-     * @return The constructed WHERE clause.
-     */
-    private static String buildSingleWordWhereClause(String[] matchColumns) {
-        StringBuilder sb = new StringBuilder(" (");
-        final int count = matchColumns.length;
-        for (int n = 0; n < count; n++) {
-            sb.append(matchColumns[n]);
-            sb.append(" like ? ");
-            if (n < count - 1) {
-                sb.append(" OR ");
+        // TODO (b/33577327) Retrieve all search results with a single query.
+
+        /**
+         * Creates and executes the query which matches prefixes of the first word of the given
+         * columns.
+         *
+         * @param matchColumns The columns to match on
+         * @param baseRank     The highest rank achievable by these results
+         * @return A set of the matching results.
+         */
+        private Set<SearchResult> firstWordQuery(String[] matchColumns, int baseRank) {
+            final String whereClause = buildSingleWordWhereClause(matchColumns);
+            final String query = mQueryText + "%";
+            final String[] selection = buildSingleWordSelection(query, matchColumns.length);
+
+            return query(whereClause, selection, baseRank);
+        }
+
+        /**
+         * Creates and executes the query which matches prefixes of the non-first words of the
+         * given columns.
+         *
+         * @param matchColumns The columns to match on
+         * @param baseRank     The highest rank achievable by these results
+         * @return A set of the matching results.
+         */
+        private Set<SearchResult> secondaryWordQuery(String[] matchColumns, int baseRank) {
+            final String whereClause = buildSingleWordWhereClause(matchColumns);
+            final String query = "% " + mQueryText + "%";
+            final String[] selection = buildSingleWordSelection(query, matchColumns.length);
+
+            return query(whereClause, selection, baseRank);
+        }
+
+        /**
+         * Creates and executes the query which matches prefixes of the any word of the given
+         * columns.
+         *
+         * @param matchColumns The columns to match on
+         * @param baseRank     The highest rank achievable by these results
+         * @return A set of the matching results.
+         */
+        private Set<SearchResult> anyWordQuery(String[] matchColumns, int baseRank) {
+            final String whereClause = buildTwoWordWhereClause(matchColumns);
+            final String[] selection = buildAnyWordSelection(matchColumns.length * 2);
+
+            return query(whereClause, selection, baseRank);
+        }
+
+        /**
+         * Generic method used by all of the query methods above to execute a query.
+         *
+         * @param whereClause Where clause for the SQL query which uses bindings.
+         * @param selection   List of the transformed query to match each bind in the whereClause
+         * @param baseRank    The highest rank achievable by these results.
+         * @return A set of the matching results.
+         */
+        private Set<SearchResult> query(String whereClause, String[] selection, int baseRank) {
+            final SQLiteDatabase database =
+                    IndexDatabaseHelper.getInstance(mContext).getReadableDatabase();
+            try (Cursor resultCursor = database.query(TABLE_PREFS_INDEX, SELECT_COLUMNS,
+                    whereClause,
+                    selection, null, null, null)) {
+                return mConverter.convertCursor(mSiteMapManager, resultCursor, baseRank);
             }
         }
-        sb.append(") AND enabled = 1");
-        return sb.toString();
-    }
 
-    /**
-     * Builds the SQLite WHERE clause that matches all matchColumns to two different queries.
-     *
-     * @param matchColumns List of columns that will be used for matching.
-     * @return The constructed WHERE clause.
-     */
-    private static String buildTwoWordWhereClause(String[] matchColumns) {
-        StringBuilder sb = new StringBuilder(" (");
-        final int count = matchColumns.length;
-        for (int n = 0; n < count; n++) {
-            sb.append(matchColumns[n]);
-            sb.append(" like ? OR ");
-            sb.append(matchColumns[n]);
-            sb.append(" like ?");
-            if (n < count - 1) {
-                sb.append(" OR ");
+        /**
+         * Builds the SQLite WHERE clause that matches all matchColumns for a single query.
+         *
+         * @param matchColumns List of columns that will be used for matching.
+         * @return The constructed WHERE clause.
+         */
+        private static String buildSingleWordWhereClause(String[] matchColumns) {
+            StringBuilder sb = new StringBuilder(" (");
+            final int count = matchColumns.length;
+            for (int n = 0; n < count; n++) {
+                sb.append(matchColumns[n]);
+                sb.append(" like ? ");
+                if (n < count - 1) {
+                    sb.append(" OR ");
+                }
             }
+            sb.append(") AND enabled = 1");
+            return sb.toString();
         }
-        sb.append(") AND enabled = 1");
-        return sb.toString();
-    }
 
-    /**
-     * Fills out the selection array to match the query as the prefix of a single word.
-     *
-     * @param size is the number of columns to be matched.
-     */
-    private String[] buildSingleWordSelection(String query, int size) {
-        String[] selection = new String[size];
-
-        for (int i = 0; i < size; i++) {
-            selection[i] = query;
+        /**
+         * Builds the SQLite WHERE clause that matches all matchColumns to two different queries.
+         *
+         * @param matchColumns List of columns that will be used for matching.
+         * @return The constructed WHERE clause.
+         */
+        private static String buildTwoWordWhereClause(String[] matchColumns) {
+            StringBuilder sb = new StringBuilder(" (");
+            final int count = matchColumns.length;
+            for (int n = 0; n < count; n++) {
+                sb.append(matchColumns[n]);
+                sb.append(" like ? OR ");
+                sb.append(matchColumns[n]);
+                sb.append(" like ?");
+                if (n < count - 1) {
+                    sb.append(" OR ");
+                }
+            }
+            sb.append(") AND enabled = 1");
+            return sb.toString();
         }
-        return selection;
-    }
 
-    /**
-     * Fills out the selection array to match the query as the prefix of a word.
-     *
-     * @param size is twice the number of columns to be matched. The first match is for the prefix
-     *             of the first word in the column. The second match is for any subsequent word
-     *             prefix match.
-     */
-    private String[] buildAnyWordSelection(int size) {
-        String[] selection = new String[size];
-        final String query = mQueryText + "%";
-        final String subStringQuery = "% " + mQueryText + "%";
+        /**
+         * Fills out the selection array to match the query as the prefix of a single word.
+         *
+         * @param size is the number of columns to be matched.
+         */
+        private String[] buildSingleWordSelection(String query, int size) {
+            String[] selection = new String[size];
 
-        for (int i = 0; i < (size - 1); i += 2) {
-            selection[i] = query;
-            selection[i + 1] = subStringQuery;
+            for (int i = 0; i < size; i++) {
+                selection[i] = query;
+            }
+            return selection;
         }
-        return selection;
+
+        /**
+         * Fills out the selection array to match the query as the prefix of a word.
+         *
+         * @param size is twice the number of columns to be matched. The first match is for the
+         *             prefix
+         *             of the first word in the column. The second match is for any subsequent word
+         *             prefix match.
+         */
+        private String[] buildAnyWordSelection(int size) {
+            String[] selection = new String[size];
+            final String query = mQueryText + "%";
+            final String subStringQuery = "% " + mQueryText + "%";
+
+            for (int i = 0; i < (size - 1); i += 2) {
+                selection[i] = query;
+                selection[i + 1] = subStringQuery;
+            }
+            return selection;
+        }
+
+        private List<SearchResult> getDynamicRankedResults(Set<SearchResult> unsortedSet,
+                List<Pair<String, Float>> searchRankScores) {
+            TreeSet<SearchResult> dbResultsSortedByScores = new TreeSet<>(
+                    (o1, o2) -> {
+                        float score1 = getRankingScoreByStableId(searchRankScores, o1.stableId);
+                        float score2 = getRankingScoreByStableId(searchRankScores, o2.stableId);
+                        if (score1 > score2) {
+                            return -1;
+                        } else if (score1 == score2) {
+                            return 0;
+                        } else {
+                            return 1;
+                        }
+                    });
+            dbResultsSortedByScores.addAll(unsortedSet);
+
+            return new ArrayList<>(dbResultsSortedByScores);
+        }
+
+        /**
+         * Looks up ranking score for stableId
+         *
+         * @param stableId String of stableId
+         * @return the ranking score corresponding to the given stableId. If there is no score
+         * available for this stableId, -Float.MAX_VALUE is returned.
+         */
+        @VisibleForTesting
+        Float getRankingScoreByStableId(List<Pair<String, Float>> searchRankScores, int stableId) {
+            for (Pair<String, Float> rankingScore : searchRankScores) {
+                if (Integer.toString(stableId).compareTo(rankingScore.first) == 0) {
+                    return rankingScore.second;
+                }
+            }
+            // If stableId not found in the list, we assign the minimum score so it will appear at
+            // the end of the list.
+            Log.w(TAG, "stableId " + stableId + " was not in the ranking scores.");
+            return -Float.MAX_VALUE;
+        }
     }
 }
\ No newline at end of file
diff --git a/src/com/android/settings/search/InputDeviceResultLoader.java b/src/com/android/settings/search/InputDeviceResultLoader.java
index e5e6553..598281c 100644
--- a/src/com/android/settings/search/InputDeviceResultLoader.java
+++ b/src/com/android/settings/search/InputDeviceResultLoader.java
@@ -26,6 +26,7 @@
 import android.hardware.input.InputManager;
 import android.hardware.input.KeyboardLayout;
 import android.support.annotation.VisibleForTesting;
+import android.util.Log;
 import android.view.InputDevice;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodManager;
@@ -35,20 +36,24 @@
 import com.android.settings.dashboard.SiteMapManager;
 import com.android.settings.inputmethod.AvailableVirtualKeyboardFragment;
 import com.android.settings.inputmethod.PhysicalKeyboardFragment;
-import com.android.settings.utils.AsyncLoader;
 import com.android.settingslib.inputmethod.InputMethodAndSubtypeUtil;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.FutureTask;
 
 /**
  * Search result for input devices (physical/virtual keyboard, game controllers, etc)
  */
-public class InputDeviceResultLoader extends AsyncLoader<Set<? extends SearchResult>> {
-    private static final int NAME_NO_MATCH = -1;
+
+public class InputDeviceResultLoader extends FutureTask<List<? extends SearchResult>> {
+
+    private static final String TAG = "InputResultFutureTask";
 
     @VisibleForTesting
     static final String PHYSICAL_KEYBOARD_FRAGMENT = PhysicalKeyboardFragment.class.getName();
@@ -56,145 +61,151 @@
     static final String VIRTUAL_KEYBOARD_FRAGMENT =
             AvailableVirtualKeyboardFragment.class.getName();
 
-    private final SiteMapManager mSiteMapManager;
-    private final InputManager mInputManager;
-    private final InputMethodManager mImm;
-    private final PackageManager mPackageManager;
-    @VisibleForTesting
-    final String mQuery;
-
-    private List<String> mPhysicalKeyboardBreadcrumb;
-    private List<String> mVirtualKeyboardBreadcrumb;
-
-    public InputDeviceResultLoader(Context context, String query, SiteMapManager mapManager) {
-        super(context);
-        mQuery = query;
-        mSiteMapManager = mapManager;
-        mInputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
-        mImm = (InputMethodManager) context.getSystemService(INPUT_METHOD_SERVICE);
-        mPackageManager = context.getPackageManager();
+    public InputDeviceResultLoader(Context context, String query, SiteMapManager manager) {
+        super(new InputDeviceResultCallable(context, query, manager));
     }
 
-    @Override
-    protected void onDiscardResult(Set<? extends SearchResult> result) {
-    }
+    static class InputDeviceResultCallable implements
+            Callable<List<? extends SearchResult>> {
+        private static final int NAME_NO_MATCH = -1;
 
-    @Override
-    public Set<? extends SearchResult> loadInBackground() {
-        final Set<SearchResult> results = new HashSet<>();
-        results.addAll(buildPhysicalKeyboardSearchResults());
-        results.addAll(buildVirtualKeyboardSearchResults());
-        return results;
-    }
+        private final Context mContext;
+        private final SiteMapManager mSiteMapManager;
+        private final InputManager mInputManager;
+        private final InputMethodManager mImm;
+        private final PackageManager mPackageManager;
+        @VisibleForTesting
+        final String mQuery;
 
-    private Set<SearchResult> buildPhysicalKeyboardSearchResults() {
-        final Set<SearchResult> results = new HashSet<>();
-        final Context context = getContext();
-        final String screenTitle = context.getString(R.string.physical_keyboard_title);
+        private List<String> mPhysicalKeyboardBreadcrumb;
+        private List<String> mVirtualKeyboardBreadcrumb;
 
-        for (final InputDevice device : getPhysicalFullKeyboards()) {
-            final String deviceName = device.getName();
-            final int wordDiff = InstalledAppResultLoader.getWordDifference(deviceName, mQuery);
-            if (wordDiff == NAME_NO_MATCH) {
-                continue;
+        public InputDeviceResultCallable(Context context, String query, SiteMapManager mapManager) {
+            mContext = context;
+            mQuery = query;
+            mSiteMapManager = mapManager;
+            mInputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
+            mImm = (InputMethodManager) context.getSystemService(INPUT_METHOD_SERVICE);
+            mPackageManager = context.getPackageManager();
+        }
+
+        @Override
+        public List<? extends SearchResult> call() {
+            long startTime = System.currentTimeMillis();
+            final List<SearchResult> results = new ArrayList<>();
+            results.addAll(buildPhysicalKeyboardSearchResults());
+            results.addAll(buildVirtualKeyboardSearchResults());
+            Collections.sort(results);
+            Log.i(TAG, "Input search loading took:" + (System.currentTimeMillis() - startTime));
+            return results;
+        }
+
+        private Set<SearchResult> buildPhysicalKeyboardSearchResults() {
+            final Set<SearchResult> results = new HashSet<>();
+            final String screenTitle = mContext.getString(R.string.physical_keyboard_title);
+
+            for (final InputDevice device : getPhysicalFullKeyboards()) {
+                final String deviceName = device.getName();
+                final int wordDiff = InstalledAppResultLoader.getWordDifference(deviceName,
+                        mQuery);
+                if (wordDiff == NAME_NO_MATCH) {
+                    continue;
+                }
+                final String keyboardLayoutDescriptor = mInputManager
+                        .getCurrentKeyboardLayoutForInputDevice(device.getIdentifier());
+                final KeyboardLayout keyboardLayout = (keyboardLayoutDescriptor != null)
+                        ? mInputManager.getKeyboardLayout(keyboardLayoutDescriptor) : null;
+                final String summary = (keyboardLayout != null)
+                        ? keyboardLayout.toString()
+                        : mContext.getString(R.string.keyboard_layout_default_label);
+
+                final Intent intent = DatabaseIndexingUtils.buildSearchResultPageIntent(mContext,
+                        PHYSICAL_KEYBOARD_FRAGMENT, deviceName, screenTitle);
+                results.add(new SearchResult.Builder()
+                        .setTitle(deviceName)
+                        .setPayload(new ResultPayload(intent))
+                        .setStableId(Objects.hash(PHYSICAL_KEYBOARD_FRAGMENT, deviceName))
+                        .setSummary(summary)
+                        .setRank(wordDiff)
+                        .addBreadcrumbs(getPhysicalKeyboardBreadCrumb())
+                        .build());
             }
-            final String keyboardLayoutDescriptor = mInputManager
-                    .getCurrentKeyboardLayoutForInputDevice(device.getIdentifier());
-            final KeyboardLayout keyboardLayout = (keyboardLayoutDescriptor != null)
-                    ? mInputManager.getKeyboardLayout(keyboardLayoutDescriptor) : null;
-            final String summary = (keyboardLayout != null)
-                    ? keyboardLayout.toString()
-                    : context.getString(R.string.keyboard_layout_default_label);
-            final String key = deviceName;
-
-            final Intent intent = DatabaseIndexingUtils.buildSearchResultPageIntent(context,
-                    PHYSICAL_KEYBOARD_FRAGMENT, key, screenTitle);
-            results.add(new SearchResult.Builder()
-                    .setTitle(deviceName)
-                    .setPayload(new ResultPayload(intent))
-                    .setStableId(Objects.hash(PHYSICAL_KEYBOARD_FRAGMENT, key))
-                    .setSummary(summary)
-                    .setRank(wordDiff)
-                    .addBreadcrumbs(getPhysicalKeyboardBreadCrumb())
-                    .build());
+            return results;
         }
-        return results;
-    }
 
-    private Set<SearchResult> buildVirtualKeyboardSearchResults() {
-        final Set<SearchResult> results = new HashSet<>();
-        final Context context = getContext();
-        final String screenTitle = context.getString(R.string.add_virtual_keyboard);
-        final List<InputMethodInfo> inputMethods = mImm.getInputMethodList();
-        for (InputMethodInfo info : inputMethods) {
-            final String title = info.loadLabel(mPackageManager).toString();
-            final String summary = InputMethodAndSubtypeUtil
-                    .getSubtypeLocaleNameListAsSentence(getAllSubtypesOf(info), context, info);
-            int wordDiff = InstalledAppResultLoader.getWordDifference(title, mQuery);
-            if (wordDiff == NAME_NO_MATCH) {
-                wordDiff = InstalledAppResultLoader.getWordDifference(summary, mQuery);
+        private Set<SearchResult> buildVirtualKeyboardSearchResults() {
+            final Set<SearchResult> results = new HashSet<>();
+            final String screenTitle = mContext.getString(R.string.add_virtual_keyboard);
+            final List<InputMethodInfo> inputMethods = mImm.getInputMethodList();
+            for (InputMethodInfo info : inputMethods) {
+                final String title = info.loadLabel(mPackageManager).toString();
+                final String summary = InputMethodAndSubtypeUtil
+                        .getSubtypeLocaleNameListAsSentence(getAllSubtypesOf(info), mContext, info);
+                int wordDiff = InstalledAppResultLoader.getWordDifference(title, mQuery);
+                if (wordDiff == NAME_NO_MATCH) {
+                    wordDiff = InstalledAppResultLoader.getWordDifference(summary, mQuery);
+                }
+                if (wordDiff == NAME_NO_MATCH) {
+                    continue;
+                }
+                final ServiceInfo serviceInfo = info.getServiceInfo();
+                final String key = new ComponentName(serviceInfo.packageName, serviceInfo.name)
+                        .flattenToString();
+                final Intent intent = DatabaseIndexingUtils.buildSearchResultPageIntent(mContext,
+                        VIRTUAL_KEYBOARD_FRAGMENT, key, screenTitle);
+                results.add(new SearchResult.Builder()
+                        .setTitle(title)
+                        .setSummary(summary)
+                        .setRank(wordDiff)
+                        .setStableId(Objects.hash(VIRTUAL_KEYBOARD_FRAGMENT, key))
+                        .addBreadcrumbs(getVirtualKeyboardBreadCrumb())
+                        .setPayload(new ResultPayload(intent))
+                        .build());
             }
-            if (wordDiff == NAME_NO_MATCH) {
-                continue;
+            return results;
+        }
+
+        private List<String> getPhysicalKeyboardBreadCrumb() {
+            if (mPhysicalKeyboardBreadcrumb == null || mPhysicalKeyboardBreadcrumb.isEmpty()) {
+                mPhysicalKeyboardBreadcrumb = mSiteMapManager.buildBreadCrumb(
+                        mContext, PHYSICAL_KEYBOARD_FRAGMENT,
+                        mContext.getString(R.string.physical_keyboard_title));
             }
-            final ServiceInfo serviceInfo = info.getServiceInfo();
-            final String key = new ComponentName(serviceInfo.packageName, serviceInfo.name)
-                    .flattenToString();
-            final Intent intent = DatabaseIndexingUtils.buildSearchResultPageIntent(context,
-                    VIRTUAL_KEYBOARD_FRAGMENT, key, screenTitle);
-            results.add(new SearchResult.Builder()
-                    .setTitle(title)
-                    .setSummary(summary)
-                    .setRank(wordDiff)
-                    .setStableId(Objects.hash(VIRTUAL_KEYBOARD_FRAGMENT, key))
-                    .addBreadcrumbs(getVirtualKeyboardBreadCrumb())
-                    .setPayload(new ResultPayload(intent))
-                    .build());
+            return mPhysicalKeyboardBreadcrumb;
         }
-        return results;
-    }
 
-    private List<String> getPhysicalKeyboardBreadCrumb() {
-        if (mPhysicalKeyboardBreadcrumb == null || mPhysicalKeyboardBreadcrumb.isEmpty()) {
-            final Context context = getContext();
-            mPhysicalKeyboardBreadcrumb = mSiteMapManager.buildBreadCrumb(
-                    context, PHYSICAL_KEYBOARD_FRAGMENT,
-                    context.getString(R.string.physical_keyboard_title));
+
+        private List<String> getVirtualKeyboardBreadCrumb() {
+            if (mVirtualKeyboardBreadcrumb == null || mVirtualKeyboardBreadcrumb.isEmpty()) {
+                final Context context = mContext;
+                mVirtualKeyboardBreadcrumb = mSiteMapManager.buildBreadCrumb(
+                        context, VIRTUAL_KEYBOARD_FRAGMENT,
+                        context.getString(R.string.add_virtual_keyboard));
+            }
+            return mVirtualKeyboardBreadcrumb;
         }
-        return mPhysicalKeyboardBreadcrumb;
-    }
 
-
-    private List<String> getVirtualKeyboardBreadCrumb() {
-        if (mVirtualKeyboardBreadcrumb == null || mVirtualKeyboardBreadcrumb.isEmpty()) {
-            final Context context = getContext();
-            mVirtualKeyboardBreadcrumb = mSiteMapManager.buildBreadCrumb(
-                    context, VIRTUAL_KEYBOARD_FRAGMENT,
-                    context.getString(R.string.add_virtual_keyboard));
-        }
-        return mVirtualKeyboardBreadcrumb;
-    }
-
-    private List<InputDevice> getPhysicalFullKeyboards() {
-        final List<InputDevice> keyboards = new ArrayList<>();
-        final int[] deviceIds = InputDevice.getDeviceIds();
-        if (deviceIds != null) {
-            for (int deviceId : deviceIds) {
-                final InputDevice device = InputDevice.getDevice(deviceId);
-                if (device != null && !device.isVirtual() && device.isFullKeyboard()) {
-                    keyboards.add(device);
+        private List<InputDevice> getPhysicalFullKeyboards() {
+            final List<InputDevice> keyboards = new ArrayList<>();
+            final int[] deviceIds = InputDevice.getDeviceIds();
+            if (deviceIds != null) {
+                for (int deviceId : deviceIds) {
+                    final InputDevice device = InputDevice.getDevice(deviceId);
+                    if (device != null && !device.isVirtual() && device.isFullKeyboard()) {
+                        keyboards.add(device);
+                    }
                 }
             }
+            return keyboards;
         }
-        return keyboards;
-    }
 
-    private static List<InputMethodSubtype> getAllSubtypesOf(final InputMethodInfo imi) {
-        final int subtypeCount = imi.getSubtypeCount();
-        final List<InputMethodSubtype> allSubtypes = new ArrayList<>(subtypeCount);
-        for (int index = 0; index < subtypeCount; index++) {
-            allSubtypes.add(imi.getSubtypeAt(index));
+        private static List<InputMethodSubtype> getAllSubtypesOf(final InputMethodInfo imi) {
+            final int subtypeCount = imi.getSubtypeCount();
+            final List<InputMethodSubtype> allSubtypes = new ArrayList<>(subtypeCount);
+            for (int index = 0; index < subtypeCount; index++) {
+                allSubtypes.add(imi.getSubtypeAt(index));
+            }
+            return allSubtypes;
         }
-        return allSubtypes;
     }
 }
diff --git a/src/com/android/settings/search/InstalledAppResultLoader.java b/src/com/android/settings/search/InstalledAppResultLoader.java
index 7645c15..e5d8ac1 100644
--- a/src/com/android/settings/search/InstalledAppResultLoader.java
+++ b/src/com/android/settings/search/InstalledAppResultLoader.java
@@ -29,124 +29,39 @@
 import android.provider.Settings;
 import android.support.annotation.VisibleForTesting;
 import android.text.TextUtils;
+import android.util.Log;
 
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.settings.R;
 import com.android.settings.SettingsActivity;
 import com.android.settings.applications.manageapplications.ManageApplications;
 import com.android.settings.dashboard.SiteMapManager;
-import com.android.settings.utils.AsyncLoader;
 import com.android.settingslib.wrapper.PackageManagerWrapper;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.FutureTask;
 
 /**
  * Search loader for installed apps.
  */
-public class InstalledAppResultLoader extends AsyncLoader<Set<? extends SearchResult>> {
+public class InstalledAppResultLoader extends FutureTask<List<? extends SearchResult>> {
+
+    private static final String TAG = "InstalledAppFutureTask";
 
     private static final int NAME_NO_MATCH = -1;
     private static final Intent LAUNCHER_PROBE = new Intent(Intent.ACTION_MAIN)
             .addCategory(Intent.CATEGORY_LAUNCHER);
 
-    private List<String> mBreadcrumb;
-    private SiteMapManager mSiteMapManager;
-    @VisibleForTesting
-    final String mQuery;
-    private final UserManager mUserManager;
-    private final PackageManagerWrapper mPackageManager;
-    private final List<ResolveInfo> mHomeActivities = new ArrayList<>();
-
-    public InstalledAppResultLoader(Context context, PackageManagerWrapper pmWrapper,
-            String query, SiteMapManager mapManager) {
-        super(context);
-        mSiteMapManager = mapManager;
-        mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
-        mPackageManager = pmWrapper;
-        mQuery = query;
-    }
-
-    @Override
-    public Set<? extends SearchResult> loadInBackground() {
-        final Set<AppSearchResult> results = new HashSet<>();
-        final PackageManager pm = mPackageManager.getPackageManager();
-
-        mHomeActivities.clear();
-        mPackageManager.getHomeActivities(mHomeActivities);
-
-        for (UserInfo user : getUsersToCount()) {
-            final List<ApplicationInfo> apps =
-                    mPackageManager.getInstalledApplicationsAsUser(
-                            PackageManager.MATCH_DISABLED_COMPONENTS
-                                    | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                                    | (user.isAdmin() ? PackageManager.MATCH_ANY_USER : 0),
-                            user.id);
-            for (ApplicationInfo info : apps) {
-                if (!shouldIncludeAsCandidate(info, user)) {
-                    continue;
-                }
-                final CharSequence label = info.loadLabel(pm);
-                final int wordDiff = getWordDifference(label.toString(), mQuery);
-                if (wordDiff == NAME_NO_MATCH) {
-                    continue;
-                }
-                final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
-                        .setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
-                        .setData(Uri.fromParts("package", info.packageName, null))
-                        .putExtra(SettingsActivity.EXTRA_SOURCE_METRICS_CATEGORY,
-                                MetricsProto.MetricsEvent.DASHBOARD_SEARCH_RESULTS);
-
-                final AppSearchResult.Builder builder = new AppSearchResult.Builder();
-                builder.setAppInfo(info)
-                        .setStableId(Objects.hash(info.packageName, user.id))
-                        .setTitle(info.loadLabel(pm))
-                        .setRank(getRank(wordDiff))
-                        .addBreadcrumbs(getBreadCrumb())
-                        .setPayload(new ResultPayload(intent));
-                results.add(builder.build());
-            }
-        }
-        return results;
-    }
-
-    /**
-     * Returns true if the candidate should be included in candidate list
-     * <p/>
-     * This method matches logic in {@code ApplicationState#FILTER_DOWNLOADED_AND_LAUNCHER}.
-     */
-    private boolean shouldIncludeAsCandidate(ApplicationInfo info, UserInfo user) {
-        // Not system app
-        if ((info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0
-                || (info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
-            return true;
-        }
-        // Shows up in launcher
-        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);
-        if (intents != null && intents.size() != 0) {
-            return true;
-        }
-        // Is launcher app itself
-        return isPackageInList(mHomeActivities, info.packageName);
-    }
-
-    @Override
-    protected void onDiscardResult(Set<? extends SearchResult> result) {
-
-    }
-
-    private List<UserInfo> getUsersToCount() {
-        return mUserManager.getProfiles(UserHandle.myUserId());
+    public InstalledAppResultLoader(Context context, PackageManagerWrapper wrapper,
+            String query, SiteMapManager manager) {
+        super(new InstalledAppResultCallable(context, wrapper, query, manager));
     }
 
     /**
@@ -213,35 +128,133 @@
         return NAME_NO_MATCH;
     }
 
-    private boolean isPackageInList(List<ResolveInfo> resolveInfos, String pkg) {
-        for (ResolveInfo info : resolveInfos) {
-            if (TextUtils.equals(info.activityInfo.packageName, pkg)) {
+    static class InstalledAppResultCallable implements
+            Callable<List<? extends SearchResult>> {
+
+        private final Context mContext;
+        private List<String> mBreadcrumb;
+        private SiteMapManager mSiteMapManager;
+        @VisibleForTesting
+        final String mQuery;
+        private final UserManager mUserManager;
+        private final PackageManagerWrapper mPackageManager;
+        private final List<ResolveInfo> mHomeActivities = new ArrayList<>();
+
+        public InstalledAppResultCallable(Context context, PackageManagerWrapper pmWrapper,
+                String query, SiteMapManager mapManager) {
+            mContext = context;
+            mSiteMapManager = mapManager;
+            mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+            mPackageManager = pmWrapper;
+            mQuery = query;
+        }
+
+        @Override
+        public List<? extends SearchResult> call() throws Exception {
+            long startTime = System.currentTimeMillis();
+            final List<AppSearchResult> results = new ArrayList<>();
+            final PackageManager pm = mPackageManager.getPackageManager();
+
+            mHomeActivities.clear();
+            mPackageManager.getHomeActivities(mHomeActivities);
+
+            for (UserInfo user : getUsersToCount()) {
+                final List<ApplicationInfo> apps =
+                        mPackageManager.getInstalledApplicationsAsUser(
+                                PackageManager.MATCH_DISABLED_COMPONENTS
+                                        | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+                                        | (user.isAdmin() ? PackageManager.MATCH_ANY_USER : 0),
+                                user.id);
+                for (ApplicationInfo info : apps) {
+                    if (!shouldIncludeAsCandidate(info, user)) {
+                        continue;
+                    }
+                    final CharSequence label = info.loadLabel(pm);
+                    final int wordDiff = getWordDifference(label.toString(), mQuery);
+                    if (wordDiff == NAME_NO_MATCH) {
+                        continue;
+                    }
+                    final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
+                            .setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
+                            .setData(Uri.fromParts("package", info.packageName, null))
+                            .putExtra(SettingsActivity.EXTRA_SOURCE_METRICS_CATEGORY,
+                                    MetricsProto.MetricsEvent.DASHBOARD_SEARCH_RESULTS);
+
+                    final AppSearchResult.Builder builder = new AppSearchResult.Builder();
+                    builder.setAppInfo(info)
+                            .setStableId(Objects.hash(info.packageName, user.id))
+                            .setTitle(info.loadLabel(pm))
+                            .setRank(getRank(wordDiff))
+                            .addBreadcrumbs(getBreadCrumb())
+                            .setPayload(new ResultPayload(intent));
+                    results.add(builder.build());
+                }
+            }
+            Collections.sort(results);
+            Log.i(TAG, "App search loading took:" + (System.currentTimeMillis() - startTime));
+            return results;
+        }
+
+        /**
+         * Returns true if the candidate should be included in candidate list
+         * <p/>
+         * This method matches logic in {@code ApplicationState#FILTER_DOWNLOADED_AND_LAUNCHER}.
+         */
+        private boolean shouldIncludeAsCandidate(ApplicationInfo info, UserInfo user) {
+            // Not system app
+            if ((info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0
+                    || (info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
                 return true;
             }
+            // Shows up in launcher
+            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);
+            if (intents != null && intents.size() != 0) {
+                return true;
+            }
+            // Is launcher app itself
+            return isPackageInList(mHomeActivities, info.packageName);
         }
-        return false;
-    }
 
-    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));
+        private List<UserInfo> getUsersToCount() {
+            return mUserManager.getProfiles(UserHandle.myUserId());
         }
-        return mBreadcrumb;
-    }
 
-    /**
-     * A temporary ranking scheme for installed apps.
-     *
-     * @param wordDiff difference between query length and app name length.
-     * @return the ranking.
-     */
-    private int getRank(int wordDiff) {
-        if (wordDiff < 6) {
-            return 2;
+        private boolean isPackageInList(List<ResolveInfo> resolveInfos, String pkg) {
+            for (ResolveInfo info : resolveInfos) {
+                if (TextUtils.equals(info.activityInfo.packageName, pkg)) {
+                    return true;
+                }
+            }
+            return false;
         }
-        return 3;
+
+        private List<String> getBreadCrumb() {
+            if (mBreadcrumb == null || mBreadcrumb.isEmpty()) {
+                mBreadcrumb = mSiteMapManager.buildBreadCrumb(
+                        mContext, ManageApplications.class.getName(),
+                        mContext.getString(R.string.applications_settings));
+            }
+            return mBreadcrumb;
+        }
+
+        /**
+         * A temporary ranking scheme for installed apps.
+         *
+         * @param wordDiff difference between query length and app name length.
+         * @return the ranking.
+         */
+        private int getRank(int wordDiff) {
+            if (wordDiff < 6) {
+                return 2;
+            }
+            return 3;
+        }
     }
 }
diff --git a/src/com/android/settings/search/SearchFeatureProvider.java b/src/com/android/settings/search/SearchFeatureProvider.java
index 4df8203..42afee9 100644
--- a/src/com/android/settings/search/SearchFeatureProvider.java
+++ b/src/com/android/settings/search/SearchFeatureProvider.java
@@ -19,10 +19,14 @@
 import android.annotation.NonNull;
 import android.content.ComponentName;
 import android.content.Context;
+import android.util.Pair;
 import android.view.View;
 
 import com.android.settings.dashboard.SiteMapManager;
-import com.android.settings.search.ranking.SearchResultsRankerCallback;
+
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.FutureTask;
 
 /**
  * FeatureProvider for Settings Search
@@ -44,25 +48,30 @@
             throws SecurityException, IllegalArgumentException;
 
     /**
+     * Returns a new loader to get settings search results.
+     */
+    SearchResultLoader getSearchResultLoader(Context context, String query);
+
+    /**
      * Returns a new loader to search in index database.
      */
-    DatabaseResultLoader getDatabaseSearchLoader(Context context, String query);
+    DatabaseResultLoader getStaticSearchResultTask(Context context, String query);
 
     /**
      * Returns a new loader to search installed apps.
      */
-    InstalledAppResultLoader getInstalledAppSearchLoader(Context context, String query);
+    InstalledAppResultLoader getInstalledAppSearchTask(Context context, String query);
 
     /**
      * Returns a new loader to search accessibility services.
      */
-    AccessibilityServiceResultLoader getAccessibilityServiceResultLoader(Context context,
+    AccessibilityServiceResultLoader getAccessibilityServiceResultTask(Context context,
             String query);
 
     /**
      * Returns a new loader to search input devices.
      */
-    InputDeviceResultLoader getInputDeviceResultLoader(Context context, String query);
+    InputDeviceResultLoader getInputDeviceResultTask(Context context, String query);
 
     /**
      * Returns a new loader to get all recently saved queries search terms.
@@ -96,6 +105,11 @@
     boolean isIndexingComplete(Context context);
 
     /**
+     * @return a {@link ExecutorService} to be shared between search tasks.
+     */
+    ExecutorService getExecutorService();
+
+    /**
      * Initializes the feedback button in case it was dismissed.
      */
     default void initFeedbackButton() {
@@ -115,23 +129,6 @@
     }
 
     /**
-     * Query search results based on the input query.
-     *
-     * @param context                     application context
-     * @param query                       input user query
-     * @param searchResultsRankerCallback {@link SearchResultsRankerCallback}
-     */
-    default void querySearchResults(Context context, String query,
-            SearchResultsRankerCallback searchResultsRankerCallback) {
-    }
-
-    /**
-     * Cancel pending search query
-     */
-    default void cancelPendingSearchQuery(Context context) {
-    }
-
-    /**
      * Notify that a search result is clicked.
      *
      * @param context      application context
@@ -161,4 +158,10 @@
     default void searchRankingWarmup(Context context) {
     }
 
+    /**
+     * Return a FutureTask to get a list of scores for search results.
+     */
+    default FutureTask<List<Pair<String, Float>>> getRankerTask(Context context, String query) {
+        return null;
+    }
 }
diff --git a/src/com/android/settings/search/SearchFeatureProviderImpl.java b/src/com/android/settings/search/SearchFeatureProviderImpl.java
index af7f177..e0fbfd7 100644
--- a/src/com/android/settings/search/SearchFeatureProviderImpl.java
+++ b/src/com/android/settings/search/SearchFeatureProviderImpl.java
@@ -22,12 +22,15 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.settings.dashboard.SiteMapManager;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.search.indexing.IndexData;
 import com.android.settingslib.wrapper.PackageManagerWrapper;
 
 import java.util.Locale;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
 /**
  * FeatureProvider for the refactored search code.
@@ -40,6 +43,7 @@
 
     private DatabaseIndexingManager mDatabaseIndexingManager;
     private SiteMapManager mSiteMapManager;
+    private ExecutorService mExecutorService;
 
     @Override
     public boolean isEnabled(Context context) {
@@ -59,26 +63,31 @@
     }
 
     @Override
-    public DatabaseResultLoader getDatabaseSearchLoader(Context context, String query) {
+    public SearchResultLoader getSearchResultLoader(Context context, String query) {
+        return new SearchResultLoader(context, cleanQuery(query));
+    }
+
+    @Override
+    public DatabaseResultLoader getStaticSearchResultTask(Context context, String query) {
         return new DatabaseResultLoader(context, cleanQuery(query), getSiteMapManager());
     }
 
     @Override
-    public InstalledAppResultLoader getInstalledAppSearchLoader(Context context, String query) {
+    public InstalledAppResultLoader getInstalledAppSearchTask(Context context, String query) {
         return new InstalledAppResultLoader(
                 context, new PackageManagerWrapper(context.getPackageManager()),
                 cleanQuery(query), getSiteMapManager());
     }
 
     @Override
-    public AccessibilityServiceResultLoader getAccessibilityServiceResultLoader(Context context,
+    public AccessibilityServiceResultLoader getAccessibilityServiceResultTask(Context context,
             String query) {
         return new AccessibilityServiceResultLoader(context, cleanQuery(query),
                 getSiteMapManager());
     }
 
     @Override
-    public InputDeviceResultLoader getInputDeviceResultLoader(Context context, String query) {
+    public InputDeviceResultLoader getInputDeviceResultTask(Context context, String query) {
         return new InputDeviceResultLoader(context, cleanQuery(query), getSiteMapManager());
     }
 
@@ -124,12 +133,21 @@
                 .histogram(context, METRICS_ACTION_SETTINGS_INDEX, indexingTime);
     }
 
+    @Override
+    public ExecutorService getExecutorService() {
+        if (mExecutorService == null) {
+            mExecutorService = Executors.newCachedThreadPool();
+        }
+        return mExecutorService;
+    }
+
     /**
      * A generic method to make the query suitable for searching the database.
      *
      * @return the cleaned query string
      */
-    private String cleanQuery(String query) {
+    @VisibleForTesting
+    String cleanQuery(String query) {
         if (TextUtils.isEmpty(query)) {
             return null;
         }
diff --git a/src/com/android/settings/search/SearchFragment.java b/src/com/android/settings/search/SearchFragment.java
index ca951c6..e6316a8 100644
--- a/src/com/android/settings/search/SearchFragment.java
+++ b/src/com/android/settings/search/SearchFragment.java
@@ -54,8 +54,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * This fragment manages the lifecycle of indexing and searching.
@@ -68,7 +66,7 @@
  * the query if the user has entered text.
  */
 public class SearchFragment extends InstrumentedFragment implements SearchView.OnQueryTextListener,
-        LoaderManager.LoaderCallbacks<Set<? extends SearchResult>>, IndexingCallback {
+        LoaderManager.LoaderCallbacks<List<? extends SearchResult>>, IndexingCallback {
     private static final String TAG = "SearchFragment";
 
     // State values
@@ -78,23 +76,14 @@
 
     static final class SearchLoaderId {
         // Search Query IDs
-        public static final int DATABASE = 1;
-        public static final int INSTALLED_APPS = 2;
-        public static final int ACCESSIBILITY_SERVICES = 3;
-        public static final int INPUT_DEVICES = 4;
+        public static final int SEARCH_RESULT = 1;
 
         // Saved Query IDs
-        public static final int SAVE_QUERY_TASK = 5;
-        public static final int REMOVE_QUERY_TASK = 6;
-        public static final int SAVED_QUERIES = 7;
+        public static final int SAVE_QUERY_TASK = 2;
+        public static final int REMOVE_QUERY_TASK = 3;
+        public static final int SAVED_QUERIES = 4;
     }
 
-
-    private static final int NUM_QUERY_LOADERS = 4;
-
-    @VisibleForTesting
-    AtomicInteger mUnfinishedLoadersCount = new AtomicInteger(NUM_QUERY_LOADERS);
-
     @VisibleForTesting
     String mQuery;
 
@@ -147,7 +136,7 @@
         setHasOptionsMenu(true);
 
         final LoaderManager loaderManager = getLoaderManager();
-        mSearchAdapter = new SearchResultsAdapter(this, mSearchFeatureProvider);
+        mSearchAdapter = new SearchResultsAdapter(this /* fragment */);
         mSavedQueryController = new SavedQueryController(
                 getContext(), loaderManager, mSearchAdapter);
         mSearchFeatureProvider.initFeedbackButton();
@@ -277,15 +266,11 @@
 
         if (isEmptyQuery) {
             final LoaderManager loaderManager = getLoaderManager();
-            loaderManager.destroyLoader(SearchLoaderId.DATABASE);
-            loaderManager.destroyLoader(SearchLoaderId.INSTALLED_APPS);
-            loaderManager.destroyLoader(SearchLoaderId.ACCESSIBILITY_SERVICES);
-            loaderManager.destroyLoader(SearchLoaderId.INPUT_DEVICES);
+            loaderManager.destroyLoader(SearchLoaderId.SEARCH_RESULT);
             mShowingSavedQuery = true;
             mSavedQueryController.loadSavedQueries();
             mSearchFeatureProvider.hideFeedbackButton();
         } else {
-            mSearchAdapter.initializeSearch(mQuery);
             restartLoaders();
         }
 
@@ -301,35 +286,25 @@
     }
 
     @Override
-    public Loader<Set<? extends SearchResult>> onCreateLoader(int id, Bundle args) {
+    public Loader<List<? extends SearchResult>> onCreateLoader(int id, Bundle args) {
         final Activity activity = getActivity();
 
-        switch (id) {
-            case SearchLoaderId.DATABASE:
-                return mSearchFeatureProvider.getDatabaseSearchLoader(activity, mQuery);
-            case SearchLoaderId.INSTALLED_APPS:
-                return mSearchFeatureProvider.getInstalledAppSearchLoader(activity, mQuery);
-            case SearchLoaderId.ACCESSIBILITY_SERVICES:
-                return mSearchFeatureProvider.getAccessibilityServiceResultLoader(activity, mQuery);
-            case SearchLoaderId.INPUT_DEVICES:
-                return mSearchFeatureProvider.getInputDeviceResultLoader(activity, mQuery);
+        switch(id) {
+            case SearchLoaderId.SEARCH_RESULT:
+                return mSearchFeatureProvider.getSearchResultLoader(activity, mQuery);
             default:
                 return null;
         }
     }
 
     @Override
-    public void onLoadFinished(Loader<Set<? extends SearchResult>> loader,
-            Set<? extends SearchResult> data) {
-        mSearchAdapter.addSearchResults(data, loader.getClass().getName());
-        if (mUnfinishedLoadersCount.decrementAndGet() != 0) {
-            return;
-        }
-        mSearchAdapter.notifyResultsLoaded();
+    public void onLoadFinished(Loader<List<? extends SearchResult>> loader,
+            List<? extends SearchResult> data) {
+        mSearchAdapter.postSearchResults(data);
     }
 
     @Override
-    public void onLoaderReset(Loader<Set<? extends SearchResult>> loader) {
+    public void onLoaderReset(Loader<List<? extends SearchResult>> loader) {
     }
 
     /**
@@ -344,13 +319,8 @@
             mSavedQueryController.loadSavedQueries();
         } else {
             final LoaderManager loaderManager = getLoaderManager();
-            loaderManager.initLoader(SearchLoaderId.DATABASE, null /* args */, this /* callback */);
-            loaderManager.initLoader(
-                    SearchLoaderId.INSTALLED_APPS, null /* args */, this /* callback */);
-            loaderManager.initLoader(
-                    SearchLoaderId.ACCESSIBILITY_SERVICES, null /* args */, this /* callback */);
-            loaderManager.initLoader(
-                    SearchLoaderId.INPUT_DEVICES, null /* args */, this /* callback */);
+            loaderManager.initLoader(SearchLoaderId.SEARCH_RESULT, null /* args */,
+                    this /* callback */);
         }
 
         requery();
@@ -388,15 +358,8 @@
     private void restartLoaders() {
         mShowingSavedQuery = false;
         final LoaderManager loaderManager = getLoaderManager();
-        mUnfinishedLoadersCount.set(NUM_QUERY_LOADERS);
         loaderManager.restartLoader(
-                SearchLoaderId.DATABASE, null /* args */, this /* callback */);
-        loaderManager.restartLoader(
-                SearchLoaderId.INSTALLED_APPS, null /* args */, this /* callback */);
-        loaderManager.restartLoader(
-                SearchLoaderId.ACCESSIBILITY_SERVICES, null /* args */, this /* callback */);
-        loaderManager.restartLoader(
-                SearchLoaderId.INPUT_DEVICES, null /* args */, this /* callback */);
+                SearchLoaderId.SEARCH_RESULT, null /* args */, this /* callback */);
     }
 
     public String getQuery() {
@@ -453,9 +416,7 @@
         taggedData.add(Pair.create(
                 MetricsEvent.FIELD_SETTINGS_SEARCH_RESULT_RANK,
                 resultViewHolder.getAdapterPosition()));
-        taggedData.add(Pair.create(
-                MetricsEvent.FIELD_SETTINGS_SEARCH_RESULT_ASYNC_RANKING_STATE,
-                mSearchAdapter.getAsyncRankingState()));
+        // TODO (b/67744820) Move metrics to SettingsIntelligence (including ranking state).
         taggedData.add(Pair.create(
                 MetricsEvent.FIELD_SETTINGS_SEARCH_QUERY_LENGTH,
                 TextUtils.isEmpty(mQuery) ? 0 : mQuery.length()));
diff --git a/src/com/android/settings/search/SearchIndexableResources.java b/src/com/android/settings/search/SearchIndexableResources.java
index cb39970..69b2f9f 100644
--- a/src/com/android/settings/search/SearchIndexableResources.java
+++ b/src/com/android/settings/search/SearchIndexableResources.java
@@ -23,7 +23,6 @@
 import com.android.settings.DateTimeSettings;
 import com.android.settings.DeviceInfoSettings;
 import com.android.settings.DisplaySettings;
-import com.android.settings.EncryptionAndCredential;
 import com.android.settings.LegalSettings;
 import com.android.settings.ScreenPinningSettings;
 import com.android.settings.SecuritySettings;
@@ -75,6 +74,7 @@
 import com.android.settings.notification.ZenModeBehaviorSettings;
 import com.android.settings.notification.ZenModeSettings;
 import com.android.settings.print.PrintSettingsFragment;
+import com.android.settings.security.EncryptionAndCredential;
 import com.android.settings.security.LockscreenDashboardFragment;
 import com.android.settings.sim.SimSettings;
 import com.android.settings.support.SupportDashboardActivity;
diff --git a/src/com/android/settings/search/SearchResultAggregator.java b/src/com/android/settings/search/SearchResultAggregator.java
new file mode 100644
index 0000000..890e3f0
--- /dev/null
+++ b/src/com/android/settings/search/SearchResultAggregator.java
@@ -0,0 +1,177 @@
+package com.android.settings.search;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.settings.overlay.FeatureFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Collects the sorted list of all setting search results.
+ *
+ * TODO (b/64939692) Convert the timing logs to metrics
+ */
+public class SearchResultAggregator {
+
+    private static final String TAG = "SearchResultAggregator";
+
+    /**
+     * Timeout for first task. Allows for longer delay.
+     */
+    private static final long LONG_CHECK_TASK_TIMEOUT_MS = 500;
+
+    /**
+     * Timeout for subsequent tasks to allow for fast returning tasks.
+     */
+    private static final long SHORT_CHECK_TASK_TIMEOUT_MS = 150;
+
+    private static SearchResultAggregator sResultAggregator;
+
+    // TODO (b/33577327) Merge the other loaders into a single dynamic loader
+    static final class ResultLoaderId {
+        static final int STATIC_RESULTS = 1;
+        static final int INSTALLED_RESULTS = 2;
+        static final int INPUT_RESULTS = 3;
+        static final int ACCESSIBILITY_RESULTS = 4;
+    }
+
+    private SearchResultAggregator() {
+    }
+
+    public static SearchResultAggregator getInstance() {
+        if (sResultAggregator == null) {
+            sResultAggregator = new SearchResultAggregator();
+        }
+
+        return sResultAggregator;
+    }
+
+    @NonNull
+    public synchronized List<? extends SearchResult> fetchResults(Context context, String query) {
+        SearchFeatureProvider mFeatureProvider = FeatureFactory.getFactory(
+                context).getSearchFeatureProvider();
+        ExecutorService executorService = mFeatureProvider.getExecutorService();
+
+        final DatabaseResultLoader staticResultsTask =
+                mFeatureProvider.getStaticSearchResultTask(context, query);
+        final InstalledAppResultLoader installedAppTask =
+                mFeatureProvider.getInstalledAppSearchTask(context, query);
+        final InputDeviceResultLoader inputDevicesTask =
+                mFeatureProvider.getInputDeviceResultTask(context, query);
+        final AccessibilityServiceResultLoader accessibilityServicesTask =
+                mFeatureProvider.getAccessibilityServiceResultTask(context,
+                        query);
+
+        executorService.execute(staticResultsTask);
+        executorService.execute(installedAppTask);
+        executorService.execute(inputDevicesTask);
+        executorService.execute(accessibilityServicesTask);
+
+        SparseArray<List<? extends SearchResult>> resultsArray = new SparseArray<>();
+        List<? extends SearchResult> EMPTY_LIST = new ArrayList<>();
+
+        long allTasksStart = System.currentTimeMillis();
+        try {
+            resultsArray.put(ResultLoaderId.INPUT_RESULTS,
+                    inputDevicesTask.get(SHORT_CHECK_TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } catch (TimeoutException | InterruptedException | ExecutionException e) {
+            Log.d(TAG, "Could not retrieve input devices results in time: " + e);
+            resultsArray.put(ResultLoaderId.INPUT_RESULTS, EMPTY_LIST);
+        }
+
+        try {
+            resultsArray.put(ResultLoaderId.ACCESSIBILITY_RESULTS,
+                    accessibilityServicesTask.get(SHORT_CHECK_TASK_TIMEOUT_MS,
+                            TimeUnit.MILLISECONDS));
+        } catch (TimeoutException | InterruptedException | ExecutionException e) {
+            Log.d(TAG, "Could not retrieve accessibility results in time: " + e);
+            resultsArray.put(ResultLoaderId.ACCESSIBILITY_RESULTS, EMPTY_LIST);
+        }
+
+        try {
+            resultsArray.put(ResultLoaderId.STATIC_RESULTS,
+                    staticResultsTask.get(LONG_CHECK_TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } catch (TimeoutException | InterruptedException | ExecutionException e) {
+            Log.d(TAG, "Could not retrieve static results: " + e);
+            resultsArray.put(ResultLoaderId.STATIC_RESULTS, EMPTY_LIST);
+        }
+
+        try {
+            resultsArray.put(ResultLoaderId.INSTALLED_RESULTS,
+                    installedAppTask.get(SHORT_CHECK_TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } catch (TimeoutException | InterruptedException | ExecutionException e) {
+            Log.d(TAG, "Could not retrieve installed app results in time: " + e);
+
+            resultsArray.put(ResultLoaderId.INSTALLED_RESULTS, EMPTY_LIST);
+        }
+
+        long mergeStartTime = System.currentTimeMillis();
+        Log.i(TAG, "Total result loader time: " + (mergeStartTime - allTasksStart));
+        List<? extends SearchResult> mergedResults = mergeSearchResults(resultsArray);
+        Log.i(TAG, "Total merge time: " + (System.currentTimeMillis() - mergeStartTime));
+        Log.i(TAG, "Total aggregator time: " + (System.currentTimeMillis() - allTasksStart));
+
+        return mergedResults;
+    }
+
+    // TODO (b/68255021) scale the dynamic search results ranks and do a k-way merge
+    private List<? extends SearchResult> mergeSearchResults(
+            SparseArray<List<? extends SearchResult>> resultsArray) {
+        List<? extends SearchResult> staticResults = resultsArray.get(
+                ResultLoaderId.STATIC_RESULTS);
+        List<? extends SearchResult> installedAppResults = resultsArray.get(
+                ResultLoaderId.INSTALLED_RESULTS);
+        List<? extends SearchResult> accessibilityResults = resultsArray.get(
+                ResultLoaderId.ACCESSIBILITY_RESULTS);
+        List<? extends SearchResult> inputDeviceResults = resultsArray.get(
+                ResultLoaderId.INPUT_RESULTS);
+        List<SearchResult> searchResults;
+
+        int staticSize = staticResults.size();
+        int appSize = installedAppResults.size();
+        int a11ySize = accessibilityResults.size();
+        int inputDeviceSize = inputDeviceResults.size();
+        int appIndex = 0;
+        int a11yIndex = 0;
+        int inputDeviceIndex = 0;
+        int rank = SearchResult.TOP_RANK;
+
+        // TODO: We need a helper method to do k-way merge.
+        searchResults = new ArrayList<>(staticSize + appSize + a11ySize + inputDeviceSize);
+        searchResults.addAll(resultsArray.get(ResultLoaderId.STATIC_RESULTS));
+
+        while (rank <= SearchResult.BOTTOM_RANK) {
+            while ((appIndex < appSize) && (installedAppResults.get(appIndex).rank == rank)) {
+                searchResults.add(installedAppResults.get(appIndex++));
+            }
+            while ((a11yIndex < a11ySize) && (accessibilityResults.get(a11yIndex).rank == rank)) {
+                searchResults.add(accessibilityResults.get(a11yIndex++));
+            }
+            while (inputDeviceIndex < inputDeviceSize
+                    && inputDeviceResults.get(inputDeviceIndex).rank == rank) {
+                searchResults.add(inputDeviceResults.get(inputDeviceIndex++));
+            }
+            rank++;
+        }
+
+        while (appIndex < appSize) {
+            searchResults.add(installedAppResults.get(appIndex++));
+        }
+        while (a11yIndex < a11ySize) {
+            searchResults.add(accessibilityResults.get(a11yIndex++));
+        }
+        while (inputDeviceIndex < inputDeviceSize) {
+            searchResults.add(inputDeviceResults.get(inputDeviceIndex++));
+        }
+
+        return searchResults;
+    }
+}
diff --git a/src/com/android/settings/search/SearchResultDiffCallback.java b/src/com/android/settings/search/SearchResultDiffCallback.java
index b7bbc66..0f0b977 100644
--- a/src/com/android/settings/search/SearchResultDiffCallback.java
+++ b/src/com/android/settings/search/SearchResultDiffCallback.java
@@ -26,10 +26,11 @@
  */
 public class SearchResultDiffCallback extends DiffUtil.Callback {
 
-    private List<SearchResult> mOldList;
-    private List<SearchResult> mNewList;
+    private List<? extends SearchResult> mOldList;
+    private List<? extends SearchResult> mNewList;
 
-    public SearchResultDiffCallback(List<SearchResult> oldList, List<SearchResult> newList) {
+    public SearchResultDiffCallback(List<? extends SearchResult> oldList,
+            List<? extends SearchResult> newList) {
         mOldList = oldList;
         mNewList = newList;
     }
diff --git a/src/com/android/settings/search/SearchResultLoader.java b/src/com/android/settings/search/SearchResultLoader.java
new file mode 100644
index 0000000..7ec3146
--- /dev/null
+++ b/src/com/android/settings/search/SearchResultLoader.java
@@ -0,0 +1,30 @@
+package com.android.settings.search;
+
+import com.android.settings.utils.AsyncLoader;
+
+import android.content.Context;
+
+import java.util.List;
+
+/**
+ * Loads a sorted list of Search results for a given query.
+ */
+public class SearchResultLoader extends AsyncLoader<List<? extends SearchResult>> {
+
+    private final String mQuery;
+
+    public SearchResultLoader(Context context, String query) {
+        super(context);
+        mQuery = query;
+    }
+
+    @Override
+    public List<? extends SearchResult> loadInBackground() {
+        SearchResultAggregator aggregator = SearchResultAggregator.getInstance();
+        return aggregator.fetchResults(getContext(), mQuery);
+    }
+
+    @Override
+    protected void onDiscardResult(List<? extends SearchResult> result) {
+    }
+}
diff --git a/src/com/android/settings/search/SearchResultsAdapter.java b/src/com/android/settings/search/SearchResultsAdapter.java
index 5fedc52..c05ce18 100644
--- a/src/com/android/settings/search/SearchResultsAdapter.java
+++ b/src/com/android/settings/search/SearchResultsAdapter.java
@@ -18,87 +18,25 @@
 package com.android.settings.search;
 
 import android.content.Context;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.support.annotation.IntDef;
-import android.support.annotation.MainThread;
-import android.support.annotation.VisibleForTesting;
 import android.support.v7.util.DiffUtil;
 import android.support.v7.widget.RecyclerView;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.util.Pair;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 
 import com.android.settings.R;
-import com.android.settings.search.ranking.SearchResultsRankerCallback;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeSet;
 
-public class SearchResultsAdapter extends RecyclerView.Adapter<SearchViewHolder>
-        implements SearchResultsRankerCallback {
-    private static final String TAG = "SearchResultsAdapter";
-
-    @VisibleForTesting
-    static final String DB_RESULTS_LOADER_KEY = DatabaseResultLoader.class.getName();
-
-    @VisibleForTesting
-    static final String APP_RESULTS_LOADER_KEY = InstalledAppResultLoader.class.getName();
-    @VisibleForTesting
-    static final String ACCESSIBILITY_LOADER_KEY = AccessibilityServiceResultLoader.class.getName();
-    @VisibleForTesting
-    static final String INPUT_DEVICE_LOADER_KEY = InputDeviceResultLoader.class.getName();
-
-    @VisibleForTesting
-    static final int MSG_RANKING_TIMED_OUT = 1;
+public class SearchResultsAdapter extends RecyclerView.Adapter<SearchViewHolder> {
 
     private final SearchFragment mFragment;
-    private final Context mContext;
     private final List<SearchResult> mSearchResults;
-    private final List<SearchResult> mStaticallyRankedSearchResults;
-    private Map<String, Set<? extends SearchResult>> mResultsMap;
-    private final SearchFeatureProvider mSearchFeatureProvider;
-    private List<Pair<String, Float>> mSearchRankingScores;
-    private Handler mHandler;
-    private boolean mSearchResultsLoaded;
-    private boolean mSearchResultsUpdated;
 
-    @IntDef({DISABLED, PENDING_RESULTS, SUCCEEDED, FAILED, TIMED_OUT})
-    @Retention(RetentionPolicy.SOURCE)
-    private @interface AsyncRankingState {}
-    @VisibleForTesting
-    static final int DISABLED = 0;
-    @VisibleForTesting
-    static final int PENDING_RESULTS = 1;
-    @VisibleForTesting
-    static final int SUCCEEDED = 2;
-    @VisibleForTesting
-    static final int FAILED = 3;
-    @VisibleForTesting
-    static final int TIMED_OUT = 4;
-    private @AsyncRankingState int mAsyncRankingState;
-
-    public SearchResultsAdapter(SearchFragment fragment,
-            SearchFeatureProvider searchFeatureProvider) {
+    public SearchResultsAdapter(SearchFragment fragment) {
         mFragment = fragment;
-        mContext = fragment.getContext().getApplicationContext();
         mSearchResults = new ArrayList<>();
-        mResultsMap = new ArrayMap<>();
-        mSearchRankingScores = new ArrayList<>();
-        mStaticallyRankedSearchResults = new ArrayList<>();
-        mSearchFeatureProvider = searchFeatureProvider;
 
         setHasStableIds(true);
     }
@@ -149,298 +87,30 @@
         return mSearchResults.size();
     }
 
-    @MainThread
-    @Override
-    public void onRankingScoresAvailable(List<Pair<String, Float>> searchRankingScores) {
-        // Received the scores, stop the timeout timer.
-        getHandler().removeMessages(MSG_RANKING_TIMED_OUT);
-        if (mAsyncRankingState == PENDING_RESULTS) {
-            mAsyncRankingState = SUCCEEDED;
-            mSearchRankingScores.clear();
-            mSearchRankingScores.addAll(searchRankingScores);
-            if (canUpdateSearchResults()) {
-                updateSearchResults();
-            }
-        } else {
-            Log.w(TAG, "Ranking scores became available in invalid state: " + mAsyncRankingState);
-        }
-    }
-
-    @MainThread
-    @Override
-    public void onRankingFailed() {
-        if (mAsyncRankingState == PENDING_RESULTS) {
-            mAsyncRankingState = FAILED;
-            if (canUpdateSearchResults()) {
-                updateSearchResults();
-            }
-        } else {
-            Log.w(TAG, "Ranking scores failed in invalid states: " + mAsyncRankingState);
-        }
-    }
-
-   /**
-     * Store the results from each of the loaders to be merged when all loaders are finished.
-     *
-     * @param results         the results from the loader.
-     * @param loaderClassName class name of the loader.
-     */
-    @MainThread
-    public void addSearchResults(Set<? extends SearchResult> results, String loaderClassName) {
-        if (results == null) {
-            return;
-        }
-        mResultsMap.put(loaderClassName, results);
-    }
-
     /**
      * Displays recent searched queries.
-     *
-     * @return The number of saved queries to display
      */
-    public int displaySavedQuery(List<? extends SearchResult> data) {
+    public void displaySavedQuery(List<? extends SearchResult> data) {
         clearResults();
         mSearchResults.addAll(data);
         notifyDataSetChanged();
-        return mSearchResults.size();
-    }
-
-    /**
-     * Notifies the adapter that all the unsorted results are loaded and now the ladapter can
-     * proceed with ranking the results.
-     */
-    @MainThread
-    public void notifyResultsLoaded() {
-        mSearchResultsLoaded = true;
-        // static ranking is skipped only if asyc ranking is already succeeded.
-        if (mAsyncRankingState != SUCCEEDED) {
-            doStaticRanking();
-        }
-        if (canUpdateSearchResults()) {
-            updateSearchResults();
-        }
     }
 
     public void clearResults() {
         mSearchResults.clear();
-        mStaticallyRankedSearchResults.clear();
-        mResultsMap.clear();
         notifyDataSetChanged();
     }
 
-    @VisibleForTesting
     public List<SearchResult> getSearchResults() {
         return mSearchResults;
     }
 
-    @MainThread
-    public void initializeSearch(String query) {
-        clearResults();
-        mSearchResultsLoaded = false;
-        mSearchResultsUpdated = false;
-        if (mSearchFeatureProvider.isSmartSearchRankingEnabled(mContext)) {
-            mAsyncRankingState = PENDING_RESULTS;
-            mSearchFeatureProvider.cancelPendingSearchQuery(mContext);
-            final Handler handler = getHandler();
-            final long timeoutMs = mSearchFeatureProvider.smartSearchRankingTimeoutMs(mContext);
-            handler.sendMessageDelayed(
-                    handler.obtainMessage(MSG_RANKING_TIMED_OUT), timeoutMs);
-            mSearchFeatureProvider.querySearchResults(mContext, query, this);
-        } else {
-            mAsyncRankingState = DISABLED;
-        }
-    }
-
-    @AsyncRankingState int getAsyncRankingState() {
-        return mAsyncRankingState;
-    }
-
-    /**
-     * Merge the results from each of the loaders into one list for the adapter.
-     * Prioritizes results from the local database over installed apps.
-     */
-    private void doStaticRanking() {
-        List<? extends SearchResult> databaseResults =
-                getSortedLoadedResults(DB_RESULTS_LOADER_KEY);
-        List<? extends SearchResult> installedAppResults =
-                getSortedLoadedResults(APP_RESULTS_LOADER_KEY);
-        List<? extends SearchResult> accessibilityResults =
-                getSortedLoadedResults(ACCESSIBILITY_LOADER_KEY);
-        List<? extends SearchResult> inputDeviceResults =
-                getSortedLoadedResults(INPUT_DEVICE_LOADER_KEY);
-
-        int dbSize = databaseResults.size();
-        int appSize = installedAppResults.size();
-        int a11ySize = accessibilityResults.size();
-        int inputDeviceSize = inputDeviceResults.size();
-        int dbIndex = 0;
-        int appIndex = 0;
-        int a11yIndex = 0;
-        int inputDeviceIndex = 0;
-        int rank = SearchResult.TOP_RANK;
-
-        // TODO: We need a helper method to do k-way merge.
-        mStaticallyRankedSearchResults.clear();
-        while (rank <= SearchResult.BOTTOM_RANK) {
-            while ((dbIndex < dbSize) && (databaseResults.get(dbIndex).rank == rank)) {
-                mStaticallyRankedSearchResults.add(databaseResults.get(dbIndex++));
-            }
-            while ((appIndex < appSize) && (installedAppResults.get(appIndex).rank == rank)) {
-                mStaticallyRankedSearchResults.add(installedAppResults.get(appIndex++));
-            }
-            while ((a11yIndex < a11ySize) && (accessibilityResults.get(a11yIndex).rank == rank)) {
-                mStaticallyRankedSearchResults.add(accessibilityResults.get(a11yIndex++));
-            }
-            while (inputDeviceIndex < inputDeviceSize
-                    && inputDeviceResults.get(inputDeviceIndex).rank == rank) {
-                mStaticallyRankedSearchResults.add(inputDeviceResults.get(inputDeviceIndex++));
-            }
-            rank++;
-        }
-
-        while (dbIndex < dbSize) {
-            mStaticallyRankedSearchResults.add(databaseResults.get(dbIndex++));
-        }
-        while (appIndex < appSize) {
-            mStaticallyRankedSearchResults.add(installedAppResults.get(appIndex++));
-        }
-        while(a11yIndex < a11ySize) {
-            mStaticallyRankedSearchResults.add(accessibilityResults.get(a11yIndex++));
-        }
-        while (inputDeviceIndex < inputDeviceSize) {
-            mStaticallyRankedSearchResults.add(inputDeviceResults.get(inputDeviceIndex++));
-        }
-    }
-
-    private void updateSearchResults() {
-        switch (mAsyncRankingState) {
-            case PENDING_RESULTS:
-                break;
-            case DISABLED:
-            case FAILED:
-            case TIMED_OUT:
-                // When DISABLED or FAILED or TIMED_OUT, we use static ranking results.
-                postSearchResults(mStaticallyRankedSearchResults, false);
-                break;
-            case SUCCEEDED:
-                postSearchResults(doAsyncRanking(), true);
-                break;
-        }
-    }
-
-    private boolean canUpdateSearchResults() {
-        // Results are not updated yet and db results are loaded and we are not waiting on async
-        // ranking scores.
-        return !mSearchResultsUpdated
-                && mSearchResultsLoaded
-                && mAsyncRankingState != PENDING_RESULTS;
-    }
-
-    @VisibleForTesting
-    List<SearchResult> doAsyncRanking() {
-        Set<? extends SearchResult> databaseResults =
-                getUnsortedLoadedResults(DB_RESULTS_LOADER_KEY);
-        List<? extends SearchResult> installedAppResults =
-                getSortedLoadedResults(APP_RESULTS_LOADER_KEY);
-        List<? extends SearchResult> accessibilityResults =
-                getSortedLoadedResults(ACCESSIBILITY_LOADER_KEY);
-        List<? extends SearchResult> inputDeviceResults =
-                getSortedLoadedResults(INPUT_DEVICE_LOADER_KEY);
-        int dbSize = databaseResults.size();
-        int appSize = installedAppResults.size();
-        int a11ySize = accessibilityResults.size();
-        int inputDeviceSize = inputDeviceResults.size();
-
-        final List<SearchResult> asyncRankingResults = new ArrayList<>(
-                dbSize + appSize + a11ySize + inputDeviceSize);
-        TreeSet<SearchResult> dbResultsSortedByScores = new TreeSet<>(
-                new Comparator<SearchResult>() {
-                    @Override
-                    public int compare(SearchResult o1, SearchResult o2) {
-                        float score1 = getRankingScoreByStableId(o1.stableId);
-                        float score2 = getRankingScoreByStableId(o2.stableId);
-                        if (score1 > score2) {
-                            return -1;
-                        } else if (score1 == score2) {
-                            return 0;
-                        } else {
-                            return 1;
-                        }
-                    }
-                });
-        dbResultsSortedByScores.addAll(databaseResults);
-        asyncRankingResults.addAll(dbResultsSortedByScores);
-        // Other results are not ranked by async ranking and appended at the end of the list.
-        asyncRankingResults.addAll(installedAppResults);
-        asyncRankingResults.addAll(accessibilityResults);
-        asyncRankingResults.addAll(inputDeviceResults);
-        return asyncRankingResults;
-    }
-
-    @VisibleForTesting
-    Set<? extends SearchResult> getUnsortedLoadedResults(String loaderKey) {
-        return mResultsMap.containsKey(loaderKey) ? mResultsMap.get(loaderKey) : new HashSet<>();
-    }
-
-    @VisibleForTesting
-    List<? extends SearchResult> getSortedLoadedResults(String loaderKey) {
-        List<? extends SearchResult> sortedLoadedResults =
-                new ArrayList<>(getUnsortedLoadedResults(loaderKey));
-        Collections.sort(sortedLoadedResults);
-        return sortedLoadedResults;
-    }
-
-    /**
-     * Looks up ranking score for stableId
-     * @param stableId String of stableId
-     * @return the ranking score corresponding to the given stableId. If there is no score
-     * available for this stableId, -Float.MAX_VALUE is returned.
-     */
-    @VisibleForTesting
-    Float getRankingScoreByStableId(int stableId) {
-        for (Pair<String, Float> rankingScore : mSearchRankingScores) {
-            if (Integer.toString(stableId).compareTo(rankingScore.first) == 0) {
-                return rankingScore.second;
-            }
-        }
-        // If stableId not found in the list, we assign the minimum score so it will appear at
-        // the end of the list.
-        Log.w(TAG, "stableId " + stableId + " was not in the ranking scores.");
-        return -Float.MAX_VALUE;
-    }
-
-    @VisibleForTesting
-    Handler getHandler() {
-        if (mHandler == null) {
-            mHandler = new Handler(Looper.getMainLooper()) {
-                @Override
-                public void handleMessage(Message msg) {
-                    if (msg.what == MSG_RANKING_TIMED_OUT) {
-                        mSearchFeatureProvider.cancelPendingSearchQuery(mContext);
-                        if (mAsyncRankingState == PENDING_RESULTS) {
-                            mAsyncRankingState = TIMED_OUT;
-                            if (canUpdateSearchResults()) {
-                                updateSearchResults();
-                            }
-                        } else {
-                            Log.w(TAG, "Ranking scores timed out in invalid state: " +
-                                    mAsyncRankingState);
-                        }
-                    }
-                }
-            };
-        }
-        return mHandler;
-    }
-
-    @VisibleForTesting
-    public void postSearchResults(List<SearchResult> newSearchResults, boolean detectMoves) {
+    public void postSearchResults(List<? extends SearchResult> newSearchResults) {
         final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(
-                new SearchResultDiffCallback(mSearchResults, newSearchResults), detectMoves);
+                new SearchResultDiffCallback(mSearchResults, newSearchResults));
         mSearchResults.clear();
         mSearchResults.addAll(newSearchResults);
         diffResult.dispatchUpdatesTo(this);
         mFragment.onSearchResultsDisplayed(mSearchResults.size());
-        mSearchResultsUpdated = true;
     }
 }
diff --git a/src/com/android/settings/EncryptionAndCredential.java b/src/com/android/settings/security/EncryptionAndCredential.java
similarity index 93%
rename from src/com/android/settings/EncryptionAndCredential.java
rename to src/com/android/settings/security/EncryptionAndCredential.java
index 4892f7e..0972e3e 100644
--- a/src/com/android/settings/EncryptionAndCredential.java
+++ b/src/com/android/settings/security/EncryptionAndCredential.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007 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,13 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.settings;
+package com.android.settings.security;
 
-import android.app.Activity;
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.content.Intent;
-import android.os.Bundle;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.SearchIndexableResource;
@@ -30,11 +28,12 @@
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.settings.R;
 import com.android.settings.dashboard.DashboardFragment;
 import com.android.settings.search.BaseSearchIndexProvider;
-import com.android.settings.search.Indexable;
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.settingslib.RestrictedPreference;
+import com.android.settingslib.core.AbstractPreferenceController;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -43,7 +42,7 @@
  * Encryption and Credential settings.
  * TODO: Extends this from {@link DashboardFragment} instead
  */
-public class EncryptionAndCredential extends SettingsPreferenceFragment implements Indexable {
+public class EncryptionAndCredential extends DashboardFragment {
 
     private static final String TAG = "EncryptionAndCredential";
 
@@ -69,12 +68,19 @@
     }
 
     @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
+    protected String getLogTag() {
+        return TAG;
+    }
 
-        final Activity activity = getActivity();
+    @Override
+    protected List<AbstractPreferenceController> getPreferenceControllers(Context context) {
+        mUm = (UserManager) context.getSystemService(Context.USER_SERVICE);
+        return null;
+    }
 
-        mUm = UserManager.get(activity);
+    @Override
+    protected int getPreferenceScreenResId() {
+        return 0;
     }
 
     /**
diff --git a/src/com/android/settings/utils/ManagedServiceSettings.java b/src/com/android/settings/utils/ManagedServiceSettings.java
index 7a888db..d488dfe 100644
--- a/src/com/android/settings/utils/ManagedServiceSettings.java
+++ b/src/com/android/settings/utils/ManagedServiceSettings.java
@@ -21,7 +21,6 @@
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.Fragment;
-import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
@@ -46,6 +45,7 @@
 import com.android.settings.Utils;
 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
 import com.android.settings.notification.EmptyTextSettings;
+import com.android.settings.widget.AppSwitchPreference;
 
 import java.util.Collections;
 import java.util.List;
@@ -127,7 +127,7 @@
                 Log.e(TAG, "can't find package name", e);
             }
             final String summary = service.loadLabel(mPm).toString();
-            final SwitchPreference pref = new SwitchPreference(getPrefContext());
+            final SwitchPreference pref = new AppSwitchPreference(getPrefContext());
             pref.setPersistent(false);
             pref.setIcon(mIconDrawableFactory.getBadgedIcon(service, service.applicationInfo,
                     UserHandle.getUserId(service.applicationInfo.uid)));
diff --git a/src/com/android/settings/vpn2/LegacyVpnPreference.java b/src/com/android/settings/vpn2/LegacyVpnPreference.java
index 4ef2808..8fa9680 100644
--- a/src/com/android/settings/vpn2/LegacyVpnPreference.java
+++ b/src/com/android/settings/vpn2/LegacyVpnPreference.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.vpn2;
 
+import static com.android.internal.net.LegacyVpnInfo.STATE_CONNECTED;
+
 import android.content.Context;
 import android.support.v7.preference.Preference;
 import android.text.TextUtils;
@@ -23,7 +25,6 @@
 
 import com.android.internal.net.VpnProfile;
 import com.android.settings.R;
-import static com.android.internal.net.LegacyVpnInfo.STATE_CONNECTED;
 
 /**
  * {@link android.support.v7.preference.Preference} tracks the underlying legacy vpn profile and
@@ -35,6 +36,7 @@
     LegacyVpnPreference(Context context) {
         super(context, null /* attrs */);
         setIcon(R.drawable.ic_vpn_key);
+        setUseSmallIcon(true);
     }
 
     public VpnProfile getProfile() {
diff --git a/src/com/android/settings/widget/AppPreference.java b/src/com/android/settings/widget/AppPreference.java
new file mode 100644
index 0000000..8a0e6a1
--- /dev/null
+++ b/src/com/android/settings/widget/AppPreference.java
@@ -0,0 +1,63 @@
+/*
+ * 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.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ProgressBar;
+
+import com.android.settings.R;
+
+public class AppPreference extends Preference {
+
+    private int mProgress;
+    private boolean mProgressVisible;
+
+    public AppPreference(Context context) {
+        super(context);
+        setLayoutResource(R.layout.preference_app);
+    }
+
+    public AppPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setLayoutResource(R.layout.preference_app);
+    }
+
+    public void setProgress(int amount) {
+        mProgress = amount;
+        mProgressVisible = true;
+        notifyChanged();
+    }
+
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder view) {
+        super.onBindViewHolder(view);
+
+        view.findViewById(R.id.summary_container)
+                .setVisibility(TextUtils.isEmpty(getSummary()) ? View.GONE : View.VISIBLE);
+        final ProgressBar progress = (ProgressBar) view.findViewById(android.R.id.progress);
+        if (mProgressVisible) {
+            progress.setProgress(mProgress);
+            progress.setVisibility(View.VISIBLE);
+        } else {
+            progress.setVisibility(View.GONE);
+        }
+    }
+}
diff --git a/src/com/android/settings/widget/AppSwitchPreference.java b/src/com/android/settings/widget/AppSwitchPreference.java
new file mode 100644
index 0000000..d67e895
--- /dev/null
+++ b/src/com/android/settings/widget/AppSwitchPreference.java
@@ -0,0 +1,41 @@
+/*
+ * 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.v14.preference.SwitchPreference;
+import android.support.v7.preference.PreferenceViewHolder;
+import android.text.TextUtils;
+import android.view.View;
+
+import com.android.settings.R;
+
+public class AppSwitchPreference extends SwitchPreference {
+
+    public AppSwitchPreference(Context context) {
+        super(context);
+        setLayoutResource(R.layout.preference_app);
+    }
+
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder view) {
+        super.onBindViewHolder(view);
+
+        view.findViewById(R.id.summary_container)
+                .setVisibility(TextUtils.isEmpty(getSummary()) ? View.GONE : View.VISIBLE);
+    }
+}
diff --git a/src/com/android/settings/widget/RadioButtonPickerFragment.java b/src/com/android/settings/widget/RadioButtonPickerFragment.java
index e446340..7489a77 100644
--- a/src/com/android/settings/widget/RadioButtonPickerFragment.java
+++ b/src/com/android/settings/widget/RadioButtonPickerFragment.java
@@ -21,6 +21,7 @@
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.support.annotation.LayoutRes;
 import android.support.annotation.VisibleForTesting;
 import android.support.v7.preference.Preference;
 import android.support.v7.preference.PreferenceScreen;
@@ -133,8 +134,13 @@
         final String systemDefaultKey = getSystemDefaultKey();
         final PreferenceScreen screen = getPreferenceScreen();
         screen.removeAll();
+
+        final int customLayoutResId = getRadioButtonPreferenceCustomLayoutResId();
         if (shouldShowItemNone()) {
             final RadioButtonPreference nonePref = new RadioButtonPreference(getPrefContext());
+            if (customLayoutResId > 0) {
+                nonePref.setLayoutResource(customLayoutResId);
+            }
             nonePref.setIcon(R.drawable.ic_remove_circle);
             nonePref.setTitle(R.string.app_list_preference_none);
             nonePref.setChecked(TextUtils.isEmpty(defaultKey));
@@ -144,6 +150,9 @@
         if (candidateList != null) {
             for (CandidateInfo info : candidateList) {
                 RadioButtonPreference pref = new RadioButtonPreference(getPrefContext());
+                if (customLayoutResId > 0) {
+                    pref.setLayoutResource(customLayoutResId);
+                }
                 bindPreference(pref, info.getKey(), info, defaultKey);
                 bindPreferenceExtra(pref, info.getKey(), info, defaultKey, systemDefaultKey);
                 screen.addPreference(pref);
@@ -206,6 +215,14 @@
         return null;
     }
 
+    /**
+     * Provides a custom layout for each candidate row.
+     */
+    @LayoutRes
+    protected int getRadioButtonPreferenceCustomLayoutResId() {
+        return 0;
+    }
+
     public static abstract class CandidateInfo {
 
         public final boolean enabled;
diff --git a/src/com/android/settings/wifi/WifiConfigController.java b/src/com/android/settings/wifi/WifiConfigController.java
index 8ee340a..5a10ebf 100644
--- a/src/com/android/settings/wifi/WifiConfigController.java
+++ b/src/com/android/settings/wifi/WifiConfigController.java
@@ -61,6 +61,7 @@
 import com.android.settings.ProxySelector;
 import com.android.settings.R;
 import com.android.settingslib.Utils;
+import com.android.settingslib.utils.ThreadUtils;
 import com.android.settingslib.wifi.AccessPoint;
 
 import java.net.Inet4Address;
@@ -116,8 +117,6 @@
     /* Full list of phase2 methods */
     private final ArrayAdapter<String> mPhase2FullAdapter;
 
-    private final Handler mTextViewChangedHandler;
-
     // e.g. AccessPoint.SECURITY_NONE
     private int mAccessPointSecurity;
     private TextView mPasswordView;
@@ -175,7 +174,6 @@
                 accessPoint.getSecurity();
         mMode = mode;
 
-        mTextViewChangedHandler = new Handler();
         mContext = mConfigUi.getContext();
         final Resources res = mContext.getResources();
 
@@ -1258,12 +1256,10 @@
 
     @Override
     public void afterTextChanged(Editable s) {
-        mTextViewChangedHandler.post(new Runnable() {
-                public void run() {
-                    showWarningMessagesIfAppropriate();
-                    enableSubmitIfAppropriate();
-                }
-            });
+        ThreadUtils.postOnMainThread(() -> {
+            showWarningMessagesIfAppropriate();
+            enableSubmitIfAppropriate();
+        });
     }
 
     @Override
diff --git a/src/com/android/settings/wifi/WriteWifiConfigToNfcDialog.java b/src/com/android/settings/wifi/WriteWifiConfigToNfcDialog.java
index dfed801..dc98e21 100644
--- a/src/com/android/settings/wifi/WriteWifiConfigToNfcDialog.java
+++ b/src/com/android/settings/wifi/WriteWifiConfigToNfcDialog.java
@@ -63,7 +63,6 @@
     private View mView;
     private Button mSubmitButton;
     private Button mCancelButton;
-    private Handler mOnTextChangedHandler;
     private TextView mPasswordView;
     private TextView mLabelView;
     private CheckBox mPasswordCheckBox;
@@ -79,7 +78,6 @@
         mContext = context;
         mWakeLock = ((PowerManager) context.getSystemService(Context.POWER_SERVICE))
                 .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WriteWifiConfigToNfcDialog:wakeLock");
-        mOnTextChangedHandler = new Handler();
         mSecurity = security;
         mWifiManager = wifiManager;
     }
@@ -90,7 +88,6 @@
         mContext = context;
         mWakeLock = ((PowerManager) context.getSystemService(Context.POWER_SERVICE))
                 .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WriteWifiConfigToNfcDialog:wakeLock");
-        mOnTextChangedHandler = new Handler();
         mSecurity = savedState.getInt(SECURITY);
         mWifiManager = wifiManager;
     }
@@ -226,12 +223,7 @@
 
     @Override
     public void onTextChanged(CharSequence s, int start, int before, int count) {
-        mOnTextChangedHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                enableSubmitIfAppropriate();
-            }
-        });
+        enableSubmitIfAppropriate();
     }
 
     private void enableSubmitIfAppropriate() {
diff --git a/tests/anomaly-tester/res/values/strings.xml b/tests/anomaly-tester/res/values/strings.xml
index 99de34e..da56356 100644
--- a/tests/anomaly-tester/res/values/strings.xml
+++ b/tests/anomaly-tester/res/values/strings.xml
@@ -15,5 +15,5 @@
 -->
 
 <resources>
-    <string name="app_name">AnomalyTester</string>
+    <string name="app_name" translatable="false">AnomalyTester</string>
 </resources>
diff --git a/tests/robotests/README.md b/tests/robotests/README.md
new file mode 100644
index 0000000..648f1af
--- /dev/null
+++ b/tests/robotests/README.md
@@ -0,0 +1,24 @@
+# Running Settings Robolectric tests
+
+
+## The full suite
+```
+$ croot
+$ make RunSettingsRoboTests
+```
+
+## Running a single test class
+
+```
+$ croot
+$ make RunSettingsRoboTests ROBOTEST_FILTER=<ClassName>
+```
+
+For example:
+
+```
+make RunSettingsRoboTests ROBOTEST_FILTER=CodeInspectionTest
+```
+
+You can also use partial class name in ROBOTEST_FILTER. If the partial class name matches
+multiple file names, all of them will be executed.
diff --git a/tests/robotests/src/com/android/settings/accounts/ProviderPreferenceTest.java b/tests/robotests/src/com/android/settings/accounts/ProviderPreferenceTest.java
new file mode 100644
index 0000000..4e4bb4f
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/accounts/ProviderPreferenceTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.accounts;
+
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class ProviderPreferenceTest {
+
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+    }
+
+    @Test
+    public void shouldUseSmallIcon() {
+        final ProviderPreference providerPreference = new ProviderPreference(
+                mContext, "account_type", null /* icon */, "provider_name");
+        final boolean useSmallIcon =
+                ReflectionHelpers.getField(providerPreference, "mUseSmallIcon");
+        assertThat(useSmallIcon).isTrue();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAppPickerFragmentTest.java b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAppPickerFragmentTest.java
index 66ec80a..3621edd 100644
--- a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAppPickerFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAppPickerFragmentTest.java
@@ -17,6 +17,14 @@
 package com.android.settings.applications.defaultapps;
 
 
+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.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
 import android.app.Activity;
 import android.content.Context;
 import android.os.UserManager;
@@ -24,9 +32,10 @@
 import android.util.Pair;
 
 import com.android.internal.logging.nano.MetricsProto;
-import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settings.R;
 import com.android.settings.TestConfig;
 import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
 import com.android.settings.widget.RadioButtonPreference;
 
 import org.junit.Before;
@@ -41,13 +50,6 @@
 import java.util.ArrayList;
 import java.util.List;
 
-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 DefaultAppPickerFragmentTest {
@@ -97,6 +99,12 @@
                 any(Pair.class));
     }
 
+    @Test
+    public void shouldHaveAppPreferenceLayout() {
+        assertThat(mFragment.getRadioButtonPreferenceCustomLayoutResId())
+                .isEqualTo(R.layout.preference_app);
+    }
+
     public static class TestFragment extends DefaultAppPickerFragment {
 
         boolean setDefaultAppKeyCalled;
diff --git a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceControllerTest.java
index 120f3ae..a02a2de 100644
--- a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceControllerTest.java
@@ -26,8 +26,9 @@
 import android.support.v7.preference.Preference;
 
 import com.android.settings.R;
-import com.android.settings.testutils.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settingslib.TwoTargetPreference;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -79,6 +80,16 @@
         verify(mPreference).setSummary(R.string.app_list_preference_none);
     }
 
+    @Test
+    public void updateState_twoTargetPref_shouldUseSmallIcon() {
+        final TwoTargetPreference pref = mock(TwoTargetPreference.class);
+        mController = new TestPreferenceController(mContext);
+
+        mController.updateState(pref);
+
+        verify(pref).setUseSmallIcon(true);
+    }
+
     private static class TestPreferenceController extends DefaultAppPreferenceController {
 
         private DefaultAppInfo mAppInfo;
diff --git a/tests/robotests/src/com/android/settings/applications/manageapplications/ApplicationViewHolderTest.java b/tests/robotests/src/com/android/settings/applications/manageapplications/ApplicationViewHolderTest.java
index cf2403b..c32b262 100644
--- a/tests/robotests/src/com/android/settings/applications/manageapplications/ApplicationViewHolderTest.java
+++ b/tests/robotests/src/com/android/settings/applications/manageapplications/ApplicationViewHolderTest.java
@@ -22,7 +22,6 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.FrameLayout;
 
@@ -48,9 +47,8 @@
     @Before
     public void seUp() {
         mContext = RuntimeEnvironment.application;
-        mView = ApplicationViewHolder.newView(LayoutInflater.from(mContext),
-                new FrameLayout(mContext));
-        mHolder = new ApplicationViewHolder(mView);
+        mView = ApplicationViewHolder.newView(new FrameLayout(mContext));
+        mHolder = new ApplicationViewHolder(mView, false /* useStableHeight */);
     }
 
     @Test
@@ -71,6 +69,10 @@
 
         mHolder.setSummary(R.string.disabled);
         assertThat(mHolder.mSummary.getText()).isEqualTo(mContext.getText(R.string.disabled));
+        assertThat(mHolder.mSummaryContainer.getVisibility()).isEqualTo(View.VISIBLE);
+
+        mHolder.setSummary(null);
+        assertThat(mHolder.mSummaryContainer.getVisibility()).isEqualTo(View.GONE);
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/applications/manageapplications/ManageApplicationsTest.java b/tests/robotests/src/com/android/settings/applications/manageapplications/ManageApplicationsTest.java
index 9eb36ef..a0b0146 100644
--- a/tests/robotests/src/com/android/settings/applications/manageapplications/ManageApplicationsTest.java
+++ b/tests/robotests/src/com/android/settings/applications/manageapplications/ManageApplicationsTest.java
@@ -18,6 +18,10 @@
 
 import static com.android.settings.applications.manageapplications.AppFilterRegistry
         .FILTER_APPS_ALL;
+import static com.android.settings.applications.manageapplications.ManageApplications
+        .LIST_TYPE_MAIN;
+import static com.android.settings.applications.manageapplications.ManageApplications
+        .LIST_TYPE_NOTIFICATION;
 import static com.google.common.truth.Truth.assertThat;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
@@ -102,7 +106,7 @@
     @Test
     public void updateMenu_mainListType_showAppReset() {
         setUpOptionMenus();
-        ReflectionHelpers.setField(mFragment, "mListType", ManageApplications.LIST_TYPE_MAIN);
+        ReflectionHelpers.setField(mFragment, "mListType", LIST_TYPE_MAIN);
         ReflectionHelpers.setField(mFragment, "mOptionsMenu", mMenu);
 
         mFragment.updateOptionsMenu();
@@ -183,6 +187,21 @@
     }
 
     @Test
+    public void shouldUseStableItemHeight_mainType_yes() {
+        assertThat(ManageApplications.ApplicationsAdapter.shouldUseStableItemHeight(
+                LIST_TYPE_MAIN))
+                .isTrue();
+        assertThat(ManageApplications.ApplicationsAdapter.shouldUseStableItemHeight(
+                LIST_TYPE_NOTIFICATION))
+                .isFalse();
+    }
+
+    @Test
+    public void shouldUseStableItemHeight_notificationType_no() {
+
+    }
+
+    @Test
     public void onRebuildComplete_shouldHideLoadingView() {
         final Context context = RuntimeEnvironment.application;
         final ManageApplications fragment = mock(ManageApplications.class);
diff --git a/tests/robotests/src/com/android/settings/applications/manageapplications/MusicViewHolderControllerTest.java b/tests/robotests/src/com/android/settings/applications/manageapplications/MusicViewHolderControllerTest.java
index 28ac9d9..fc761cc 100644
--- a/tests/robotests/src/com/android/settings/applications/manageapplications/MusicViewHolderControllerTest.java
+++ b/tests/robotests/src/com/android/settings/applications/manageapplications/MusicViewHolderControllerTest.java
@@ -27,7 +27,6 @@
 import android.os.UserHandle;
 import android.os.storage.VolumeInfo;
 import android.provider.DocumentsContract;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.FrameLayout;
 
@@ -68,9 +67,8 @@
         mController = new MusicViewHolderController(mContext, mSource, mVolume.fsUuid,
                 new UserHandle(0));
 
-        LayoutInflater inflater = LayoutInflater.from(mContext);
-        mView = ApplicationViewHolder.newView(inflater, new FrameLayout(mContext));
-        mHolder = new ApplicationViewHolder(mView);
+        mView = ApplicationViewHolder.newView(new FrameLayout(mContext));
+        mHolder = new ApplicationViewHolder(mView, false /* useStableHeight */);
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/applications/manageapplications/PhotosViewHolderControllerTest.java b/tests/robotests/src/com/android/settings/applications/manageapplications/PhotosViewHolderControllerTest.java
index 2a26e31..0a147ac 100644
--- a/tests/robotests/src/com/android/settings/applications/manageapplications/PhotosViewHolderControllerTest.java
+++ b/tests/robotests/src/com/android/settings/applications/manageapplications/PhotosViewHolderControllerTest.java
@@ -26,7 +26,6 @@
 import android.content.Intent;
 import android.os.UserHandle;
 import android.os.storage.VolumeInfo;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.FrameLayout;
 
@@ -67,9 +66,8 @@
                 new PhotosViewHolderController(
                         mContext, mSource, mVolume.fsUuid, new UserHandle(0));
 
-        final LayoutInflater inflater = LayoutInflater.from(mContext);
-        mView = ApplicationViewHolder.newView(inflater, new FrameLayout(mContext));
-        mHolder = new ApplicationViewHolder(mView);
+        mView = ApplicationViewHolder.newView(new FrameLayout(mContext));
+        mHolder = new ApplicationViewHolder(mView, false /* useStableHeight */);
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/bluetooth/UtilsTest.java b/tests/robotests/src/com/android/settings/bluetooth/UtilsTest.java
index 220d829..e0d4638 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/UtilsTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/UtilsTest.java
@@ -15,21 +15,15 @@
  */
 package com.android.settings.bluetooth;
 
-import static com.google.common.truth.Truth.assertThat;
-
-import android.bluetooth.BluetoothDevice;
 import android.content.Context;
-import android.graphics.drawable.Drawable;
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.settings.R;
 import com.android.settings.testutils.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
 import com.android.settings.core.instrumentation.MetricsFeatureProvider;
 import com.android.settings.testutils.FakeFeatureFactory;
 import com.android.settings.testutils.shadow.SettingsShadowResources;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.android.settingslib.graph.BluetoothDeviceLayerDrawable;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -37,7 +31,6 @@
 import org.mockito.Answers;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 
 import static org.mockito.Matchers.anyInt;
@@ -76,20 +69,4 @@
         verify(mMetricsFeatureProvider).visible(eq(mContext), anyInt(),
                 eq(MetricsEvent.ACTION_SETTINGS_BLUETOOTH_CONNECT_ERROR));
     }
-
-    @Test
-    public void testGetBluetoothDrawable_noBatteryLevel_returnSimpleDrawable() {
-        final Drawable drawable = Utils.getBluetoothDrawable(RuntimeEnvironment.application,
-                R.drawable.ic_bt_laptop, BluetoothDevice.BATTERY_LEVEL_UNKNOWN, 1 /* iconScale */);
-
-        assertThat(drawable).isNotInstanceOf(BluetoothDeviceLayerDrawable.class);
-    }
-
-    @Test
-    public void testGetBluetoothDrawable_hasBatteryLevel_returnLayerDrawable() {
-        final Drawable drawable = Utils.getBluetoothDrawable(RuntimeEnvironment.application,
-                R.drawable.ic_bt_laptop, 10 /* batteryLevel */, 1 /* iconScale */);
-
-        assertThat(drawable).isInstanceOf(BluetoothDeviceLayerDrawable.class);
-    }
 }
diff --git a/tests/robotests/src/com/android/settings/core/InstrumentedPreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/core/InstrumentedPreferenceFragmentTest.java
index ab7228c..30d60cc 100644
--- a/tests/robotests/src/com/android/settings/core/InstrumentedPreferenceFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/core/InstrumentedPreferenceFragmentTest.java
@@ -74,17 +74,6 @@
     }
 
     @Test
-    public void onCreatePreferences_preferenceScreenTitleFeatureOff_shouldNotAddPreference() {
-        SettingsShadowSystemProperties.set(
-                FeatureFlagUtils.FFLAG_PREFIX + mFragment.FEATURE_FLAG_USE_PREFERENCE_SCREEN_TITLE,
-                "false");
-
-        mFragment.onCreatePreferences(Bundle.EMPTY, null /* rootKey */);
-
-        verify(mFragment, never()).addPreferencesFromResource(anyInt());
-    }
-
-    @Test
     public void onCreatePreferences_noPreferenceScreenResId_shouldNotAddPreference() {
         SettingsShadowSystemProperties.set(
                 FeatureFlagUtils.FFLAG_PREFIX + mFragment.FEATURE_FLAG_USE_PREFERENCE_SCREEN_TITLE,
diff --git a/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImplTest.java
index 5e56714..8feef92 100644
--- a/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImplTest.java
+++ b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImplTest.java
@@ -133,13 +133,6 @@
     }
 
     @Test
-    public void isSuggestionEnabled_isNotLowMemoryDevice_shouldReturnTrue() {
-        when(mActivityManager.isLowRamDevice()).thenReturn(false);
-
-        assertThat(mProvider.isSuggestionEnabled(mContext)).isTrue();
-    }
-
-    @Test
     public void isSuggestionV2Enabled_isNotLowMemoryDevice_sysPropOn_shouldReturnTrue() {
         when(mActivityManager.isLowRamDevice()).thenReturn(false);
         SettingsShadowSystemProperties.set(
@@ -148,14 +141,6 @@
     }
 
     @Test
-    public void isSuggestionV2Enabled_isNotLowMemoryDevice_sysPropOff_shouldReturnTrue() {
-        when(mActivityManager.isLowRamDevice()).thenReturn(false);
-        SettingsShadowSystemProperties.set(
-                FeatureFlagUtils.FFLAG_PREFIX + mProvider.FEATURE_FLAG_SUGGESTIONS_V2, "false");
-        assertThat(mProvider.isSuggestionV2Enabled(mContext)).isFalse();
-    }
-
-    @Test
     public void dismissSuggestion_noParserOrSuggestion_noop() {
         mProvider.dismissSuggestion(mContext, null, (Tile) null);
         mProvider.dismissSuggestion(mContext, mSuggestionParser, null);
diff --git a/tests/robotests/src/com/android/settings/development/OemUnlockPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/OemUnlockPreferenceControllerTest.java
index 1367870..f59c29f 100644
--- a/tests/robotests/src/com/android/settings/development/OemUnlockPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/development/OemUnlockPreferenceControllerTest.java
@@ -18,18 +18,17 @@
 
 import static com.android.settings.development.DevelopmentOptionsActivityRequestCodes
         .REQUEST_CODE_ENABLE_OEM_UNLOCK;
-
 import static com.google.common.truth.Truth.assertThat;
-
 import static org.mockito.Mockito.doNothing;
 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;
 
 import android.app.Activity;
+import android.app.FragmentManager;
 import android.content.Context;
-import android.content.DialogInterface;
 import android.content.res.Resources;
 import android.os.UserManager;
 import android.service.oemlock.OemLockManager;
@@ -43,6 +42,7 @@
 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;
@@ -81,6 +81,8 @@
         mController = new OemUnlockPreferenceController(mContext, mActivity, mFragment);
         when(mPreferenceScreen.findPreference(mController.getPreferenceKey())).thenReturn(
                 mPreference);
+        when(mFragment.getChildFragmentManager()).thenReturn(
+                mock(FragmentManager.class, Answers.RETURNS_DEEP_STUBS));
         mController.displayPreference(mPreferenceScreen);
     }
 
@@ -114,8 +116,8 @@
     @Test
     public void onPreferenceChanged_turnOffUnlock() {
         mController.onPreferenceChange(null, false);
-
         verify(mOemLockManager).setOemUnlockAllowedByUser(false);
+        verify(mFragment).getChildFragmentManager();
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/deviceinfo/storage/AutomaticStorageManagementSwitchPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/storage/AutomaticStorageManagementSwitchPreferenceControllerTest.java
index b737d1f..7d47fc2 100644
--- a/tests/robotests/src/com/android/settings/deviceinfo/storage/AutomaticStorageManagementSwitchPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/deviceinfo/storage/AutomaticStorageManagementSwitchPreferenceControllerTest.java
@@ -32,6 +32,7 @@
 import android.provider.Settings;
 import android.support.v7.preference.Preference;
 import android.support.v7.preference.PreferenceScreen;
+
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settings.TestConfig;
 import com.android.settings.core.instrumentation.MetricsFeatureProvider;
@@ -41,6 +42,8 @@
 import com.android.settings.testutils.SettingsRobolectricTestRunner;
 import com.android.settings.testutils.shadow.SettingsShadowSystemProperties;
 import com.android.settings.widget.MasterSwitchPreference;
+
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -83,6 +86,10 @@
                 mContext, mMetricsFeature, mFragmentManager);
         when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
     }
+    @After
+    public void tearDown() {
+        SettingsShadowSystemProperties.clear();
+    }
 
     @Test
     public void isAvailable_shouldReturnTrue_forHighRamDevice() {
@@ -146,6 +153,9 @@
     public void togglingOnShouldTriggerWarningFragment() {
         FragmentTransaction transaction = mock(FragmentTransaction.class);
         when (mFragmentManager.beginTransaction()).thenReturn(transaction);
+        SettingsShadowSystemProperties.set(
+                AutomaticStorageManagementSwitchPreferenceController
+                        .STORAGE_MANAGER_ENABLED_BY_DEFAULT_PROPERTY, "false");
 
         mController.onSwitchToggled(true);
 
diff --git a/tests/robotests/src/com/android/settings/display/ShowOperatorNamePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/ShowOperatorNamePreferenceControllerTest.java
new file mode 100644
index 0000000..23b43d6
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/display/ShowOperatorNamePreferenceControllerTest.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.display;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.support.v14.preference.SwitchPreference;
+
+import com.android.settings.R;
+import com.android.settings.testutils.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 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 ShowOperatorNamePreferenceControllerTest {
+
+    private static final String KEY_SHOW_OPERATOR_NAME = "show_operator_name";
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+    @Mock
+    private SwitchPreference mPreference;
+
+    private ShowOperatorNamePreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mController = new ShowOperatorNamePreferenceController(mContext);
+    }
+
+    @Test
+    public void testIsAvailable_configIsTrue_ReturnTrue() {
+        when(mContext.getResources().getBoolean(R.bool.config_showOperatorNameInStatusBar))
+                .thenReturn(true);
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void testIsAvailable_configIsFalse_ReturnFalse() {
+        when(mContext.getResources().getBoolean(R.bool.config_showOperatorNameInStatusBar))
+                .thenReturn(false);
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void testOnPreferenceChange_TurnOn_ReturnOn() {
+        mController.onPreferenceChange(mPreference, true);
+
+        final int mode = Settings.Secure.getInt(mContext.getContentResolver(),
+                KEY_SHOW_OPERATOR_NAME, 0);
+        assertThat(mode).isEqualTo(1);
+    }
+
+    @Test
+    public void testOnPreferenceChange_TurnOff_ReturnOff() {
+        mController.onPreferenceChange(mPreference, false);
+
+        final int mode = Settings.Secure.getInt(mContext.getContentResolver(),
+                KEY_SHOW_OPERATOR_NAME, 1);
+        assertThat(mode).isEqualTo(0);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/enterprise/ApplicationListPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/enterprise/ApplicationListPreferenceControllerTest.java
index 0d22d2e..81223a5 100644
--- a/tests/robotests/src/com/android/settings/enterprise/ApplicationListPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/enterprise/ApplicationListPreferenceControllerTest.java
@@ -16,6 +16,16 @@
 
 package com.android.settings.enterprise;
 
+import static com.android.settings.testutils.ApplicationTestUtils.buildInfo;
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Answers.RETURNS_DEEP_STUBS;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
@@ -23,10 +33,10 @@
 import android.support.v7.preference.PreferenceScreen;
 
 import com.android.settings.SettingsPreferenceFragment;
-import com.android.settings.testutils.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
 import com.android.settings.applications.ApplicationFeatureProvider;
 import com.android.settings.applications.UserAppInfo;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -43,16 +53,6 @@
 import java.util.List;
 import java.util.Set;
 
-import static com.android.settings.testutils.ApplicationTestUtils.buildInfo;
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Answers.RETURNS_DEEP_STUBS;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-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 ApplicationListPreferenceControllerTest {
@@ -120,7 +120,7 @@
             implements ApplicationListPreferenceController.ApplicationListBuilder {
         @Override
         public void buildApplicationList(Context context,
-                                         ApplicationFeatureProvider.ListOfAppsCallback callback) {
+                ApplicationFeatureProvider.ListOfAppsCallback callback) {
             final List<UserAppInfo> apps = new ArrayList<>();
             final UserInfo user = new UserInfo(MAIN_USER_ID, "main", UserInfo.FLAG_ADMIN);
             apps.add(new UserAppInfo(user, buildInfo(MAIN_USER_APP_UID, APP_1, 0, 0)));
diff --git a/tests/robotests/src/com/android/settings/fingerprint/FingerprintEnrollEnrollingTest.java b/tests/robotests/src/com/android/settings/fingerprint/FingerprintEnrollEnrollingTest.java
new file mode 100644
index 0000000..ace535e
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fingerprint/FingerprintEnrollEnrollingTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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.fingerprint;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.verify;
+import static org.robolectric.RuntimeEnvironment.application;
+
+import android.content.Intent;
+import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintManager.EnrollmentCallback;
+import android.media.AudioAttributes;
+import android.os.CancellationSignal;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.widget.TextView;
+
+import com.android.settings.R;
+import com.android.settings.TestConfig;
+import com.android.settings.password.ChooseLockSettingsHelper;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settings.testutils.shadow.ShadowUtils;
+import com.android.settings.testutils.shadow.ShadowVibrator;
+import com.android.settings.wrapper.FingerprintManagerWrapper;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.Robolectric;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+import java.util.concurrent.TimeUnit;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(
+        manifest = TestConfig.MANIFEST_PATH,
+        sdk = Config.NEWEST_SDK,
+        shadows = {
+                ShadowUtils.class,
+                ShadowVibrator.class
+        })
+public class FingerprintEnrollEnrollingTest {
+
+    @Mock
+    private FingerprintManagerWrapper mFingerprintManager;
+
+    private FingerprintEnrollEnrolling mActivity;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowUtils.setFingerprintManager(mFingerprintManager);
+        ShadowVibrator.addToServiceMap();
+
+        mActivity = Robolectric.buildActivity(
+                FingerprintEnrollEnrolling.class,
+                new Intent()
+                        // Set the challenge token so the confirm screen will not be shown
+                        .putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, new byte[0]))
+                .setup().get();
+    }
+
+    @After
+    public void tearDown() {
+        ShadowUtils.reset();
+        ShadowVibrator.reset();
+    }
+
+    @Test
+    public void fingerprintEnrollHelp_shouldShowHelpTextAndVibrate() {
+        EnrollmentCallback enrollmentCallback = verifyAndCaptureEnrollmentCallback();
+
+        enrollmentCallback.onEnrollmentProgress(123);
+        enrollmentCallback.onEnrollmentHelp(
+                FingerprintManager.FINGERPRINT_ERROR_UNABLE_TO_PROCESS,
+                "test enrollment help");
+
+        TextView errorText = mActivity.findViewById(R.id.error_text);
+        assertThat(errorText.getText()).isEqualTo("test enrollment help");
+
+        Robolectric.getForegroundThreadScheduler().advanceBy(2, TimeUnit.MILLISECONDS);
+
+
+        ShadowVibrator shadowVibrator =
+                Shadow.extract(application.getSystemService(Vibrator.class));
+        verify(shadowVibrator.delegate).vibrate(
+                anyInt(),
+                nullable(String.class),
+                any(VibrationEffect.class),
+                nullable(AudioAttributes.class));
+    }
+
+    private EnrollmentCallback verifyAndCaptureEnrollmentCallback() {
+        ArgumentCaptor<EnrollmentCallback> callbackCaptor =
+                ArgumentCaptor.forClass(EnrollmentCallback.class);
+        verify(mFingerprintManager).enroll(
+                any(byte[].class),
+                any(CancellationSignal.class),
+                anyInt(),
+                anyInt(),
+                callbackCaptor.capture());
+
+        return callbackCaptor.getValue();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerGaugePreferenceTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerGaugePreferenceTest.java
index 1259670..32f6a96 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/PowerGaugePreferenceTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerGaugePreferenceTest.java
@@ -15,6 +15,8 @@
  */
 package com.android.settings.fuelgauge;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.VectorDrawable;
@@ -35,8 +37,6 @@
 import org.robolectric.RuntimeEnvironment;
 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 PowerGaugePreferenceTest {
@@ -53,7 +53,7 @@
         MockitoAnnotations.initMocks(this);
 
         mContext = RuntimeEnvironment.application;
-        mRootView = LayoutInflater.from(mContext).inflate(R.layout.preference,
+        mRootView = LayoutInflater.from(mContext).inflate(R.layout.preference_app,
                 null);
         mWidgetView = LayoutInflater.from(mContext).inflate(R.layout.preference_widget_summary,
                 null);
@@ -61,6 +61,7 @@
         mPreferenceViewHolder = PreferenceViewHolder.createInstanceForTests(mRootView);
 
         mPowerGaugePreference = new PowerGaugePreference(mContext);
+        assertThat(mPowerGaugePreference.getLayoutResource()).isEqualTo(R.layout.preference_app);
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAdvancedTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAdvancedTest.java
index 8030463..2b5e704 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAdvancedTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAdvancedTest.java
@@ -31,6 +31,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
 import android.net.ConnectivityManager;
 import android.os.UserManager;
 import android.support.v7.preference.PreferenceCategory;
@@ -93,6 +94,10 @@
     private PreferenceGroup mUsageListGroup;
     @Mock
     private ConnectivityManager mConnectivityManager;
+    @Mock
+    private UserInfo mNormalUserInfo;
+    @Mock
+    private UserInfo mManagedUserInfo;
     private PowerUsageAdvanced mPowerUsageAdvanced;
     private PowerUsageData mPowerUsageData;
     private Context mShadowContext;
@@ -136,6 +141,11 @@
         mMaxBatterySipper.totalPowerMah = TYPE_BLUETOOTH_USAGE;
         mMaxBatterySipper.drainType = DrainType.BLUETOOTH;
         mNormalBatterySipper.drainType = DrainType.SCREEN;
+
+        doReturn(true).when(mNormalUserInfo).isEnabled();
+        doReturn(false).when(mNormalUserInfo).isManagedProfile();
+        doReturn(true).when(mManagedUserInfo).isEnabled();
+        doReturn(true).when(mManagedUserInfo).isManagedProfile();
     }
 
     @Test
@@ -293,9 +303,12 @@
     }
 
     @Test
-    public void testShouldHideCategory_typeUserAndOnlyOne_returnTrue() {
+    public void testShouldHideCategory_typeUserAndOnlyOneNormalUser_returnTrue() {
         mPowerUsageData.usageType = UsageType.USER;
-        doReturn(1).when(mUserManager).getUserCount();
+        List<UserInfo> userInfos = new ArrayList<>();
+        userInfos.add(mNormalUserInfo);
+        userInfos.add(mManagedUserInfo);
+        doReturn(userInfos).when(mUserManager).getUsers();
 
         assertThat(mPowerUsageAdvanced.shouldHideCategory(mPowerUsageData)).isTrue();
     }
@@ -321,7 +334,10 @@
     @Test
     public void testShouldHideCategory_typeUserAndMoreThanOne_returnFalse() {
         mPowerUsageData.usageType = UsageType.USER;
-        doReturn(2).when(mUserManager).getUserCount();
+        List<UserInfo> userInfos = new ArrayList<>();
+        userInfos.add(mNormalUserInfo);
+        userInfos.add(mNormalUserInfo);
+        doReturn(userInfos).when(mUserManager).getUsers();
 
         assertThat(mPowerUsageAdvanced.shouldHideCategory(mPowerUsageData)).isFalse();
     }
@@ -423,4 +439,5 @@
         mPowerUsageAdvanced.refreshUi();
         verify(mHistPref, atLeastOnce()).setBottomSummary(any());
     }
+
 }
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyUtilsTest.java
index 8cd5a06..38391c9 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyUtilsTest.java
@@ -18,8 +18,13 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.os.Build;
+import static org.mockito.Mockito.verify;
 
+import android.os.Build;
+import android.util.Pair;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.core.instrumentation.MetricsFeatureProvider;
 import com.android.settings.fuelgauge.anomaly.action.StopAndBackgroundCheckAction;
 import com.android.settings.testutils.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
@@ -31,18 +36,42 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+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;
+
 @RunWith(SettingsRobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION, shadows = {
         ShadowKeyValueListParserWrapperImpl.class})
 public class AnomalyUtilsTest {
+    private static final String PACKAGE_NAME_WAKEUP = "com.android.app1";
+    private static final String PACKAGE_NAME_WAKELOCK = "com.android.app2";
+    private static final int CONTEXT_ID = 55;
+
+    @Mock
+    private MetricsFeatureProvider mMetricsFeatureProvider;
     private AnomalyUtils mAnomalyUtils;
+    private Anomaly mWakeupAnomaly;
+    private Anomaly mWakeLockAnomaly;
 
     @Before
     public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
         mAnomalyUtils = new AnomalyUtils(RuntimeEnvironment.application);
+
+        mWakeLockAnomaly = new Anomaly.Builder()
+                .setType(Anomaly.AnomalyType.WAKE_LOCK)
+                .setPackageName(PACKAGE_NAME_WAKELOCK)
+                .build();
+        mWakeupAnomaly = new Anomaly.Builder()
+                .setType(Anomaly.AnomalyType.WAKEUP_ALARM)
+                .setPackageName(PACKAGE_NAME_WAKEUP)
+                .build();
     }
 
     @Test
@@ -97,4 +126,47 @@
         assertThat(mAnomalyUtils.getAnomalyDetector(Anomaly.AnomalyType.WAKEUP_ALARM)).isInstanceOf(
                 WakeupAlarmAnomalyDetector.class);
     }
+
+    @Test
+    public void testLogAnomaly() {
+        mAnomalyUtils.logAnomaly(mMetricsFeatureProvider, mWakeLockAnomaly, CONTEXT_ID);
+
+        verify(mMetricsFeatureProvider).action(RuntimeEnvironment.application,
+                MetricsProto.MetricsEvent.ANOMALY_TYPE_WAKELOCK,
+                PACKAGE_NAME_WAKELOCK,
+                Pair.create(
+                        MetricsProto.MetricsEvent.FIELD_CONTEXT,
+                        CONTEXT_ID),
+                Pair.create(
+                        MetricsProto.MetricsEvent.FIELD_ANOMALY_ACTION_TYPE,
+                        Anomaly.AnomalyActionType.FORCE_STOP));
+    }
+
+    @Test
+    public void testLogAnomalies() {
+        final List<Anomaly> anomalies = new ArrayList<>();
+        anomalies.add(mWakeLockAnomaly);
+        anomalies.add(mWakeupAnomaly);
+
+        mAnomalyUtils.logAnomalies(mMetricsFeatureProvider, anomalies, CONTEXT_ID);
+
+        verify(mMetricsFeatureProvider).action(RuntimeEnvironment.application,
+                MetricsProto.MetricsEvent.ANOMALY_TYPE_WAKELOCK,
+                PACKAGE_NAME_WAKELOCK,
+                Pair.create(
+                        MetricsProto.MetricsEvent.FIELD_CONTEXT,
+                        CONTEXT_ID),
+                Pair.create(
+                        MetricsProto.MetricsEvent.FIELD_ANOMALY_ACTION_TYPE,
+                        Anomaly.AnomalyActionType.FORCE_STOP));
+        verify(mMetricsFeatureProvider).action(RuntimeEnvironment.application,
+                MetricsProto.MetricsEvent.ANOMALY_TYPE_WAKEUP_ALARM,
+                PACKAGE_NAME_WAKEUP,
+                Pair.create(
+                        MetricsProto.MetricsEvent.FIELD_CONTEXT,
+                        CONTEXT_ID),
+                Pair.create(
+                        MetricsProto.MetricsEvent.FIELD_ANOMALY_ACTION_TYPE,
+                        Anomaly.AnomalyActionType.STOP_AND_BACKGROUND_CHECK));
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/search/AccessibilityServiceResultLoaderTest.java b/tests/robotests/src/com/android/settings/search/AccessibilityServiceResultFutureTaskTest.java
similarity index 83%
rename from tests/robotests/src/com/android/settings/search/AccessibilityServiceResultLoaderTest.java
rename to tests/robotests/src/com/android/settings/search/AccessibilityServiceResultFutureTaskTest.java
index 4896dc4..0e4abe1 100644
--- a/tests/robotests/src/com/android/settings/search/AccessibilityServiceResultLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/search/AccessibilityServiceResultFutureTaskTest.java
@@ -17,6 +17,7 @@
 package com.android.settings.search;
 
 import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -46,7 +47,7 @@
 
 @RunWith(SettingsRobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
-public class AccessibilityServiceResultLoaderTest {
+public class AccessibilityServiceResultFutureTaskTest {
 
     private static final String QUERY = "test_query";
 
@@ -59,7 +60,7 @@
     @Mock
     private SiteMapManager mSiteMapManager;
 
-    private AccessibilityServiceResultLoader mLoader;
+    private AccessibilityServiceResultLoader.AccessibilityServiceResultCallable mCallable;
 
     @Before
     public void setUp() {
@@ -68,19 +69,20 @@
                 .thenReturn(mAccessibilityManager);
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
 
-        mLoader = new AccessibilityServiceResultLoader(mContext, QUERY, mSiteMapManager);
+        mCallable = new AccessibilityServiceResultLoader.AccessibilityServiceResultCallable(
+                mContext, QUERY, mSiteMapManager);
     }
 
     @Test
-    public void query_noService_shouldNotReturnAnything() {
-        assertThat(mLoader.loadInBackground()).isEmpty();
+    public void query_noService_shouldNotReturnAnything() throws Exception {
+        assertThat(mCallable.call()).isEmpty();
     }
 
     @Test
-    public void query_hasServiceMatchingTitle_shouldReturnResult() {
+    public void query_hasServiceMatchingTitle_shouldReturnResult() throws Exception {
         addFakeAccessibilityService();
 
-        List<? extends SearchResult> results = new ArrayList<>(mLoader.loadInBackground());
+        List<? extends SearchResult> results = mCallable.call();
         assertThat(results).hasSize(1);
 
         SearchResult result = results.get(0);
@@ -88,13 +90,14 @@
     }
 
     @Test
-    public void query_serviceDoesNotMatchTitle_shouldReturnResult() {
+    public void query_serviceDoesNotMatchTitle_shouldReturnResult() throws Exception {
         addFakeAccessibilityService();
 
-        mLoader = new AccessibilityServiceResultLoader(mContext,
+        mCallable = new AccessibilityServiceResultLoader.AccessibilityServiceResultCallable(
+                mContext,
                 QUERY + "no_match", mSiteMapManager);
 
-        assertThat(mLoader.loadInBackground()).isEmpty();
+        assertThat(mCallable.call()).isEmpty();
     }
 
     private void addFakeAccessibilityService() {
diff --git a/tests/robotests/src/com/android/settings/search/InputDeviceResultLoaderTest.java b/tests/robotests/src/com/android/settings/search/InputDeviceResultFutureTaskTest.java
similarity index 87%
rename from tests/robotests/src/com/android/settings/search/InputDeviceResultLoaderTest.java
rename to tests/robotests/src/com/android/settings/search/InputDeviceResultFutureTaskTest.java
index a955af1..e31b3d7 100644
--- a/tests/robotests/src/com/android/settings/search/InputDeviceResultLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/search/InputDeviceResultFutureTaskTest.java
@@ -17,9 +17,11 @@
 package com.android.settings.search;
 
 import static android.content.Context.INPUT_METHOD_SERVICE;
+
 import static com.android.settings.search.InputDeviceResultLoader.PHYSICAL_KEYBOARD_FRAGMENT;
 import static com.android.settings.search.InputDeviceResultLoader.VIRTUAL_KEYBOARD_FRAGMENT;
 import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verifyZeroInteractions;
@@ -58,7 +60,7 @@
         shadows = {
                 ShadowInputDevice.class
         })
-public class InputDeviceResultLoaderTest {
+public class InputDeviceResultFutureTaskTest {
 
     private static final String QUERY = "test_query";
     private static final List<String> PHYSICAL_KEYBOARD_BREADCRUMB;
@@ -84,7 +86,7 @@
     @Mock
     private PackageManager mPackageManager;
 
-    private InputDeviceResultLoader mLoader;
+    private InputDeviceResultLoader.InputDeviceResultCallable mCallable;
 
     @Before
     public void setUp() {
@@ -99,7 +101,8 @@
         when(mContext.getString(anyInt()))
                 .thenAnswer(invocation -> RuntimeEnvironment.application.getString(
                         (Integer) invocation.getArguments()[0]));
-        mLoader = new InputDeviceResultLoader(mContext, QUERY, mSiteMapManager);
+        mCallable = new InputDeviceResultLoader.InputDeviceResultCallable(mContext, QUERY,
+                mSiteMapManager);
     }
 
     @After
@@ -108,18 +111,19 @@
     }
 
     @Test
-    public void query_noKeyboard_shouldNotReturnAnything() {
-        assertThat(mLoader.loadInBackground()).isEmpty();
+    public void query_noKeyboard_shouldNotReturnAnything() throws Exception {
+
+        assertThat(mCallable.call()).isEmpty();
     }
 
     @Test
-    public void query_hasPhysicalKeyboard_match() {
+    public void query_hasPhysicalKeyboard_match() throws Exception {
         addPhysicalKeyboard(QUERY);
         when(mSiteMapManager.buildBreadCrumb(mContext, PHYSICAL_KEYBOARD_FRAGMENT,
                 RuntimeEnvironment.application.getString(R.string.physical_keyboard_title)))
                 .thenReturn(PHYSICAL_KEYBOARD_BREADCRUMB);
 
-        final List<SearchResult> results = new ArrayList<>(mLoader.loadInBackground());
+        final List<? extends SearchResult> results = mCallable.call();
 
         assertThat(results).hasSize(1);
         assertThat(results.get(0).title).isEqualTo(QUERY);
@@ -128,13 +132,13 @@
     }
 
     @Test
-    public void query_hasVirtualKeyboard_match() {
+    public void query_hasVirtualKeyboard_match() throws Exception {
         addVirtualKeyboard(QUERY);
         when(mSiteMapManager.buildBreadCrumb(mContext, VIRTUAL_KEYBOARD_FRAGMENT,
                 RuntimeEnvironment.application.getString(R.string.add_virtual_keyboard)))
                 .thenReturn(VIRTUAL_KEYBOARD_BREADCRUMB);
 
-        final List<SearchResult> results = new ArrayList<>(mLoader.loadInBackground());
+        final List<? extends SearchResult> results = mCallable.call();
         assertThat(results).hasSize(1);
         assertThat(results.get(0).title).isEqualTo(QUERY);
         assertThat(results.get(0).breadcrumbs)
@@ -142,11 +146,11 @@
     }
 
     @Test
-    public void query_hasPhysicalVirtualKeyboard_doNotMatch() {
+    public void query_hasPhysicalVirtualKeyboard_doNotMatch() throws Exception {
         addPhysicalKeyboard("abc");
         addVirtualKeyboard("def");
 
-        assertThat(mLoader.loadInBackground()).isEmpty();
+        assertThat(mCallable.call()).isEmpty();
         verifyZeroInteractions(mSiteMapManager);
     }
 
@@ -170,4 +174,4 @@
         when(mImm.getInputMethodList()).thenReturn(imis);
     }
 
-}
+}
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/search/InstalledAppResultLoaderTest.java b/tests/robotests/src/com/android/settings/search/InstalledAppResultLoaderTest.java
index f1a25a1..0e84dc7 100644
--- a/tests/robotests/src/com/android/settings/search/InstalledAppResultLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/search/InstalledAppResultLoaderTest.java
@@ -19,7 +19,9 @@
 
 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.anyList;
@@ -53,8 +55,6 @@
 import org.mockito.Answers;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
 import org.robolectric.annotation.Config;
 
 import java.util.ArrayList;
@@ -77,7 +77,7 @@
     @Mock
     private SiteMapManager mSiteMapManager;
 
-    private InstalledAppResultLoader mLoader;
+    private InstalledAppResultLoader.InstalledAppResultCallable mCallable;
 
     @Before
     public void setUp() {
@@ -109,49 +109,50 @@
     }
 
     @Test
-    public void query_noMatchingQuery_shouldReturnEmptyResult() {
+    public void query_noMatchingQuery_shouldReturnEmptyResult() throws Exception {
         final String query = "abc";
 
-        mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+        mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
+                mPackageManagerWrapper, query,
                 mSiteMapManager);
 
-        assertThat(mLoader.loadInBackground()).isEmpty();
+        assertThat(mCallable.call()).isEmpty();
     }
 
     @Test
-    public void query_matchingQuery_shouldReturnNonSystemApps() {
+    public void query_matchingQuery_shouldReturnNonSystemApps() throws Exception {
         final String query = "app";
 
-        mLoader = spy(new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+        mCallable = spy(new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
+                mPackageManagerWrapper, query,
                 mSiteMapManager));
-        when(mLoader.getContext()).thenReturn(mContext);
         when(mSiteMapManager.buildBreadCrumb(eq(mContext), anyString(), anyString()))
                 .thenReturn(Arrays.asList(new String[]{"123"}));
 
-        assertThat(mLoader.loadInBackground().size()).isEqualTo(3);
+        assertThat(mCallable.call()).hasSize(3);
         verify(mSiteMapManager)
                 .buildBreadCrumb(eq(mContext), anyString(), anyString());
     }
 
     @Test
-    public void query_matchingQuery_shouldReturnSystemAppUpdates() {
+    public void query_matchingQuery_shouldReturnSystemAppUpdates() throws Exception {
         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,
+        mCallable = spy(new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
+                mPackageManagerWrapper, query,
                 mSiteMapManager));
-        when(mLoader.getContext()).thenReturn(mContext);
 
-        assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
+        assertThat(mCallable.call()).hasSize(1);
         verify(mSiteMapManager)
                 .buildBreadCrumb(eq(mContext), anyString(), anyString());
     }
 
     @Test
-    public void query_matchingQuery_shouldReturnSystemAppIfLaunchable() {
+    public void query_matchingQuery_shouldReturnSystemAppIfLaunchable() throws Exception {
         when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
                 .thenReturn(Arrays.asList(
                         ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_SYSTEM,
@@ -164,14 +165,15 @@
 
         final String query = "app";
 
-        mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+        mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
+                mPackageManagerWrapper, query,
                 mSiteMapManager);
 
-        assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
+        assertThat(mCallable.call()).hasSize(1);
     }
 
     @Test
-    public void query_matchingQuery_shouldReturnSystemAppIfHomeApp() {
+    public void query_matchingQuery_shouldReturnSystemAppIfHomeApp() throws Exception {
         when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
                 .thenReturn(Arrays.asList(
                         ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_SYSTEM,
@@ -180,28 +182,26 @@
                 any(Intent.class), anyInt(), anyInt()))
                 .thenReturn(null);
 
-        when(mPackageManagerWrapper.getHomeActivities(anyList())).thenAnswer(new Answer<Object>() {
-            @Override
-            public Object answer(InvocationOnMock invocation) throws Throwable {
-                final List<ResolveInfo> list = (List<ResolveInfo>) invocation.getArguments()[0];
-                final ResolveInfo info = new ResolveInfo();
-                info.activityInfo = new ActivityInfo();
-                info.activityInfo.packageName = "app1";
-                list.add(info);
-                return null;
-            }
+        when(mPackageManagerWrapper.getHomeActivities(anyList())).thenAnswer(invocation -> {
+            final List<ResolveInfo> list = (List<ResolveInfo>) invocation.getArguments()[0];
+            final ResolveInfo info = new ResolveInfo();
+            info.activityInfo = new ActivityInfo();
+            info.activityInfo.packageName = "app1";
+            list.add(info);
+            return null;
         });
 
         final String query = "app";
 
-        mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+        mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
+                mPackageManagerWrapper, query,
                 mSiteMapManager);
 
-        assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
+        assertThat(mCallable.call()).hasSize(1);
     }
 
     @Test
-    public void query_matchingQuery_shouldNotReturnSystemAppIfNotLaunchable() {
+    public void query_matchingQuery_shouldNotReturnSystemAppIfNotLaunchable() throws Exception {
         when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
                 .thenReturn(Arrays.asList(
                         ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_SYSTEM,
@@ -212,21 +212,23 @@
 
         final String query = "app";
 
-        mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+        mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
+                mPackageManagerWrapper, query,
                 mSiteMapManager);
 
-        assertThat(mLoader.loadInBackground()).isEmpty();
+        assertThat(mCallable.call()).isEmpty();
         verify(mSiteMapManager, never())
                 .buildBreadCrumb(eq(mContext), anyString(), anyString());
     }
 
     @Test
-    public void query_matchingQuery_multipleResults() {
+    public void query_matchingQuery_multipleResults() throws Exception {
         final String query = "app";
 
-        mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+        mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
+                mPackageManagerWrapper, query,
                 mSiteMapManager);
-        final Set<? extends SearchResult> results = mLoader.loadInBackground();
+        final List<? extends SearchResult> results = mCallable.call();
 
         Set<CharSequence> expectedTitles = new HashSet<>(Arrays.asList("app4", "app", "appBuffer"));
         Set<CharSequence> actualTitles = new HashSet<>();
@@ -237,161 +239,172 @@
     }
 
     @Test
-    public void query_normalWord_MatchPrefix() {
+    public void query_normalWord_MatchPrefix() throws Exception {
         final String query = "ba";
         final String packageName = "Bananas";
         when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
                 .thenReturn(Arrays.asList(
                         ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
                                 0 /* targetSdkVersion */)));
-        mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+        mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
+                mPackageManagerWrapper, query,
                 mSiteMapManager);
 
-        assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
+        assertThat(mCallable.call()).hasSize(1);
     }
 
     @Test
-    public void query_CapitalCase_DoestMatchSecondWord() {
+    public void query_CapitalCase_DoestMatchSecondWord() throws Exception {
         final String query = "Apples";
         final String packageName = "BananasApples";
         when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
                 .thenReturn(Arrays.asList(
                         ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
                                 0 /* targetSdkVersion */)));
-        mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+        mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
+                mPackageManagerWrapper, query,
                 mSiteMapManager);
 
-        assertThat(mLoader.loadInBackground().size()).isEqualTo(0);
+        assertThat(mCallable.call()).isEmpty();
     }
 
     @Test
-    public void query_TwoWords_MatchesFirstWord() {
+    public void query_TwoWords_MatchesFirstWord() throws Exception {
         final String query = "Banana";
         final String packageName = "Bananas Apples";
         when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
                 .thenReturn(Arrays.asList(
                         ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
                                 0 /* targetSdkVersion */)));
-        mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+        mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
+                mPackageManagerWrapper, query,
                 mSiteMapManager);
 
-        assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
+        assertThat(mCallable.call()).hasSize(1);
     }
 
     @Test
-    public void query_TwoWords_MatchesSecondWord() {
+    public void query_TwoWords_MatchesSecondWord() throws Exception {
         final String query = "Apple";
         final String packageName = "Bananas Apples";
         when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
                 .thenReturn(Arrays.asList(
                         ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
                                 0 /* targetSdkVersion */)));
-        mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+        mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
+                mPackageManagerWrapper, query,
                 mSiteMapManager);
 
-        assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
+        assertThat(mCallable.call()).hasSize(1);
     }
 
     @Test
-    public void query_ThreeWords_MatchesThirdWord() {
+    public void query_ThreeWords_MatchesThirdWord() throws Exception {
         final String query = "Pear";
         final String packageName = "Bananas Apples Pears";
         when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
                 .thenReturn(Arrays.asList(
                         ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
                                 0 /* targetSdkVersion */)));
-        mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+        mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
+                mPackageManagerWrapper, query,
                 mSiteMapManager);
 
-        assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
+        assertThat(mCallable.call()).hasSize(1);
     }
 
     @Test
-    public void query_DoubleSpacedWords_MatchesSecondWord() {
+    public void query_DoubleSpacedWords_MatchesSecondWord() throws Exception {
         final String query = "Apple";
         final String packageName = "Bananas  Apples";
         when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
                 .thenReturn(Arrays.asList(
                         ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
                                 0 /* targetSdkVersion */)));
-        mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+        mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
+                mPackageManagerWrapper, query,
                 mSiteMapManager);
 
-        assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
+        assertThat(mCallable.call()).hasSize(1);
     }
 
     @Test
-    public void query_SpecialChar_MatchesSecondWord() {
+    public void query_SpecialChar_MatchesSecondWord() throws Exception {
         final String query = "Apple";
         final String packageName = "Bananas & Apples";
         when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
                 .thenReturn(Arrays.asList(
                         ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
                                 0 /* targetSdkVersion */)));
-        mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+        mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
+                mPackageManagerWrapper, query,
                 mSiteMapManager);
 
-        assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
+        assertThat(mCallable.call()).hasSize(1);
     }
 
     @Test
-    public void query_TabSeparated_MatchesSecondWord() {
+    public void query_TabSeparated_MatchesSecondWord() throws Exception {
         final String query = "Apple";
         final String packageName = "Bananas\tApples";
         when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
                 .thenReturn(Arrays.asList(
                         ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
                                 0 /* targetSdkVersion */)));
-        mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+        mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
+                mPackageManagerWrapper, query,
                 mSiteMapManager);
 
-        assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
+        assertThat(mCallable.call()).hasSize(1);
     }
 
     @Test
-    public void query_LeadingNumber_MatchesWord() {
+    public void query_LeadingNumber_MatchesWord() throws Exception {
         final String query = "4";
         final String packageName = "4Bananas";
         when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
                 .thenReturn(Arrays.asList(
                         ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
                                 0 /* targetSdkVersion */)));
-        mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+        mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
+                mPackageManagerWrapper, query,
                 mSiteMapManager);
 
-        assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
+        assertThat(mCallable.call()).hasSize(1);
     }
 
     @Test
-    public void query_FirstWordPrefixOfQuery_NoMatch() {
+    public void query_FirstWordPrefixOfQuery_NoMatch() throws Exception {
         final String query = "Bananass";
         final String packageName = "Bananas Apples";
         when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
                 .thenReturn(Arrays.asList(
                         ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
                                 0 /* targetSdkVersion */)));
-        mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+        mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
+                mPackageManagerWrapper, query,
                 mSiteMapManager);
 
-        assertThat(mLoader.loadInBackground().size()).isEqualTo(0);
+        assertThat(mCallable.call()).isEmpty();
     }
 
     @Test
-    public void query_QueryLongerThanAppName_NoMatch() {
+    public void query_QueryLongerThanAppName_NoMatch() throws Exception {
         final String query = "BananasApples";
         final String packageName = "Bananas";
         when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
                 .thenReturn(Arrays.asList(
                         ApplicationTestUtils.buildInfo(0 /* uid */, packageName, 0 /* flags */,
                                 0 /* targetSdkVersion */)));
-        mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+        mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
+                mPackageManagerWrapper, query,
                 mSiteMapManager);
 
-        assertThat(mLoader.loadInBackground().size()).isEqualTo(0);
+        assertThat(mCallable.call()).isEmpty();
     }
 
     @Test
-    public void query_appExistsInBothProfiles() {
+    public void query_appExistsInBothProfiles() throws Exception {
         final String query = "carrot";
         final String packageName = "carrot";
         final int user1 = 0;
@@ -414,10 +427,11 @@
                                 packageName, 0 /* flags */,
                                 0 /* targetSdkVersion */)));
 
-        mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
+        mCallable = new InstalledAppResultLoader.InstalledAppResultCallable(mContext,
+                mPackageManagerWrapper, query,
                 mSiteMapManager);
 
-        Set<AppSearchResult> searchResults = (Set<AppSearchResult>) mLoader.loadInBackground();
+        List<AppSearchResult> searchResults = (List<AppSearchResult>) mCallable.call();
         assertThat(searchResults).hasSize(2);
 
         Set<Integer> uidResults = searchResults.stream().map(result -> result.info.uid).collect(
diff --git a/tests/robotests/src/com/android/settings/search/MockAccessibilityLoader.java b/tests/robotests/src/com/android/settings/search/MockAccessibilityLoader.java
deleted file mode 100644
index 0a06a35..0000000
--- a/tests/robotests/src/com/android/settings/search/MockAccessibilityLoader.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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.search;
-
-import android.content.Context;
-
-import java.util.HashSet;
-import java.util.Set;
-
-public class MockAccessibilityLoader extends AccessibilityServiceResultLoader {
-
-    public MockAccessibilityLoader(Context context) {
-        super(context, "test_query", null);
-    }
-
-    @Override
-    public Set<? extends SearchResult> loadInBackground() {
-        return new HashSet<>();
-    }
-
-    @Override
-    protected void onDiscardResult(Set<? extends SearchResult> result) {
-
-    }
-}
diff --git a/tests/robotests/src/com/android/settings/search/MockAppLoader.java b/tests/robotests/src/com/android/settings/search/MockAppLoader.java
deleted file mode 100644
index c68cbdf..0000000
--- a/tests/robotests/src/com/android/settings/search/MockAppLoader.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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.search;
-
-import android.content.Context;
-import com.android.settings.search.InstalledAppResultLoader;
-import com.android.settings.search.SearchResult;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Mock loader to subvert the requirements of returning data while also driving the Loader
- * lifecycle.
- */
-class MockAppLoader extends InstalledAppResultLoader {
-
-    public MockAppLoader(Context context) {
-        super(context, null, "", null);
-    }
-
-    @Override
-    public Set<? extends SearchResult> loadInBackground() {
-        return new HashSet<>();
-    }
-
-    @Override
-    protected void onDiscardResult(Set<? extends SearchResult> result) {
-
-    }
-}
diff --git a/tests/robotests/src/com/android/settings/search/MockDBLoader.java b/tests/robotests/src/com/android/settings/search/MockDBLoader.java
deleted file mode 100644
index b28c1ed..0000000
--- a/tests/robotests/src/com/android/settings/search/MockDBLoader.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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.search;
-
-import android.content.Context;
-import com.android.settings.search.DatabaseResultLoader;
-import com.android.settings.search.SearchResult;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Mock loader to subvert the requirements of returning data while also driving the Loader
- * lifecycle.
- */
-class MockDBLoader extends DatabaseResultLoader {
-
-    public MockDBLoader(Context context) {
-        super(context, "test", null);
-    }
-
-    @Override
-    public Set<? extends SearchResult> loadInBackground() {
-        return new HashSet<>();
-    }
-
-    @Override
-    protected void onDiscardResult(Set<? extends SearchResult> result) {
-
-    }
-}
diff --git a/tests/robotests/src/com/android/settings/search/MockInputDeviceResultLoader.java b/tests/robotests/src/com/android/settings/search/MockInputDeviceResultLoader.java
deleted file mode 100644
index 2c16b14..0000000
--- a/tests/robotests/src/com/android/settings/search/MockInputDeviceResultLoader.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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.search;
-
-import android.content.Context;
-
-import java.util.HashSet;
-import java.util.Set;
-
-public class MockInputDeviceResultLoader extends InputDeviceResultLoader {
-    public MockInputDeviceResultLoader(Context context) {
-        super(context, "test_query", null);
-    }
-
-    @Override
-    public Set<? extends SearchResult> loadInBackground() {
-        return new HashSet<>();
-    }
-
-    @Override
-    protected void onDiscardResult(Set<? extends SearchResult> result) {
-
-    }
-}
diff --git a/tests/robotests/src/com/android/settings/search/MockSearchResultLoader.java b/tests/robotests/src/com/android/settings/search/MockSearchResultLoader.java
new file mode 100644
index 0000000..6af258a
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/search/MockSearchResultLoader.java
@@ -0,0 +1,29 @@
+package com.android.settings.search;
+
+import android.content.Context;
+
+import com.android.settings.search.SearchResult;
+import com.android.settings.search.SearchResultLoader;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Mock loader to subvert the requirements of returning data while also driving the Loader
+ * lifecycle.
+ */
+public class MockSearchResultLoader extends SearchResultLoader {
+
+    public MockSearchResultLoader(Context context) {
+        super(context, "test");
+    }
+
+    @Override
+    public List<? extends SearchResult> loadInBackground() {
+        return new ArrayList<>();
+    }
+
+    @Override
+    protected void onDiscardResult(List<? extends SearchResult> result) {
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java
index 050d7aa..a529b0b 100644
--- a/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java
+++ b/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java
@@ -21,7 +21,6 @@
 
 import android.app.Activity;
 import android.content.ComponentName;
-
 import com.android.settings.TestConfig;
 import com.android.settings.dashboard.SiteMapManager;
 import com.android.settings.testutils.SettingsRobolectricTestRunner;
@@ -33,6 +32,10 @@
 import org.robolectric.Robolectric;
 import org.robolectric.annotation.Config;
 
+import static org.mockito.ArgumentMatchers.eq;
+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 SearchFeatureProviderImplTest {
@@ -43,7 +46,7 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mActivity = Robolectric.buildActivity(Activity.class).create().visible().get();
-        mProvider = new SearchFeatureProviderImpl();
+        mProvider = spy(new SearchFeatureProviderImpl());
     }
 
     @Test
@@ -57,18 +60,19 @@
     @Test
     public void getDatabaseSearchLoader_shouldCleanupQuery() {
         final String query = "  space ";
-        final DatabaseResultLoader loader = mProvider.getDatabaseSearchLoader(mActivity, query);
 
-        assertThat(loader.mQueryText).isEqualTo(query.trim());
+        mProvider.getStaticSearchResultTask(mActivity, query);
+
+        verify(mProvider).cleanQuery(eq(query));
     }
 
     @Test
     public void getInstalledAppSearchLoader_shouldCleanupQuery() {
         final String query = "  space ";
-        final InstalledAppResultLoader loader =
-                mProvider.getInstalledAppSearchLoader(mActivity, query);
 
-        assertThat(loader.mQuery).isEqualTo(query.trim());
+        mProvider.getInstalledAppSearchTask(mActivity, query);
+
+        verify(mProvider).cleanQuery(eq(query));
     }
 
     @Test(expected = IllegalArgumentException.class)
@@ -87,4 +91,12 @@
         final ComponentName cn = new ComponentName(mActivity.getPackageName(), "class");
         mProvider.verifyLaunchSearchResultPageCaller(mActivity, cn);
     }
+
+    @Test
+    public void cleanQuery_trimsWhitespace() {
+        final String query = "  space ";
+        final String cleanQuery = "space";
+
+        assertThat(mProvider.cleanQuery(query)).isEqualTo(cleanQuery);
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/search/SearchFragmentTest.java b/tests/robotests/src/com/android/settings/search/SearchFragmentTest.java
index b897008..a3f3334 100644
--- a/tests/robotests/src/com/android/settings/search/SearchFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/search/SearchFragmentTest.java
@@ -18,6 +18,7 @@
 package com.android.settings.search;
 
 import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
@@ -54,9 +55,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Answers;
-import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatcher;
-import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.Robolectric;
@@ -65,7 +64,7 @@
 import org.robolectric.annotation.Config;
 import org.robolectric.util.ReflectionHelpers;
 
-import java.util.Set;
+import java.util.List;
 
 @RunWith(SettingsRobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH,
@@ -79,22 +78,13 @@
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private Context mContext;
     @Mock
-    private DatabaseResultLoader mDatabaseResultLoader;
-    @Mock
-    private InstalledAppResultLoader mInstalledAppResultLoader;
-    @Mock
-    private AccessibilityServiceResultLoader mAccessibilityServiceResultLoader;
-    @Mock
-    private InputDeviceResultLoader mInputDeviceResultLoader;
-
+    private SearchResultLoader mSearchResultLoader;
     @Mock
     private SavedQueryLoader mSavedQueryLoader;
     @Mock
     private SavedQueryController mSavedQueryController;
     @Mock
     private SearchResultsAdapter mSearchResultsAdapter;
-    @Captor
-    private ArgumentCaptor<String> mQueryCaptor = ArgumentCaptor.forClass(String.class);
 
     private FakeFeatureFactory mFeatureFactory;
 
@@ -113,17 +103,8 @@
     @Test
     public void screenRotate_shouldPersistQuery() {
         when(mFeatureFactory.searchFeatureProvider
-                .getDatabaseSearchLoader(any(Context.class), anyString()))
-                .thenReturn(mDatabaseResultLoader);
-        when(mFeatureFactory.searchFeatureProvider
-                .getInstalledAppSearchLoader(any(Context.class), anyString()))
-                .thenReturn(mInstalledAppResultLoader);
-        when(mFeatureFactory.searchFeatureProvider
-                .getAccessibilityServiceResultLoader(any(Context.class), anyString()))
-                .thenReturn(mAccessibilityServiceResultLoader);
-        when(mFeatureFactory.searchFeatureProvider
-                .getInputDeviceResultLoader(any(Context.class), anyString()))
-                .thenReturn(mInputDeviceResultLoader);
+                .getSearchResultLoader(any(Context.class), anyString()))
+                .thenReturn(new MockSearchResultLoader(RuntimeEnvironment.application));
         when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
                 .thenReturn(mSavedQueryLoader);
 
@@ -168,25 +149,16 @@
         activityController.setup(bundle);
 
         verify(mFeatureFactory.searchFeatureProvider, never())
-                .getDatabaseSearchLoader(any(Context.class), anyString());
+                .getStaticSearchResultTask(any(Context.class), anyString());
         verify(mFeatureFactory.searchFeatureProvider, never())
-                .getInstalledAppSearchLoader(any(Context.class), anyString());
+                .getInstalledAppSearchTask(any(Context.class), anyString());
     }
 
     @Test
-    public void queryTextChange_shouldTriggerLoaderAndInitializeSearch() {
+    public void queryTextChange_shouldTriggerLoader() {
         when(mFeatureFactory.searchFeatureProvider
-                .getDatabaseSearchLoader(any(Context.class), anyString()))
-                .thenReturn(mDatabaseResultLoader);
-        when(mFeatureFactory.searchFeatureProvider
-                .getInstalledAppSearchLoader(any(Context.class), anyString()))
-                .thenReturn(mInstalledAppResultLoader);
-        when(mFeatureFactory.searchFeatureProvider
-                .getAccessibilityServiceResultLoader(any(Context.class), anyString()))
-                .thenReturn(mAccessibilityServiceResultLoader);
-        when(mFeatureFactory.searchFeatureProvider
-                .getInputDeviceResultLoader(any(Context.class), anyString()))
-                .thenReturn(mInputDeviceResultLoader);
+                .getSearchResultLoader(any(Context.class), anyString()))
+                .thenReturn(mSearchResultLoader);
         when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
                 .thenReturn(mSavedQueryLoader);
 
@@ -199,7 +171,6 @@
         when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
                 .thenReturn(true);
 
-        ReflectionHelpers.setField(fragment, "mSearchAdapter", mSearchResultsAdapter);
         fragment.onQueryTextChange(testQuery);
         activityController.get().onBackPressed();
 
@@ -209,11 +180,7 @@
                 any(Context.class),
                 eq(MetricsProto.MetricsEvent.ACTION_LEAVE_SEARCH_RESULT_WITHOUT_QUERY));
         verify(mFeatureFactory.searchFeatureProvider)
-                .getDatabaseSearchLoader(any(Context.class), anyString());
-        verify(mFeatureFactory.searchFeatureProvider)
-                .getInstalledAppSearchLoader(any(Context.class), anyString());
-        verify(mSearchResultsAdapter).initializeSearch(mQueryCaptor.capture());
-        assertThat(mQueryCaptor.getValue()).isEqualTo(testQuery);
+                .getSearchResultLoader(any(Context.class), anyString());
     }
 
     @Test
@@ -238,18 +205,6 @@
 
     @Test
     public void queryTextChangeToEmpty_shouldLoadSavedQueryAndNotInitializeSearch() {
-        when(mFeatureFactory.searchFeatureProvider
-                .getDatabaseSearchLoader(any(Context.class), anyString()))
-                .thenReturn(mDatabaseResultLoader);
-        when(mFeatureFactory.searchFeatureProvider
-                .getInstalledAppSearchLoader(any(Context.class), anyString()))
-                .thenReturn(mInstalledAppResultLoader);
-        when(mFeatureFactory.searchFeatureProvider
-                .getAccessibilityServiceResultLoader(any(Context.class), anyString()))
-                .thenReturn(mAccessibilityServiceResultLoader);
-        when(mFeatureFactory.searchFeatureProvider
-                .getInputDeviceResultLoader(any(Context.class), anyString()))
-                .thenReturn(mInputDeviceResultLoader);
         when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
                 .thenReturn(mSavedQueryLoader);
         ActivityController<SearchActivity> activityController =
@@ -266,27 +221,14 @@
         fragment.onQueryTextChange("");
 
         verify(mFeatureFactory.searchFeatureProvider, never())
-                .getDatabaseSearchLoader(any(Context.class), anyString());
+                .getStaticSearchResultTask(any(Context.class), anyString());
         verify(mFeatureFactory.searchFeatureProvider, never())
-                .getInstalledAppSearchLoader(any(Context.class), anyString());
+                .getInstalledAppSearchTask(any(Context.class), anyString());
         verify(mSavedQueryController).loadSavedQueries();
-        verify(mSearchResultsAdapter, never()).initializeSearch(anyString());
     }
 
     @Test
     public void updateIndex_TriggerOnCreate() {
-        when(mFeatureFactory.searchFeatureProvider
-                .getDatabaseSearchLoader(any(Context.class), anyString()))
-                .thenReturn(mDatabaseResultLoader);
-        when(mFeatureFactory.searchFeatureProvider
-                .getInstalledAppSearchLoader(any(Context.class), anyString()))
-                .thenReturn(mInstalledAppResultLoader);
-        when(mFeatureFactory.searchFeatureProvider
-                .getAccessibilityServiceResultLoader(any(Context.class), anyString()))
-                .thenReturn(mAccessibilityServiceResultLoader);
-        when(mFeatureFactory.searchFeatureProvider
-                .getInputDeviceResultLoader(any(Context.class), anyString()))
-                .thenReturn(mInputDeviceResultLoader);
         when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
                 .thenReturn(mSavedQueryLoader);
 
@@ -304,40 +246,10 @@
     }
 
     @Test
-    public void syncLoaders_MergeWhenAllLoadersDone() {
-        when(mFeatureFactory.searchFeatureProvider
-                .getDatabaseSearchLoader(any(Context.class), anyString()))
-                .thenReturn(new MockDBLoader(RuntimeEnvironment.application));
-        when(mFeatureFactory.searchFeatureProvider
-                .getInstalledAppSearchLoader(any(Context.class), anyString()))
-                .thenReturn(new MockAppLoader(RuntimeEnvironment.application));
-        when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
-                .thenReturn(mSavedQueryLoader);
-
-        ActivityController<SearchActivity> activityController =
-                Robolectric.buildActivity(SearchActivity.class);
-        activityController.setup();
-
-        SearchFragment fragment = (SearchFragment) spy(activityController.get().getFragmentManager()
-                .findFragmentById(R.id.main_content));
-        when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
-                .thenReturn(true);
-
-        fragment.onQueryTextChange("non-empty");
-
-        Robolectric.flushForegroundThreadScheduler();
-
-        verify(fragment, times(2)).onLoadFinished(any(Loader.class), any(Set.class));
-    }
-
-    @Test
     public void whenNoQuery_HideFeedbackIsCalled() {
         when(mFeatureFactory.searchFeatureProvider
-                .getDatabaseSearchLoader(any(Context.class), anyString()))
-                .thenReturn(new MockDBLoader(RuntimeEnvironment.application));
-        when(mFeatureFactory.searchFeatureProvider
-                .getInstalledAppSearchLoader(any(Context.class), anyString()))
-                .thenReturn(new MockAppLoader(RuntimeEnvironment.application));
+                .getSearchResultLoader(any(Context.class), anyString()))
+                .thenReturn(new MockSearchResultLoader(RuntimeEnvironment.application));
         when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
                 .thenReturn(mSavedQueryLoader);
 
@@ -359,17 +271,8 @@
     @Test
     public void onLoadFinished_ShowsFeedback() {
         when(mFeatureFactory.searchFeatureProvider
-                .getDatabaseSearchLoader(any(Context.class), anyString()))
-                .thenReturn(new MockDBLoader(RuntimeEnvironment.application));
-        when(mFeatureFactory.searchFeatureProvider
-                .getInstalledAppSearchLoader(any(Context.class), anyString()))
-                .thenReturn(new MockAppLoader(RuntimeEnvironment.application));
-        when(mFeatureFactory.searchFeatureProvider
-                .getAccessibilityServiceResultLoader(any(Context.class), anyString()))
-                .thenReturn(new MockAccessibilityLoader(RuntimeEnvironment.application));
-        when(mFeatureFactory.searchFeatureProvider
-                .getInputDeviceResultLoader(any(Context.class), anyString()))
-                .thenReturn(new MockInputDeviceResultLoader(RuntimeEnvironment.application));
+                .getSearchResultLoader(any(Context.class), anyString()))
+                .thenReturn(new MockSearchResultLoader(RuntimeEnvironment.application));
         when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
                 .thenReturn(mSavedQueryLoader);
         ActivityController<SearchActivity> activityController =
@@ -413,9 +316,7 @@
 
         fragment.onIndexingFinished();
 
-        verify(loaderManager).initLoader(eq(SearchFragment.SearchLoaderId.DATABASE),
-                eq(null), any(LoaderManager.LoaderCallbacks.class));
-        verify(loaderManager).initLoader(eq(SearchFragment.SearchLoaderId.INSTALLED_APPS),
+        verify(loaderManager).initLoader(eq(SearchFragment.SearchLoaderId.SEARCH_RESULT),
                 eq(null), any(LoaderManager.LoaderCallbacks.class));
     }
 
@@ -480,16 +381,13 @@
                 eq("test_setting"),
                 argThat(pairMatches(MetricsProto.MetricsEvent.FIELD_SETTINGS_SEARCH_RESULT_COUNT)),
                 argThat(pairMatches(MetricsProto.MetricsEvent.FIELD_SETTINGS_SEARCH_RESULT_RANK)),
-                argThat(pairMatches(MetricsProto.MetricsEvent
-                                .FIELD_SETTINGS_SEARCH_RESULT_ASYNC_RANKING_STATE)),
                 argThat(pairMatches(MetricsProto.MetricsEvent.FIELD_SETTINGS_SEARCH_QUERY_LENGTH)));
-
         verify(mFeatureFactory.searchFeatureProvider).searchResultClicked(nullable(Context.class),
                 nullable(String.class), eq(searchResult));
     }
 
     @Test
-    public void onResume_shouldCallSearchRankingWarmupIfSmartSearchRankingEnabled(){
+    public void onResume_shouldCallSearchRankingWarmupIfSmartSearchRankingEnabled() {
         when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(any(Context.class)))
                 .thenReturn(true);
 
@@ -504,7 +402,7 @@
     }
 
     @Test
-    public void onResume_shouldNotCallSearchRankingWarmupIfSmartSearchRankingDisabled(){
+    public void onResume_shouldNotCallSearchRankingWarmupIfSmartSearchRankingDisabled() {
         when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(any(Context.class)))
                 .thenReturn(false);
 
diff --git a/tests/robotests/src/com/android/settings/search/SearchResultAggregatorTest.java b/tests/robotests/src/com/android/settings/search/SearchResultAggregatorTest.java
new file mode 100644
index 0000000..286d7cf9
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/search/SearchResultAggregatorTest.java
@@ -0,0 +1,271 @@
+package com.android.settings.search;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.Intent;
+
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+
+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 java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SearchResultAggregatorTest {
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+
+    private FakeFeatureFactory mFeatureFactory;
+
+    private SearchResultAggregator mAggregator;
+
+    @Mock
+    private DatabaseResultLoader mStaticTask;
+    @Mock
+    private InstalledAppResultLoader mAppTask;
+    @Mock
+    private InputDeviceResultLoader mInputTask;
+    @Mock
+    private AccessibilityServiceResultLoader mMAccessibilityTask;
+    @Mock
+    private ExecutorService mService;
+
+
+    private String[] DB_TITLES = {"static_one", "static_two"};
+    private String[] INPUT_TITLES = {"input_one", "input_two"};
+    private String[] ACCESS_TITLES = {"access_one", "access_two"};
+    private String[] APP_TITLES = {"app_one", "app_two"};
+
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mAggregator = spy(SearchResultAggregator.getInstance());
+        FakeFeatureFactory.setupForTest(mContext);
+        mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
+
+        // Return mock loaders from feature provider
+        when(mFeatureFactory.searchFeatureProvider.getStaticSearchResultTask(any(Context.class),
+                anyString())).thenReturn(mStaticTask);
+        when(mFeatureFactory.searchFeatureProvider.getInstalledAppSearchTask(any(Context.class),
+                anyString())).thenReturn(mAppTask);
+        when(mFeatureFactory.searchFeatureProvider.getInputDeviceResultTask(any(Context.class),
+                anyString())).thenReturn(mInputTask);
+        when(mFeatureFactory.searchFeatureProvider.getAccessibilityServiceResultTask(
+                any(Context.class),
+                anyString())).thenReturn(mMAccessibilityTask);
+        when(mFeatureFactory.searchFeatureProvider.getExecutorService()).thenReturn(mService);
+
+        // Return fake data from the loaders
+        List<? extends SearchResult> dbResults = getDummyDbResults();
+        doReturn(dbResults).when(mStaticTask).get(anyLong(), any(TimeUnit.class));
+
+        List<? extends SearchResult> appResults = getDummyAppResults();
+        doReturn(appResults).when(mAppTask).get(anyLong(), any(TimeUnit.class));
+
+        List<? extends SearchResult> inputResults = getDummyInputDeviceResults();
+        doReturn(inputResults).when(mInputTask).get(anyLong(), any(TimeUnit.class));
+
+        List<? extends SearchResult> accessResults = getDummyAccessibilityResults();
+        doReturn(accessResults).when(mMAccessibilityTask).get(anyLong(), any(TimeUnit.class));
+    }
+
+    @Test
+    public void testStaticResults_mergedProperly() {
+        when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(mContext))
+                .thenReturn(false);
+
+        List<? extends SearchResult> results = mAggregator.fetchResults(mContext, "test");
+
+        assertThat(results).hasSize(8);
+        assertThat(results.get(0).title).isEqualTo(DB_TITLES[0]);
+        assertThat(results.get(1).title).isEqualTo(DB_TITLES[1]);
+        assertThat(results.get(2).title).isEqualTo(APP_TITLES[0]);
+        assertThat(results.get(3).title).isEqualTo(ACCESS_TITLES[0]);
+        assertThat(results.get(4).title).isEqualTo(INPUT_TITLES[0]);
+        assertThat(results.get(5).title).isEqualTo(APP_TITLES[1]);
+        assertThat(results.get(6).title).isEqualTo(ACCESS_TITLES[1]);
+        assertThat(results.get(7).title).isEqualTo(INPUT_TITLES[1]);
+    }
+
+    @Test
+    public void testStaticRanking_staticThrowsException_dbResultsAreMissing() throws Exception {
+        when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(mContext))
+                .thenReturn(false);
+        when(mStaticTask.get(anyLong(), any(TimeUnit.class))).thenThrow(new InterruptedException());
+
+        List<? extends SearchResult> results = mAggregator.fetchResults(mContext, "test");
+
+        assertThat(results).hasSize(6);
+        assertThat(results.get(0).title).isEqualTo(APP_TITLES[0]);
+        assertThat(results.get(1).title).isEqualTo(ACCESS_TITLES[0]);
+        assertThat(results.get(2).title).isEqualTo(INPUT_TITLES[0]);
+        assertThat(results.get(3).title).isEqualTo(APP_TITLES[1]);
+        assertThat(results.get(4).title).isEqualTo(ACCESS_TITLES[1]);
+        assertThat(results.get(5).title).isEqualTo(INPUT_TITLES[1]);
+    }
+
+    @Test
+    public void testStaticRanking_appsThrowException_appResultsAreMissing() throws Exception {
+        when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(mContext))
+                .thenReturn(false);
+        when(mAppTask.get(anyLong(), any(TimeUnit.class))).thenThrow(new InterruptedException());
+
+        List<? extends SearchResult> results = mAggregator.fetchResults(mContext, "test");
+
+        assertThat(results).hasSize(6);
+        assertThat(results.get(0).title).isEqualTo(DB_TITLES[0]);
+        assertThat(results.get(1).title).isEqualTo(DB_TITLES[1]);
+        assertThat(results.get(2).title).isEqualTo(ACCESS_TITLES[0]);
+        assertThat(results.get(3).title).isEqualTo(INPUT_TITLES[0]);
+        assertThat(results.get(4).title).isEqualTo(ACCESS_TITLES[1]);
+        assertThat(results.get(5).title).isEqualTo(INPUT_TITLES[1]);
+    }
+
+    @Test
+    public void testStaticRanking_inputThrowException_inputResultsAreMissing() throws Exception {
+        when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(mContext))
+                .thenReturn(false);
+        when(mInputTask.get(anyLong(), any(TimeUnit.class))).thenThrow(new InterruptedException());
+
+        List<? extends SearchResult> results = mAggregator.fetchResults(mContext, "test");
+
+        assertThat(results).hasSize(6);
+        assertThat(results.get(0).title).isEqualTo(DB_TITLES[0]);
+        assertThat(results.get(1).title).isEqualTo(DB_TITLES[1]);
+        assertThat(results.get(2).title).isEqualTo(APP_TITLES[0]);
+        assertThat(results.get(3).title).isEqualTo(ACCESS_TITLES[0]);
+        assertThat(results.get(4).title).isEqualTo(APP_TITLES[1]);
+        assertThat(results.get(5).title).isEqualTo(ACCESS_TITLES[1]);
+    }
+
+    @Test
+    public void testStaticRanking_accessThrowException_accessResultsAreMissing() throws Exception {
+        when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(mContext))
+                .thenReturn(false);
+        when(mMAccessibilityTask.get(anyLong(), any(TimeUnit.class))).thenThrow(
+                new InterruptedException());
+
+        List<? extends SearchResult> results = mAggregator.fetchResults(mContext, "test");
+
+        assertThat(results).hasSize(6);
+        assertThat(results.get(0).title).isEqualTo(DB_TITLES[0]);
+        assertThat(results.get(1).title).isEqualTo(DB_TITLES[1]);
+        assertThat(results.get(2).title).isEqualTo(APP_TITLES[0]);
+        assertThat(results.get(3).title).isEqualTo(INPUT_TITLES[0]);
+        assertThat(results.get(4).title).isEqualTo(APP_TITLES[1]);
+        assertThat(results.get(5).title).isEqualTo(INPUT_TITLES[1]);
+    }
+
+    @Test
+    public void testDynamicRanking_sortsWithDynamicRanking() {
+        when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(
+                true);
+
+        List<? extends SearchResult> results = mAggregator.fetchResults(mContext, "test");
+
+        assertThat(results).hasSize(8);
+        assertThat(results.get(0).title).isEqualTo(DB_TITLES[0]);
+        assertThat(results.get(1).title).isEqualTo(DB_TITLES[1]);
+        assertThat(results.get(2).title).isEqualTo(APP_TITLES[0]);
+        assertThat(results.get(3).title).isEqualTo(ACCESS_TITLES[0]);
+        assertThat(results.get(4).title).isEqualTo(INPUT_TITLES[0]);
+        assertThat(results.get(5).title).isEqualTo(APP_TITLES[1]);
+        assertThat(results.get(6).title).isEqualTo(ACCESS_TITLES[1]);
+        assertThat(results.get(7).title).isEqualTo(INPUT_TITLES[1]);
+    }
+
+    private List<? extends SearchResult> getDummyDbResults() {
+        List<SearchResult> results = new ArrayList<>();
+        ResultPayload payload = new ResultPayload(new Intent());
+        SearchResult.Builder builder = new SearchResult.Builder();
+        builder.setPayload(payload)
+                .setTitle(DB_TITLES[0])
+                .setRank(1)
+                .setStableId(Objects.hash(DB_TITLES[0], "db"));
+        results.add(builder.build());
+
+        builder.setTitle(DB_TITLES[1])
+                .setRank(2)
+                .setStableId(Objects.hash(DB_TITLES[1], "db"));
+        results.add(builder.build());
+
+        return results;
+    }
+
+    private List<? extends SearchResult> getDummyAppResults() {
+        List<AppSearchResult> results = new ArrayList<>();
+        ResultPayload payload = new ResultPayload(new Intent());
+        AppSearchResult.Builder builder = new AppSearchResult.Builder();
+        builder.setPayload(payload)
+                .setTitle(APP_TITLES[0])
+                .setRank(1)
+                .setStableId(Objects.hash(APP_TITLES[0], "app"));
+        results.add(builder.build());
+
+        builder.setTitle(APP_TITLES[1])
+                .setRank(2)
+                .setStableId(Objects.hash(APP_TITLES[1], "app"));
+        results.add(builder.build());
+
+        return results;
+    }
+
+    public List<? extends SearchResult> getDummyInputDeviceResults() {
+        List<SearchResult> results = new ArrayList<>();
+        ResultPayload payload = new ResultPayload(new Intent());
+        AppSearchResult.Builder builder = new AppSearchResult.Builder();
+        builder.setPayload(payload)
+                .setTitle(INPUT_TITLES[0])
+                .setRank(1)
+                .setStableId(Objects.hash(INPUT_TITLES[0], "app"));
+        results.add(builder.build());
+
+        builder.setTitle(INPUT_TITLES[1])
+                .setRank(2)
+                .setStableId(Objects.hash(INPUT_TITLES[1], "app"));
+        results.add(builder.build());
+
+        return results;
+    }
+
+    public List<? extends SearchResult> getDummyAccessibilityResults() {
+        List<SearchResult> results = new ArrayList<>();
+        ResultPayload payload = new ResultPayload(new Intent());
+        AppSearchResult.Builder builder = new AppSearchResult.Builder();
+        builder.setPayload(payload)
+                .setTitle(ACCESS_TITLES[0])
+                .setRank(1)
+                .setStableId(Objects.hash(ACCESS_TITLES[0], "app"));
+        results.add(builder.build());
+
+        builder.setTitle(ACCESS_TITLES[1])
+                .setRank(2)
+                .setStableId(Objects.hash(ACCESS_TITLES[1], "app"));
+        results.add(builder.build());
+
+        return results;
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/search/SearchResultsAdapterTest.java b/tests/robotests/src/com/android/settings/search/SearchResultsAdapterTest.java
index a2afb35..4baf8d2 100644
--- a/tests/robotests/src/com/android/settings/search/SearchResultsAdapterTest.java
+++ b/tests/robotests/src/com/android/settings/search/SearchResultsAdapterTest.java
@@ -18,44 +18,32 @@
 package com.android.settings.search;
 
 import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.never;
-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.graphics.drawable.Drawable;
-import android.util.Pair;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
-import com.android.settings.R;
 import com.android.settings.TestConfig;
-import com.android.settings.search.SearchResult.Builder;
-import com.android.settings.search.ranking.SearchResultsRankerCallback;
 import com.android.settings.testutils.SettingsRobolectricTestRunner;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.Robolectric;
 import org.robolectric.annotation.Config;
-import org.robolectric.shadows.ShadowLooper;
 
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
-import java.util.Set;
 
 @RunWith(SettingsRobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
@@ -67,25 +55,18 @@
     private SearchFeatureProvider mSearchFeatureProvider;
     @Mock
     private Context mMockContext;
-    @Captor
-    private ArgumentCaptor<Integer> mSearchResultsCountCaptor =
-            ArgumentCaptor.forClass(Integer.class);
     private SearchResultsAdapter mAdapter;
     private Context mContext;
-    private String mLoaderClassName;
-
-    private String[] TITLES = {"alpha", "bravo", "charlie", "appAlpha", "appBravo", "appCharlie"};
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mContext = Robolectric.buildActivity(Activity.class).get();
-        mLoaderClassName = DatabaseResultLoader.class.getName();
         when(mFragment.getContext()).thenReturn(mMockContext);
         when(mMockContext.getApplicationContext()).thenReturn(mContext);
         when(mSearchFeatureProvider.smartSearchRankingTimeoutMs(any(Context.class)))
                 .thenReturn(300L);
-        mAdapter = new SearchResultsAdapter(mFragment, mSearchFeatureProvider);
+        mAdapter = new SearchResultsAdapter(mFragment);
     }
 
     @Test
@@ -95,17 +76,6 @@
     }
 
     @Test
-    public void testSingleSourceMerge_exactCopyReturned() {
-        Set<SearchResult> intentResults = getIntentSampleResults();
-        mAdapter.initializeSearch("");
-        mAdapter.addSearchResults(intentResults, mLoaderClassName);
-        mAdapter.notifyResultsLoaded();
-
-        List<SearchResult> updatedResults = mAdapter.getSearchResults();
-        assertThat(updatedResults).containsAllIn(intentResults);
-    }
-
-    @Test
     public void testCreateViewHolder_returnsIntentResult() {
         ViewGroup group = new FrameLayout(mContext);
         SearchViewHolder view = mAdapter.onCreateViewHolder(group,
@@ -123,387 +93,13 @@
     }
 
     @Test
-    public void testEndToEndSearch_properResultsMerged_correctOrder() {
-        mAdapter.initializeSearch("");
-        mAdapter.addSearchResults(new HashSet<>(getDummyAppResults()),
-                InstalledAppResultLoader.class.getName());
-        mAdapter.addSearchResults(new HashSet<>(getDummyDbResults()),
-                DatabaseResultLoader.class.getName());
-        mAdapter.notifyResultsLoaded();
+    public void testPostSearchResults_addsDataAndDisplays() {
+        List<SearchResult> results = getDummyDbResults();
 
-        List<SearchResult> results = mAdapter.getSearchResults();
-        assertThat(results.get(0).title).isEqualTo(TITLES[0]); // alpha
-        assertThat(results.get(1).title).isEqualTo(TITLES[3]); // appAlpha
-        assertThat(results.get(2).title).isEqualTo(TITLES[4]); // appBravo
-        assertThat(results.get(3).title).isEqualTo(TITLES[1]); // bravo
-        assertThat(results.get(4).title).isEqualTo(TITLES[5]); // appCharlie
-        assertThat(results.get(5).title).isEqualTo(TITLES[2]); // charlie
-        verify(mFragment).onSearchResultsDisplayed(mSearchResultsCountCaptor.capture());
-        assertThat(mSearchResultsCountCaptor.getValue()).isEqualTo(6);
-    }
+        mAdapter.postSearchResults(results);
 
-    @Test
-    public void testEndToEndSearch_addResults_resultsAddedInOrder() {
-        List<SearchResult> appResults = getDummyAppResults();
-        List<SearchResult> dbResults = getDummyDbResults();
-        mAdapter.initializeSearch("");
-        // Add two individual items
-        mAdapter.addSearchResults(new HashSet<>(appResults.subList(0, 1)),
-                InstalledAppResultLoader.class.getName());
-        mAdapter.addSearchResults(new HashSet<>(dbResults.subList(0, 1)),
-                DatabaseResultLoader.class.getName());
-        mAdapter.notifyResultsLoaded();
-        // Add super-set of items
-        mAdapter.initializeSearch("");
-        mAdapter.addSearchResults(
-                new HashSet<>(appResults), InstalledAppResultLoader.class.getName());
-        mAdapter.addSearchResults(
-                new HashSet<>(dbResults), DatabaseResultLoader.class.getName());
-        mAdapter.notifyResultsLoaded();
-
-        List<SearchResult> results = mAdapter.getSearchResults();
-        assertThat(results.get(0).title).isEqualTo(TITLES[0]); // alpha
-        assertThat(results.get(1).title).isEqualTo(TITLES[3]); // appAlpha
-        assertThat(results.get(2).title).isEqualTo(TITLES[4]); // appBravo
-        assertThat(results.get(3).title).isEqualTo(TITLES[1]); // bravo
-        assertThat(results.get(4).title).isEqualTo(TITLES[5]); // appCharlie
-        assertThat(results.get(5).title).isEqualTo(TITLES[2]); // charlie
-        verify(mFragment, times(2)).onSearchResultsDisplayed(mSearchResultsCountCaptor.capture());
-        assertThat(mSearchResultsCountCaptor.getAllValues().toArray())
-                .isEqualTo(new Integer[] {2, 6});
-    }
-
-    @Test
-    public void testEndToEndSearch_removeResults_resultsAdded() {
-        List<SearchResult> appResults = getDummyAppResults();
-        List<SearchResult> dbResults = getDummyDbResults();
-        // Add list of items
-        mAdapter.initializeSearch("");
-        mAdapter.addSearchResults(new HashSet<>(appResults),
-                InstalledAppResultLoader.class.getName());
-        mAdapter.addSearchResults(new HashSet<>(dbResults),
-                DatabaseResultLoader.class.getName());
-        mAdapter.notifyResultsLoaded();
-        // Add subset of items
-        mAdapter.initializeSearch("");
-        mAdapter.addSearchResults(new HashSet<>(appResults.subList(0, 1)),
-                InstalledAppResultLoader.class.getName());
-        mAdapter.addSearchResults(new HashSet<>(dbResults.subList(0, 1)),
-                DatabaseResultLoader.class.getName());
-        mAdapter.notifyResultsLoaded();
-
-        List<SearchResult> results = mAdapter.getSearchResults();
-        assertThat(results.get(0).title).isEqualTo(TITLES[0]);
-        assertThat(results.get(1).title).isEqualTo(TITLES[3]);
-        verify(mFragment, times(2)).onSearchResultsDisplayed(mSearchResultsCountCaptor.capture());
-        assertThat(mSearchResultsCountCaptor.getAllValues().toArray())
-                .isEqualTo(new Integer[] {6, 2});
-    }
-    @Test
-    public void testEndToEndSearch_smartSearchRankingEnabledAndSucceededAfterResultsLoaded() {
-        when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(true);
-
-        List<SearchResult> appResults = getDummyAppResults();
-        List<SearchResult> dbResults = getDummyDbResults();
-        mAdapter.initializeSearch("");
-        mAdapter.addSearchResults(
-                new HashSet<>(appResults), InstalledAppResultLoader.class.getName());
-        mAdapter.addSearchResults(
-                new HashSet<>(dbResults), DatabaseResultLoader.class.getName());
-        mAdapter.notifyResultsLoaded();
-        mAdapter.onRankingScoresAvailable(getDummyRankingScores());
-
-        List<SearchResult> results = mAdapter.getSearchResults();
-        assertThat(results.get(0).title).isEqualTo(TITLES[2]); // charlie
-        assertThat(results.get(1).title).isEqualTo(TITLES[0]); // alpha
-        assertThat(results.get(2).title).isEqualTo(TITLES[1]); // bravo
-        assertThat(results.get(3).title).isEqualTo(TITLES[3]); // appAlpha
-        assertThat(results.get(4).title).isEqualTo(TITLES[4]); // appBravo
-        assertThat(results.get(5).title).isEqualTo(TITLES[5]); // appCharlie
-        verify(mFragment).onSearchResultsDisplayed(mSearchResultsCountCaptor.capture());
-        assertThat(mSearchResultsCountCaptor.getValue()).isEqualTo(6);
-        assertThat(mAdapter.getAsyncRankingState()).isEqualTo(SearchResultsAdapter.SUCCEEDED);
-    }
-
-    @Test
-    public void testEndToEndSearch_smartSearchRankingEnabledAndSucceededBeforeResultsLoaded() {
-        when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(true);
-
-        List<SearchResult> appResults = getDummyAppResults();
-        List<SearchResult> dbResults = getDummyDbResults();
-        mAdapter.initializeSearch("");
-        mAdapter.onRankingScoresAvailable(getDummyRankingScores());
-        mAdapter.addSearchResults(
-                new HashSet<>(appResults), InstalledAppResultLoader.class.getName());
-        mAdapter.addSearchResults(
-                new HashSet<>(dbResults), DatabaseResultLoader.class.getName());
-        mAdapter.notifyResultsLoaded();
-
-        List<SearchResult> results = mAdapter.getSearchResults();
-        assertThat(results.get(0).title).isEqualTo(TITLES[2]); // charlie
-        assertThat(results.get(1).title).isEqualTo(TITLES[0]); // alpha
-        assertThat(results.get(2).title).isEqualTo(TITLES[1]); // bravo
-        assertThat(results.get(3).title).isEqualTo(TITLES[3]); // appAlpha
-        assertThat(results.get(4).title).isEqualTo(TITLES[4]); // appBravo
-        assertThat(results.get(5).title).isEqualTo(TITLES[5]); // appCharlie
-        verify(mFragment).onSearchResultsDisplayed(mSearchResultsCountCaptor.capture());
-        assertThat(mSearchResultsCountCaptor.getValue()).isEqualTo(6);
-        assertThat(mAdapter.getAsyncRankingState()).isEqualTo(SearchResultsAdapter.SUCCEEDED);
-    }
-
-    @Test
-    public void testEndToEndSearch_smartSearchRankingEnabledAndFailedAfterResultsLoaded() {
-        when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(true);
-
-        List<SearchResult> appResults = getDummyAppResults();
-        List<SearchResult> dbResults = getDummyDbResults();
-        mAdapter.initializeSearch("");
-        mAdapter.addSearchResults(
-                new HashSet<>(appResults), InstalledAppResultLoader.class.getName());
-        mAdapter.addSearchResults(
-                new HashSet<>(dbResults), DatabaseResultLoader.class.getName());
-        mAdapter.notifyResultsLoaded();
-        mAdapter.onRankingFailed();
-
-        List<SearchResult> results = mAdapter.getSearchResults();
-        assertThat(results.get(0).title).isEqualTo(TITLES[0]); // alpha
-        assertThat(results.get(1).title).isEqualTo(TITLES[3]); // appAlpha
-        assertThat(results.get(2).title).isEqualTo(TITLES[4]); // appBravo
-        assertThat(results.get(3).title).isEqualTo(TITLES[1]); // bravo
-        assertThat(results.get(4).title).isEqualTo(TITLES[5]); // appCharlie
-        assertThat(results.get(5).title).isEqualTo(TITLES[2]); // charlie
-        verify(mFragment).onSearchResultsDisplayed(mSearchResultsCountCaptor.capture());
-        assertThat(mSearchResultsCountCaptor.getValue()).isEqualTo(6);
-        assertThat(mAdapter.getAsyncRankingState()).isEqualTo(SearchResultsAdapter.FAILED);
-    }
-
-    @Test
-    public void testEndToEndSearch_smartSearchRankingEnabledAndFailedBeforeResultsLoaded() {
-        when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(true);
-
-        List<SearchResult> appResults = getDummyAppResults();
-        List<SearchResult> dbResults = getDummyDbResults();
-        mAdapter.initializeSearch("");
-        mAdapter.onRankingFailed();
-        mAdapter.addSearchResults(
-                new HashSet<>(appResults), InstalledAppResultLoader.class.getName());
-        mAdapter.addSearchResults(
-                new HashSet<>(dbResults), DatabaseResultLoader.class.getName());
-        mAdapter.notifyResultsLoaded();
-
-        List<SearchResult> results = mAdapter.getSearchResults();
-        assertThat(results.get(0).title).isEqualTo(TITLES[0]); // alpha
-        assertThat(results.get(1).title).isEqualTo(TITLES[3]); // appAlpha
-        assertThat(results.get(2).title).isEqualTo(TITLES[4]); // appBravo
-        assertThat(results.get(3).title).isEqualTo(TITLES[1]); // bravo
-        assertThat(results.get(4).title).isEqualTo(TITLES[5]); // appCharlie
-        assertThat(results.get(5).title).isEqualTo(TITLES[2]); // charlie
-        verify(mFragment).onSearchResultsDisplayed(mSearchResultsCountCaptor.capture());
-        assertThat(mSearchResultsCountCaptor.getValue()).isEqualTo(6);
-        assertThat(mAdapter.getAsyncRankingState()).isEqualTo(SearchResultsAdapter.FAILED);
-    }
-
-    @Test
-    public void testEndToEndSearch_smartSearchRankingEnabledAndTimedoutAfterResultsLoaded() {
-        when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(true);
-
-        List<SearchResult> appResults = getDummyAppResults();
-        List<SearchResult> dbResults = getDummyDbResults();
-        mAdapter.initializeSearch("");
-        mAdapter.addSearchResults(
-                new HashSet<>(appResults), InstalledAppResultLoader.class.getName());
-        mAdapter.addSearchResults(
-                new HashSet<>(dbResults), DatabaseResultLoader.class.getName());
-        mAdapter.notifyResultsLoaded();
-
-        waitUntilRankingTimesOut();
-
-        List<SearchResult> results = mAdapter.getSearchResults();
-        assertThat(results.get(0).title).isEqualTo(TITLES[0]); // alpha
-        assertThat(results.get(1).title).isEqualTo(TITLES[3]); // appAlpha
-        assertThat(results.get(2).title).isEqualTo(TITLES[4]); // appBravo
-        assertThat(results.get(3).title).isEqualTo(TITLES[1]); // bravo
-        assertThat(results.get(4).title).isEqualTo(TITLES[5]); // appCharlie
-        assertThat(results.get(5).title).isEqualTo(TITLES[2]); // charlie
-        verify(mFragment).onSearchResultsDisplayed(mSearchResultsCountCaptor.capture());
-        assertThat(mSearchResultsCountCaptor.getValue()).isEqualTo(6);
-        assertThat(mAdapter.getAsyncRankingState()).isEqualTo(SearchResultsAdapter.TIMED_OUT);
-    }
-
-    @Test
-    public void testEndToEndSearch_smartSearchRankingEnabledAndTimedoutBeforeResultsLoaded() {
-        when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(true);
-
-        List<SearchResult> appResults = getDummyAppResults();
-        List<SearchResult> dbResults = getDummyDbResults();
-        mAdapter.initializeSearch("");
-
-        waitUntilRankingTimesOut();
-
-        mAdapter.addSearchResults(
-                new HashSet<>(appResults), InstalledAppResultLoader.class.getName());
-        mAdapter.addSearchResults(
-                new HashSet<>(dbResults), DatabaseResultLoader.class.getName());
-        mAdapter.notifyResultsLoaded();
-
-        List<SearchResult> results = mAdapter.getSearchResults();
-        assertThat(results.get(0).title).isEqualTo(TITLES[0]); // alpha
-        assertThat(results.get(1).title).isEqualTo(TITLES[3]); // appAlpha
-        assertThat(results.get(2).title).isEqualTo(TITLES[4]); // appBravo
-        assertThat(results.get(3).title).isEqualTo(TITLES[1]); // bravo
-        assertThat(results.get(4).title).isEqualTo(TITLES[5]); // appCharlie
-        assertThat(results.get(5).title).isEqualTo(TITLES[2]); // charlie
-        verify(mFragment).onSearchResultsDisplayed(mSearchResultsCountCaptor.capture());
-        assertThat(mSearchResultsCountCaptor.getValue()).isEqualTo(6);
-        assertThat(mAdapter.getAsyncRankingState()).isEqualTo(SearchResultsAdapter.TIMED_OUT);
-    }
-
-    @Test
-    public void testDoSmartRanking_shouldRankAppResultsAfterDbResults() {
-        when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(true);
-
-        List<SearchResult> appResults = getDummyAppResults();
-        List<SearchResult> dbResults = getDummyDbResults();
-        mAdapter.initializeSearch("");
-        mAdapter.addSearchResults(
-                new HashSet<>(appResults), InstalledAppResultLoader.class.getName());
-        mAdapter.addSearchResults(
-                new HashSet<>(dbResults), DatabaseResultLoader.class.getName());
-        mAdapter.notifyResultsLoaded();
-        mAdapter.onRankingScoresAvailable(getDummyRankingScores());
-        List<SearchResult> results = mAdapter.doAsyncRanking();
-        assertThat(results.get(0).title).isEqualTo(TITLES[2]); // charlie
-        assertThat(results.get(1).title).isEqualTo(TITLES[0]); // alpha
-        assertThat(results.get(2).title).isEqualTo(TITLES[1]); // bravo
-        assertThat(results.get(3).title).isEqualTo(TITLES[3]); // appAlpha
-        assertThat(results.get(4).title).isEqualTo(TITLES[4]); // appBravo
-        assertThat(results.get(5).title).isEqualTo(TITLES[5]); // appCharlie
-    }
-
-    @Test
-    public void testDoSmartRanking_shouldRankResultsWithMissingScoresAfterScoredResults() {
-        when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(true);
-
-        List<SearchResult> appResults = getDummyAppResults();
-        List<SearchResult> dbResults = getDummyDbResults();
-        mAdapter.initializeSearch("");
-        mAdapter.addSearchResults(
-                new HashSet<>(appResults), InstalledAppResultLoader.class.getName());
-        mAdapter.addSearchResults(
-                new HashSet<>(dbResults), DatabaseResultLoader.class.getName());
-        mAdapter.notifyResultsLoaded();
-        List<Pair<String, Float>> rankingScores = getDummyRankingScores();
-        rankingScores.remove(1); // no ranking score for alpha
-        mAdapter.onRankingScoresAvailable(rankingScores);
-        List<SearchResult> results = mAdapter.doAsyncRanking();
-        assertThat(results.get(0).title).isEqualTo(TITLES[2]); // charlie
-        assertThat(results.get(1).title).isEqualTo(TITLES[1]); // bravo
-        assertThat(results.get(2).title).isEqualTo(TITLES[0]); // alpha
-        assertThat(results.get(3).title).isEqualTo(TITLES[3]); // appAlpha
-        assertThat(results.get(4).title).isEqualTo(TITLES[4]); // appBravo
-        assertThat(results.get(5).title).isEqualTo(TITLES[5]); // appCharlie
-    }
-
-    @Test
-    public void testGetUnsortedLoadedResults () {
-        List<SearchResult> appResults = getDummyAppResults();
-        List<SearchResult> dbResults = getDummyDbResults();
-        mAdapter.initializeSearch("");
-        mAdapter.addSearchResults(
-                new HashSet<>(appResults), InstalledAppResultLoader.class.getName());
-        mAdapter.addSearchResults(
-                new HashSet<>(dbResults), DatabaseResultLoader.class.getName());
-        Set<CharSequence> expectedDbTitles = new HashSet<>(
-                Arrays.asList("alpha", "bravo", "charlie"));
-        Set<CharSequence> expectedAppTitles = new HashSet<>(
-                Arrays.asList("appAlpha", "appBravo", "appCharlie"));
-        Set<CharSequence> actualDbTitles = new HashSet<>();
-        Set<CharSequence> actualAppTitles = new HashSet<>();
-        for (SearchResult result : mAdapter.getUnsortedLoadedResults(SearchResultsAdapter
-                .DB_RESULTS_LOADER_KEY)) {
-            actualDbTitles.add(result.title);
-        }
-        for (SearchResult result : mAdapter.getUnsortedLoadedResults(SearchResultsAdapter
-                .APP_RESULTS_LOADER_KEY)) {
-            actualAppTitles.add(result.title);
-        }
-        assertThat(actualDbTitles).isEqualTo(expectedDbTitles);
-        assertThat(actualAppTitles).isEqualTo(expectedAppTitles);
-    }
-
-    @Test
-    public void testGetSortedLoadedResults() {
-        List<SearchResult> appResults = getDummyAppResults();
-        List<SearchResult> dbResults = getDummyDbResults();
-        mAdapter.initializeSearch("");
-        mAdapter.addSearchResults(
-                new HashSet<>(appResults), InstalledAppResultLoader.class.getName());
-        mAdapter.addSearchResults(
-                new HashSet<>(dbResults), DatabaseResultLoader.class.getName());
-        List<? extends SearchResult> actualDbResults =
-                mAdapter.getSortedLoadedResults(SearchResultsAdapter.DB_RESULTS_LOADER_KEY);
-        List<? extends SearchResult> actualAppResults =
-                mAdapter.getSortedLoadedResults(SearchResultsAdapter.APP_RESULTS_LOADER_KEY);
-        assertThat(actualDbResults.get(0).title).isEqualTo(TITLES[0]); // charlie
-        assertThat(actualDbResults.get(1).title).isEqualTo(TITLES[1]); // bravo
-        assertThat(actualDbResults.get(2).title).isEqualTo(TITLES[2]); // alpha
-        assertThat(actualAppResults.get(0).title).isEqualTo(TITLES[3]); // appAlpha
-        assertThat(actualAppResults.get(1).title).isEqualTo(TITLES[4]); // appBravo
-        assertThat(actualAppResults.get(2).title).isEqualTo(TITLES[5]); // appCharlie
-    }
-
-    @Test
-    public void testInitializeSearch_shouldNotRunSmartRankingIfDisabled() {
-        when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(false);
-        mAdapter.initializeSearch("");
-        mAdapter.notifyResultsLoaded();
-        verify(mSearchFeatureProvider, never()).querySearchResults(
-                any(Context.class), anyString(), any(SearchResultsRankerCallback.class));
-        assertThat(mAdapter.getAsyncRankingState()).isEqualTo(SearchResultsAdapter.DISABLED);
-    }
-
-    @Test
-    public void testInitialSearch_shouldRunSmartRankingIfEnabled() {
-        when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(true);
-        mAdapter.initializeSearch("");
-        mAdapter.notifyResultsLoaded();
-        verify(mSearchFeatureProvider, times(1)).querySearchResults(
-                any(Context.class), anyString(), any(SearchResultsRankerCallback.class));
-        assertThat(mAdapter.getAsyncRankingState())
-                .isEqualTo(SearchResultsAdapter.PENDING_RESULTS);
-    }
-
-    @Test
-    public void testGetRankingScoreByStableId() {
-        when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(true);
-
-        List<SearchResult> appResults = getDummyAppResults();
-        List<SearchResult> dbResults = getDummyDbResults();
-        mAdapter.initializeSearch("");
-        mAdapter.onRankingScoresAvailable(getDummyRankingScores());
-        assertThat(mAdapter.getRankingScoreByStableId(dbResults.get(0).stableId))
-                .isWithin(1e-10f).of(0.8f);
-        assertThat(mAdapter.getRankingScoreByStableId(dbResults.get(1).stableId))
-                .isWithin(1e-10f).of(0.2f);
-        assertThat(mAdapter.getRankingScoreByStableId(dbResults.get(2).stableId))
-                .isWithin(1e-10f).of(0.9f);
-        assertThat(mAdapter.getRankingScoreByStableId(appResults.get(0).stableId))
-                .isEqualTo(-Float.MAX_VALUE);
-        assertThat(mAdapter.getRankingScoreByStableId(appResults.get(1).stableId))
-                .isEqualTo(-Float.MAX_VALUE);
-        assertThat(mAdapter.getRankingScoreByStableId(appResults.get(2).stableId))
-                .isEqualTo(-Float.MAX_VALUE);
-    }
-
-    private void waitUntilRankingTimesOut() {
-        while (mAdapter.getHandler().hasMessages(mAdapter.MSG_RANKING_TIMED_OUT)) {
-            try {
-                ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
-                Thread.sleep(100);
-            } catch (InterruptedException e) {
-                // Do nothing
-            }
-        }
+        assertThat(mAdapter.getSearchResults()).containsExactlyElementsIn(results);
+        verify(mFragment).onSearchResultsDisplayed(anyInt());
     }
 
     private List<SearchResult> getDummyDbResults() {
@@ -511,78 +107,21 @@
         ResultPayload payload = new ResultPayload(new Intent());
         SearchResult.Builder builder = new SearchResult.Builder();
         builder.setPayload(payload)
-                .setTitle(TITLES[0])
+                .setTitle("one")
                 .setRank(1)
-                .setStableId(Objects.hash(TITLES[0], "db"));
+                .setStableId(Objects.hash("one", "db"));
         results.add(builder.build());
 
-        builder.setTitle(TITLES[1])
+        builder.setTitle("two")
                 .setRank(3)
-                .setStableId(Objects.hash(TITLES[1], "db"));
+                .setStableId(Objects.hash("two", "db"));
         results.add(builder.build());
 
-        builder.setTitle(TITLES[2])
+        builder.setTitle("three")
                 .setRank(6)
-                .setStableId(Objects.hash(TITLES[2], "db"));
+                .setStableId(Objects.hash("three", "db"));
         results.add(builder.build());
 
         return results;
     }
-
-    private List<SearchResult> getDummyAppResults() {
-        List<SearchResult> results = new ArrayList<>();
-        ResultPayload payload = new ResultPayload(new Intent());
-        AppSearchResult.Builder builder = new AppSearchResult.Builder();
-        builder.setPayload(payload)
-                .setTitle(TITLES[3])
-                .setRank(1)
-                .setStableId(Objects.hash(TITLES[3], "app"));
-        results.add(builder.build());
-
-        builder.setTitle(TITLES[4])
-                .setRank(2)
-                .setStableId(Objects.hash(TITLES[4], "app"));
-        results.add(builder.build());
-
-        builder.setTitle(TITLES[5])
-                .setRank(4)
-                .setStableId(Objects.hash(TITLES[5], "app"));
-        results.add(builder.build());
-
-        return results;
-    }
-
-    private Set<SearchResult> getIntentSampleResults() {
-        Set<SearchResult> sampleResults = new HashSet<>();
-        ArrayList<String> breadcrumbs = new ArrayList<>();
-        final Drawable icon = mContext.getDrawable(R.drawable.ic_search_24dp);
-        final ResultPayload payload = new ResultPayload(null);
-        final SearchResult.Builder builder = new Builder();
-        builder.setTitle("title")
-                .setSummary("summary")
-                .setRank(1)
-                .addBreadcrumbs(breadcrumbs)
-                .setIcon(icon)
-                .setPayload(payload)
-                .setStableId(Objects.hash("title", "summary", 1));
-        sampleResults.add(builder.build());
-
-        builder.setRank(2)
-                .setStableId(Objects.hash("title", "summary", 2));
-        sampleResults.add(builder.build());
-
-        builder.setRank(3)
-                .setStableId(Objects.hash("title", "summary", 3));
-        sampleResults.add(builder.build());
-        return sampleResults;
-    }
-
-    private List<Pair<String, Float>> getDummyRankingScores() {
-        List<SearchResult> results = getDummyDbResults();
-        List<Pair<String, Float>> scores = new ArrayList<>();
-        scores.add(new Pair<>(Long.toString(results.get(2).stableId), 0.9f)); // charlie
-        scores.add(new Pair<>(Long.toString(results.get(0).stableId), 0.8f)); // alpha
-        scores.add(new Pair<>(Long.toString(results.get(1).stableId), 0.2f)); // bravo
-        return scores;
-    }
 }
diff --git a/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java b/tests/robotests/src/com/android/settings/search/StaticSearchResultFutureTaskTest.java
similarity index 61%
rename from tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java
rename to tests/robotests/src/com/android/settings/search/StaticSearchResultFutureTaskTest.java
index dd7b7a9..e285555 100644
--- a/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/search/StaticSearchResultFutureTaskTest.java
@@ -21,9 +21,11 @@
 import android.content.Context;
 import android.content.Intent;
 import android.database.sqlite.SQLiteDatabase;
+import android.util.Pair;
 
 import com.android.settings.TestConfig;
 import com.android.settings.dashboard.SiteMapManager;
+import com.android.settings.search.DatabaseResultLoader.StaticSearchResultCallable;
 import com.android.settings.search.indexing.IndexData;
 import com.android.settings.testutils.DatabaseTestUtils;
 import com.android.settings.testutils.FakeFeatureFactory;
@@ -39,37 +41,54 @@
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 
-import java.util.Arrays;
+import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
 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;
 
 @RunWith(SettingsRobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
-public class DatabaseResultLoaderTest {
+public class StaticSearchResultFutureTaskTest {
 
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private Context mMockContext;
     @Mock
     private SiteMapManager mSiteMapManager;
+    @Mock
+    private ExecutorService mService;
     private Context mContext;
 
     SQLiteDatabase mDb;
 
+    FakeFeatureFactory mFeatureFactory;
+
+    private final String[] STATIC_TITLES = {"static one", "static two", "static three"};
+    private final int[] STABLE_IDS =
+            {"id_one".hashCode(), "id_two".hashCode(), "id_three".hashCode()};
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mContext = RuntimeEnvironment.application;
-        FakeFeatureFactory.setupForTest(mMockContext);
-        FakeFeatureFactory factory =
-                (FakeFeatureFactory) FakeFeatureFactory.getFactory(mMockContext);
-        when(factory.searchFeatureProvider.getSiteMapManager())
+        mFeatureFactory = FakeFeatureFactory.setupForTest(mMockContext);
+        when(mFeatureFactory.searchFeatureProvider.getExecutorService()).thenReturn(mService);
+        when(mFeatureFactory.searchFeatureProvider.getSiteMapManager())
                 .thenReturn(mSiteMapManager);
         mDb = IndexDatabaseHelper.getInstance(mContext).getWritableDatabase();
         setUpDb();
@@ -81,159 +100,252 @@
     }
 
     @Test
-    public void testMatchTitle() {
-        DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "title", mSiteMapManager);
-        assertThat(loader.loadInBackground().size()).isEqualTo(2);
+    public void testMatchTitle() throws Exception {
+        StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "title",
+                mSiteMapManager);
+
+        assertThat(loader.call()).hasSize(2);
         verify(mSiteMapManager, times(2)).buildBreadCrumb(eq(mContext), anyString(), anyString());
     }
 
     @Test
-    public void testMatchSummary() {
-        DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "summary",
+    public void testMatchSummary() throws Exception {
+        StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "summary",
                 mSiteMapManager);
-        assertThat(loader.loadInBackground().size()).isEqualTo(2);
+
+        assertThat(loader.call()).hasSize(2);
     }
 
     @Test
-    public void testMatchKeywords() {
-        DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "keywords",
+    public void testMatchKeywords() throws Exception {
+        StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "keywords",
                 mSiteMapManager);
-        assertThat(loader.loadInBackground().size()).isEqualTo(2);
+
+        assertThat(loader.call()).hasSize(2);
     }
 
     @Test
-    public void testMatchEntries() {
-        DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "entries",
+    public void testMatchEntries() throws Exception {
+        StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "entries",
                 mSiteMapManager);
-        assertThat(loader.loadInBackground().size()).isEqualTo(2);
+
+        assertThat(loader.call()).hasSize(2);
     }
 
     @Test
-    public void testSpecialCaseWord_matchesNonPrefix() {
+    public void testSpecialCaseWord_matchesNonPrefix() throws Exception {
         insertSpecialCase("Data usage");
-        DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "usage", mSiteMapManager);
-        assertThat(loader.loadInBackground().size()).isEqualTo(1);
+
+        StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "usage",
+                mSiteMapManager);
+
+        assertThat(loader.call()).hasSize(1);
     }
 
     @Test
-    public void testSpecialCaseDash_matchesWordNoDash() {
+    public void testSpecialCaseDash_matchesWordNoDash() throws Exception {
         insertSpecialCase("wi-fi calling");
-        DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "wifi", mSiteMapManager);
-        assertThat(loader.loadInBackground().size()).isEqualTo(1);
-    }
 
-    @Test
-    public void testSpecialCaseDash_matchesWordWithDash() {
-        insertSpecialCase("priorités seulment");
-        DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "priorités",
+        StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "wifi",
                 mSiteMapManager);
-        assertThat(loader.loadInBackground().size()).isEqualTo(1);
+
+        assertThat(loader.call()).hasSize(1);
     }
 
     @Test
-    public void testSpecialCaseDash_matchesWordWithoutDash() {
+    public void testSpecialCaseDash_matchesWordWithDash() throws Exception {
         insertSpecialCase("priorités seulment");
-        DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "priorites",
+
+        StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "priorités",
                 mSiteMapManager);
-        assertThat(loader.loadInBackground().size()).isEqualTo(1);
+
+        assertThat(loader.call()).hasSize(1);
     }
 
     @Test
-    public void testSpecialCaseDash_matchesEntireQueryWithoutDash() {
+    public void testSpecialCaseDash_matchesWordWithoutDash() throws Exception {
+        insertSpecialCase("priorités seulment");
+
+        StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "priorites",
+                mSiteMapManager);
+
+        assertThat(loader.call()).hasSize(1);
+    }
+
+    @Test
+    public void testSpecialCaseDash_matchesEntireQueryWithoutDash() throws Exception {
         insertSpecialCase("wi-fi calling");
-        DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "wifi calling",
+
+        StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "wifi calling",
                 mSiteMapManager);
-        assertThat(loader.loadInBackground().size()).isEqualTo(1);
+
+        assertThat(loader.call()).hasSize(1);
     }
 
     @Test
-    public void testSpecialCasePrefix_matchesPrefixOfEntry() {
+    public void testSpecialCasePrefix_matchesPrefixOfEntry() throws Exception {
         insertSpecialCase("Photos");
-        DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "pho", mSiteMapManager);
-        assertThat(loader.loadInBackground().size()).isEqualTo(1);
+
+        StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "pho",
+                mSiteMapManager);
+
+        assertThat(loader.call()).hasSize(1);
     }
 
     @Test
-    public void testSpecialCasePrefix_DoesNotMatchNonPrefixSubstring() {
+    public void testSpecialCasePrefix_DoesNotMatchNonPrefixSubstring() throws Exception {
         insertSpecialCase("Photos");
-        DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "hot", mSiteMapManager);
-        assertThat(loader.loadInBackground().size()).isEqualTo(0);
+
+        StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "hot",
+                mSiteMapManager);
+
+        assertThat(loader.call()).hasSize(0);
     }
 
     @Test
-    public void testSpecialCaseMultiWordPrefix_matchesPrefixOfEntry() {
+    public void testSpecialCaseMultiWordPrefix_matchesPrefixOfEntry() throws Exception {
         insertSpecialCase("Apps Notifications");
-        DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "Apps", mSiteMapManager);
-        assertThat(loader.loadInBackground().size()).isEqualTo(1);
+
+        StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "Apps",
+                mSiteMapManager);
+
+        assertThat(loader.call()).hasSize(1);
     }
 
     @Test
-    public void testSpecialCaseMultiWordPrefix_matchesSecondWordPrefixOfEntry() {
+    public void testSpecialCaseMultiWordPrefix_matchesSecondWordPrefixOfEntry() throws Exception {
         insertSpecialCase("Apps Notifications");
-        DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "Not", mSiteMapManager);
-        assertThat(loader.loadInBackground().size()).isEqualTo(1);
+
+        StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "Not",
+                mSiteMapManager);
+
+        assertThat(loader.call()).hasSize(1);
     }
 
     @Test
-    public void testSpecialCaseMultiWordPrefix_DoesNotMatchMatchesPrefixOfFirstEntry() {
+    public void testSpecialCaseMultiWordPrefix_DoesNotMatchMatchesPrefixOfFirstEntry()
+            throws Exception {
         insertSpecialCase("Apps Notifications");
-        DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "pp", mSiteMapManager);
-        assertThat(loader.loadInBackground().size()).isEqualTo(0);
+
+        StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "pp",
+                mSiteMapManager);
+
+        assertThat(loader.call()).hasSize(0);
     }
 
     @Test
-    public void testSpecialCaseMultiWordPrefix_DoesNotMatchMatchesPrefixOfSecondEntry() {
+    public void testSpecialCaseMultiWordPrefix_DoesNotMatchMatchesPrefixOfSecondEntry()
+            throws Exception {
         insertSpecialCase("Apps Notifications");
-        DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "tion", mSiteMapManager);
-        assertThat(loader.loadInBackground().size()).isEqualTo(0);
+
+        StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "tion",
+                mSiteMapManager);
+
+        assertThat(loader.call()).hasSize(0);
     }
 
     @Test
-    public void testSpecialCaseMultiWordPrefixWithSpecial_matchesPrefixOfEntry() {
+    public void testSpecialCaseMultiWordPrefixWithSpecial_matchesPrefixOfEntry() throws
+            Exception {
         insertSpecialCase("Apps & Notifications");
-        DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "App", mSiteMapManager);
-        assertThat(loader.loadInBackground().size()).isEqualTo(1);
+
+        StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "App",
+                mSiteMapManager);
+
+        assertThat(loader.call()).hasSize(1);
     }
 
     @Test
-    public void testSpecialCaseMultiWordPrefixWithSpecial_matchesPrefixOfSecondEntry() {
+    public void testSpecialCaseMultiWordPrefixWithSpecial_matchesPrefixOfSecondEntry()
+            throws Exception {
         insertSpecialCase("Apps & Notifications");
-        DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "No", mSiteMapManager);
-        assertThat(loader.loadInBackground().size()).isEqualTo(1);
+
+        StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "No",
+                mSiteMapManager);
+
+        assertThat(loader.call()).hasSize(1);
     }
 
     @Test
-    public void testResultMatchedByMultipleQueries_duplicatesRemoved() {
+    public void testResultMatchedByMultipleQueries_duplicatesRemoved() throws Exception {
         String key = "durr";
         insertSameValueAllFieldsCase(key);
-        DatabaseResultLoader loader = new DatabaseResultLoader(mContext, key, null);
 
-        assertThat(loader.loadInBackground().size()).isEqualTo(1);
+        StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, key, null);
+
+        assertThat(loader.call()).hasSize(1);
     }
 
     @Test
-    public void testSpecialCaseTwoWords_multipleResults() {
+    public void testSpecialCaseTwoWords_multipleResults() throws Exception {
         final String caseOne = "Apple pear";
         final String caseTwo = "Banana apple";
         insertSpecialCase(caseOne);
         insertSpecialCase(caseTwo);
-        DatabaseResultLoader loader = new DatabaseResultLoader(mContext, "App", null);
-        Set<? extends SearchResult> results = loader.loadInBackground();
-        Set<CharSequence> expectedTitles = new HashSet<>(Arrays.asList(caseOne, caseTwo));
-        Set<CharSequence> actualTitles = new HashSet<>();
+        StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "App", null);
+
+        List<? extends SearchResult> results = loader.call();
+
+        Set<String> actualTitles = new HashSet<>();
         for (SearchResult result : results) {
-            actualTitles.add(result.title);
+            actualTitles.add(result.title.toString());
         }
-        assertThat(actualTitles).isEqualTo(expectedTitles);
+        assertThat(actualTitles).containsAllOf(caseOne, caseTwo);
+    }
+
+    @Test
+    public void testGetRankingScoreByStableId_sortedDynamically() throws Exception {
+        FutureTask<List<Pair<String, Float>>> task = mock(FutureTask.class);
+        when(task.get(anyLong(), any(TimeUnit.class))).thenReturn(getDummyRankingScores());
+        when(mFeatureFactory.searchFeatureProvider.getRankerTask(any(Context.class),
+                anyString())).thenReturn(task);
+        when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(
+                true);
+
+        insertSpecialCase(STATIC_TITLES[0], STABLE_IDS[0]);
+        insertSpecialCase(STATIC_TITLES[1], STABLE_IDS[1]);
+        insertSpecialCase(STATIC_TITLES[2], STABLE_IDS[2]);
+
+        StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "Static",
+                null);
+
+        List<? extends SearchResult> results = loader.call();
+
+        assertThat(results.get(0).title).isEqualTo(STATIC_TITLES[2]);
+        assertThat(results.get(1).title).isEqualTo(STATIC_TITLES[0]);
+        assertThat(results.get(2).title).isEqualTo(STATIC_TITLES[1]);
+    }
+
+    @Test
+    public void testGetRankingScoreByStableId_scoresTimeout_sortedStatically() throws Exception {
+        Callable<List<Pair<String, Float>>> callable = mock(Callable.class);
+        when(callable.call()).thenThrow(new TimeoutException());
+        FutureTask<List<Pair<String, Float>>> task = new FutureTask<>(callable);
+        when(mFeatureFactory.searchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(
+                true);
+        when(mFeatureFactory.searchFeatureProvider.getRankerTask(any(Context.class),
+                anyString())).thenReturn(task);
+        insertSpecialCase("title", STABLE_IDS[0]);
+
+        StaticSearchResultCallable loader = new StaticSearchResultCallable(mContext, "title", null);
+
+        List<? extends SearchResult> results = loader.call();
+        assertThat(results.get(0).title).isEqualTo("title");
+        assertThat(results.get(1).title).isEqualTo("alpha_title");
+        assertThat(results.get(2).title).isEqualTo("bravo_title");
     }
 
     private void insertSpecialCase(String specialCase) {
+        insertSpecialCase(specialCase, specialCase.hashCode());
+    }
+
+    private void insertSpecialCase(String specialCase, int docId) {
         String normalized = IndexData.normalizeHyphen(specialCase);
         normalized = IndexData.normalizeString(normalized);
         final ResultPayload payload = new ResultPayload(new Intent());
 
         ContentValues values = new ContentValues();
-        values.put(IndexDatabaseHelper.IndexColumns.DOCID, normalized.hashCode());
+        values.put(IndexDatabaseHelper.IndexColumns.DOCID, docId);
         values.put(IndexDatabaseHelper.IndexColumns.LOCALE, "en-us");
         values.put(IndexDatabaseHelper.IndexColumns.DATA_RANK, 1);
         values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE, specialCase);
@@ -373,4 +485,33 @@
 
         mDb.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX, null, values);
     }
+
+    private List<? extends SearchResult> getDummyDbResults() {
+        List<SearchResult> results = new ArrayList<>();
+        ResultPayload payload = new ResultPayload(new Intent());
+        SearchResult.Builder builder = new SearchResult.Builder();
+        builder.setPayload(payload)
+                .setTitle(STATIC_TITLES[0])
+                .setStableId(STABLE_IDS[0]);
+        results.add(builder.build());
+
+        builder.setTitle(STATIC_TITLES[1])
+                .setStableId(STABLE_IDS[1]);
+        results.add(builder.build());
+
+        builder.setTitle(STATIC_TITLES[2])
+                .setStableId(STABLE_IDS[2]);
+        results.add(builder.build());
+
+        return results;
+    }
+
+    private List<Pair<String, Float>> getDummyRankingScores() {
+        List<? extends SearchResult> results = getDummyDbResults();
+        List<Pair<String, Float>> scores = new ArrayList<>();
+        scores.add(new Pair<>(Long.toString(results.get(2).stableId), 0.9f)); // static_three
+        scores.add(new Pair<>(Long.toString(results.get(0).stableId), 0.8f)); // static_one
+        scores.add(new Pair<>(Long.toString(results.get(1).stableId), 0.2f)); // static_two
+        return scores;
+    }
 }
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/EncryptionAndCredentialTest.java b/tests/robotests/src/com/android/settings/security/EncryptionAndCredentialTest.java
similarity index 95%
rename from tests/robotests/src/com/android/settings/EncryptionAndCredentialTest.java
rename to tests/robotests/src/com/android/settings/security/EncryptionAndCredentialTest.java
index 7a3875e..c87c964 100644
--- a/tests/robotests/src/com/android/settings/EncryptionAndCredentialTest.java
+++ b/tests/robotests/src/com/android/settings/security/EncryptionAndCredentialTest.java
@@ -11,14 +11,14 @@
  * 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
+ * limitations under the License.
  */
 
-package com.android.settings;
+package com.android.settings.security;
 
 import static android.app.admin.DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE;
 import static android.app.admin.DevicePolicyManager.ENCRYPTION_STATUS_INACTIVE;
-import static com.android.settings.EncryptionAndCredential.SEARCH_INDEX_DATA_PROVIDER;
+import static com.android.settings.security.EncryptionAndCredential.SEARCH_INDEX_DATA_PROVIDER;
 import static com.google.common.truth.Truth.assertThat;
 import static org.mockito.Mockito.when;
 
@@ -28,6 +28,8 @@
 import android.provider.SearchIndexableResource;
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settings.R;
+import com.android.settings.TestConfig;
 import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settings.testutils.SettingsRobolectricTestRunner;
 
diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowVibrator.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowVibrator.java
new file mode 100644
index 0000000..9046720
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowVibrator.java
@@ -0,0 +1,56 @@
+/*
+ * 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.shadow;
+
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.os.SystemVibrator;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.fakes.RoboVibrator;
+import org.robolectric.shadows.ShadowContextImpl;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.Map;
+
+@Implements(SystemVibrator.class)
+public class ShadowVibrator {
+
+    private static Map<String, String> getSystemServiceMap() {
+        return ReflectionHelpers.getStaticField(ShadowContextImpl.class, "SYSTEM_SERVICE_MAP");
+    }
+
+    public static void addToServiceMap() {
+        getSystemServiceMap().put(Context.VIBRATOR_SERVICE, SystemVibrator.class.getName());
+    }
+
+    public static void reset() {
+        getSystemServiceMap().put(Context.VIBRATOR_SERVICE, RoboVibrator.class.getName());
+    }
+
+    public final Vibrator delegate = mock(Vibrator.class);
+
+    @Implementation
+    public void vibrate(int uid, String opPkg, VibrationEffect vibe, AudioAttributes attributes) {
+        delegate.vibrate(uid, opPkg, vibe, attributes);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/widget/AppPreferenceTest.java b/tests/robotests/src/com/android/settings/widget/AppPreferenceTest.java
new file mode 100644
index 0000000..d489094
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/widget/AppPreferenceTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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 static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.support.v7.preference.PreferenceViewHolder;
+import android.view.View;
+
+import com.android.settings.R;
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+
+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 AppPreferenceTest {
+
+    private Context mContext;
+    private View mRootView;
+    private AppPreference mPref;
+    private PreferenceViewHolder mHolder;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mRootView = View.inflate(mContext, R.layout.preference_app, null /* parent */);
+        mHolder = PreferenceViewHolder.createInstanceForTests(mRootView);
+        mPref = new AppPreference(mContext);
+    }
+
+    @Test
+    public void setProgress_showProgress() {
+        mPref.setProgress(1);
+        mPref.onBindViewHolder(mHolder);
+
+        assertThat(mHolder.findViewById(android.R.id.progress).getVisibility())
+                .isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void setSummary_showSummaryContainer() {
+        mPref.setSummary("test");
+        mPref.onBindViewHolder(mHolder);
+
+        assertThat(mHolder.findViewById(R.id.summary_container).getVisibility())
+                .isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void noSummary_hideSummaryContainer() {
+        mPref.setSummary(null);
+        mPref.onBindViewHolder(mHolder);
+
+        assertThat(mHolder.findViewById(R.id.summary_container).getVisibility())
+                .isEqualTo(View.GONE);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/widget/AppSwitchPreferenceTest.java b/tests/robotests/src/com/android/settings/widget/AppSwitchPreferenceTest.java
new file mode 100644
index 0000000..a7c8d7c
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/widget/AppSwitchPreferenceTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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 static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.support.v7.preference.PreferenceViewHolder;
+import android.view.View;
+
+import com.android.settings.R;
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+
+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 AppSwitchPreferenceTest {
+
+    private Context mContext;
+    private View mRootView;
+    private AppSwitchPreference mPref;
+    private PreferenceViewHolder mHolder;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mRootView = View.inflate(mContext, R.layout.preference_app, null /* parent */);
+        mHolder = PreferenceViewHolder.createInstanceForTests(mRootView);
+        mPref = new AppSwitchPreference(mContext);
+    }
+
+    @Test
+    public void setSummary_showSummaryContainer() {
+        mPref.setSummary("test");
+        mPref.onBindViewHolder(mHolder);
+
+        assertThat(mHolder.findViewById(R.id.summary_container).getVisibility())
+                .isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void noSummary_hideSummaryContainer() {
+        mPref.setSummary(null);
+        mPref.onBindViewHolder(mHolder);
+
+        assertThat(mHolder.findViewById(R.id.summary_container).getVisibility())
+                .isEqualTo(View.GONE);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/widget/RadioButtonPickerFragmentTest.java b/tests/robotests/src/com/android/settings/widget/RadioButtonPickerFragmentTest.java
index e314ef3..40d73eb 100644
--- a/tests/robotests/src/com/android/settings/widget/RadioButtonPickerFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/widget/RadioButtonPickerFragmentTest.java
@@ -16,15 +16,21 @@
 
 package com.android.settings.widget;
 
+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.verify;
+import static org.mockito.Mockito.when;
+
 import android.app.Activity;
 import android.content.Context;
 import android.os.UserManager;
 import android.support.v7.preference.PreferenceScreen;
 
-import com.android.settings.testutils.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
 import com.android.settings.applications.defaultapps.DefaultAppInfo;
 import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -38,12 +44,6 @@
 import java.util.ArrayList;
 import java.util.List;
 
-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.verify;
-import static org.mockito.Mockito.when;
-
 @RunWith(SettingsRobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
 public class RadioButtonPickerFragmentTest {
@@ -99,6 +99,11 @@
         assertThat(mFragment.setDefaultKeyCalled).isTrue();
     }
 
+    @Test
+    public void shouldHaveNoCustomPreferenceLayout() {
+        assertThat(mFragment.getRadioButtonPreferenceCustomLayoutResId()).isEqualTo(0);
+    }
+
     public static class TestFragment extends RadioButtonPickerFragment {
 
         boolean setDefaultKeyCalled;
diff --git a/tests/unit/Android.mk b/tests/unit/Android.mk
index ec0f0b6..58fe7dd 100644
--- a/tests/unit/Android.mk
+++ b/tests/unit/Android.mk
@@ -10,13 +10,13 @@
 LOCAL_STATIC_JAVA_LIBRARIES := \
     android-support-test \
     espresso-core \
-    legacy-android-test \
-    mockito-target-minus-junit4 \
-    truth-prebuilt \
-    ub-uiautomator \
     espresso-contrib-nodep \
     espresso-intents-nodep \
-
+    legacy-android-test \
+    mockito-target-minus-junit4 \
+    platform-test-annotations \
+    truth-prebuilt \
+    ub-uiautomator
 
 # Include all test java files.
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/unit/src/com/android/settings/core/PreferenceControllerContractTest.java b/tests/unit/src/com/android/settings/core/PreferenceControllerContractTest.java
new file mode 100644
index 0000000..c75ca13
--- /dev/null
+++ b/tests/unit/src/com/android/settings/core/PreferenceControllerContractTest.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.core;
+
+import static junit.framework.Assert.fail;
+
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+import android.provider.SearchIndexableResource;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.ArraySet;
+
+import com.android.settings.search.DatabaseIndexingUtils;
+import com.android.settings.search.Indexable;
+import com.android.settings.search.SearchIndexableResources;
+import com.android.settingslib.core.AbstractPreferenceController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.Set;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class PreferenceControllerContractTest {
+
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getTargetContext();
+    }
+
+    @Test
+    @Presubmit
+    public void controllersInSearchShouldImplementPreferenceControllerMixin() {
+        final Set<String> errorClasses = new ArraySet<>();
+
+        for (SearchIndexableResource page : SearchIndexableResources.values()) {
+            final Class<?> clazz = DatabaseIndexingUtils.getIndexableClass(page.className);
+
+            final Indexable.SearchIndexProvider provider =
+                    DatabaseIndexingUtils.getSearchIndexProvider(clazz);
+            if (provider == null) {
+                continue;
+            }
+
+            final List<AbstractPreferenceController> controllers =
+                    provider.getPreferenceControllers(mContext);
+            if (controllers == null) {
+                continue;
+            }
+            for (AbstractPreferenceController controller : controllers) {
+                if (!(controller instanceof PreferenceControllerMixin)) {
+                    errorClasses.add(controller.getClass().getName());
+                }
+            }
+        }
+
+        if (!errorClasses.isEmpty()) {
+            final StringBuilder errorMessage = new StringBuilder()
+                    .append("Each preference must implement PreferenceControllerMixin, ")
+                    .append("the following classes don't:\n");
+            for (String c : errorClasses) {
+                errorMessage.append(c).append("\n");
+            }
+            fail(errorMessage.toString());
+        }
+    }
+}
diff --git a/tests/unit/src/com/android/settings/UniquePreferenceTest.java b/tests/unit/src/com/android/settings/core/UniquePreferenceTest.java
similarity index 98%
rename from tests/unit/src/com/android/settings/UniquePreferenceTest.java
rename to tests/unit/src/com/android/settings/core/UniquePreferenceTest.java
index 74f46eb..4c51772 100644
--- a/tests/unit/src/com/android/settings/UniquePreferenceTest.java
+++ b/tests/unit/src/com/android/settings/core/UniquePreferenceTest.java
@@ -14,13 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.settings;
+package com.android.settings.core;
 
 import static junit.framework.Assert.fail;
 
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
+import android.platform.test.annotations.Presubmit;
 import android.provider.SearchIndexableResource;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
@@ -83,6 +84,7 @@
      * should have a key.
      */
     @Test
+    @Presubmit
     public void allPreferencesShouldHaveUniqueKey()
             throws IOException, XmlPullParserException, Resources.NotFoundException {
         final Set<String> uniqueKeys = new HashSet<>();