Merge "Ringtone -> Sound"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4fb17f7..8da8678 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -216,6 +216,11 @@
             </intent-filter>
         </activity>
 
+        <activity android:name=".search2.SearchActivity"
+                  android:label="@string/search_settings"
+                  android:icon="@drawable/ic_search_history">
+        </activity>
+
         <!-- Top-level settings -->
 
         <activity android:name="Settings$WifiSettingsActivity"
@@ -3275,14 +3280,12 @@
         </activity-alias>
 
         <activity-alias android:name="BackupResetDashboardAlias"
-                        android:targetActivity="Settings$PrivacySettingsActivity">
+                        android:targetActivity=".BackupSettingsActivity">
             <intent-filter android:priority="60">
                 <action android:name="com.android.settings.action.SETTINGS" />
             </intent-filter>
             <meta-data android:name="com.android.settings.category"
                        android:value="com.android.settings.category.ia.system" />
-            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
-                       android:value="com.android.settings.PrivacySettings" />
             <meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
                        android:value="true" />
         </activity-alias>
@@ -3316,6 +3319,17 @@
                        android:value="true" />
         </activity-alias>
 
+        <activity-alias android:name="MemorySettingsDashboardAlias"
+                        android:targetActivity=".Settings$MemorySettingsActivity">
+            <intent-filter>
+                <action android:name="com.android.settings.action.SETTINGS" />
+            </intent-filter>
+            <meta-data android:name="com.android.settings.category"
+                       android:value="com.android.settings.category.ia.development" />
+            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
+                       android:value="com.android.settings.applications.ProcessStatsSummary" />
+        </activity-alias>
+
         <activity android:name=".Settings$ConnectedDeviceDashboardActivity"
                   android:label="@string/connected_devices_dashboard_title"
                   android:icon="@drawable/ic_bt_laptop">
diff --git a/res/layout/search_intent_item.xml b/res/layout/search_intent_item.xml
new file mode 100644
index 0000000..b68f65b
--- /dev/null
+++ b/res/layout/search_intent_item.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<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:paddingEnd="?android:attr/scrollbarSize"
+    android:orientation="horizontal">
+
+    <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:gravity="center"
+            android:orientation="horizontal">
+
+        <ImageView
+                android:id="@+id/icon"
+                android:layout_width="@dimen/search_result_item_image_size"
+                android:layout_height="@dimen/search_result_item_image_size"
+                android:layout_marginStart="@dimen/search_result_item_image_margin_start"
+                android:layout_marginEnd="@dimen/search_result_item_image_margin_end"
+                android:scaleType="centerInside"/>
+    </LinearLayout>
+
+    <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingTop="10dp"
+            android:orientation="vertical">
+
+        <TextView android:id="@+id/title"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:singleLine="true"
+                  android:textAppearance="?android:attr/textAppearanceMedium"
+                  android:ellipsize="marquee"
+                  android:fadingEdge="horizontal" />
+
+        <TextView android:id="@+id/summary"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:singleLine="true"
+                  android:textAppearance="?android:attr/textAppearanceSmall"
+                  android:ellipsize="marquee"
+                  android:fadingEdge="horizontal" />
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/res/layout/search_main.xml b/res/layout/search_main.xml
new file mode 100644
index 0000000..ab728cd
--- /dev/null
+++ b/res/layout/search_main.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright 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.
+*/
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             android:id="@+id/main_content"
+             android:layout_height="match_parent"
+             android:layout_width="match_parent"
+             android:background="@color/material_grey_300"/>
diff --git a/res/layout/search_panel_2.xml b/res/layout/search_panel_2.xml
new file mode 100644
index 0000000..671c19c
--- /dev/null
+++ b/res/layout/search_panel_2.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:id="@+id/search_panel"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:layout_gravity="center"
+              android:orientation="vertical">
+
+    <LinearLayout android:id="@+id/layout_recent_searches"
+                  android:layout_width="match_parent"
+                  android:layout_height="wrap_content"
+                  android:layout_gravity="center"
+                  android:orientation="vertical">
+
+        <!-- Padding is included in the background -->
+        <android.support.v7.widget.RecyclerView android:id="@+id/list_recent_searches"
+                  android:layout_width="match_parent"
+                  android:layout_height="match_parent"
+                  android:paddingStart="@dimen/dashboard_padding_start"
+                  android:paddingEnd="@dimen/dashboard_padding_end"
+                  android:paddingTop="@dimen/dashboard_padding_top"
+                  android:paddingBottom="@dimen/dashboard_padding_bottom"
+                  android:scrollbarStyle="outsideOverlay"
+                  android:headerDividersEnabled="false"
+                  android:background="@drawable/search_panel_list_background"
+                  android:elevation="@dimen/search_panel_elevation"/>
+    </LinearLayout>
+
+    <LinearLayout android:id="@+id/layout_results"
+                  android:layout_width="match_parent"
+                  android:layout_height="match_parent"
+                  android:layout_gravity="center"
+                  android:orientation="vertical">
+
+        <!-- Padding is included in the background -->
+        <android.support.v7.widget.RecyclerView android:id="@+id/list_results"
+                  android:layout_width="match_parent"
+                  android:layout_height="match_parent"
+                  android:paddingStart="@dimen/dashboard_padding_start"
+                  android:paddingEnd="@dimen/dashboard_padding_end"
+                  android:paddingTop="@dimen/dashboard_padding_top"
+                  android:paddingBottom="@dimen/dashboard_padding_bottom"
+                  android:scrollbarStyle="outsideOverlay"
+                  android:scrollbars="vertical"
+                  android:background="@drawable/search_panel_list_background"/>
+    </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/menu/search_options_menu.xml b/res/menu/search_options_menu.xml
new file mode 100644
index 0000000..25a79d4
--- /dev/null
+++ b/res/menu/search_options_menu.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:id="@+id/search"
+        android:title="@string/search_menu"
+        android:icon="@*android:drawable/ic_search_api_material"
+        android:showAsAction="collapseActionView|ifRoom"
+        android:actionViewClass="android.widget.SearchView"/>
+</menu>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 9a74fa5..7d9ab4a 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -134,6 +134,7 @@
     <declare-styleable name="DividerPreference">
         <attr name="allowDividerAbove" format="boolean" />
         <attr name="allowDividerBelow" format="boolean" />
+        <attr name="multiLine" format="boolean" />
     </declare-styleable>
 
     <!-- For GesturePreference -->
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 464cb75..3c9a4cc 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -3161,7 +3161,7 @@
 
     <!-- Title for actual Settings license activity. --> <skip />
     <!-- About phone settings, Legal information setting option name and title of dialog box holding license info -->
-    <string name="settings_license_activity_title">Open source licenses</string>
+    <string name="settings_license_activity_title">Third-party licenses</string>
     <!-- About phone settings screen, Open source license dialog message when licenses cannot be loaded -->
     <string name="settings_license_activity_unavailable">There is a problem loading the licenses.</string>
     <!-- About phone settings screen, Open source license dialog title until license is fully loaded -->
@@ -6066,11 +6066,11 @@
     <!-- Sound: Title for the option defining the phone ringtone. [CHAR LIMIT=30] -->
     <string name="ringtone_title">Phone ringtone</string>
 
-    <!-- Sound: Title for the option defining the default notification ringtone. [CHAR LIMIT=30] -->
-    <string name="notification_ringtone_title">Default notification ringtone</string>
+    <!-- Sound: Title for the option defining the default notification sound. [CHAR LIMIT=30] -->
+    <string name="notification_ringtone_title">Default notification sound</string>
 
-    <!-- Sound: Title for the option defining the default alarm ringtone. [CHAR LIMIT=30] -->
-    <string name="alarm_ringtone_title">Default alarm ringtone</string>
+    <!-- Sound: Title for the option defining the default alarm sound. [CHAR LIMIT=30] -->
+    <string name="alarm_ringtone_title">Default alarm sound</string>
 
     <!-- Sound: Title for the option managing whether or not to vibrate when ringing. [CHAR LIMIT=30] -->
     <string name="vibrate_when_ringing_title">Also vibrate for calls</string>
@@ -7982,4 +7982,21 @@
     <string name="enterprise_privacy_settings_title">Privacy</string>
     <!-- Enterprise Privacy settings activity header, summarizing the powers that the admin has. [CHAR LIMIT=NONE] -->
     <string name="enterprise_privacy_header">To provide access to your work data, your organization may change settings and install software on your device, which could cause some of your personal content to be visible to your admin. Contact your organization\'s admin for more details.</string>
+    <!-- Title for the 'What types of information can your organization see?' preference category. [CHAR LIMIT=60] -->
+    <string name="exposure_category">What types of information can your organization see?</string>
+    <!-- Title for the 'What changes affect what your organization can see?' preference category. [CHAR LIMIT=60] -->
+    <string name="exposure_changes_category">What changes affect what your organization can see?</string>
+    <!-- Title for the 'What actions may impact your access to this device?' preference category. [CHAR LIMIT=60] -->
+    <string name="device_access_category">What actions may impact your access to this device?</string>
+    <!-- Label explaining that the admin can see data associated with his/her work account. [CHAR LIMIT=NONE] -->
+    <string name="enterprise_data">Data associated with your work account, such as email and calendar</string>
+    <!-- Label explaining that the admin can see all apps installed on the device. [CHAR LIMIT=NONE] -->
+    <string name="number_installed_packages_default">List of all apps on your device</string>
+    <!-- Label explaining that the admin can see all apps installed on the device. [CHAR LIMIT=NONE] -->
+    <plurals name="number_installed_packages">
+        <item quantity="one">List of all <xliff:g id="count">%d</xliff:g> app on your device</item>
+        <item quantity="other">List of all <xliff:g id="count">%d</xliff:g> apps on your device</item>
+    </plurals>
+    <!-- Label explaining that the admin can see app usage statistics. [CHAR LIMIT=NONE] -->
+    <string name="usage_stats">Usage (time spent and amount of data used) of each app on your device</string>
 </resources>
diff --git a/res/xml/development_prefs.xml b/res/xml/development_prefs.xml
index e6fc521..d84b008 100644
--- a/res/xml/development_prefs.xml
+++ b/res/xml/development_prefs.xml
@@ -63,12 +63,6 @@
         android:summary="@string/runningservices_settings_summary"
         android:fragment="com.android.settings.applications.RunningServices" />
 
-    <Preference
-        android:key="process_stats"
-        android:title="@string/memory_settings_title"
-        android:icon="@drawable/ic_settings_memory"
-        android:fragment="com.android.settings.applications.ProcessStatsSummary"/>
-
     <PreferenceScreen
         android:key="convert_to_file_encryption"
         android:title="@string/convert_to_file_encryption"
diff --git a/res/xml/enterprise_privacy_settings.xml b/res/xml/enterprise_privacy_settings.xml
index 26cc6a8..1dbb66c 100644
--- a/res/xml/enterprise_privacy_settings.xml
+++ b/res/xml/enterprise_privacy_settings.xml
@@ -23,4 +23,29 @@
     <Preference android:key="enterprise_privacy_header"
             android:summary="@string/enterprise_privacy_header"
             android:selectable="false"/>
+
+    <PreferenceCategory android:title="@string/exposure_category">
+        <com.android.settings.DividerPreference
+                android:key="enterprise_data"
+                android:layout_height="wrap_content"
+                android:title="@string/enterprise_data"
+                settings:allowDividerBelow="true"
+                settings:multiLine="true"/>
+        <com.android.settings.DividerPreference
+                android:key="number_installed_packages"
+                android:title="@string/number_installed_packages_default"
+                settings:allowDividerBelow="true"
+                settings:multiLine="true"/>
+        <com.android.settings.DividerPreference
+                android:key="usage_stats"
+                android:title="@string/usage_stats"
+                settings:allowDividerBelow="true"
+                settings:multiLine="true"/>
+    </PreferenceCategory>
+
+    <PreferenceCategory android:title="@string/exposure_changes_category">
+    </PreferenceCategory>
+
+    <PreferenceCategory android:title="@string/device_access_category">
+    </PreferenceCategory>
 </PreferenceScreen>
diff --git a/res/xml/user_and_accounts_settings.xml b/res/xml/user_and_accounts_settings.xml
index 64b9292..1c487ae 100644
--- a/res/xml/user_and_accounts_settings.xml
+++ b/res/xml/user_and_accounts_settings.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
+<!-- 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.
@@ -16,40 +16,40 @@
 
 <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
                   xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
-        android:key="account"
         android:title="@string/account_settings_title"
         settings:keywords="@string/keywords_accounts">
 
-    <PreferenceCategory
-            android:key="account_for_header"
-            android:title="@string/account_for_section_header" />
-
     <Preference
-            android:key="emergency_info"
-            android:title="@string/emergency_info_title" />
+        android:key="emergency_info"
+        android:title="@string/emergency_info_title"
+        android:order="100"/>
 
     <PreferenceCategory
-            android:key="account_configuration_header"
-            android:title="@string/configure_section_header">
-
+        android:key="account_configuration_header"
+        android:title="@string/configure_section_header"
+        android:order="101">
     </PreferenceCategory>
 
     <SwitchPreference
-            android:key="auto_sync_account_data"
-            android:title="@string/auto_sync_account_title" />
+        android:key="auto_sync_account_data"
+        android:title="@string/auto_sync_account_title"
+        android:order="102" />
 
     <SwitchPreference
-            android:key="auto_sync_work_account_data"
-            android:title="@string/account_settings_menu_auto_sync_work" />
+        android:key="auto_sync_work_account_data"
+        android:title="@string/account_settings_menu_auto_sync_work"
+        android:order="103"/>
 
     <SwitchPreference
-            android:key="auto_sync_personal_account_data"
-            android:title="@string/account_settings_menu_auto_sync_personal" />
+        android:key="auto_sync_personal_account_data"
+        android:title="@string/account_settings_menu_auto_sync_personal"
+        android:order="104"/>
 
     <com.android.settingslib.RestrictedSwitchPreference
-            android:key="add_users_when_locked"
-            android:title="@string/user_add_on_lockscreen_menu"
-            android:summary="@string/user_add_on_lockscreen_menu_summary"
-            settings:useAdditionalSummary="true" />
+        android:key="add_users_when_locked"
+        android:title="@string/user_add_on_lockscreen_menu"
+        android:summary="@string/user_add_on_lockscreen_menu_summary"
+        settings:useAdditionalSummary="true"
+        android:order="105"/>
 
 </PreferenceScreen>
diff --git a/src/com/android/settings/DateTimeSettings.java b/src/com/android/settings/DateTimeSettings.java
index cbe3cdd..749a4ed 100644
--- a/src/com/android/settings/DateTimeSettings.java
+++ b/src/com/android/settings/DateTimeSettings.java
@@ -41,6 +41,8 @@
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settings.dashboard.SummaryLoader;
+import com.android.settings.datetime.TimeFormatPreferenceController;
+import com.android.settings.datetime.UpdateTimeAndDateCallback;
 import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settings.search.Indexable;
 import com.android.settingslib.RestrictedLockUtils;
@@ -49,20 +51,13 @@
 
 import java.util.ArrayList;
 import java.util.Calendar;
-import java.util.Date;
 import java.util.List;
 
 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 
 public class DateTimeSettings extends SettingsPreferenceFragment
-        implements OnTimeSetListener, OnDateSetListener, OnPreferenceChangeListener, Indexable {
-
-    private static final String HOURS_12 = "12";
-    private static final String HOURS_24 = "24";
-
-    // Used for showing the current date format, which looks like "12/31/2010", "2010/12/13", etc.
-    // The date value is dummy (independent of actual date).
-    private Calendar mDummyDate;
+        implements UpdateTimeAndDateCallback, OnTimeSetListener, OnDateSetListener,
+        OnPreferenceChangeListener, Indexable {
 
     private static final String KEY_AUTO_TIME = "auto_time";
     private static final String KEY_AUTO_TIME_ZONE = "auto_zone";
@@ -71,14 +66,14 @@
     private static final int DIALOG_TIMEPICKER = 1;
 
     // have we been launched from the setup wizard?
-    protected static final String EXTRA_IS_FIRST_RUN = "firstRun";
+    protected static final String EXTRA_IS_FROM_SUW = "firstRun";
 
     // Minimum time is Nov 5, 2007, 0:00.
     private static final long MIN_DATE = 1194220800000L;
 
+    private TimeFormatPreferenceController mTimeFormatPreferenceController;
     private RestrictedSwitchPreference mAutoTimePref;
     private Preference mTimePref;
-    private Preference mTime24Pref;
     private SwitchPreference mAutoTimeZonePref;
     private Preference mTimeZone;
     private Preference mDatePref;
@@ -98,19 +93,21 @@
     }
 
     private void initUI() {
+        final Activity activity = getActivity();
+        final Intent intent = activity.getIntent();
+        final boolean isFromSUW = intent.getBooleanExtra(EXTRA_IS_FROM_SUW, false);
         boolean autoTimeEnabled = getAutoState(Settings.Global.AUTO_TIME);
         boolean autoTimeZoneEnabled = getAutoState(Settings.Global.AUTO_TIME_ZONE);
 
+        mTimeFormatPreferenceController = new TimeFormatPreferenceController(
+                activity, this /* UpdateTimeAndDateCallback */, isFromSUW);
+        mTimeFormatPreferenceController.displayPreference(getPreferenceScreen());
+
         mAutoTimePref = (RestrictedSwitchPreference) findPreference(KEY_AUTO_TIME);
         mAutoTimePref.setOnPreferenceChangeListener(this);
-        EnforcedAdmin admin = RestrictedLockUtils.checkIfAutoTimeRequired(getActivity());
+        EnforcedAdmin admin = RestrictedLockUtils.checkIfAutoTimeRequired(activity);
         mAutoTimePref.setDisabledByAdmin(admin);
 
-        Intent intent = getActivity().getIntent();
-        boolean isFirstRun = intent.getBooleanExtra(EXTRA_IS_FIRST_RUN, false);
-
-        mDummyDate = Calendar.getInstance();
-
         // If device admin requires auto time device policy manager will set
         // Settings.Global.AUTO_TIME to true. Note that this app listens to that change.
         mAutoTimePref.setChecked(autoTimeEnabled);
@@ -118,19 +115,15 @@
         mAutoTimeZonePref.setOnPreferenceChangeListener(this);
         // Override auto-timezone if it's a wifi-only device or if we're still in setup wizard.
         // TODO: Remove the wifiOnly test when auto-timezone is implemented based on wifi-location.
-        if (Utils.isWifiOnly(getActivity()) || isFirstRun) {
+        if (Utils.isWifiOnly(activity) || isFromSUW) {
             getPreferenceScreen().removePreference(mAutoTimeZonePref);
             autoTimeZoneEnabled = false;
         }
         mAutoTimeZonePref.setChecked(autoTimeZoneEnabled);
 
         mTimePref = findPreference("time");
-        mTime24Pref = findPreference("24 hour");
         mTimeZone = findPreference("timezone");
         mDatePref = findPreference("date");
-        if (isFirstRun) {
-            getPreferenceScreen().removePreference(mTime24Pref);
-        }
 
         mTimePref.setEnabled(!autoTimeEnabled);
         mDatePref.setEnabled(!autoTimeEnabled);
@@ -140,9 +133,6 @@
     @Override
     public void onResume() {
         super.onResume();
-
-        ((SwitchPreference)mTime24Pref).setChecked(is24Hour());
-
         // Register for time ticks and other reasons for time change
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_TIME_TICK);
@@ -159,18 +149,15 @@
         getActivity().unregisterReceiver(mIntentReceiver);
     }
 
+    @Override
     public void updateTimeAndDateDisplay(Context context) {
         final Calendar now = Calendar.getInstance();
-        mDummyDate.setTimeZone(now.getTimeZone());
-        // We use December 31st because it's unambiguous when demonstrating the date format.
-        // We use 13:00 so we can demonstrate the 12/24 hour options.
-        mDummyDate.set(now.get(Calendar.YEAR), 11, 31, 13, 0, 0);
-        Date dummyDate = mDummyDate.getTime();
         mDatePref.setSummary(DateFormat.getLongDateFormat(context).format(now.getTime()));
         mTimePref.setSummary(DateFormat.getTimeFormat(getActivity()).format(now.getTime()));
         mTimeZone.setSummary(ZoneGetter.getTimeZoneOffsetAndName(context,
                 now.getTimeZone(), now.getTime()));
-        mTime24Pref.setSummary(DateFormat.getTimeFormat(getActivity()).format(dummyDate));
+        mTimeFormatPreferenceController.updateState(findPreference(
+                mTimeFormatPreferenceController.getPreferenceKey()));
     }
 
     @Override
@@ -216,24 +203,24 @@
     public Dialog onCreateDialog(int id) {
         final Calendar calendar = Calendar.getInstance();
         switch (id) {
-        case DIALOG_DATEPICKER:
-            DatePickerDialog d = new DatePickerDialog(
-                    getActivity(),
-                    this,
-                    calendar.get(Calendar.YEAR),
-                    calendar.get(Calendar.MONTH),
-                    calendar.get(Calendar.DAY_OF_MONTH));
-            configureDatePicker(d.getDatePicker());
-            return d;
-        case DIALOG_TIMEPICKER:
-            return new TimePickerDialog(
-                    getActivity(),
-                    this,
-                    calendar.get(Calendar.HOUR_OF_DAY),
-                    calendar.get(Calendar.MINUTE),
-                    DateFormat.is24HourFormat(getActivity()));
-        default:
-            throw new IllegalArgumentException();
+            case DIALOG_DATEPICKER:
+                DatePickerDialog d = new DatePickerDialog(
+                        getActivity(),
+                        this,
+                        calendar.get(Calendar.YEAR),
+                        calendar.get(Calendar.MONTH),
+                        calendar.get(Calendar.DAY_OF_MONTH));
+                configureDatePicker(d.getDatePicker());
+                return d;
+            case DIALOG_TIMEPICKER:
+                return new TimePickerDialog(
+                        getActivity(),
+                        this,
+                        calendar.get(Calendar.HOUR_OF_DAY),
+                        calendar.get(Calendar.MINUTE),
+                        DateFormat.is24HourFormat(getActivity()));
+            default:
+                throw new IllegalArgumentException();
         }
     }
 
@@ -288,17 +275,15 @@
     */
     @Override
     public boolean onPreferenceTreeClick(Preference preference) {
+        if (mTimeFormatPreferenceController.handlePreferenceTreeClick(preference)) {
+            return super.onPreferenceTreeClick(preference);
+        }
         if (preference == mDatePref) {
             showDialog(DIALOG_DATEPICKER);
         } else if (preference == mTimePref) {
             // The 24-hour mode may have changed, so recreate the dialog
             removeDialog(DIALOG_TIMEPICKER);
             showDialog(DIALOG_TIMEPICKER);
-        } else if (preference == mTime24Pref) {
-            final boolean is24Hour = ((SwitchPreference)mTime24Pref).isChecked();
-            set24Hour(is24Hour);
-            updateTimeAndDateDisplay(getActivity());
-            timeUpdated(is24Hour);
         }
         return super.onPreferenceTreeClick(preference);
     }
@@ -309,24 +294,6 @@
         updateTimeAndDateDisplay(getActivity());
     }
 
-    private void timeUpdated(boolean is24Hour) {
-        Intent timeChanged = new Intent(Intent.ACTION_TIME_CHANGED);
-        timeChanged.putExtra(Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT, is24Hour);
-        getActivity().sendBroadcast(timeChanged);
-    }
-
-    /*  Get & Set values from the system settings  */
-
-    private boolean is24Hour() {
-        return DateFormat.is24HourFormat(getActivity());
-    }
-
-    private void set24Hour(boolean is24Hour) {
-        Settings.System.putString(getContentResolver(),
-                Settings.System.TIME_12_24,
-                is24Hour? HOURS_24 : HOURS_12);
-    }
-
     private boolean getAutoState(String name) {
         try {
             return Settings.Global.getInt(getContentResolver(), name) > 0;
@@ -335,7 +302,8 @@
         }
     }
 
-    /* package */ static void setDate(Context context, int year, int month, int day) {
+    /* package */
+    static void setDate(Context context, int year, int month, int day) {
         Calendar c = Calendar.getInstance();
 
         c.set(Calendar.YEAR, year);
@@ -348,7 +316,8 @@
         }
     }
 
-    /* package */ static void setTime(Context context, int hourOfDay, int minute) {
+    /* package */
+    static void setTime(Context context, int hourOfDay, int minute) {
         Calendar c = Calendar.getInstance();
 
         c.set(Calendar.HOUR_OF_DAY, hourOfDay);
@@ -396,7 +365,7 @@
             = new SummaryLoader.SummaryProviderFactory() {
         @Override
         public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity,
-                                                                   SummaryLoader summaryLoader) {
+                SummaryLoader summaryLoader) {
             return new SummaryProvider(activity, summaryLoader);
         }
     };
diff --git a/src/com/android/settings/DevelopmentSettings.java b/src/com/android/settings/DevelopmentSettings.java
index dd9a7fe..867fbfd 100644
--- a/src/com/android/settings/DevelopmentSettings.java
+++ b/src/com/android/settings/DevelopmentSettings.java
@@ -58,6 +58,7 @@
 import android.provider.SearchIndexableResource;
 import android.provider.Settings;
 import android.service.persistentdata.PersistentDataBlockManager;
+import android.support.annotation.VisibleForTesting;
 import android.support.v14.preference.SwitchPreference;
 import android.support.v7.preference.ListPreference;
 import android.support.v7.preference.Preference;
@@ -81,14 +82,16 @@
 import com.android.internal.app.LocalePicker;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settings.applications.BackgroundCheckSummary;
-import com.android.settings.applications.ProcessStatsPreferenceController;
+import com.android.settings.dashboard.DashboardFeatureProvider;
 import com.android.settings.fuelgauge.InactiveApps;
+import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settings.search.Indexable;
 import com.android.settings.widget.SwitchBar;
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 import com.android.settingslib.RestrictedSwitchPreference;
+import com.android.settingslib.drawer.CategoryKey;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -100,7 +103,7 @@
  */
 public class DevelopmentSettings extends RestrictedSettingsFragment
         implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener,
-                OnPreferenceChangeListener, SwitchBar.OnSwitchChangeListener, Indexable {
+        OnPreferenceChangeListener, SwitchBar.OnSwitchChangeListener, Indexable {
     private static final String TAG = "DevelopmentSettings";
 
     /**
@@ -136,7 +139,7 @@
     private static final String WAIT_FOR_DEBUGGER_KEY = "wait_for_debugger";
     private static final String MOCK_LOCATION_APP_KEY = "mock_location_app";
     private static final String VERIFY_APPS_OVER_USB_KEY = "verify_apps_over_usb";
-    private static final String DEBUG_VIEW_ATTRIBUTES =  "debug_view_attributes";
+    private static final String DEBUG_VIEW_ATTRIBUTES = "debug_view_attributes";
     private static final String FORCE_ALLOW_ON_EXTERNAL_KEY = "force_allow_on_external";
     private static final String STRICT_MODE_KEY = "strict_mode";
     private static final String POINTER_LOCATION_KEY = "pointer_location";
@@ -178,7 +181,8 @@
     private static final String SELECT_LOGPERSIST_PROPERTY_SERVICE = "logcatd";
     private static final String SELECT_LOGPERSIST_PROPERTY_CLEAR = "clear";
     private static final String SELECT_LOGPERSIST_PROPERTY_STOP = "stop";
-    private static final String SELECT_LOGPERSIST_PROPERTY_BUFFER = "persist.logd.logpersistd.buffer";
+    private static final String SELECT_LOGPERSIST_PROPERTY_BUFFER =
+            "persist.logd.logpersistd.buffer";
     private static final String ACTUAL_LOGPERSIST_PROPERTY_BUFFER = "logd.logpersistd.buffer";
     private static final String ACTUAL_LOGPERSIST_PROPERTY_ENABLE = "logd.logpersistd.enable";
 
@@ -193,9 +197,9 @@
     private static final String COLOR_TEMPERATURE_KEY = "color_temperature";
 
     private static final String BLUETOOTH_DISABLE_ABSOLUTE_VOLUME_KEY =
-                                    "bluetooth_disable_absolute_volume";
+            "bluetooth_disable_absolute_volume";
     private static final String BLUETOOTH_DISABLE_ABSOLUTE_VOLUME_PROPERTY =
-                                    "persist.bluetooth.disableabsvol";
+            "persist.bluetooth.disableabsvol";
 
     private static final String INACTIVE_APPS_KEY = "inactive_apps";
 
@@ -225,7 +229,7 @@
 
     private static final int REQUEST_CODE_ENABLE_OEM_UNLOCK = 0;
 
-    private static final int[] MOCK_LOCATION_APP_OPS = new int[] {AppOpsManager.OP_MOCK_LOCATION};
+    private static final int[] MOCK_LOCATION_APP_OPS = new int[]{AppOpsManager.OP_MOCK_LOCATION};
 
     private IWindowManager mWindowManager;
     private IBackupManager mBackupManager;
@@ -308,7 +312,6 @@
     private SwitchPreference mForceResizable;
 
     private SwitchPreference mColorTemperaturePreference;
-    private ProcessStatsPreferenceController mProcessStatsPreferenceController;
 
     private final ArrayList<Preference> mAllPrefs = new ArrayList<Preference>();
 
@@ -326,6 +329,7 @@
 
     private boolean mLogpersistCleared;
     private Dialog mLogpersistClearDialog;
+    private DashboardFeatureProvider mDashboardFeatureProvider;
 
     public DevelopmentSettings() {
         super(UserManager.DISALLOW_DEBUGGING_FEATURES);
@@ -337,19 +341,26 @@
     }
 
     @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        mDashboardFeatureProvider = FeatureFactory.getFactory(context)
+                .getDashboardFeatureProvider(context);
+    }
+
+    @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
 
         mWindowManager = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
         mBackupManager = IBackupManager.Stub.asInterface(
                 ServiceManager.getService(Context.BACKUP_SERVICE));
-        mWebViewUpdateService  =
-            IWebViewUpdateService.Stub.asInterface(ServiceManager.getService("webviewupdate"));
-        mOemUnlockManager = (PersistentDataBlockManager)getActivity()
+        mWebViewUpdateService =
+                IWebViewUpdateService.Stub.asInterface(ServiceManager.getService("webviewupdate"));
+        mOemUnlockManager = (PersistentDataBlockManager) getActivity()
                 .getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
         mTelephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
 
-        mDpm = (DevicePolicyManager)getActivity().getSystemService(Context.DEVICE_POLICY_SERVICE);
+        mDpm = (DevicePolicyManager) getActivity().getSystemService(Context.DEVICE_POLICY_SERVICE);
         mUm = (UserManager) getSystemService(Context.USER_SERVICE);
 
         mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
@@ -362,12 +373,9 @@
             setPreferenceScreen(new PreferenceScreen(getPrefContext(), null));
             return;
         }
-        mProcessStatsPreferenceController = new ProcessStatsPreferenceController(getActivity());
 
         addPreferencesFromResource(R.xml.development_prefs);
 
-        mProcessStatsPreferenceController.displayPreference(getPreferenceScreen());
-
         final PreferenceGroup debugDebuggingCategory = (PreferenceGroup)
                 findPreference(DEBUG_DEBUGGING_CATEGORY_KEY);
         mEnableAdb = findAndInitSwitchPref(ENABLE_ADB);
@@ -485,7 +493,7 @@
         }
 
         PreferenceScreen convertFbePreference =
-            (PreferenceScreen) findPreference(KEY_CONVERT_FBE);
+                (PreferenceScreen) findPreference(KEY_CONVERT_FBE);
 
         try {
             IBinder service = ServiceManager.getService("mount");
@@ -495,9 +503,9 @@
             } else if ("file".equals(SystemProperties.get("ro.crypto.type", "none"))) {
                 convertFbePreference.setEnabled(false);
                 convertFbePreference.setSummary(getResources()
-                                   .getString(R.string.convert_to_file_encryption_done));
+                        .getString(R.string.convert_to_file_encryption_done));
             }
-        } catch(RemoteException e) {
+        } catch (RemoteException e) {
             removePreference(KEY_CONVERT_FBE);
         }
 
@@ -519,6 +527,20 @@
             removePreference(COLOR_TEMPERATURE_KEY);
             mColorTemperaturePreference = null;
         }
+
+        addDashboardCategoryPreferences();
+    }
+
+    @VisibleForTesting
+    void addDashboardCategoryPreferences() {
+        final PreferenceScreen screen = getPreferenceScreen();
+        final List<Preference> tilePrefs = mDashboardFeatureProvider.getPreferencesForCategory(
+                getActivity(), getPrefContext(), CategoryKey.CATEGORY_SYSTEM_DEVELOPMENT);
+        if (tilePrefs != null) {
+            for (Preference preference : tilePrefs) {
+                screen.addPreference(preference);
+            }
+        }
     }
 
     private ListPreference addListPreference(String prefKey) {
@@ -552,7 +574,7 @@
         final SettingsActivity activity = (SettingsActivity) getActivity();
 
         mSwitchBar = activity.getSwitchBar();
-       if (mUnavailable) {
+        if (mUnavailable) {
             mSwitchBar.setEnabled(false);
             return;
         }
@@ -736,7 +758,7 @@
 
     private void resetDangerousOptions() {
         mDontPokeProperties = true;
-        for (int i=0; i< mResetSwitchPrefs.size(); i++) {
+        for (int i = 0; i < mResetSwitchPrefs.size(); i++) {
             SwitchPreference cb = mResetSwitchPrefs.get(i);
             if (cb.isChecked()) {
                 cb.setChecked(false);
@@ -770,7 +792,7 @@
             }
             ArrayList<String> options = new ArrayList<String>();
             ArrayList<String> values = new ArrayList<String>();
-            for(int n = 0; n < providers.length; n++) {
+            for (int n = 0; n < providers.length; n++) {
                 if (Utils.isPackageEnabled(getActivity(), providers[n].packageName)) {
                     options.add(providers[n].description);
                     values.add(providers[n].packageName);
@@ -790,7 +812,7 @@
                     return;
                 }
             }
-        } catch(RemoteException e) {
+        } catch (RemoteException e) {
         }
     }
 
@@ -809,7 +831,7 @@
             String wv_package = mWebViewUpdateService.getCurrentWebViewPackageName();
             ActivityManager.getService().killPackageDependents(
                     wv_package, UserHandle.USER_ALL);
-        } catch(RemoteException e) {
+        } catch (RemoteException e) {
         }
     }
 
@@ -858,7 +880,7 @@
                     newValue == null ? "" : newValue.toString());
             updateWebViewProviderOptions();
             return newValue != null && newValue.equals(updatedProvider);
-        } catch(RemoteException e) {
+        } catch (RemoteException e) {
         }
         return false;
     }
@@ -866,7 +888,7 @@
     private void writeDebuggerOptions() {
         try {
             ActivityManager.getService().setDebugApp(
-                mDebugApp, mWaitForDebugger.isChecked(), true);
+                    mDebugApp, mWaitForDebugger.isChecked(), true);
         } catch (RemoteException ex) {
         }
     }
@@ -972,8 +994,9 @@
     }
 
     private void updateVerifyAppsOverUsbOptions() {
-        updateSwitchPreference(mVerifyAppsOverUsb, Settings.Global.getInt(getActivity().getContentResolver(),
-                Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, 1) != 0);
+        updateSwitchPreference(mVerifyAppsOverUsb,
+                Settings.Global.getInt(getActivity().getContentResolver(),
+                        Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, 1) != 0);
         mVerifyAppsOverUsb.setEnabled(enableVerifierSetting());
     }
 
@@ -1328,7 +1351,7 @@
 
     /**
      * @return <code>true</code> if the color space preference is currently
-     *         controlled by development settings
+     * controlled by development settings
      */
     private boolean usingDevelopmentColorSpace() {
         final ContentResolver cr = getContentResolver();
@@ -1488,7 +1511,7 @@
             }
             if (mLogpersist != null) {
                 String currentLogpersistEnable
-                    = SystemProperties.get(ACTUAL_LOGPERSIST_PROPERTY_ENABLE);
+                        = SystemProperties.get(ACTUAL_LOGPERSIST_PROPERTY_ENABLE);
                 if ((currentLogpersistEnable == null)
                         || !currentLogpersistEnable.equals("true")
                         || currentValue.equals(SELECT_LOGD_OFF_SIZE_MARKER_VALUE)) {
@@ -1525,7 +1548,7 @@
 
     private void writeLogdSizeOption(Object newValue) {
         boolean disable = (newValue != null) &&
-            (newValue.toString().equals(SELECT_LOGD_OFF_SIZE_MARKER_VALUE));
+                (newValue.toString().equals(SELECT_LOGD_OFF_SIZE_MARKER_VALUE));
         String currentTag = SystemProperties.get(SELECT_LOGD_TAG_PROPERTY);
         if (currentTag == null) {
             currentTag = "";
@@ -1558,7 +1581,7 @@
         }
         String defaultValue = defaultLogdSizeValue();
         final String size = ((newValue != null) && (newValue.toString().length() != 0)) ?
-            newValue.toString() : defaultValue;
+                newValue.toString() : defaultValue;
         SystemProperties.set(SELECT_LOGD_SIZE_PROPERTY, defaultValue.equals(size) ? "" : size);
         SystemProperties.set("ctl.start", "logd-reinit");
         pokeSystemProperties();
@@ -1588,7 +1611,7 @@
                     currentBuffers.contains("kernel")) {
                 index = 2;
                 if (!currentBuffers.contains("default")) {
-                    String[] contains = { "main", "events", "system", "crash" };
+                    String[] contains = {"main", "events", "system", "crash"};
                     for (int i = 0; i < contains.length; i++) {
                         if (!currentBuffers.contains(contains[i])) {
                             index = 1;
@@ -1598,8 +1621,10 @@
                 }
             }
         }
-        mLogpersist.setValue(getResources().getStringArray(R.array.select_logpersist_values)[index]);
-        mLogpersist.setSummary(getResources().getStringArray(R.array.select_logpersist_summaries)[index]);
+        mLogpersist.setValue(
+                getResources().getStringArray(R.array.select_logpersist_values)[index]);
+        mLogpersist.setSummary(
+                getResources().getStringArray(R.array.select_logpersist_summaries)[index]);
         mLogpersist.setOnPreferenceChangeListener(this);
         if (index != 0) {
             mLogpersistCleared = false;
@@ -1617,7 +1642,7 @@
         SystemProperties.set(ACTUAL_LOGPERSIST_PROPERTY_BUFFER, "");
         SystemProperties.set(SELECT_LOGPERSIST_PROPERTY, "");
         SystemProperties.set(ACTUAL_LOGPERSIST_PROPERTY,
-            update ? "" : SELECT_LOGPERSIST_PROPERTY_STOP);
+                update ? "" : SELECT_LOGPERSIST_PROPERTY_STOP);
         pokeSystemProperties();
         if (update) {
             updateLogpersistValues();
@@ -1710,7 +1735,7 @@
     }
 
     private void writeUsbConfigurationOption(Object newValue) {
-        UsbManager manager = (UsbManager)getActivity().getSystemService(Context.USB_SERVICE);
+        UsbManager manager = (UsbManager) getActivity().getSystemService(Context.USB_SERVICE);
         String function = newValue.toString();
         if (function.equals("none")) {
             manager.setCurrentFunction(function, false);
@@ -1729,7 +1754,8 @@
 
     private void updateImmediatelyDestroyActivitiesOptions() {
         updateSwitchPreference(mImmediatelyDestroyActivities, Settings.Global.getInt(
-                getActivity().getContentResolver(), Settings.Global.ALWAYS_FINISH_ACTIVITIES, 0) != 0);
+                getActivity().getContentResolver(), Settings.Global.ALWAYS_FINISH_ACTIVITIES, 0)
+                != 0);
     }
 
     private void updateAnimationScaleValue(int which, ListPreference pref) {
@@ -1739,7 +1765,7 @@
                 mHaveDebugSettings = true;
             }
             CharSequence[] values = pref.getEntryValues();
-            for (int i=0; i<values.length; i++) {
+            for (int i = 0; i < values.length; i++) {
                 float val = Float.parseFloat(values[i].toString());
                 if (scale <= val) {
                     pref.setValueIndex(i);
@@ -1747,7 +1773,7 @@
                     return;
                 }
             }
-            pref.setValueIndex(values.length-1);
+            pref.setValueIndex(values.length - 1);
             pref.setSummary(pref.getEntries()[0]);
         } catch (RemoteException e) {
         }
@@ -1797,7 +1823,7 @@
         try {
             int limit = ActivityManager.getService().getProcessLimit();
             CharSequence[] values = mAppProcessLimit.getEntryValues();
-            for (int i=0; i<values.length; i++) {
+            for (int i = 0; i < values.length; i++) {
                 int val = Integer.parseInt(values[i].toString());
                 if (val >= limit) {
                     if (i != 0) {
@@ -1844,15 +1870,16 @@
             }
         };
 
-        DialogInterface.OnDismissListener onDismissListener = new DialogInterface.OnDismissListener() {
-            @Override
-            public void onDismiss(DialogInterface dialog) {
-                if (getActivity() == null) {
-                    return;
-                }
-                updateAllOptions();
-            }
-        };
+        DialogInterface.OnDismissListener onDismissListener =
+                new DialogInterface.OnDismissListener() {
+                    @Override
+                    public void onDismiss(DialogInterface dialog) {
+                        if (getActivity() == null) {
+                            return;
+                        }
+                        updateAllOptions();
+                    }
+                };
 
         new AlertDialog.Builder(getActivity())
                 .setTitle(R.string.confirm_enable_oem_unlock_title)
@@ -1945,10 +1972,10 @@
         } else if (preference == mClearAdbKeys) {
             if (mAdbKeysDialog != null) dismissDialogs();
             mAdbKeysDialog = new AlertDialog.Builder(getActivity())
-                        .setMessage(R.string.adb_keys_warning_message)
-                        .setPositiveButton(android.R.string.ok, this)
-                        .setNegativeButton(android.R.string.cancel, null)
-                        .show();
+                    .setMessage(R.string.adb_keys_warning_message)
+                    .setPositiveButton(android.R.string.ok, this)
+                    .setNegativeButton(android.R.string.cancel, null)
+                    .show();
         } else if (preference == mEnableTerminal) {
             final PackageManager pm = getActivity().getPackageManager();
             pm.setApplicationEnabledSetting(TERMINAL_APP_PACKAGE,
@@ -2275,8 +2302,8 @@
                 private boolean isShowingDeveloperOptions(Context context) {
                     return context.getSharedPreferences(DevelopmentSettings.PREF_FILE,
                             Context.MODE_PRIVATE).getBoolean(
-                                    DevelopmentSettings.PREF_SHOW,
-                                    android.os.Build.TYPE.equals("eng"));
+                            DevelopmentSettings.PREF_SHOW,
+                            android.os.Build.TYPE.equals("eng"));
                 }
 
                 @Override
@@ -2333,7 +2360,7 @@
                 // the device hasn't been able to confirm which restrictions (SIM-lock or otherwise)
                 // apply.
                 oemUnlockSummary =
-                    R.string.oem_unlock_enable_disabled_summary_connectivity_or_locked;
+                        R.string.oem_unlock_enable_disabled_summary_connectivity_or_locked;
             }
             mEnableOemUnlock.setSummary(getString(oemUnlockSummary));
         }
diff --git a/src/com/android/settings/DividerPreference.java b/src/com/android/settings/DividerPreference.java
index d499e52..5251ec3 100644
--- a/src/com/android/settings/DividerPreference.java
+++ b/src/com/android/settings/DividerPreference.java
@@ -19,11 +19,13 @@
 import android.support.v7.preference.Preference;
 import android.support.v7.preference.PreferenceViewHolder;
 import android.util.AttributeSet;
+import android.widget.TextView;
 
 public class DividerPreference extends Preference {
 
     private Boolean mAllowAbove;
     private Boolean mAllowBelow;
+    private Boolean mMultiLine;
 
     public DividerPreference(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -34,6 +36,9 @@
         if (a.hasValue(R.styleable.DividerPreference_allowDividerBelow)) {
             mAllowBelow = a.getBoolean(R.styleable.DividerPreference_allowDividerBelow, false);
         }
+        if (a.hasValue(R.styleable.DividerPreference_multiLine)) {
+            mMultiLine = a.getBoolean(R.styleable.DividerPreference_multiLine, false);
+        }
     }
 
     public DividerPreference(Context context) {
@@ -59,5 +64,12 @@
         if (mAllowBelow != null) {
             holder.setDividerAllowedBelow(mAllowBelow);
         }
+
+        if (mMultiLine != null && mMultiLine) {
+            TextView textView = (TextView)holder.findViewById(android.R.id.title);
+            if (textView != null) {
+                textView.setSingleLine(false);
+            }
+        }
     }
 }
diff --git a/src/com/android/settings/SecuritySettings.java b/src/com/android/settings/SecuritySettings.java
index c42b524..46e7fe5 100644
--- a/src/com/android/settings/SecuritySettings.java
+++ b/src/com/android/settings/SecuritySettings.java
@@ -66,8 +66,6 @@
 import com.android.settingslib.RestrictedPreference;
 import com.android.settingslib.RestrictedSwitchPreference;
 import com.android.settingslib.drawer.CategoryKey;
-import com.android.settingslib.drawer.DashboardCategory;
-import com.android.settingslib.drawer.Tile;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -410,7 +408,8 @@
         Index.getInstance(getActivity())
                 .updateFromClassNameResource(SecuritySettings.class.getName(), true, true);
 
-        final List<Preference> tilePrefs = getDynamicTilesForSecurity();
+        final List<Preference> tilePrefs = mDashboardFeatureProvider.getPreferencesForCategory(
+                getActivity(), getPrefContext(), CategoryKey.CATEGORY_SECURITY);
         if (tilePrefs != null && !tilePrefs.isEmpty()) {
             for (Preference preference : tilePrefs) {
                 root.addPreference(preference);
@@ -763,31 +762,6 @@
                 SET_OR_CHANGE_LOCK_METHOD_REQUEST_PROFILE, extras);
     }
 
-    private List<Preference> getDynamicTilesForSecurity() {
-        if (!mDashboardFeatureProvider.isEnabled()) {
-            return null;
-        }
-        final DashboardCategory category =
-                mDashboardFeatureProvider.getTilesForCategory(CategoryKey.CATEGORY_SECURITY);
-        if (category == null) {
-            Log.d(TAG, "NO dashboard tiles for " + TAG);
-            return null;
-        }
-        final List<Tile> tiles = category.tiles;
-        if (tiles == null) {
-            Log.d(TAG, "tile list is empty, skipping category " + category.title);
-            return null;
-        }
-        final List<Preference> preferences = new ArrayList<>();
-        for (Tile tile : tiles) {
-            final Preference pref = new Preference(getPrefContext());
-            mDashboardFeatureProvider.bindPreferenceToTile(getActivity(), pref, tile,
-                    null /* key */, Preference.DEFAULT_ORDER/* baseOrder */);
-            preferences.add(pref);
-        }
-        return preferences;
-    }
-
     @Override
     public boolean onPreferenceChange(Preference preference, Object value) {
         boolean result = true;
diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java
index f67f73f..9d703c1 100644
--- a/src/com/android/settings/SettingsActivity.java
+++ b/src/com/android/settings/SettingsActivity.java
@@ -94,13 +94,11 @@
 import com.android.settings.deviceinfo.StorageDashboardFragment;
 import com.android.settings.deviceinfo.StorageSettings;
 import com.android.settings.display.NightDisplaySettings;
-import com.android.settings.enterprise.EnterprisePrivacyFeatureProvider;
 import com.android.settings.enterprise.EnterprisePrivacySettings;
 import com.android.settings.fuelgauge.BatterySaverSettings;
 import com.android.settings.fuelgauge.PowerUsageDetail;
 import com.android.settings.fuelgauge.PowerUsageSummary;
 import com.android.settings.gestures.DoubleTapPowerSettings;
-import com.android.settings.gestures.DoubleTapScreenPreferenceController;
 import com.android.settings.gestures.DoubleTapScreenSettings;
 import com.android.settings.gestures.DoubleTwistGestureSettings;
 import com.android.settings.gestures.GestureSettings;
@@ -138,6 +136,8 @@
 import com.android.settings.qstile.DevelopmentTiles;
 import com.android.settings.search.DynamicIndexableContentMonitor;
 import com.android.settings.search.Index;
+import com.android.settings.search2.SearchFeatureProvider;
+import com.android.settings.search2.SearchFragment;
 import com.android.settings.sim.SimSettings;
 import com.android.settings.system.SystemDashboardFragment;
 import com.android.settings.tts.TextToSpeechSettings;
@@ -479,6 +479,8 @@
     private SearchResultsSummary mSearchResultsFragment;
     private String mSearchQuery;
 
+    private SearchFeatureProvider mSearchFeatureProvider;
+
     // Categories
     private ArrayList<DashboardCategory> mCategories = new ArrayList<DashboardCategory>();
 
@@ -528,9 +530,14 @@
         }
 
         MenuInflater inflater = getMenuInflater();
+        if (mSearchFeatureProvider.isEnabled()) {
+            mSearchFeatureProvider.setUpSearchMenu(menu, this);
+            return true;
+        }
         inflater.inflate(R.menu.options_menu, menu);
 
-        // Cache the search query (can be overriden by the OnQueryTextListener)
+
+        // Cache the search query (can be overridden by the OnQueryTextListener)
         final String query = mSearchQuery;
 
         mSearchMenuItem = menu.findItem(R.id.search);
@@ -553,7 +560,6 @@
             mSearchMenuItem.expandActionView();
         }
         mSearchView.setQuery(query, true /* submit */);
-
         return true;
     }
 
@@ -596,8 +602,12 @@
     protected void onCreate(Bundle savedState) {
         super.onCreate(savedState);
         long startTime = System.currentTimeMillis();
-        mDashboardFeatureProvider =
-                FeatureFactory.getFactory(this).getDashboardFeatureProvider(this);
+
+        final FeatureFactory factory = FeatureFactory.getFactory(this);
+
+        mDashboardFeatureProvider = factory.getDashboardFeatureProvider(this);
+        mSearchFeatureProvider = factory.getSearchFeatureProvider(this);
+
         // Should happen before any call to getIntent()
         getMetaData();
 
@@ -1220,18 +1230,25 @@
                 Log.e(LOG_TAG, "Invalid backup intent URI!", e);
             }
         }
+
+        // Enable/disble BackupSettingsActivity and its alias.
         setTileEnabled(new ComponentName(packageName,
                 BackupSettingsActivity.class.getName()), hasBackupActivity, isAdmin, pm);
+        setTileEnabled(new ComponentName(packageName,
+                "com.android.settings.BackupResetDashboardAlias"), hasBackupActivity, isAdmin, pm);
 
         setTileEnabled(new ComponentName(packageName,
                 Settings.EnterprisePrivacySettingsActivity.class.getName()),
                 FeatureFactory.getFactory(this).getEnterprisePrivacyFeatureProvider(this)
                         .hasDeviceOwner(), isAdmin, pm);
-
+        setTileEnabled(new ComponentName(packageName,
+                        "com.android.settings.EnterprisePrivacyDashboardAlias"),
+                FeatureFactory.getFactory(this).getEnterprisePrivacyFeatureProvider(this)
+                        .hasDeviceOwner(), isAdmin, pm);
     }
 
     private void setTileEnabled(ComponentName component, boolean enabled, boolean isAdmin,
-                                PackageManager pm) {
+            PackageManager pm) {
         if (UserHandle.MU_ENABLED && !isAdmin && getPackageName().equals(component.getPackageName())
                 && !ArrayUtils.contains(SETTINGS_FOR_RESTRICTED, component.getClassName())) {
             enabled = false;
@@ -1274,19 +1291,24 @@
         return super.shouldUpRecreateTask(new Intent(this, SettingsActivity.class));
     }
 
+    @Deprecated
     @Override
     public boolean onQueryTextSubmit(String query) {
-        switchToSearchResultsFragmentIfNeeded();
+        if (mSearchFeatureProvider.isEnabled()) {
+            return false;
+        }
         mSearchQuery = query;
+        switchToSearchResultsFragmentIfNeeded();
         return mSearchResultsFragment.onQueryTextSubmit(query);
     }
 
+    @Deprecated
     @Override
     public boolean onQueryTextChange(String newText) {
-        mSearchQuery = newText;
-        if (mSearchResultsFragment == null) {
+        if (mSearchFeatureProvider.isEnabled() || mSearchResultsFragment == null) {
             return false;
         }
+        mSearchQuery = newText;
         return mSearchResultsFragment.onQueryTextChange(newText);
     }
 
@@ -1330,6 +1352,7 @@
         }
     }
 
+    @Deprecated
     private void switchToSearchResultsFragmentIfNeeded() {
         if (mSearchResultsFragment != null) {
             return;
@@ -1347,10 +1370,12 @@
         mSearchMenuItemExpanded = true;
     }
 
+    @Deprecated
     public void needToRevertToInitialFragment() {
         mNeedToRevertToInitialFragment = true;
     }
 
+    @Deprecated
     private void revertToInitialFragment() {
         mNeedToRevertToInitialFragment = false;
         mSearchResultsFragment = null;
diff --git a/src/com/android/settings/ZonePicker.java b/src/com/android/settings/ZonePicker.java
index 5d8f156..f6d6a6c 100644
--- a/src/com/android/settings/ZonePicker.java
+++ b/src/com/android/settings/ZonePicker.java
@@ -30,6 +30,7 @@
 import android.view.ViewGroup;
 import android.widget.ListView;
 import android.widget.SimpleAdapter;
+import android.widget.TextView;
 
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.settings.core.instrumentation.VisibilityLoggerMixin;
@@ -86,10 +87,15 @@
      */
     public static SimpleAdapter constructTimezoneAdapter(Context context,
             boolean sortedByName, int layoutId) {
-        final String[] from = new String[] {ZoneGetter.KEY_DISPLAYNAME, ZoneGetter.KEY_GMT};
+        final String[] from = new String[] {
+                ZoneGetter.KEY_DISPLAY_LABEL,
+                ZoneGetter.KEY_OFFSET_LABEL
+        };
         final int[] to = new int[] {android.R.id.text1, android.R.id.text2};
 
-        final String sortKey = (sortedByName ? ZoneGetter.KEY_DISPLAYNAME : ZoneGetter.KEY_OFFSET);
+        final String sortKey = (sortedByName
+                ? ZoneGetter.KEY_DISPLAY_LABEL
+                : ZoneGetter.KEY_OFFSET);
         final MyComparator comparator = new MyComparator(sortKey);
         final List<Map<String, Object>> sortedList = ZoneGetter.getZonesList(context);
         Collections.sort(sortedList, comparator);
@@ -98,10 +104,25 @@
                 layoutId,
                 from,
                 to);
-
+        adapter.setViewBinder(new TimeZoneViewBinder());
         return adapter;
     }
 
+    private static class TimeZoneViewBinder implements SimpleAdapter.ViewBinder {
+
+        /**
+         * Set the text to the given {@link CharSequence} as is, instead of calling toString, so
+         * that additional information stored in the CharSequence is, like spans added to a
+         * {@link android.text.SpannableString} are preserved.
+         */
+        @Override
+        public boolean setViewValue(View view, Object data, String textRepresentation) {
+            TextView textView = (TextView) view;
+            textView.setText((CharSequence) data);
+            return true;
+        }
+    }
+
     /**
      * Searches {@link TimeZone} from the given {@link SimpleAdapter} object, and returns
      * the index for the TimeZone.
diff --git a/src/com/android/settings/accounts/AccountPreference.java b/src/com/android/settings/accounts/AccountPreference.java
new file mode 100644
index 0000000..38cb437
--- /dev/null
+++ b/src/com/android/settings/accounts/AccountPreference.java
@@ -0,0 +1,104 @@
+/*
+ * 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.accounts;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.Preference.OnPreferenceClickListener;
+
+import com.android.settings.R;
+import com.android.settings.Utils;
+
+import static android.content.Intent.EXTRA_USER;
+
+public class AccountPreference extends Preference implements OnPreferenceClickListener {
+    /**
+     * Title of the tile that is shown to the user.
+     * @attr ref android.R.styleable#PreferenceHeader_title
+     */
+    private final CharSequence mTitle;
+
+    /**
+     * Packange name used to resolve the resources of the title shown to the user in the new
+     * fragment.
+     */
+    private final String mTitleResPackageName;
+
+    /**
+     * Resource id of the title shown to the user in the new fragment.
+     */
+    private final int mTitleResId;
+
+    /**
+     * Full class name of the fragment to display when this tile is
+     * selected.
+     * @attr ref android.R.styleable#PreferenceHeader_fragment
+     */
+    private final String mFragment;
+
+    /**
+     * Optional arguments to supply to the fragment when it is
+     * instantiated.
+     */
+    private final Bundle mFragmentArguments;
+
+    public AccountPreference(Context context, CharSequence title, String titleResPackageName,
+        int titleResId, String fragment, Bundle fragmentArguments, Drawable icon) {
+        super(context);
+        mTitle = title;
+        mTitleResPackageName = titleResPackageName;
+        mTitleResId = titleResId;
+        mFragment = fragment;
+        mFragmentArguments = fragmentArguments;
+        setWidgetLayoutResource(R.layout.account_type_preference);
+
+        setTitle(title);
+        setIcon(icon);
+
+        setOnPreferenceClickListener(this);
+    }
+
+    @Override
+    public boolean onPreferenceClick(Preference preference) {
+        if (mFragment != null) {
+            UserManager userManager =
+                (UserManager) getContext().getSystemService(Context.USER_SERVICE);
+            UserHandle user = mFragmentArguments.getParcelable(EXTRA_USER);
+            if (user != null && Utils.startQuietModeDialogIfNecessary(getContext(), userManager,
+                user.getIdentifier())) {
+                return true;
+            } else if (user != null && Utils.unlockWorkProfileIfNecessary(getContext(),
+                user.getIdentifier())) {
+                return true;
+            }
+            Utils.startWithFragment(getContext(), mFragment, mFragmentArguments,
+                null /* resultTo */, 0 /* resultRequestCode */, mTitleResPackageName,
+                mTitleResId, null /* title */);
+            return true;
+        }
+        return false;
+    }
+
+    public CharSequence getitle() {
+        return mTitle;
+    }
+
+}
diff --git a/src/com/android/settings/accounts/AccountPreferenceController.java b/src/com/android/settings/accounts/AccountPreferenceController.java
new file mode 100644
index 0000000..fa99f77
--- /dev/null
+++ b/src/com/android/settings/accounts/AccountPreferenceController.java
@@ -0,0 +1,552 @@
+/*
+ * 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.accounts;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.support.v14.preference.PreferenceFragment;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.Preference.OnPreferenceClickListener;
+import android.support.v7.preference.PreferenceGroup;
+import android.support.v7.preference.PreferenceScreen;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settings.AccessiblePreferenceCategory;
+import com.android.settings.DimmableIconPreference;
+import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.Utils;
+import com.android.settings.core.PreferenceController;
+import com.android.settings.core.lifecycle.LifecycleObserver;
+import com.android.settings.core.lifecycle.events.OnPause;
+import com.android.settings.core.lifecycle.events.OnResume;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.search.Index;
+import com.android.settings.search.SearchIndexableRaw;
+import com.android.settingslib.RestrictedPreference;
+import com.android.settingslib.accounts.AuthenticatorHelper;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import static android.content.Intent.EXTRA_USER;
+import static android.os.UserManager.DISALLOW_MODIFY_ACCOUNTS;
+import static android.os.UserManager.DISALLOW_REMOVE_USER;
+import static android.provider.Settings.EXTRA_AUTHORITIES;
+
+public class AccountPreferenceController extends PreferenceController
+        implements AuthenticatorHelper.OnAccountsUpdateListener,
+        OnPreferenceClickListener, LifecycleObserver, OnPause, OnResume {
+
+    private static final String TAG = "AccountPrefController";
+    private static final String ADD_ACCOUNT_ACTION = "android.settings.ADD_ACCOUNT_SETTINGS";
+
+    private static final int ORDER_ACCOUNT_PROFILES = 1;
+    private static final int ORDER_LAST = 1002;
+    private static final int ORDER_NEXT_TO_LAST = 1001;
+    private static final int ORDER_NEXT_TO_NEXT_TO_LAST = 1000;
+
+    private UserManager mUm;
+    private SparseArray<ProfileData> mProfiles = new SparseArray<ProfileData>();
+    private ManagedProfileBroadcastReceiver mManagedProfileBroadcastReceiver
+                = new ManagedProfileBroadcastReceiver();
+    private Preference mProfileNotAvailablePreference;
+    private String[] mAuthorities;
+    private int mAuthoritiesCount = 0;
+    private PreferenceFragment mParent;
+    private boolean mIAEnabled;
+    private int mAccountProfileOrder = ORDER_ACCOUNT_PROFILES;
+    private AccountRestrictionHelper mHelper;
+
+    /**
+     * Holds data related to the accounts belonging to one profile.
+     */
+    public static class ProfileData {
+        /**
+         * The preference that displays the accounts.
+         */
+        public PreferenceGroup preferenceGroup;
+        /**
+         * The preference that displays the add account button.
+         */
+        public DimmableIconPreference addAccountPreference;
+        /**
+         * The preference that displays the button to remove the managed profile
+         */
+        public RestrictedPreference removeWorkProfilePreference;
+        /**
+         * The preference that displays managed profile settings.
+         */
+        public Preference managedProfilePreference;
+        /**
+         * The {@link AuthenticatorHelper} that holds accounts data for this profile.
+         */
+        public AuthenticatorHelper authenticatorHelper;
+        /**
+         * The {@link UserInfo} of the profile.
+         */
+        public UserInfo userInfo;
+    }
+
+    public AccountPreferenceController(Context context, PreferenceFragment parent,
+        String[] authorities) {
+        this(context, parent, authorities, new AccountRestrictionHelper(context));
+    }
+
+    @VisibleForTesting
+    AccountPreferenceController(Context context, PreferenceFragment parent,
+        String[] authorities, AccountRestrictionHelper helper) {
+        super(context);
+        mUm = (UserManager) context.getSystemService(Context.USER_SERVICE);
+        mAuthorities = authorities;
+        mParent = parent;
+        if (mAuthorities != null) {
+            mAuthoritiesCount = mAuthorities.length;
+        }
+        mIAEnabled = FeatureFactory.getFactory(mContext).getDashboardFeatureProvider(mContext)
+            .isEnabled();
+        mHelper = helper;
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return !mUm.isManagedProfile();
+    }
+
+    @Override
+    public boolean handlePreferenceTreeClick(Preference preference) {
+        return false;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return null;
+    }
+
+    @Override
+    public void updateRawDataToIndex(List<SearchIndexableRaw> rawData) {
+        if (!isAvailable()) {
+            return;
+        }
+        final Resources res = mContext.getResources();
+        final String screenTitle = res.getString(R.string.account_settings_title);
+
+        List<UserInfo> profiles = mUm.getProfiles(UserHandle.myUserId());
+        final int profilesCount = profiles.size();
+        for (int i = 0; i < profilesCount; i++) {
+            UserInfo userInfo = profiles.get(i);
+            if (userInfo.isEnabled()) {
+                if (!mHelper.hasBaseUserRestriction(DISALLOW_MODIFY_ACCOUNTS, userInfo.id)) {
+                    SearchIndexableRaw data = new SearchIndexableRaw(mContext);
+                    data.title = res.getString(R.string.add_account_label);
+                    data.screenTitle = screenTitle;
+                    rawData.add(data);
+                }
+                if (userInfo.isManagedProfile()) {
+                    if (!mHelper.hasBaseUserRestriction(DISALLOW_REMOVE_USER,
+                        UserHandle.myUserId())) {
+                        SearchIndexableRaw data = new SearchIndexableRaw(mContext);
+                        data.title = res.getString(R.string.remove_managed_profile_label);
+                        data.screenTitle = screenTitle;
+                        rawData.add(data);
+                    }
+                    {
+                        SearchIndexableRaw data = new SearchIndexableRaw(mContext);
+                        data.title = res.getString(R.string.managed_profile_settings_title);
+                        data.screenTitle = screenTitle;
+                        rawData.add(data);
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onResume() {
+        cleanUpPreferences();
+        updateUi();
+        mManagedProfileBroadcastReceiver.register(mContext);
+        listenToAccountUpdates();
+    }
+
+    @Override
+    public void onPause() {
+        stopListeningToAccountUpdates();
+        mManagedProfileBroadcastReceiver.unregister(mContext);
+    }
+
+    @Override
+    public void onAccountsUpdate(UserHandle userHandle) {
+        final ProfileData profileData = mProfiles.get(userHandle.getIdentifier());
+        if (profileData != null) {
+            updateAccountTypes(profileData);
+        } else {
+            Log.w(TAG, "Missing Settings screen for: " + userHandle.getIdentifier());
+        }
+    }
+
+    @Override
+    public boolean onPreferenceClick(Preference preference) {
+        // Check the preference
+        final int count = mProfiles.size();
+        for (int i = 0; i < count; i++) {
+            ProfileData profileData = mProfiles.valueAt(i);
+            if (preference == profileData.addAccountPreference) {
+                Intent intent = new Intent(ADD_ACCOUNT_ACTION);
+                intent.putExtra(EXTRA_USER, profileData.userInfo.getUserHandle());
+                intent.putExtra(EXTRA_AUTHORITIES, mAuthorities);
+                mContext.startActivity(intent);
+                return true;
+            }
+            if (preference == profileData.removeWorkProfilePreference) {
+                final int userId = profileData.userInfo.id;
+                RemoveUserFragment.newInstance(userId).show(mParent.getFragmentManager(),
+                        "removeUser");
+                return true;
+            }
+            if (preference == profileData.managedProfilePreference) {
+                Bundle arguments = new Bundle();
+                arguments.putParcelable(Intent.EXTRA_USER, profileData.userInfo.getUserHandle());
+                ((SettingsActivity) mParent.getActivity()).startPreferencePanel(
+                        ManagedProfileSettings.class.getName(), arguments,
+                        R.string.managed_profile_settings_title, null, null, 0);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    SparseArray<ProfileData> getProfileData() {
+        return mProfiles;
+    }
+
+    private void updateUi() {
+        if (!mIAEnabled) {
+            // Load the preferences from an XML resource
+            mParent.addPreferencesFromResource(R.xml.account_settings);
+        }
+
+        if (!isAvailable()) {
+            // This should not happen
+            Log.e(TAG, "We should not be showing settings for a managed profile");
+            if (!mIAEnabled) {
+                ((AccountSettings) mParent).finish();
+            }
+            return;
+        }
+
+        if (mUm.isLinkedUser()) {
+            // Restricted user or similar
+            UserInfo userInfo = mUm.getUserInfo(UserHandle.myUserId());
+            updateProfileUi(userInfo);
+        } else {
+            List<UserInfo> profiles = mUm.getProfiles(UserHandle.myUserId());
+            final int profilesCount = profiles.size();
+            final boolean addCategory = profilesCount > 1;
+            for (int i = 0; i < profilesCount; i++) {
+                updateProfileUi(profiles.get(i));
+            }
+        }
+
+        // Add all preferences, starting with one for the primary profile.
+        // Note that we're relying on the ordering given by the SparseArray keys, and on the
+        // value of UserHandle.USER_OWNER being smaller than all the rest.
+        final int profilesCount = mProfiles.size();
+        for (int i = 0; i < profilesCount; i++) {
+            updateAccountTypes(mProfiles.valueAt(i));
+        }
+    }
+
+    private void updateProfileUi(final UserInfo userInfo) {
+        final Context context = mContext;
+        final ProfileData profileData = new ProfileData();
+        profileData.userInfo = userInfo;
+        AccessiblePreferenceCategory preferenceGroup =
+            mHelper.createAccessiblePreferenceCategory(mParent.getPreferenceManager().getContext());
+        preferenceGroup.setOrder(mAccountProfileOrder++);
+        if (isSingleProfile()) {
+            preferenceGroup.setTitle(R.string.account_for_section_header);
+            preferenceGroup.setContentDescription(
+                mContext.getString(R.string.account_settings));
+        } else if (userInfo.isManagedProfile()) {
+            preferenceGroup.setLayoutResource(R.layout.work_profile_category);
+            preferenceGroup.setTitle(R.string.category_work);
+            String workGroupSummary = getWorkGroupSummary(context, userInfo);
+            preferenceGroup.setSummary(workGroupSummary);
+            preferenceGroup.setContentDescription(
+                mContext.getString(R.string.accessibility_category_work, workGroupSummary));
+            profileData.removeWorkProfilePreference = newRemoveWorkProfilePreference(context);
+            mHelper.enforceRestrictionOnPreference(profileData.removeWorkProfilePreference,
+                DISALLOW_REMOVE_USER, UserHandle.myUserId());
+            profileData.managedProfilePreference = newManagedProfileSettings();
+        } else {
+            preferenceGroup.setTitle(R.string.category_personal);
+            preferenceGroup.setContentDescription(
+                mContext.getString(R.string.accessibility_category_personal));
+        }
+        mParent.getPreferenceScreen().addPreference(preferenceGroup);
+        profileData.preferenceGroup = preferenceGroup;
+        if (userInfo.isEnabled()) {
+            profileData.authenticatorHelper = new AuthenticatorHelper(context,
+                    userInfo.getUserHandle(), this);
+            profileData.addAccountPreference = newAddAccountPreference(context);
+            mHelper.enforceRestrictionOnPreference(profileData.addAccountPreference,
+                DISALLOW_MODIFY_ACCOUNTS, userInfo.id);
+        }
+        mProfiles.put(userInfo.id, profileData);
+        Index.getInstance(mContext).updateFromClassNameResource(
+                AccountSettings.class.getName(), true, true);
+    }
+
+    private DimmableIconPreference newAddAccountPreference(Context context) {
+        DimmableIconPreference preference =
+            new DimmableIconPreference(mParent.getPreferenceManager().getContext());
+        preference.setTitle(R.string.add_account_label);
+        preference.setIcon(R.drawable.ic_menu_add);
+        preference.setOnPreferenceClickListener(this);
+        preference.setOrder(ORDER_NEXT_TO_NEXT_TO_LAST);
+        return preference;
+    }
+
+    private RestrictedPreference newRemoveWorkProfilePreference(Context context) {
+        RestrictedPreference preference = new RestrictedPreference(
+            mParent.getPreferenceManager().getContext());
+        preference.setTitle(R.string.remove_managed_profile_label);
+        preference.setIcon(R.drawable.ic_menu_delete);
+        preference.setOnPreferenceClickListener(this);
+        preference.setOrder(ORDER_LAST);
+        return preference;
+    }
+
+
+    private Preference newManagedProfileSettings() {
+        Preference preference = new Preference(mParent.getPreferenceManager().getContext());
+        preference.setTitle(R.string.managed_profile_settings_title);
+        preference.setIcon(R.drawable.ic_settings);
+        preference.setOnPreferenceClickListener(this);
+        preference.setOrder(ORDER_NEXT_TO_LAST);
+        return preference;
+    }
+
+    private String getWorkGroupSummary(Context context, UserInfo userInfo) {
+        PackageManager packageManager = context.getPackageManager();
+        ApplicationInfo adminApplicationInfo = Utils.getAdminApplicationInfo(context, userInfo.id);
+        if (adminApplicationInfo == null) {
+            return null;
+        }
+        CharSequence appLabel = packageManager.getApplicationLabel(adminApplicationInfo);
+        return mContext.getString(R.string.managing_admin, appLabel);
+    }
+
+    void cleanUpPreferences() {
+        PreferenceScreen screen = mParent.getPreferenceScreen();
+        for (int i = 0; i < mProfiles.size(); i++) {
+            final PreferenceGroup preferenceGroup = mProfiles.valueAt(i).preferenceGroup;
+            screen.removePreference(preferenceGroup);
+        }
+        mProfiles.clear();
+        mAccountProfileOrder = ORDER_ACCOUNT_PROFILES;
+    }
+
+    private void listenToAccountUpdates() {
+        final int count = mProfiles.size();
+        for (int i = 0; i < count; i++) {
+            AuthenticatorHelper authenticatorHelper = mProfiles.valueAt(i).authenticatorHelper;
+            if (authenticatorHelper != null) {
+                authenticatorHelper.listenToAccountUpdates();
+            }
+        }
+    }
+
+    private void stopListeningToAccountUpdates() {
+        final int count = mProfiles.size();
+        for (int i = 0; i < count; i++) {
+            AuthenticatorHelper authenticatorHelper = mProfiles.valueAt(i).authenticatorHelper;
+            if (authenticatorHelper != null) {
+                authenticatorHelper.stopListeningToAccountUpdates();
+            }
+        }
+    }
+
+    private void updateAccountTypes(ProfileData profileData) {
+        profileData.preferenceGroup.removeAll();
+        if (profileData.userInfo.isEnabled()) {
+            final ArrayList<AccountPreference> preferences = getAccountTypePreferences(
+                    profileData.authenticatorHelper, profileData.userInfo.getUserHandle());
+            final int count = preferences.size();
+            for (int i = 0; i < count; i++) {
+                profileData.preferenceGroup.addPreference(preferences.get(i));
+            }
+            if (profileData.addAccountPreference != null) {
+                profileData.preferenceGroup.addPreference(profileData.addAccountPreference);
+            }
+        } else {
+            // Put a label instead of the accounts list
+            if (mProfileNotAvailablePreference == null) {
+                mProfileNotAvailablePreference =
+                    new Preference(mParent.getPreferenceManager().getContext());
+            }
+            mProfileNotAvailablePreference.setEnabled(false);
+            mProfileNotAvailablePreference.setIcon(R.drawable.empty_icon);
+            mProfileNotAvailablePreference.setTitle(null);
+            mProfileNotAvailablePreference.setSummary(
+                    R.string.managed_profile_not_available_label);
+            profileData.preferenceGroup.addPreference(mProfileNotAvailablePreference);
+        }
+        if (profileData.removeWorkProfilePreference != null) {
+            profileData.preferenceGroup.addPreference(profileData.removeWorkProfilePreference);
+        }
+        if (profileData.managedProfilePreference != null) {
+            profileData.preferenceGroup.addPreference(profileData.managedProfilePreference);
+        }
+    }
+
+    private ArrayList<AccountPreference> getAccountTypePreferences(AuthenticatorHelper helper,
+            UserHandle userHandle) {
+        final String[] accountTypes = helper.getEnabledAccountTypes();
+        final ArrayList<AccountPreference> accountTypePreferences =
+                new ArrayList<AccountPreference>(accountTypes.length);
+
+        for (int i = 0; i < accountTypes.length; i++) {
+            final String accountType = accountTypes[i];
+            // Skip showing any account that does not have any of the requested authorities
+            if (!accountTypeHasAnyRequestedAuthorities(helper, accountType)) {
+                continue;
+            }
+            final CharSequence label = helper.getLabelForType(mContext, accountType);
+            if (label == null) {
+                continue;
+            }
+            final String titleResPackageName = helper.getPackageForType(accountType);
+            final int titleResId = helper.getLabelIdForType(accountType);
+
+            final Account[] accounts = AccountManager.get(mContext)
+                    .getAccountsByTypeAsUser(accountType, userHandle);
+            final boolean skipToAccount = accounts.length == 1
+                    && !helper.hasAccountPreferences(accountType);
+
+            if (skipToAccount) {
+                final Bundle fragmentArguments = new Bundle();
+                fragmentArguments.putParcelable(AccountSyncSettings.ACCOUNT_KEY,
+                        accounts[0]);
+                fragmentArguments.putParcelable(EXTRA_USER, userHandle);
+
+                accountTypePreferences.add(new AccountPreference(
+                    mParent.getPreferenceManager().getContext(), label, titleResPackageName,
+                    titleResId, AccountSyncSettings.class.getName(), fragmentArguments,
+                    helper.getDrawableForType(mContext, accountType)));
+            } else {
+                final Bundle fragmentArguments = new Bundle();
+                fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
+                fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_LABEL,
+                        label.toString());
+                fragmentArguments.putParcelable(EXTRA_USER, userHandle);
+
+                accountTypePreferences.add(new AccountPreference(
+                    mParent.getPreferenceManager().getContext(), label, titleResPackageName,
+                    titleResId, ManageAccountsSettings.class.getName(), fragmentArguments,
+                    helper.getDrawableForType(mContext, accountType)));
+            }
+            helper.preloadDrawableForType(mContext, accountType);
+        }
+        // Sort by label
+        Collections.sort(accountTypePreferences, new Comparator<AccountPreference>() {
+            @Override
+            public int compare(AccountPreference t1, AccountPreference t2) {
+                return t1.getitle().toString().compareTo(t2.getitle().toString());
+            }
+        });
+        return accountTypePreferences;
+    }
+
+    private boolean accountTypeHasAnyRequestedAuthorities(AuthenticatorHelper helper,
+            String accountType) {
+        if (mAuthoritiesCount == 0) {
+            // No authorities required
+            return true;
+        }
+        final ArrayList<String> authoritiesForType = helper.getAuthoritiesForAccountType(
+                accountType);
+        if (authoritiesForType == null) {
+            Log.d(TAG, "No sync authorities for account type: " + accountType);
+            return false;
+        }
+        for (int j = 0; j < mAuthoritiesCount; j++) {
+            if (authoritiesForType.contains(mAuthorities[j])) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean isSingleProfile() {
+        return mUm.isLinkedUser() || mUm.getProfiles(UserHandle.myUserId()).size() == 1;
+    }
+
+    private class ManagedProfileBroadcastReceiver extends BroadcastReceiver {
+        private boolean mListeningToManagedProfileEvents;
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            Log.v(TAG, "Received broadcast: " + action);
+            if (action.equals(Intent.ACTION_MANAGED_PROFILE_REMOVED)
+                    || action.equals(Intent.ACTION_MANAGED_PROFILE_ADDED)) {
+                // Clean old state
+                stopListeningToAccountUpdates();
+                cleanUpPreferences();
+                // Build new state
+                updateUi();
+                listenToAccountUpdates();
+                return;
+            }
+            Log.w(TAG, "Cannot handle received broadcast: " + intent.getAction());
+        }
+
+        public void register(Context context) {
+            if (!mListeningToManagedProfileEvents) {
+                IntentFilter intentFilter = new IntentFilter();
+                intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
+                intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
+                context.registerReceiver(this, intentFilter);
+                mListeningToManagedProfileEvents = true;
+            }
+        }
+
+        public void unregister(Context context) {
+            if (mListeningToManagedProfileEvents) {
+                context.unregisterReceiver(this);
+                mListeningToManagedProfileEvents = false;
+            }
+        }
+    }
+}
diff --git a/src/com/android/settings/accounts/AccountRestrictionHelper.java b/src/com/android/settings/accounts/AccountRestrictionHelper.java
new file mode 100644
index 0000000..868fa1c
--- /dev/null
+++ b/src/com/android/settings/accounts/AccountRestrictionHelper.java
@@ -0,0 +1,58 @@
+/*
+ * 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.accounts;
+
+import android.annotation.UserIdInt;
+import android.content.Context;
+import com.android.settings.AccessiblePreferenceCategory;
+import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.RestrictedPreference;
+
+public class AccountRestrictionHelper {
+
+    private final Context mContext;
+
+    public AccountRestrictionHelper(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Configure the UI of the preference by checking user restriction.
+     * @param preference The preference we are configuring.
+     * @param userRestriction The user restriction related to the preference.
+     * @param userId The user that we retrieve user restriction of.
+     */
+    public void enforceRestrictionOnPreference(RestrictedPreference preference,
+        String userRestriction, @UserIdInt int userId) {
+        if (preference == null) {
+            return;
+        }
+        if (hasBaseUserRestriction(userRestriction, userId)) {
+            preference.setEnabled(false);
+        } else {
+            preference.checkRestrictionAndSetDisabled(userRestriction, userId);
+        }
+    }
+
+    public boolean hasBaseUserRestriction(String userRestriction, @UserIdInt int userId) {
+        return RestrictedLockUtils.hasBaseUserRestriction(mContext, userRestriction, userId);
+    }
+
+    public AccessiblePreferenceCategory createAccessiblePreferenceCategory(Context context) {
+        return new AccessiblePreferenceCategory(context);
+    }
+
+}
diff --git a/src/com/android/settings/accounts/AccountSettings.java b/src/com/android/settings/accounts/AccountSettings.java
index 9b9fb87..de721bb 100644
--- a/src/com/android/settings/accounts/AccountSettings.java
+++ b/src/com/android/settings/accounts/AccountSettings.java
@@ -16,32 +16,13 @@
 
 package com.android.settings.accounts;
 
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.annotation.UserIdInt;
 import android.app.ActivityManager;
-import android.app.Dialog;
-import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.Process;
 import android.os.UserHandle;
-import android.os.UserManager;
 import android.provider.SearchIndexableResource;
-import android.support.v7.preference.Preference;
-import android.support.v7.preference.Preference.OnPreferenceClickListener;
-import android.support.v7.preference.PreferenceGroup;
-import android.support.v7.preference.PreferenceScreen;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.Menu;
@@ -49,31 +30,16 @@
 import android.view.MenuItem;
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.settings.AccessiblePreferenceCategory;
-import com.android.settings.DimmableIconPreference;
 import com.android.settings.R;
-import com.android.settings.SettingsActivity;
 import com.android.settings.SettingsPreferenceFragment;
-import com.android.settings.Utils;
-import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
 import com.android.settings.search.BaseSearchIndexProvider;
-import com.android.settings.search.Index;
 import com.android.settings.search.Indexable;
 import com.android.settings.search.SearchIndexableRaw;
-import com.android.settings.users.UserDialogs;
-import com.android.settingslib.RestrictedLockUtils;
-import com.android.settingslib.RestrictedPreference;
-import com.android.settingslib.accounts.AuthenticatorHelper;
 
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
 import java.util.List;
 
-import static android.content.Intent.EXTRA_USER;
-import static android.os.UserManager.DISALLOW_MODIFY_ACCOUNTS;
-import static android.os.UserManager.DISALLOW_REMOVE_USER;
 import static android.provider.Settings.EXTRA_AUTHORITIES;
 
 /**
@@ -83,57 +49,13 @@
  * An extra {@link UserHandle} can be specified in the intent as {@link EXTRA_USER}, if the user for
  * which the action needs to be performed is different to the one the Settings App will run in.
  */
-public class AccountSettings extends SettingsPreferenceFragment
-        implements AuthenticatorHelper.OnAccountsUpdateListener,
-        OnPreferenceClickListener, Indexable {
+public class AccountSettings extends SettingsPreferenceFragment implements Indexable {
     public static final String TAG = "AccountSettings";
 
-    private static final String KEY_ACCOUNT = "account";
 
-    private static final String ADD_ACCOUNT_ACTION = "android.settings.ADD_ACCOUNT_SETTINGS";
-    private static final String TAG_CONFIRM_AUTO_SYNC_CHANGE = "confirmAutoSyncChange";
-
-    private static final int ORDER_LAST = 1002;
-    private static final int ORDER_NEXT_TO_LAST = 1001;
-    private static final int ORDER_NEXT_TO_NEXT_TO_LAST = 1000;
-
-    private UserManager mUm;
-    private SparseArray<ProfileData> mProfiles = new SparseArray<ProfileData>();
-    private ManagedProfileBroadcastReceiver mManagedProfileBroadcastReceiver
-                = new ManagedProfileBroadcastReceiver();
-    private Preference mProfileNotAvailablePreference;
     private String[] mAuthorities;
-    private int mAuthoritiesCount = 0;
 
-    /**
-     * Holds data related to the accounts belonging to one profile.
-     */
-    public static class ProfileData {
-        /**
-         * The preference that displays the accounts.
-         */
-        public PreferenceGroup preferenceGroup;
-        /**
-         * The preference that displays the add account button.
-         */
-        public DimmableIconPreference addAccountPreference;
-        /**
-         * The preference that displays the button to remove the managed profile
-         */
-        public RestrictedPreference removeWorkProfilePreference;
-        /**
-         * The preference that displays managed profile settings.
-         */
-        public Preference managedProfilePreference;
-        /**
-         * The {@link AuthenticatorHelper} that holds accounts data for this profile.
-         */
-        public AuthenticatorHelper authenticatorHelper;
-        /**
-         * The {@link UserInfo} of the profile.
-         */
-        public UserInfo userInfo;
-    }
+    private AccountPreferenceController mAccountPreferenceController;
 
     @Override
     public int getMetricsCategory() {
@@ -143,13 +65,11 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        mUm = (UserManager) getSystemService(Context.USER_SERVICE);
-        mProfileNotAvailablePreference = new Preference(getPrefContext());
         mAuthorities = getActivity().getIntent().getStringArrayExtra(EXTRA_AUTHORITIES);
-        if (mAuthorities != null) {
-            mAuthoritiesCount = mAuthorities.length;
-        }
         setHasOptionsMenu(true);
+        mAccountPreferenceController =
+            new AccountPreferenceController(getActivity(), this, mAuthorities);
+        getLifecycle().addObserver(mAccountPreferenceController);
     }
 
     @Override
@@ -161,461 +81,34 @@
     @Override
     public void onPrepareOptionsMenu(Menu menu) {
         final UserHandle currentProfile = Process.myUserHandle();
-        if (mProfiles.size() == 1) {
+        SparseArray<AccountPreferenceController.ProfileData> profiles =
+            mAccountPreferenceController.getProfileData();
+
+        if (profiles.size() == 1) {
             menu.findItem(R.id.account_settings_menu_auto_sync)
-                    .setVisible(true)
-                    .setOnMenuItemClickListener(new MasterSyncStateClickListener(currentProfile))
-                    .setChecked(ContentResolver.getMasterSyncAutomaticallyAsUser(
-                            currentProfile.getIdentifier()));
+                .setVisible(true)
+                .setOnMenuItemClickListener(new MasterSyncStateClickListener(currentProfile))
+                .setChecked(ContentResolver.getMasterSyncAutomaticallyAsUser(
+                    currentProfile.getIdentifier()));
             menu.findItem(R.id.account_settings_menu_auto_sync_personal).setVisible(false);
             menu.findItem(R.id.account_settings_menu_auto_sync_work).setVisible(false);
-        } else if (mProfiles.size() > 1) {
+        } else if (profiles.size() > 1) {
             // We assume there's only one managed profile, otherwise UI needs to change
-            final UserHandle managedProfile = mProfiles.valueAt(1).userInfo.getUserHandle();
+            final UserHandle managedProfile = profiles.valueAt(1).userInfo.getUserHandle();
 
             menu.findItem(R.id.account_settings_menu_auto_sync_personal)
-                    .setVisible(true)
-                    .setOnMenuItemClickListener(new MasterSyncStateClickListener(currentProfile))
-                    .setChecked(ContentResolver.getMasterSyncAutomaticallyAsUser(
-                            currentProfile.getIdentifier()));
+                .setVisible(true)
+                .setOnMenuItemClickListener(new MasterSyncStateClickListener(currentProfile))
+                .setChecked(ContentResolver.getMasterSyncAutomaticallyAsUser(
+                    currentProfile.getIdentifier()));
             menu.findItem(R.id.account_settings_menu_auto_sync_work)
-                    .setVisible(true)
-                    .setOnMenuItemClickListener(new MasterSyncStateClickListener(managedProfile))
-                    .setChecked(ContentResolver.getMasterSyncAutomaticallyAsUser(
-                            managedProfile.getIdentifier()));
+                .setVisible(true)
+                .setOnMenuItemClickListener(new MasterSyncStateClickListener(managedProfile))
+                .setChecked(ContentResolver.getMasterSyncAutomaticallyAsUser(
+                    managedProfile.getIdentifier()));
             menu.findItem(R.id.account_settings_menu_auto_sync).setVisible(false);
-         } else {
-             Log.w(TAG, "Method onPrepareOptionsMenu called before mProfiles was initialized");
-         }
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        cleanUpPreferences();
-        updateUi();
-        mManagedProfileBroadcastReceiver.register(getActivity());
-        listenToAccountUpdates();
-    }
-
-    @Override
-    public void onPause() {
-        super.onPause();
-        stopListeningToAccountUpdates();
-        mManagedProfileBroadcastReceiver.unregister(getActivity());
-    }
-
-    @Override
-    public void onAccountsUpdate(UserHandle userHandle) {
-        final ProfileData profileData = mProfiles.get(userHandle.getIdentifier());
-        if (profileData != null) {
-            updateAccountTypes(profileData);
         } else {
-            Log.w(TAG, "Missing Settings screen for: " + userHandle.getIdentifier());
-        }
-    }
-
-    @Override
-    public boolean onPreferenceClick(Preference preference) {
-        // Check the preference
-        final int count = mProfiles.size();
-        for (int i = 0; i < count; i++) {
-            ProfileData profileData = mProfiles.valueAt(i);
-            if (preference == profileData.addAccountPreference) {
-                Intent intent = new Intent(ADD_ACCOUNT_ACTION);
-                intent.putExtra(EXTRA_USER, profileData.userInfo.getUserHandle());
-                intent.putExtra(EXTRA_AUTHORITIES, mAuthorities);
-                startActivity(intent);
-                return true;
-            }
-            if (preference == profileData.removeWorkProfilePreference) {
-                final int userId = profileData.userInfo.id;
-                RemoveUserFragment.newInstance(userId).show(getActivity().getFragmentManager(),
-                        "removeUser");
-                return true;
-            }
-            if (preference == profileData.managedProfilePreference) {
-                Bundle arguments = new Bundle();
-                arguments.putParcelable(Intent.EXTRA_USER, profileData.userInfo.getUserHandle());
-                ((SettingsActivity) getActivity()).startPreferencePanel(
-                        ManagedProfileSettings.class.getName(), arguments,
-                        R.string.managed_profile_settings_title, null, null, 0);
-                return true;
-            }
-        }
-        return false;
-    }
-
-    void updateUi() {
-        // Load the preferences from an XML resource
-        addPreferencesFromResource(R.xml.account_settings);
-
-        if (mUm.isManagedProfile()) {
-            // This should not happen
-            Log.e(TAG, "We should not be showing settings for a managed profile");
-            finish();
-            return;
-        }
-
-        final PreferenceScreen preferenceScreen = (PreferenceScreen) findPreference(KEY_ACCOUNT);
-        if(mUm.isLinkedUser()) {
-            // Restricted user or similar
-            UserInfo userInfo = mUm.getUserInfo(UserHandle.myUserId());
-            updateProfileUi(userInfo, false /* no category needed */, preferenceScreen);
-        } else {
-            List<UserInfo> profiles = mUm.getProfiles(UserHandle.myUserId());
-            final int profilesCount = profiles.size();
-            final boolean addCategory = profilesCount > 1;
-            for (int i = 0; i < profilesCount; i++) {
-                updateProfileUi(profiles.get(i), addCategory, preferenceScreen);
-            }
-        }
-
-        // Add all preferences, starting with one for the primary profile.
-        // Note that we're relying on the ordering given by the SparseArray keys, and on the
-        // value of UserHandle.USER_OWNER being smaller than all the rest.
-        final int profilesCount = mProfiles.size();
-        for (int i = 0; i < profilesCount; i++) {
-            ProfileData profileData = mProfiles.valueAt(i);
-            if (!profileData.preferenceGroup.equals(preferenceScreen)) {
-                preferenceScreen.addPreference(profileData.preferenceGroup);
-            }
-            updateAccountTypes(profileData);
-        }
-    }
-
-    private void updateProfileUi(final UserInfo userInfo, boolean addCategory,
-            PreferenceScreen parent) {
-        final Context context = getActivity();
-        final ProfileData profileData = new ProfileData();
-        profileData.userInfo = userInfo;
-        if (addCategory) {
-            profileData.preferenceGroup = new AccessiblePreferenceCategory(getPrefContext());
-            if (userInfo.isManagedProfile()) {
-                profileData.preferenceGroup.setLayoutResource(R.layout.work_profile_category);
-                profileData.preferenceGroup.setTitle(R.string.category_work);
-                String workGroupSummary = getWorkGroupSummary(context, userInfo);
-                profileData.preferenceGroup.setSummary(workGroupSummary);
-                ((AccessiblePreferenceCategory) profileData.preferenceGroup).setContentDescription(
-                        getString(R.string.accessibility_category_work, workGroupSummary));
-                profileData.removeWorkProfilePreference = newRemoveWorkProfilePreference(context);
-                enforceRestrictionOnPreference(profileData.removeWorkProfilePreference,
-                        DISALLOW_REMOVE_USER, UserHandle.myUserId());
-                profileData.managedProfilePreference = newManagedProfileSettings();
-            } else {
-                profileData.preferenceGroup.setTitle(R.string.category_personal);
-                ((AccessiblePreferenceCategory) profileData.preferenceGroup).setContentDescription(
-                        getString(R.string.accessibility_category_personal));
-            }
-            parent.addPreference(profileData.preferenceGroup);
-        } else {
-            profileData.preferenceGroup = parent;
-        }
-        if (userInfo.isEnabled()) {
-            profileData.authenticatorHelper = new AuthenticatorHelper(context,
-                    userInfo.getUserHandle(), this);
-            profileData.addAccountPreference = newAddAccountPreference(context);
-            enforceRestrictionOnPreference(profileData.addAccountPreference,
-                    DISALLOW_MODIFY_ACCOUNTS, userInfo.id);
-        }
-        mProfiles.put(userInfo.id, profileData);
-        Index.getInstance(getActivity()).updateFromClassNameResource(
-                AccountSettings.class.getName(), true, true);
-    }
-
-    /**
-     * Configure the UI of the preference by checking user restriction.
-     * @param preference The preference we are configuring.
-     * @param userRestriction The user restriction related to the preference.
-     * @param userId The user that we retrieve user restriction of.
-     */
-    private void enforceRestrictionOnPreference(RestrictedPreference preference,
-            String userRestriction, @UserIdInt int userId) {
-        if (RestrictedLockUtils.hasBaseUserRestriction(getActivity(), userRestriction, userId)) {
-            preference.setEnabled(false);
-        } else {
-            preference.checkRestrictionAndSetDisabled(userRestriction, userId);
-        }
-    }
-
-    private DimmableIconPreference newAddAccountPreference(Context context) {
-        DimmableIconPreference preference = new DimmableIconPreference(getPrefContext());
-        preference.setTitle(R.string.add_account_label);
-        preference.setIcon(R.drawable.ic_menu_add);
-        preference.setOnPreferenceClickListener(this);
-        preference.setOrder(ORDER_NEXT_TO_NEXT_TO_LAST);
-        return preference;
-    }
-
-    private RestrictedPreference newRemoveWorkProfilePreference(Context context) {
-        RestrictedPreference preference = new RestrictedPreference(getPrefContext());
-        preference.setTitle(R.string.remove_managed_profile_label);
-        preference.setIcon(R.drawable.ic_menu_delete);
-        preference.setOnPreferenceClickListener(this);
-        preference.setOrder(ORDER_LAST);
-        return preference;
-    }
-
-    private Preference newManagedProfileSettings() {
-        Preference preference = new Preference(getPrefContext());
-        preference.setTitle(R.string.managed_profile_settings_title);
-        preference.setIcon(R.drawable.ic_settings);
-        preference.setOnPreferenceClickListener(this);
-        preference.setOrder(ORDER_NEXT_TO_LAST);
-        return preference;
-    }
-
-    private String getWorkGroupSummary(Context context, UserInfo userInfo) {
-        PackageManager packageManager = context.getPackageManager();
-        ApplicationInfo adminApplicationInfo = Utils.getAdminApplicationInfo(context, userInfo.id);
-        if (adminApplicationInfo == null) {
-            return null;
-        }
-        CharSequence appLabel = packageManager.getApplicationLabel(adminApplicationInfo);
-        return getString(R.string.managing_admin, appLabel);
-    }
-
-    private void cleanUpPreferences() {
-        PreferenceScreen preferenceScreen = getPreferenceScreen();
-        if (preferenceScreen != null) {
-            preferenceScreen.removeAll();
-        }
-        mProfiles.clear();
-    }
-
-    private void listenToAccountUpdates() {
-        final int count = mProfiles.size();
-        for (int i = 0; i < count; i++) {
-            AuthenticatorHelper authenticatorHelper = mProfiles.valueAt(i).authenticatorHelper;
-            if (authenticatorHelper != null) {
-                authenticatorHelper.listenToAccountUpdates();
-            }
-        }
-    }
-
-    private void stopListeningToAccountUpdates() {
-        final int count = mProfiles.size();
-        for (int i = 0; i < count; i++) {
-            AuthenticatorHelper authenticatorHelper = mProfiles.valueAt(i).authenticatorHelper;
-            if (authenticatorHelper != null) {
-                authenticatorHelper.stopListeningToAccountUpdates();
-            }
-        }
-    }
-
-    private void updateAccountTypes(ProfileData profileData) {
-        profileData.preferenceGroup.removeAll();
-        if (profileData.userInfo.isEnabled()) {
-            final ArrayList<AccountPreference> preferences = getAccountTypePreferences(
-                    profileData.authenticatorHelper, profileData.userInfo.getUserHandle());
-            final int count = preferences.size();
-            for (int i = 0; i < count; i++) {
-                profileData.preferenceGroup.addPreference(preferences.get(i));
-            }
-            if (profileData.addAccountPreference != null) {
-                profileData.preferenceGroup.addPreference(profileData.addAccountPreference);
-            }
-        } else {
-            // Put a label instead of the accounts list
-            mProfileNotAvailablePreference.setEnabled(false);
-            mProfileNotAvailablePreference.setIcon(R.drawable.empty_icon);
-            mProfileNotAvailablePreference.setTitle(null);
-            mProfileNotAvailablePreference.setSummary(
-                    R.string.managed_profile_not_available_label);
-            profileData.preferenceGroup.addPreference(mProfileNotAvailablePreference);
-        }
-        if (profileData.removeWorkProfilePreference != null) {
-            profileData.preferenceGroup.addPreference(profileData.removeWorkProfilePreference);
-        }
-        if (profileData.managedProfilePreference != null) {
-            profileData.preferenceGroup.addPreference(profileData.managedProfilePreference);
-        }
-    }
-
-    private ArrayList<AccountPreference> getAccountTypePreferences(AuthenticatorHelper helper,
-            UserHandle userHandle) {
-        final String[] accountTypes = helper.getEnabledAccountTypes();
-        final ArrayList<AccountPreference> accountTypePreferences =
-                new ArrayList<AccountPreference>(accountTypes.length);
-
-        for (int i = 0; i < accountTypes.length; i++) {
-            final String accountType = accountTypes[i];
-            // Skip showing any account that does not have any of the requested authorities
-            if (!accountTypeHasAnyRequestedAuthorities(helper, accountType)) {
-                continue;
-            }
-            final CharSequence label = helper.getLabelForType(getActivity(), accountType);
-            if (label == null) {
-                continue;
-            }
-            final String titleResPackageName = helper.getPackageForType(accountType);
-            final int titleResId = helper.getLabelIdForType(accountType);
-
-            final Account[] accounts = AccountManager.get(getActivity())
-                    .getAccountsByTypeAsUser(accountType, userHandle);
-            final boolean skipToAccount = accounts.length == 1
-                    && !helper.hasAccountPreferences(accountType);
-
-            if (skipToAccount) {
-                final Bundle fragmentArguments = new Bundle();
-                fragmentArguments.putParcelable(AccountSyncSettings.ACCOUNT_KEY,
-                        accounts[0]);
-                fragmentArguments.putParcelable(EXTRA_USER, userHandle);
-
-                accountTypePreferences.add(new AccountPreference(getPrefContext(), label,
-                        titleResPackageName, titleResId, AccountSyncSettings.class.getName(),
-                        fragmentArguments,
-                        helper.getDrawableForType(getActivity(), accountType)));
-            } else {
-                final Bundle fragmentArguments = new Bundle();
-                fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_TYPE, accountType);
-                fragmentArguments.putString(ManageAccountsSettings.KEY_ACCOUNT_LABEL,
-                        label.toString());
-                fragmentArguments.putParcelable(EXTRA_USER, userHandle);
-
-                accountTypePreferences.add(new AccountPreference(getPrefContext(), label,
-                        titleResPackageName, titleResId, ManageAccountsSettings.class.getName(),
-                        fragmentArguments,
-                        helper.getDrawableForType(getActivity(), accountType)));
-            }
-            helper.preloadDrawableForType(getActivity(), accountType);
-        }
-        // Sort by label
-        Collections.sort(accountTypePreferences, new Comparator<AccountPreference>() {
-            @Override
-            public int compare(AccountPreference t1, AccountPreference t2) {
-                return t1.mTitle.toString().compareTo(t2.mTitle.toString());
-            }
-        });
-        return accountTypePreferences;
-    }
-
-    private boolean accountTypeHasAnyRequestedAuthorities(AuthenticatorHelper helper,
-            String accountType) {
-        if (mAuthoritiesCount == 0) {
-            // No authorities required
-            return true;
-        }
-        final ArrayList<String> authoritiesForType = helper.getAuthoritiesForAccountType(
-                accountType);
-        if (authoritiesForType == null) {
-            Log.d(TAG, "No sync authorities for account type: " + accountType);
-            return false;
-        }
-        for (int j = 0; j < mAuthoritiesCount; j++) {
-            if (authoritiesForType.contains(mAuthorities[j])) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private class AccountPreference extends Preference implements OnPreferenceClickListener {
-        /**
-         * Title of the tile that is shown to the user.
-         * @attr ref android.R.styleable#PreferenceHeader_title
-         */
-        private final CharSequence mTitle;
-
-        /**
-         * Packange name used to resolve the resources of the title shown to the user in the new
-         * fragment.
-         */
-        private final String mTitleResPackageName;
-
-        /**
-         * Resource id of the title shown to the user in the new fragment.
-         */
-        private final int mTitleResId;
-
-        /**
-         * Full class name of the fragment to display when this tile is
-         * selected.
-         * @attr ref android.R.styleable#PreferenceHeader_fragment
-         */
-        private final String mFragment;
-
-        /**
-         * Optional arguments to supply to the fragment when it is
-         * instantiated.
-         */
-        private final Bundle mFragmentArguments;
-
-        public AccountPreference(Context context, CharSequence title, String titleResPackageName,
-                int titleResId, String fragment, Bundle fragmentArguments,
-                Drawable icon) {
-            super(context);
-            mTitle = title;
-            mTitleResPackageName = titleResPackageName;
-            mTitleResId = titleResId;
-            mFragment = fragment;
-            mFragmentArguments = fragmentArguments;
-            setWidgetLayoutResource(R.layout.account_type_preference);
-
-            setTitle(title);
-            setIcon(icon);
-
-            setOnPreferenceClickListener(this);
-        }
-
-        @Override
-        public boolean onPreferenceClick(Preference preference) {
-            if (mFragment != null) {
-                UserHandle user = mFragmentArguments.getParcelable(EXTRA_USER);
-                if (user != null && Utils.startQuietModeDialogIfNecessary(getContext(), mUm,
-                        user.getIdentifier())) {
-                    return true;
-                } else if (user != null && Utils.unlockWorkProfileIfNecessary(getContext(),
-                        user.getIdentifier())) {
-                    return true;
-                }
-                Utils.startWithFragment(getContext(), mFragment, mFragmentArguments,
-                        null /* resultTo */, 0 /* resultRequestCode */, mTitleResPackageName,
-                        mTitleResId, null /* title */);
-                return true;
-            }
-            return false;
-        }
-    }
-
-    private class ManagedProfileBroadcastReceiver extends BroadcastReceiver {
-        private boolean listeningToManagedProfileEvents;
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            Log.v(TAG, "Received broadcast: " + action);
-            if (action.equals(Intent.ACTION_MANAGED_PROFILE_REMOVED)
-                    || action.equals(Intent.ACTION_MANAGED_PROFILE_ADDED)) {
-                // Clean old state
-                stopListeningToAccountUpdates();
-                cleanUpPreferences();
-                // Build new state
-                updateUi();
-                listenToAccountUpdates();
-                // Force the menu to update. Note that #onPrepareOptionsMenu uses data built by
-                // #updateUi so we must call this later
-                getActivity().invalidateOptionsMenu();
-                return;
-            }
-            Log.w(TAG, "Cannot handle received broadcast: " + intent.getAction());
-        }
-
-        public void register(Context context) {
-            if (!listeningToManagedProfileEvents) {
-                IntentFilter intentFilter = new IntentFilter();
-                intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
-                intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
-                context.registerReceiver(this, intentFilter);
-                listeningToManagedProfileEvents = true;
-            }
-        }
-
-        public void unregister(Context context) {
-            if (listeningToManagedProfileEvents) {
-                context.unregisterReceiver(this);
-                listeningToManagedProfileEvents = false;
-            }
+            Log.w(TAG, "Method onPrepareOptionsMenu called before profiles was initialized");
         }
     }
 
@@ -632,90 +125,27 @@
                 Log.d(TAG, "ignoring monkey's attempt to flip sync state");
             } else {
                 AutoSyncDataPreferenceController.ConfirmAutoSyncChangeFragment.show(
-                        AccountSettings.this, !item.isChecked(), mUserHandle, /*preference*/null);
+                    AccountSettings.this, !item.isChecked(), mUserHandle, null/*preference*/);
             }
             return true;
         }
     }
 
-    public static class RemoveUserFragment extends InstrumentedDialogFragment {
-        private static final String ARG_USER_ID = "userId";
-
-        static RemoveUserFragment newInstance(int userId) {
-            Bundle args = new Bundle();
-            args.putInt(ARG_USER_ID, userId);
-            RemoveUserFragment fragment = new RemoveUserFragment();
-            fragment.setArguments(args);
-            return fragment;
-        }
-
-        @Override
-        public Dialog onCreateDialog(Bundle savedInstanceState) {
-            final int userId = getArguments().getInt(ARG_USER_ID);
-            return UserDialogs.createRemoveDialog(getActivity(), userId,
-                    new DialogInterface.OnClickListener() {
-                        @Override
-                        public void onClick(DialogInterface dialog, int which) {
-                            UserManager um = (UserManager)
-                                    getActivity().getSystemService(Context.USER_SERVICE);
-                            um.removeUser(userId);
-                        }
-                    });
-        }
-
-        @Override
-        public int getMetricsCategory() {
-            return MetricsEvent.DIALOG_REMOVE_USER;
-        }
-    }
-
     public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
-            new BaseSearchIndexProvider() {
-        @Override
-        public List<SearchIndexableResource> getXmlResourcesToIndex(
+        new BaseSearchIndexProvider() {
+            @Override
+            public List<SearchIndexableResource> getXmlResourcesToIndex(
                 Context context, boolean enabled) {
-            final SearchIndexableResource sir = new SearchIndexableResource(context);
-            sir.xmlResId = R.xml.account_settings;
-            return Arrays.asList(sir);
-        }
-
-        @Override
-        public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
-            final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>();
-            final Resources res = context.getResources();
-            final String screenTitle = res.getString(R.string.account_settings_title);
-
-            final UserManager um = UserManager.get(context);
-            List<UserInfo> profiles = um.getProfiles(UserHandle.myUserId());
-            final int profilesCount = profiles.size();
-            for (int i = 0; i < profilesCount; i++) {
-                UserInfo userInfo = profiles.get(i);
-                if (userInfo.isEnabled()) {
-                    if (!RestrictedLockUtils.hasBaseUserRestriction(context,
-                            DISALLOW_MODIFY_ACCOUNTS, userInfo.id)) {
-                        SearchIndexableRaw data = new SearchIndexableRaw(context);
-                        data.title = res.getString(R.string.add_account_label);
-                        data.screenTitle = screenTitle;
-                        result.add(data);
-                    }
-                    if (userInfo.isManagedProfile()) {
-                        if (!RestrictedLockUtils.hasBaseUserRestriction(context,
-                                DISALLOW_REMOVE_USER, UserHandle.myUserId())) {
-                            SearchIndexableRaw data = new SearchIndexableRaw(context);
-                            data.title = res.getString(R.string.remove_managed_profile_label);
-                            data.screenTitle = screenTitle;
-                            result.add(data);
-                        }
-                        {
-                            SearchIndexableRaw data = new SearchIndexableRaw(context);
-                            data.title = res.getString(R.string.managed_profile_settings_title);
-                            data.screenTitle = screenTitle;
-                            result.add(data);
-                        }
-                    }
-                }
+                final SearchIndexableResource sir = new SearchIndexableResource(context);
+                sir.xmlResId = R.xml.account_settings;
+                return Arrays.asList(sir);
             }
-            return result;
-        }
-    };
+
+            @Override
+            public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
+                final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>();
+                new AccountPreferenceController(context, null, null).updateRawDataToIndex(result);
+                return result;
+            }
+        };
 }
diff --git a/src/com/android/settings/accounts/EmergencyInfoPreferenceController.java b/src/com/android/settings/accounts/EmergencyInfoPreferenceController.java
index 6f0dfae..0568d83 100644
--- a/src/com/android/settings/accounts/EmergencyInfoPreferenceController.java
+++ b/src/com/android/settings/accounts/EmergencyInfoPreferenceController.java
@@ -38,11 +38,6 @@
     }
 
     @Override
-    public void displayPreference(PreferenceScreen screen) {
-        super.displayPreference(screen);
-    }
-
-    @Override
     public void updateRawDataToIndex(List<SearchIndexableRaw> rawData) {
         if (isAvailable()) {
             SearchIndexableRaw data = new SearchIndexableRaw(mContext);
diff --git a/src/com/android/settings/accounts/RemoveUserFragment.java b/src/com/android/settings/accounts/RemoveUserFragment.java
new file mode 100644
index 0000000..a6772ad
--- /dev/null
+++ b/src/com/android/settings/accounts/RemoveUserFragment.java
@@ -0,0 +1,58 @@
+/*
+ * 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.accounts;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.os.UserManager;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settings.users.UserDialogs;
+
+class RemoveUserFragment extends InstrumentedDialogFragment {
+    private static final String ARG_USER_ID = "userId";
+
+    static RemoveUserFragment newInstance(int userId) {
+        Bundle args = new Bundle();
+        args.putInt(ARG_USER_ID, userId);
+        RemoveUserFragment fragment = new RemoveUserFragment();
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        final int userId = getArguments().getInt(ARG_USER_ID);
+        return UserDialogs.createRemoveDialog(getActivity(), userId,
+            new DialogInterface.OnClickListener() {
+                @Override
+                public void onClick(DialogInterface dialog, int which) {
+                    UserManager um = (UserManager)
+                        getActivity().getSystemService(Context.USER_SERVICE);
+                    um.removeUser(userId);
+                }
+            });
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        return MetricsEvent.DIALOG_REMOVE_USER;
+    }
+}
diff --git a/src/com/android/settings/accounts/UserAndAccountDashboardFragment.java b/src/com/android/settings/accounts/UserAndAccountDashboardFragment.java
index 8c48daf..aafec37 100644
--- a/src/com/android/settings/accounts/UserAndAccountDashboardFragment.java
+++ b/src/com/android/settings/accounts/UserAndAccountDashboardFragment.java
@@ -26,6 +26,8 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import static android.provider.Settings.EXTRA_AUTHORITIES;
+
 public class UserAndAccountDashboardFragment extends DashboardFragment {
 
     private static final String TAG = "UserAndAccountDashboard";
@@ -61,6 +63,11 @@
         controllers.add(new AutoSyncDataPreferenceController(context, this));
         controllers.add(new AutoSyncPersonalDataPreferenceController(context, this));
         controllers.add(new AutoSyncWorkDataPreferenceController(context, this));
+        String[] authorities = getIntent().getStringArrayExtra(EXTRA_AUTHORITIES);
+        final AccountPreferenceController accountPrefController =
+            new AccountPreferenceController(context, this, authorities);
+        getLifecycle().addObserver(accountPrefController);
+        controllers.add(accountPrefController);
         return controllers;
     }
 
diff --git a/src/com/android/settings/applications/AppCounter.java b/src/com/android/settings/applications/AppCounter.java
index fb8d580..64b1987 100644
--- a/src/com/android/settings/applications/AppCounter.java
+++ b/src/com/android/settings/applications/AppCounter.java
@@ -17,44 +17,36 @@
 import android.app.AppGlobals;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
-import android.content.pm.ParceledListSlice;
 import android.content.pm.UserInfo;
 import android.os.AsyncTask;
-import android.os.RemoteException;
-import android.os.UserHandle;
 import android.os.UserManager;
 
+import java.util.List;
+
 public abstract class AppCounter extends AsyncTask<Void, Void, Integer> {
 
-    protected final PackageManager mPm;
-    protected final IPackageManager mIpm;
+    protected final PackageManagerWrapper mPm;
     protected final UserManager mUm;
 
-    public AppCounter(Context context) {
-        mPm = context.getPackageManager();
-        mIpm = AppGlobals.getPackageManager();
+    public AppCounter(Context context, PackageManagerWrapper packageManager) {
+        mPm = packageManager;
         mUm = UserManager.get(context);
     }
 
     @Override
     protected Integer doInBackground(Void... params) {
         int count = 0;
-        for (UserInfo user : mUm.getProfiles(UserHandle.myUserId())) {
-            try {
-                @SuppressWarnings("unchecked")
-                ParceledListSlice<ApplicationInfo> list =
-                        mIpm.getInstalledApplications(PackageManager.GET_DISABLED_COMPONENTS
-                                | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS
-                                | (user.isAdmin() ? PackageManager.GET_UNINSTALLED_PACKAGES : 0),
-                                user.id);
-                for (ApplicationInfo info : list.getList()) {
-                    if (includeInCount(info)) {
-                        count++;
-                    }
+        for (UserInfo user : getUsersToCount()) {
+            final List<ApplicationInfo> list =
+                    mPm.getInstalledApplicationsAsUser(PackageManager.GET_DISABLED_COMPONENTS
+                            | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS
+                            | (user.isAdmin() ? PackageManager.GET_UNINSTALLED_PACKAGES : 0),
+                            user.id);
+            for (ApplicationInfo info : list) {
+                if (includeInCount(info)) {
+                    count++;
                 }
-            } catch (RemoteException e) {
             }
         }
         return count;
@@ -66,5 +58,6 @@
     }
 
     protected abstract void onCountComplete(int num);
+    protected abstract List<UserInfo> getUsersToCount();
     protected abstract boolean includeInCount(ApplicationInfo info);
 }
diff --git a/src/com/android/settings/applications/ApplicationFeatureProvider.java b/src/com/android/settings/applications/ApplicationFeatureProvider.java
index b6f7381..8c7b257 100644
--- a/src/com/android/settings/applications/ApplicationFeatureProvider.java
+++ b/src/com/android/settings/applications/ApplicationFeatureProvider.java
@@ -25,5 +25,17 @@
      * Returns a new {@link AppHeaderController} instance to customize app header.
      */
     AppHeaderController newAppHeaderController(Fragment fragment, View appHeader);
-}
 
+    /**
+     * Asynchronously calculates the total number of apps installed on the device, across all users
+     * and managed profiles.
+     */
+    void calculateNumberOfInstalledApps(NumberOfInstalledAppsCallback callback);
+
+    /**
+     * Callback that receives the total number of packages installed on the device.
+     */
+    public interface NumberOfInstalledAppsCallback {
+        void onNumberOfInstalledAppsResult(int num);
+    }
+}
diff --git a/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java b/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java
index b1b4474..a284a0a 100644
--- a/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java
+++ b/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java
@@ -18,17 +18,50 @@
 
 import android.app.Fragment;
 import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.UserManager;
 import android.view.View;
 
+import java.util.List;
+
 public class ApplicationFeatureProviderImpl implements ApplicationFeatureProvider {
 
     private final Context mContext;
+    private final PackageManagerWrapper mPm;
+    private final UserManager mUm;
 
-    public ApplicationFeatureProviderImpl(Context context) {
+    public ApplicationFeatureProviderImpl(Context context, PackageManagerWrapper pm) {
         mContext = context.getApplicationContext();
+        mPm = pm;
+        mUm = UserManager.get(mContext);
     }
 
+    @Override
     public AppHeaderController newAppHeaderController(Fragment fragment, View appHeader) {
         return new AppHeaderController(mContext, fragment, appHeader);
     }
+
+    @Override
+    public void calculateNumberOfInstalledApps(NumberOfInstalledAppsCallback callback) {
+        new AllUserInstalledAppCounter(callback).execute();
+    }
+
+    private class AllUserInstalledAppCounter extends InstalledAppCounter {
+        private NumberOfInstalledAppsCallback mCallback;
+
+        AllUserInstalledAppCounter(NumberOfInstalledAppsCallback callback) {
+            super(mContext, ApplicationFeatureProviderImpl.this.mPm);
+            mCallback = callback;
+        }
+
+        @Override
+        protected void onCountComplete(int num) {
+            mCallback.onNumberOfInstalledAppsResult(num);
+        }
+
+        @Override
+        protected List<UserInfo> getUsersToCount() {
+            return mUm.getUsers(true /* excludeDying */);
+        }
+    }
 }
diff --git a/src/com/android/settings/applications/InstalledAppCounter.java b/src/com/android/settings/applications/InstalledAppCounter.java
new file mode 100644
index 0000000..9faef7a
--- /dev/null
+++ b/src/com/android/settings/applications/InstalledAppCounter.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.settings.applications;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+
+import java.util.List;
+
+public abstract class InstalledAppCounter extends AppCounter {
+
+    public InstalledAppCounter(Context context, PackageManagerWrapper packageManager) {
+        super(context, packageManager);
+    }
+
+    @Override
+    protected boolean includeInCount(ApplicationInfo info) {
+        if ((info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
+            return true;
+        }
+        if ((info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+            return true;
+        }
+        Intent launchIntent = new Intent(Intent.ACTION_MAIN, null)
+                .addCategory(Intent.CATEGORY_LAUNCHER)
+                .setPackage(info.packageName);
+        int userId = UserHandle.getUserId(info.uid);
+        List<ResolveInfo> intents = mPm.queryIntentActivitiesAsUser(
+                launchIntent,
+                PackageManager.GET_DISABLED_COMPONENTS
+                        | PackageManager.MATCH_DIRECT_BOOT_AWARE
+                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+                userId);
+        return intents != null && intents.size() != 0;
+    }
+}
diff --git a/src/com/android/settings/applications/ManageApplications.java b/src/com/android/settings/applications/ManageApplications.java
index b6e3b5d..0ec9f1b 100644
--- a/src/com/android/settings/applications/ManageApplications.java
+++ b/src/com/android/settings/applications/ManageApplications.java
@@ -22,7 +22,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
 import android.icu.text.AlphabeticIndex;
 import android.os.Bundle;
 import android.os.Environment;
@@ -1259,7 +1259,8 @@
         @Override
         public void setListening(boolean listening) {
             if (listening) {
-                new AppCounter(mContext) {
+                new InstalledAppCounter(mContext,
+                        new PackageManagerWrapperImpl(mContext.getPackageManager())) {
                     @Override
                     protected void onCountComplete(int num) {
                         mLoader.setSummary(SummaryProvider.this,
@@ -1267,23 +1268,8 @@
                     }
 
                     @Override
-                    protected boolean includeInCount(ApplicationInfo info) {
-                        if ((info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
-                            return true;
-                        } else if ((info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
-                            return true;
-                        }
-                        Intent launchIntent = new Intent(Intent.ACTION_MAIN, null)
-                                .addCategory(Intent.CATEGORY_LAUNCHER)
-                                .setPackage(info.packageName);
-                        int userId = UserHandle.getUserId(info.uid);
-                        List<ResolveInfo> intents = mPm.queryIntentActivitiesAsUser(
-                                launchIntent,
-                                PackageManager.GET_DISABLED_COMPONENTS
-                                        | PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
-                                userId);
-                        return intents != null && intents.size() != 0;
+                    protected List<UserInfo> getUsersToCount() {
+                         return mUm.getProfiles(UserHandle.myUserId());
                     }
                 }.execute();
             }
diff --git a/src/com/android/settings/applications/NotificationApps.java b/src/com/android/settings/applications/NotificationApps.java
index 0d88dc4..7aaa36e 100644
--- a/src/com/android/settings/applications/NotificationApps.java
+++ b/src/com/android/settings/applications/NotificationApps.java
@@ -17,10 +17,17 @@
 import android.app.Activity;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.os.UserHandle;
+import android.os.UserManager;
+
 import com.android.settings.R;
 import com.android.settings.dashboard.SummaryLoader;
 import com.android.settings.notification.NotificationBackend;
 
+import java.util.List;
+
 /**
  * Extension of ManageApplications with no changes other than having its own
  * SummaryProvider.
@@ -42,13 +49,19 @@
         @Override
         public void setListening(boolean listening) {
             if (listening) {
-                new AppCounter(mContext) {
+                new AppCounter(mContext,
+                        new PackageManagerWrapperImpl(mContext.getPackageManager())) {
                     @Override
                     protected void onCountComplete(int num) {
                         updateSummary(num);
                     }
 
                     @Override
+                    protected List<UserInfo> getUsersToCount() {
+                         return mUm.getProfiles(UserHandle.myUserId());
+                    }
+
+                    @Override
                     protected boolean includeInCount(ApplicationInfo info) {
                         return mNotificationBackend.getNotificationsBanned(info.packageName,
                                 info.uid);
diff --git a/src/com/android/settings/applications/PackageManagerWrapper.java b/src/com/android/settings/applications/PackageManagerWrapper.java
new file mode 100644
index 0000000..d399115
--- /dev/null
+++ b/src/com/android/settings/applications/PackageManagerWrapper.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.applications;
+
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+
+import java.util.List;
+
+/**
+ * This interface replicates a subset of the android.content.pm.PackageManager (PM). The interface
+ * exists so that we can use a thin wrapper around the PM in production code and a mock in tests.
+ * We cannot directly mock or shadow the PM, because some of the methods we rely on are newer than
+ * the API version supported by Robolectric.
+ */
+public interface PackageManagerWrapper {
+    /**
+     * Calls {@code PackageManager.getInstalledApplicationsAsUser()}.
+     *
+     * @see android.content.pm.PackageManager.PackageManager#getInstalledApplicationsAsUser
+     */
+    List<ApplicationInfo> getInstalledApplicationsAsUser(int flags, int userId);
+
+    /**
+     * Calls {@code PackageManager.hasSystemFeature()}.
+     *
+     * @see android.content.pm.PackageManager.PackageManager#hasSystemFeature
+     */
+    boolean hasSystemFeature(String name);
+
+    /**
+     * Calls {@code PackageManager.queryIntentActivitiesAsUser()}.
+     *
+     * @see android.content.pm.PackageManager.PackageManager#queryIntentActivitiesAsUser
+     */
+    List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, int flags, int userId);
+}
diff --git a/src/com/android/settings/applications/PackageManagerWrapperImpl.java b/src/com/android/settings/applications/PackageManagerWrapperImpl.java
new file mode 100644
index 0000000..8966869
--- /dev/null
+++ b/src/com/android/settings/applications/PackageManagerWrapperImpl.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.applications;
+
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+
+import java.util.List;
+
+public class PackageManagerWrapperImpl implements PackageManagerWrapper {
+    private final PackageManager mPm;
+
+    public PackageManagerWrapperImpl(PackageManager pm) {
+        mPm = pm;
+    }
+
+    @Override
+    public List<ApplicationInfo> getInstalledApplicationsAsUser(int flags, int userId) {
+        return mPm.getInstalledApplicationsAsUser(flags, userId);
+    }
+
+    @Override
+    public boolean hasSystemFeature(String name) {
+        return mPm.hasSystemFeature(name);
+    }
+
+    @Override
+    public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, int flags, int userId) {
+        return mPm.queryIntentActivitiesAsUser(intent, flags, userId);
+    }
+}
diff --git a/src/com/android/settings/applications/ProcessStatsPreferenceController.java b/src/com/android/settings/applications/ProcessStatsPreferenceController.java
deleted file mode 100644
index 3ba2caf..0000000
--- a/src/com/android/settings/applications/ProcessStatsPreferenceController.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.applications;
-
-import android.content.Context;
-import android.support.v7.preference.Preference;
-
-import com.android.settings.core.PreferenceController;
-import com.android.settings.dashboard.DashboardFeatureProvider;
-import com.android.settings.overlay.FeatureFactory;
-
-public class ProcessStatsPreferenceController extends PreferenceController {
-
-    private final DashboardFeatureProvider mDashboardFeatureProvider;
-
-    public ProcessStatsPreferenceController(Context context) {
-        super(context);
-        mDashboardFeatureProvider = FeatureFactory.getFactory(context)
-                .getDashboardFeatureProvider(context);
-    }
-
-    @Override
-    public boolean isAvailable() {
-        return mDashboardFeatureProvider.isEnabled();
-    }
-
-    @Override
-    public boolean handlePreferenceTreeClick(Preference preference) {
-        return false;
-    }
-
-    @Override
-    public String getPreferenceKey() {
-        return "process_stats";
-    }
-}
diff --git a/src/com/android/settings/dashboard/DashboardDividerDecoration.java b/src/com/android/settings/dashboard/DashboardDividerDecoration.java
index 706bbc8..ed8a077 100644
--- a/src/com/android/settings/dashboard/DashboardDividerDecoration.java
+++ b/src/com/android/settings/dashboard/DashboardDividerDecoration.java
@@ -76,6 +76,9 @@
         }
         final PreferenceGroupAdapter prefAdapter = (PreferenceGroupAdapter) adapter;
         final int adapterPosition = parent.getChildAdapterPosition(view);
+        if (adapterPosition == RecyclerView.NO_POSITION) {
+            return false;
+        }
         final Preference pref = prefAdapter.getItem(adapterPosition);
         final Preference nextPref = prefAdapter.getItem(adapterPosition + 1);
         if (nextPref == null) {
diff --git a/src/com/android/settings/dashboard/DashboardFeatureProvider.java b/src/com/android/settings/dashboard/DashboardFeatureProvider.java
index 60d4abe..dee9f36 100644
--- a/src/com/android/settings/dashboard/DashboardFeatureProvider.java
+++ b/src/com/android/settings/dashboard/DashboardFeatureProvider.java
@@ -40,6 +40,19 @@
     DashboardCategory getTilesForCategory(String key);
 
     /**
+     * Get tiles (wrapped as a list of Preference) for key defined in CategoryKey.
+     *
+     * @param activity Activity hosting the preference
+     * @param context UI context to inflate preference
+     * @param key Value from CategoryKey
+     * @deprecated Pages implementing {@code DashboardFragment} should use
+     * {@link #getTilesForCategory(String)} instead. Using this method will not get the benefit
+     * of auto-ordering, progressive disclosure, auto-refreshing summary text etc.
+     */
+    @Deprecated
+    List<Preference> getPreferencesForCategory(Activity activity, Context context, String key);
+
+    /**
      * Get all tiles, grouped by category.
      */
     List<DashboardCategory> getAllCategories();
@@ -75,4 +88,5 @@
     ProgressiveDisclosureMixin getProgressiveDisclosureMixin(Context context,
             DashboardFragment fragment);
 
+
 }
diff --git a/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java b/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java
index ef1265d..a3bac9d 100644
--- a/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java
+++ b/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java
@@ -21,8 +21,10 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
+import android.support.annotation.VisibleForTesting;
 import android.support.v7.preference.Preference;
 import android.text.TextUtils;
+import android.util.Log;
 
 import com.android.settings.SettingsActivity;
 import com.android.settingslib.drawer.CategoryManager;
@@ -30,6 +32,7 @@
 import com.android.settingslib.drawer.ProfileSelectDialog;
 import com.android.settingslib.drawer.Tile;
 
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -37,6 +40,8 @@
  */
 public class DashboardFeatureProviderImpl implements DashboardFeatureProvider {
 
+    private static final String TAG = "DashboardFeatureImpl";
+
     private static final String DASHBOARD_TILE_PREF_KEY_PREFIX = "dashboard_tile_pref_";
 
     protected final Context mContext;
@@ -44,8 +49,13 @@
     private final CategoryManager mCategoryManager;
 
     public DashboardFeatureProviderImpl(Context context) {
-        mContext = context.getApplicationContext();
-        mCategoryManager = CategoryManager.get(mContext);
+        this(context.getApplicationContext(), CategoryManager.get(context));
+    }
+
+    @VisibleForTesting
+    DashboardFeatureProviderImpl(Context context, CategoryManager categoryManager) {
+        mContext = context;
+        mCategoryManager = categoryManager;
     }
 
     @Override
@@ -59,6 +69,32 @@
     }
 
     @Override
+    public List<Preference> getPreferencesForCategory(Activity activity, Context context,
+            String key) {
+        if (!isEnabled()) {
+            return null;
+        }
+        final DashboardCategory category = getTilesForCategory(key);
+        if (category == null) {
+            Log.d(TAG, "NO dashboard tiles for " + TAG);
+            return null;
+        }
+        final List<Tile> tiles = category.tiles;
+        if (tiles == null || tiles.isEmpty()) {
+            Log.d(TAG, "tile list is empty, skipping category " + category.title);
+            return null;
+        }
+        final List<Preference> preferences = new ArrayList<>();
+        for (Tile tile : tiles) {
+            final Preference pref = new Preference(context);
+            bindPreferenceToTile(activity, pref, tile, null /* key */,
+                    Preference.DEFAULT_ORDER /* baseOrder */);
+            preferences.add(pref);
+        }
+        return preferences;
+    }
+
+    @Override
     public List<DashboardCategory> getAllCategories() {
         return mCategoryManager.getCategories(mContext);
     }
diff --git a/src/com/android/settings/dashboard/SummaryLoader.java b/src/com/android/settings/dashboard/SummaryLoader.java
index 4e4dd40..2f0d8b6 100644
--- a/src/com/android/settings/dashboard/SummaryLoader.java
+++ b/src/com/android/settings/dashboard/SummaryLoader.java
@@ -25,6 +25,8 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.Process;
+import android.support.annotation.VisibleForTesting;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
@@ -141,19 +143,31 @@
                 if (DEBUG) {
                     Log.d(TAG, "setSummary " + tile.title + " - " + summary);
                 }
-                tile.summary = summary;
-                if (mSummaryConsumer != null) {
-                    mSummaryConsumer.notifySummaryChanged(tile);
-                } else {
-                    if (DEBUG) {
-                        Log.d(TAG, "SummaryConsumer is null, skipping summary update for "
-                                + tile.title);
-                    }
-                }
+
+                updateSummaryIfNeeded(tile, summary);
             }
         });
     }
 
+    @VisibleForTesting
+    void updateSummaryIfNeeded(Tile tile, CharSequence summary) {
+        if (TextUtils.equals(tile.summary, summary)) {
+            if (DEBUG) {
+                Log.d(TAG, "Summary doesn't change, skipping summary update for " + tile.title);
+            }
+            return;
+        }
+        tile.summary = summary;
+        if (mSummaryConsumer != null) {
+            mSummaryConsumer.notifySummaryChanged(tile);
+        } else {
+            if (DEBUG) {
+                Log.d(TAG, "SummaryConsumer is null, skipping summary update for "
+                        + tile.title);
+            }
+        }
+    }
+
     /**
      * Only call from the main thread.
      */
diff --git a/src/com/android/settings/datetime/TimeFormatPreferenceController.java b/src/com/android/settings/datetime/TimeFormatPreferenceController.java
new file mode 100644
index 0000000..068b790
--- /dev/null
+++ b/src/com/android/settings/datetime/TimeFormatPreferenceController.java
@@ -0,0 +1,108 @@
+/*
+ * 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.datetime;
+
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Settings;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.TwoStatePreference;
+import android.text.TextUtils;
+import android.text.format.DateFormat;
+
+import com.android.settings.core.PreferenceController;
+
+import java.util.Calendar;
+import java.util.Date;
+
+public class TimeFormatPreferenceController extends PreferenceController {
+
+    static final String HOURS_12 = "12";
+    static final String HOURS_24 = "24";
+
+    private static final String KEY_TIME_FORMAT = "24 hour";
+
+    // Used for showing the current date format, which looks like "12/31/2010", "2010/12/13", etc.
+    // The date value is dummy (independent of actual date).
+    private final Calendar mDummyDate;
+    private final boolean mIsFromSUW;
+    private final UpdateTimeAndDateCallback mUpdateTimeAndDateCallback;
+
+    public TimeFormatPreferenceController(Context context, UpdateTimeAndDateCallback callback,
+            boolean isFromSUW) {
+        super(context);
+        mIsFromSUW = isFromSUW;
+        mDummyDate = Calendar.getInstance();
+        mUpdateTimeAndDateCallback = callback;
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return !mIsFromSUW;
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        if (!(preference instanceof TwoStatePreference)
+                || !TextUtils.equals(KEY_TIME_FORMAT, preference.getKey())) {
+            return;
+        }
+        ((TwoStatePreference) preference).setChecked(is24Hour());
+        final Calendar now = Calendar.getInstance();
+        mDummyDate.setTimeZone(now.getTimeZone());
+        // We use December 31st because it's unambiguous when demonstrating the date format.
+        // We use 13:00 so we can demonstrate the 12/24 hour options.
+        mDummyDate.set(now.get(Calendar.YEAR), 11, 31, 13, 0, 0);
+        final Date dummyDate = mDummyDate.getTime();
+        preference.setSummary(DateFormat.getTimeFormat(mContext).format(dummyDate));
+    }
+
+    @Override
+    public boolean handlePreferenceTreeClick(Preference preference) {
+        if (!(preference instanceof TwoStatePreference)
+                || !TextUtils.equals(KEY_TIME_FORMAT, preference.getKey())) {
+            return false;
+        }
+        final boolean is24Hour = ((SwitchPreference) preference).isChecked();
+        set24Hour(is24Hour);
+        timeUpdated(is24Hour);
+        mUpdateTimeAndDateCallback.updateTimeAndDateDisplay(mContext);
+        return true;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_TIME_FORMAT;
+    }
+
+    private boolean is24Hour() {
+        return DateFormat.is24HourFormat(mContext);
+    }
+
+    private void timeUpdated(boolean is24Hour) {
+        Intent timeChanged = new Intent(Intent.ACTION_TIME_CHANGED);
+        timeChanged.putExtra(Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT, is24Hour);
+        mContext.sendBroadcast(timeChanged);
+    }
+
+    private void set24Hour(boolean is24Hour) {
+        Settings.System.putString(mContext.getContentResolver(),
+                Settings.System.TIME_12_24,
+                is24Hour ? HOURS_24 : HOURS_12);
+    }
+}
diff --git a/src/com/android/settings/datetime/UpdateTimeAndDateCallback.java b/src/com/android/settings/datetime/UpdateTimeAndDateCallback.java
new file mode 100644
index 0000000..e89b5da
--- /dev/null
+++ b/src/com/android/settings/datetime/UpdateTimeAndDateCallback.java
@@ -0,0 +1,24 @@
+/*
+ * 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.datetime;
+
+import android.content.Context;
+
+public interface UpdateTimeAndDateCallback {
+
+    void updateTimeAndDateDisplay(Context context);
+}
diff --git a/src/com/android/settings/deviceinfo/SerialNumberPreferenceController.java b/src/com/android/settings/deviceinfo/SerialNumberPreferenceController.java
new file mode 100644
index 0000000..1029e20
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/SerialNumberPreferenceController.java
@@ -0,0 +1,67 @@
+/*
+ * 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.deviceinfo;
+
+import android.content.Context;
+import android.os.Build;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settings.core.PreferenceController;
+
+public class SerialNumberPreferenceController extends PreferenceController {
+
+    private static final String KEY_SERIAL_NUMBER = "serial_number";
+
+    private final String mSerialNumber;
+
+    public SerialNumberPreferenceController(Context context) {
+        this(context, Build.getSerial());
+    }
+
+    @VisibleForTesting
+    SerialNumberPreferenceController(Context context, String serialNumber) {
+        super(context);
+        mSerialNumber = serialNumber;
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return !TextUtils.isEmpty(mSerialNumber);
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        final Preference pref = screen.findPreference(KEY_SERIAL_NUMBER);
+        if (pref != null) {
+            pref.setSummary(mSerialNumber);
+        }
+    }
+
+    @Override
+    public boolean handlePreferenceTreeClick(Preference preference) {
+        return false;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_SERIAL_NUMBER;
+    }
+}
diff --git a/src/com/android/settings/deviceinfo/Status.java b/src/com/android/settings/deviceinfo/Status.java
index 930e185..fb6627a 100644
--- a/src/com/android/settings/deviceinfo/Status.java
+++ b/src/com/android/settings/deviceinfo/Status.java
@@ -16,9 +16,6 @@
 
 package com.android.settings.deviceinfo;
 
-import static android.content.Context.CONNECTIVITY_SERVICE;
-import static android.content.Context.WIFI_SERVICE;
-
 import android.bluetooth.BluetoothAdapter;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -28,7 +25,6 @@
 import android.net.ConnectivityManager;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
@@ -36,6 +32,7 @@
 import android.os.SystemProperties;
 import android.os.UserManager;
 import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
 import android.text.TextUtils;
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -46,6 +43,9 @@
 
 import java.lang.ref.WeakReference;
 
+import static android.content.Context.CONNECTIVITY_SERVICE;
+import static android.content.Context.WIFI_SERVICE;
+
 /**
  * Display the following information
  * # Battery Strength  : TODO
@@ -61,7 +61,6 @@
     private static final String KEY_IP_ADDRESS = "wifi_ip_address";
     private static final String KEY_WIFI_MAC_ADDRESS = "wifi_mac_address";
     private static final String KEY_BT_ADDRESS = "bt_address";
-    private static final String KEY_SERIAL_NUMBER = "serial_number";
     private static final String KEY_WIMAX_MAC_ADDRESS = "wimax_mac_address";
     private static final String KEY_SIM_STATUS = "sim_status";
     private static final String KEY_IMEI_INFO = "imei_info";
@@ -83,9 +82,10 @@
 
     private Resources mRes;
 
-    private String mUnknown;
     private String mUnavailable;
 
+    private SerialNumberPreferenceController mSerialNumberPreferenceController;
+
     private Preference mUptime;
     private Preference mBatteryStatus;
     private Preference mBatteryLevel;
@@ -93,7 +93,6 @@
     private Preference mIpAddress;
     private Preference mWifiMacAddress;
     private Preference mWimaxMacAddress;
-
     private Handler mHandler;
 
     private static class MyHandler extends Handler {
@@ -162,6 +161,7 @@
 
         mCM = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
         mWifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
+        mSerialNumberPreferenceController = new SerialNumberPreferenceController(getActivity());
 
         addPreferencesFromResource(R.xml.device_info_status);
         mBatteryLevel = findPreference(KEY_BATTERY_LEVEL);
@@ -172,19 +172,18 @@
         mIpAddress = findPreference(KEY_IP_ADDRESS);
 
         mRes = getResources();
-        mUnknown = mRes.getString(R.string.device_info_default);
         mUnavailable = mRes.getString(R.string.status_unavailable);
 
         // Note - missing in zaku build, be careful later...
         mUptime = findPreference("up_time");
-
+        final PreferenceScreen screen = getPreferenceScreen();
         if (!hasBluetooth()) {
-            getPreferenceScreen().removePreference(mBtAddress);
+            screen.removePreference(mBtAddress);
             mBtAddress = null;
         }
 
         if (!hasWimax()) {
-            getPreferenceScreen().removePreference(mWimaxMacAddress);
+            screen.removePreference(mWimaxMacAddress);
             mWimaxMacAddress = null;
         }
 
@@ -195,12 +194,7 @@
 
         updateConnectivity();
 
-        String serial = Build.SERIAL;
-        if (serial != null && !serial.equals("")) {
-            setSummaryText(KEY_SERIAL_NUMBER, serial);
-        } else {
-            removePreferenceFromScreen(KEY_SERIAL_NUMBER);
-        }
+        mSerialNumberPreferenceController.displayPreference(screen);
 
         // Remove SimStatus and Imei for Secondary user as it access Phone b/19165700
         // Also remove on Wi-Fi only devices.
@@ -247,30 +241,6 @@
         }
     }
 
-    /**
-     * @param preference The key for the Preference item
-     * @param property The system property to fetch
-     * @param alt The default value, if the property doesn't exist
-     */
-    private void setSummary(String preference, String property, String alt) {
-        try {
-            findPreference(preference).setSummary(
-                    SystemProperties.get(property, alt));
-        } catch (RuntimeException e) {
-
-        }
-    }
-
-    private void setSummaryText(String preference, String text) {
-            if (TextUtils.isEmpty(text)) {
-               text = mUnknown;
-             }
-             // some preferences may be missing
-             if (findPreference(preference) != null) {
-                 findPreference(preference).setSummary(text);
-             }
-    }
-
     private void setWimaxStatus() {
         if (mWimaxMacAddress != null) {
             String macAddress = SystemProperties.get("net.wimax.mac.address", mUnavailable);
diff --git a/src/com/android/settings/enterprise/DevicePolicyManagerWrapper.java b/src/com/android/settings/enterprise/DevicePolicyManagerWrapper.java
index 3e45de0..2d8ca1f 100644
--- a/src/com/android/settings/enterprise/DevicePolicyManagerWrapper.java
+++ b/src/com/android/settings/enterprise/DevicePolicyManagerWrapper.java
@@ -18,10 +18,17 @@
 
 import android.content.ComponentName;
 
-// This interface replicates a subset of the android.app.admin.DevicePolicyManager (DPM). The
-// interface exists so that we can use a thin wrapper around the DPM in production code and a mock
-// in tests. We cannot directly mock or shadow the DPM, because some of the methods we rely on are
-// newer than the API version supported by Robolectric.
+/**
+ * This interface replicates a subset of the android.app.admin.DevicePolicyManager (DPM). The
+ * interface exists so that we can use a thin wrapper around the DPM in production code and a mock
+ * in tests. We cannot directly mock or shadow the DPM, because some of the methods we rely on are
+ * newer than the API version supported by Robolectric.
+ */
 public interface DevicePolicyManagerWrapper {
-    public ComponentName getDeviceOwnerComponentOnAnyUser();
+    /**
+     * Calls {@code DevicePolicyManager.getDeviceOwnerComponentOnAnyUser()}.
+     *
+     * @see android.app.admin.DevicePolicyManager#getDeviceOwnerComponentOnAnyUser
+     */
+    ComponentName getDeviceOwnerComponentOnAnyUser();
 }
diff --git a/src/com/android/settings/enterprise/DevicePolicyManagerWrapperImpl.java b/src/com/android/settings/enterprise/DevicePolicyManagerWrapperImpl.java
index 87adcd6..2b1efb4 100644
--- a/src/com/android/settings/enterprise/DevicePolicyManagerWrapperImpl.java
+++ b/src/com/android/settings/enterprise/DevicePolicyManagerWrapperImpl.java
@@ -26,6 +26,7 @@
         mDpm = dpm;
     }
 
+    @Override
     public ComponentName getDeviceOwnerComponentOnAnyUser() {
         return mDpm.getDeviceOwnerComponentOnAnyUser();
     }
diff --git a/src/com/android/settings/enterprise/EnterprisePrivacyFeatureProviderImpl.java b/src/com/android/settings/enterprise/EnterprisePrivacyFeatureProviderImpl.java
index 3d35c23..8cdf270 100644
--- a/src/com/android/settings/enterprise/EnterprisePrivacyFeatureProviderImpl.java
+++ b/src/com/android/settings/enterprise/EnterprisePrivacyFeatureProviderImpl.java
@@ -17,21 +17,23 @@
 package com.android.settings.enterprise;
 
 import android.content.pm.PackageManager;
-import android.content.Context;
+
+import com.android.settings.applications.PackageManagerWrapper;
 
 public class EnterprisePrivacyFeatureProviderImpl implements EnterprisePrivacyFeatureProvider {
 
-    private final Context mContext;
     private final DevicePolicyManagerWrapper mDpm;
+    private final PackageManagerWrapper mPm;
 
-    public EnterprisePrivacyFeatureProviderImpl(Context context, DevicePolicyManagerWrapper dpm) {
-        mContext = context.getApplicationContext();
+    public EnterprisePrivacyFeatureProviderImpl(DevicePolicyManagerWrapper dpm,
+            PackageManagerWrapper pm) {
         mDpm = dpm;
+        mPm = pm;
     }
 
     @Override
     public boolean hasDeviceOwner() {
-        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN)) {
+        if (!mPm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN)) {
             return false;
         }
         return mDpm.getDeviceOwnerComponentOnAnyUser() != null;
diff --git a/src/com/android/settings/enterprise/EnterprisePrivacySettings.java b/src/com/android/settings/enterprise/EnterprisePrivacySettings.java
index a2d1a30..9ac76df 100644
--- a/src/com/android/settings/enterprise/EnterprisePrivacySettings.java
+++ b/src/com/android/settings/enterprise/EnterprisePrivacySettings.java
@@ -17,7 +17,6 @@
 package com.android.settings.enterprise;
 
 import android.content.Context;
-import android.os.Bundle;
 import android.provider.SearchIndexableResource;
 
 import com.android.settings.R;
@@ -25,7 +24,9 @@
 import com.android.settings.core.PreferenceController;
 import com.android.settings.dashboard.DashboardFragment;
 import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.search.Indexable.SearchIndexProvider;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
@@ -55,7 +56,9 @@
 
     @Override
     protected List<PreferenceController> getPreferenceControllers(Context context) {
-        return null;
+        final List controllers = new ArrayList<PreferenceController>();
+        controllers.add(new InstalledPackagesPreferenceController(context));
+        return controllers;
     }
 
     public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
diff --git a/src/com/android/settings/enterprise/InstalledPackagesPreferenceController.java b/src/com/android/settings/enterprise/InstalledPackagesPreferenceController.java
new file mode 100644
index 0000000..34c72ed
--- /dev/null
+++ b/src/com/android/settings/enterprise/InstalledPackagesPreferenceController.java
@@ -0,0 +1,62 @@
+/*
+ * 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.enterprise;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.support.v7.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settings.applications.ApplicationFeatureProvider;
+import com.android.settings.core.PreferenceController;
+import com.android.settings.overlay.FeatureFactory;
+
+public class InstalledPackagesPreferenceController extends PreferenceController {
+
+    private static final String KEY_NUMBER_INSTALLED_PACKAGES = "number_installed_packages";
+    private final ApplicationFeatureProvider mFeatureProvider;
+
+    public InstalledPackagesPreferenceController(Context context) {
+        super(context);
+        mFeatureProvider = FeatureFactory.getFactory(context)
+                .getApplicationFeatureProvider(context);
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        mFeatureProvider.calculateNumberOfInstalledApps(
+                new ApplicationFeatureProvider.NumberOfInstalledAppsCallback() {
+                    @Override
+                    public void onNumberOfInstalledAppsResult(int num) {
+                        preference.setTitle(mContext.getResources().getQuantityString(
+                                R.plurals.number_installed_packages, num, num));
+                    }
+                });
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+
+    @Override
+    public boolean handlePreferenceTreeClick(Preference preference) {
+        return false;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_NUMBER_INSTALLED_PACKAGES;
+    }
+}
diff --git a/src/com/android/settings/overlay/FeatureFactory.java b/src/com/android/settings/overlay/FeatureFactory.java
index 55ea4bb..75f1001 100644
--- a/src/com/android/settings/overlay/FeatureFactory.java
+++ b/src/com/android/settings/overlay/FeatureFactory.java
@@ -27,6 +27,7 @@
 import com.android.settings.enterprise.EnterprisePrivacyFeatureProvider;
 import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
 import com.android.settings.localepicker.LocaleFeatureProvider;
+import com.android.settings.search2.SearchFeatureProvider;
 
 /**
  * Abstract class for creating feature controllers. Allows OEM implementations to define their own
@@ -80,6 +81,8 @@
     public abstract EnterprisePrivacyFeatureProvider getEnterprisePrivacyFeatureProvider(
             Context context);
 
+    public abstract SearchFeatureProvider getSearchFeatureProvider(Context context);
+
     public static final class FactoryNotFoundException extends RuntimeException {
         public FactoryNotFoundException(Throwable throwable) {
             super("Unable to create factory. Did you misconfigure Proguard?", throwable);
diff --git a/src/com/android/settings/overlay/FeatureFactoryImpl.java b/src/com/android/settings/overlay/FeatureFactoryImpl.java
index ec0ff46..cb172af 100644
--- a/src/com/android/settings/overlay/FeatureFactoryImpl.java
+++ b/src/com/android/settings/overlay/FeatureFactoryImpl.java
@@ -22,6 +22,7 @@
 
 import com.android.settings.applications.ApplicationFeatureProvider;
 import com.android.settings.applications.ApplicationFeatureProviderImpl;
+import com.android.settings.applications.PackageManagerWrapperImpl;
 import com.android.settings.core.instrumentation.MetricsFeatureProvider;
 import com.android.settings.core.instrumentation.MetricsFeatureProviderImpl;
 import com.android.settings.dashboard.DashboardFeatureProvider;
@@ -32,18 +33,21 @@
 import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
 import com.android.settings.localepicker.LocaleFeatureProvider;
 import com.android.settings.localepicker.LocaleFeatureProviderImpl;
+import com.android.settings.search2.SearchFeatureProvider;
+import com.android.settings.search2.SearchFeatureProviderImpl;
 
 /**
  * {@link FeatureFactory} implementation for AOSP Settings.
  */
 @Keep
-public final class FeatureFactoryImpl extends FeatureFactory {
+public class FeatureFactoryImpl extends FeatureFactory {
 
     private ApplicationFeatureProvider mApplicationFeatureProvider;
     private MetricsFeatureProvider mMetricsFeatureProvider;
     private DashboardFeatureProviderImpl mDashboardFeatureProvider;
     private LocaleFeatureProvider mLocaleFeatureProvider;
     private EnterprisePrivacyFeatureProvider mEnterprisePrivacyFeatureProvider;
+    private SearchFeatureProvider mSearchFeatureProvider;
 
     @Override
     public SupportFeatureProvider getSupportFeatureProvider(Context context) {
@@ -74,7 +78,8 @@
     @Override
     public ApplicationFeatureProvider getApplicationFeatureProvider(Context context) {
         if (mApplicationFeatureProvider == null) {
-            mApplicationFeatureProvider = new ApplicationFeatureProviderImpl(context);
+            mApplicationFeatureProvider = new ApplicationFeatureProviderImpl(context,
+                    new PackageManagerWrapperImpl(context.getPackageManager()));
         }
         return mApplicationFeatureProvider;
     }
@@ -90,10 +95,19 @@
     @Override
     public EnterprisePrivacyFeatureProvider getEnterprisePrivacyFeatureProvider(Context context) {
         if (mEnterprisePrivacyFeatureProvider == null) {
-            mEnterprisePrivacyFeatureProvider = new EnterprisePrivacyFeatureProviderImpl(context,
-                    new DevicePolicyManagerWrapperImpl((DevicePolicyManager)context
-                            .getSystemService(Context.DEVICE_POLICY_SERVICE)));
+            mEnterprisePrivacyFeatureProvider = new EnterprisePrivacyFeatureProviderImpl(
+                    new DevicePolicyManagerWrapperImpl((DevicePolicyManager) context
+                            .getSystemService(Context.DEVICE_POLICY_SERVICE)),
+                    new PackageManagerWrapperImpl(context.getPackageManager()));
         }
         return mEnterprisePrivacyFeatureProvider;
     }
+
+    @Override
+    public SearchFeatureProvider getSearchFeatureProvider(Context context) {
+        if (mSearchFeatureProvider == null) {
+            mSearchFeatureProvider = new SearchFeatureProviderImpl(context);
+        }
+        return mSearchFeatureProvider;
+    }
 }
diff --git a/src/com/android/settings/search2/DatabaseResultLoader.java b/src/com/android/settings/search2/DatabaseResultLoader.java
new file mode 100644
index 0000000..aca94b1
--- /dev/null
+++ b/src/com/android/settings/search2/DatabaseResultLoader.java
@@ -0,0 +1,124 @@
+/*
+ * 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.search2;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.VisibleForTesting;
+import com.android.settings.search.Index;
+import com.android.settings.search.IndexDatabaseHelper;
+import com.android.settings.utils.AsyncLoader;
+import com.android.settings.R;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_ICON_RESID;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_SUMMARY_ON;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_RANK;
+
+
+/**
+ * AsyncTask to retrieve Settings, First party app and any intent based results.
+ */
+public class DatabaseResultLoader extends AsyncLoader<List<SearchResult>> {
+    private final String mQueryText;
+    private final Context mContext;
+    protected final SQLiteDatabase mDatabase;
+
+    public DatabaseResultLoader(Context context, String queryText) {
+        super(context);
+        mDatabase = IndexDatabaseHelper.getInstance(context).getReadableDatabase();
+        mQueryText = queryText;
+        mContext = context;
+    }
+
+    @Override
+    protected void onDiscardResult(List<SearchResult> result) {
+        // TODO Search
+    }
+
+    @Override
+    public List<SearchResult> loadInBackground() {
+        if (mQueryText == null || mQueryText.isEmpty()) {
+            return null;
+        }
+
+        String query = getSQLQuery();
+        Cursor result  = mDatabase.rawQuery(query, null);
+
+        return parseCursorForSearch(result);
+    }
+
+    @Override
+    protected boolean onCancelLoad() {
+        // TODO
+        return super.onCancelLoad();
+    }
+
+    protected String getSQLQuery() {
+        return String.format("SELECT data_rank, data_title, data_summary_on, " +
+                "data_summary_off, data_entries, data_keywords, class_name, screen_title, icon, " +
+                "intent_action, intent_target_package, intent_target_class, enabled, " +
+                "data_key_reference FROM prefs_index WHERE prefs_index MATCH 'data_title:%s* " +
+                "OR data_title_normalized:%s* OR data_keywords:%s*' AND locale = 'en_US'",
+                mQueryText, mQueryText, mQueryText);
+    }
+
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    public ArrayList<SearchResult> parseCursorForSearch(Cursor cursorResults) {
+        if (cursorResults == null) {
+            return null;
+        }
+        final ArrayList<SearchResult> results = new ArrayList<>();
+
+        while (cursorResults.moveToNext()) {
+            final String title = cursorResults.getString(Index.COLUMN_INDEX_TITLE);
+            final String summaryOn = cursorResults.getString(COLUMN_INDEX_RAW_SUMMARY_ON);
+            final ArrayList<String> breadcrumbs = new ArrayList<>();
+            final int rank = cursorResults.getInt(COLUMN_INDEX_XML_RES_RANK);
+
+            final String intentString = cursorResults.getString(Index.COLUMN_INDEX_INTENT_ACTION);
+            final IntentPayload intentPayload = new IntentPayload(new Intent(intentString));
+            final int iconID = cursorResults.getInt(COLUMN_INDEX_RAW_ICON_RESID);
+            Drawable icon;
+            try {
+                icon = mContext.getDrawable(iconID);
+            } catch (Resources.NotFoundException nfe) {
+                icon = mContext.getDrawable(R.drawable.ic_search_history);
+            }
+
+
+            SearchResult.Builder builder = new SearchResult.Builder();
+            builder.addTitle(title)
+                    .addSummary(summaryOn)
+                    .addBreadcrumbs(breadcrumbs)
+                    .addRank(rank)
+                    .addIcon(icon)
+                    .addPayload(intentPayload);
+            results.add(builder.build());
+        }
+        Collections.sort(results);
+        return results;
+    }
+
+}
diff --git a/src/com/android/settings/search2/InlineSliderPayload.java b/src/com/android/settings/search2/InlineSliderPayload.java
new file mode 100644
index 0000000..8f08d59
--- /dev/null
+++ b/src/com/android/settings/search2/InlineSliderPayload.java
@@ -0,0 +1,62 @@
+/*
+ * 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.search2;
+
+import android.net.Uri;
+import android.os.Parcel;
+
+/**
+ * Payload for Inline Settings results represented by a Slider.
+ */
+public class InlineSliderPayload extends ResultPayload {
+    public final Uri uri;
+
+    private InlineSliderPayload(Parcel in) {
+        uri = in.readParcelable(InlineSliderPayload.class.getClassLoader());
+    }
+
+    public InlineSliderPayload(Uri newUri) {
+        uri = newUri;
+    }
+
+    @Override
+    public int getType() {
+        return PayloadType.INLINE_SLIDER;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelable(uri, flags);
+    }
+
+    public static final Creator<InlineSliderPayload> CREATOR = new Creator<InlineSliderPayload>() {
+        @Override
+        public InlineSliderPayload createFromParcel(Parcel in) {
+            return new InlineSliderPayload(in);
+        }
+
+        @Override
+        public InlineSliderPayload[] newArray(int size) {
+            return new InlineSliderPayload[size];
+        }
+    };
+}
\ No newline at end of file
diff --git a/src/com/android/settings/search2/IntentPayload.java b/src/com/android/settings/search2/IntentPayload.java
new file mode 100644
index 0000000..1ef3797
--- /dev/null
+++ b/src/com/android/settings/search2/IntentPayload.java
@@ -0,0 +1,63 @@
+/*
+ * 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.search2;
+
+import android.content.Intent;
+import android.os.Parcel;
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Encapsulates the standard intent based results as seen in first party apps and Settings results.
+ */
+public class IntentPayload extends ResultPayload {
+    public final Intent intent;
+
+    private IntentPayload(Parcel in) {
+        intent = in.readParcelable(IntentPayload.class.getClassLoader());
+    }
+
+    public IntentPayload(Intent newIntent) {
+        intent = newIntent;
+    }
+
+    @ResultPayload.PayloadType public int getType() {
+        return PayloadType.INTENT;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelable(intent, flags);
+    }
+
+    public static final Creator<IntentPayload> CREATOR = new Creator<IntentPayload>() {
+        @Override
+        public IntentPayload createFromParcel(Parcel in) {
+            return new IntentPayload(in);
+        }
+
+        @Override
+        public IntentPayload[] newArray(int size) {
+            return new IntentPayload[size];
+        }
+    };
+
+}
\ No newline at end of file
diff --git a/src/com/android/settings/search2/IntentSearchViewHolder.java b/src/com/android/settings/search2/IntentSearchViewHolder.java
new file mode 100644
index 0000000..0b99d6e
--- /dev/null
+++ b/src/com/android/settings/search2/IntentSearchViewHolder.java
@@ -0,0 +1,44 @@
+/*
+ * 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.search2;
+
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+import com.android.settings.R;
+
+/**
+ * ViewHolder for intent based search results.
+ * The DatabaseResultLoader is the primary use case for this ViewHolder.
+ */
+public class IntentSearchViewHolder extends SearchViewHolder {
+    public final TextView titleView;
+    public final TextView summaryView;
+    public final ImageView iconView;
+
+    public IntentSearchViewHolder(View view) {
+        super(view);
+        titleView = (TextView) view.findViewById(R.id.title);
+        summaryView = (TextView) view.findViewById(R.id.summary);
+        iconView= (ImageView) view.findViewById(R.id.icon);
+    }
+
+    public void onBind(SearchResult result) {
+        titleView.setText(result.title);
+        summaryView.setText(result.summary);
+        iconView.setImageDrawable(result.icon);
+    }
+}
diff --git a/src/com/android/settings/search2/ResultPayload.java b/src/com/android/settings/search2/ResultPayload.java
new file mode 100644
index 0000000..3a4e477
--- /dev/null
+++ b/src/com/android/settings/search2/ResultPayload.java
@@ -0,0 +1,40 @@
+/*
+ * 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.search2;
+
+import android.annotation.IntDef;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A interface for search results types. Examples include Inline results, third party apps
+ * or any future possibilities.
+ */
+public abstract class ResultPayload implements Parcelable {
+
+    @IntDef({PayloadType.INLINE_SLIDER, PayloadType.INLINE_SWITCH, PayloadType.INTENT})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PayloadType {
+        int INTENT = 0;
+        int INLINE_SLIDER = 1;
+        int INLINE_SWITCH = 2;
+    }
+
+    @ResultPayload.PayloadType public abstract int getType();
+}
diff --git a/src/com/android/settings/search2/SearchActivity.java b/src/com/android/settings/search2/SearchActivity.java
new file mode 100644
index 0000000..25a54cf
--- /dev/null
+++ b/src/com/android/settings/search2/SearchActivity.java
@@ -0,0 +1,41 @@
+/*
+ * 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.search2;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.os.Bundle;
+
+import com.android.settings.R;
+
+public class SearchActivity extends Activity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.search_main);
+
+        FragmentManager fragmentManager = getFragmentManager();
+        Fragment fragment = fragmentManager.findFragmentById(R.id.main_content);
+        if (fragment == null) {
+            fragmentManager.beginTransaction()
+                    .add(R.id.main_content, new SearchFragment())
+                    .commit();
+        }
+    }
+}
diff --git a/src/com/android/settings/search2/SearchFeatureProvider.java b/src/com/android/settings/search2/SearchFeatureProvider.java
new file mode 100644
index 0000000..14f5d13
--- /dev/null
+++ b/src/com/android/settings/search2/SearchFeatureProvider.java
@@ -0,0 +1,38 @@
+/*
+ * 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.search2;
+
+import android.app.Activity;
+import android.widget.SearchView;
+import android.view.Menu;
+
+/**
+ * FeatureProvider for Settings Search
+ */
+public interface SearchFeatureProvider {
+
+    /**
+     * @return true to use the new version of search
+     */
+    boolean isEnabled();
+
+    /**
+     * Inserts the Menu items into Settings activity.
+     * @param menu Items will be inserted into this menu.
+     * @param activity The activity that precedes SearchActivity.
+     */
+    void setUpSearchMenu(Menu menu, Activity activity);
+}
diff --git a/src/com/android/settings/search2/SearchFeatureProviderImpl.java b/src/com/android/settings/search2/SearchFeatureProviderImpl.java
new file mode 100644
index 0000000..3c6dc35
--- /dev/null
+++ b/src/com/android/settings/search2/SearchFeatureProviderImpl.java
@@ -0,0 +1,63 @@
+/*
+ * 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.search2;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.widget.SearchView;
+import android.view.Menu;
+
+import android.view.MenuItem;
+import com.android.settings.R;
+
+/**
+ * FeatureProvider for the refactored search code.
+ */
+public class SearchFeatureProviderImpl implements SearchFeatureProvider {
+    protected Context mContext;
+
+
+    public SearchFeatureProviderImpl(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return false;
+    }
+
+    @Override
+    public void setUpSearchMenu(Menu menu, final Activity activity) {
+        if (menu == null || activity == null) {
+            return;
+        }
+        String menuTitle = mContext.getString(R.string.search_menu);
+        MenuItem menuItem = menu.add(Menu.NONE, Menu.NONE, Menu.NONE, menuTitle)
+            .setIcon(R.drawable.abc_ic_search_api_material)
+            .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+                @Override
+                public boolean onMenuItemClick(MenuItem item) {
+                    Intent intent = new Intent(activity, SearchActivity.class);
+                    activity.startActivity(intent);
+                    return true;
+                }
+            });
+
+        menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+    }
+}
diff --git a/src/com/android/settings/search2/SearchFragment.java b/src/com/android/settings/search2/SearchFragment.java
new file mode 100644
index 0000000..18f20be
--- /dev/null
+++ b/src/com/android/settings/search2/SearchFragment.java
@@ -0,0 +1,158 @@
+/*
+ * 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.search2;
+
+import android.app.Activity;
+import android.content.Loader;
+import android.os.Bundle;
+import android.app.LoaderManager;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.widget.SearchView;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.R;
+import com.android.settings.core.InstrumentedFragment;
+
+import java.util.List;
+
+public class SearchFragment extends InstrumentedFragment implements
+        SearchView.OnQueryTextListener, MenuItem.OnActionExpandListener,
+        LoaderManager.LoaderCallbacks<List<SearchResult>>  {
+
+    private static final int DATABASE_LOADER_ID = 0;
+
+    private SearchResultsAdapter mSearchAdapter;
+
+    private DatabaseResultLoader mSearchLoader;
+
+    private RecyclerView mResultsRecyclerView;
+    private SearchView mSearchView;
+    private MenuItem mSearchMenuItem;
+
+    private String mQuery;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setHasOptionsMenu(true);
+
+        mSearchAdapter = new SearchResultsAdapter();
+
+        final LoaderManager loaderManager = getLoaderManager();
+        loaderManager.initLoader(DATABASE_LOADER_ID, null, this);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        final View view = inflater.inflate(R.layout.search_panel_2, container, false);
+        mResultsRecyclerView = (RecyclerView) view.findViewById(R.id.list_results);
+
+        mResultsRecyclerView.setAdapter(mSearchAdapter);
+        mResultsRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
+        return view;
+    }
+
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        super.onCreateOptionsMenu(menu, inflater);
+        inflater.inflate(R.menu.search_options_menu, menu);
+
+
+        mSearchMenuItem = menu.findItem(R.id.search);
+
+        mSearchView = (SearchView) mSearchMenuItem.getActionView();
+        mSearchView.setOnQueryTextListener(this);
+        mSearchView.setMaxWidth(Integer.MAX_VALUE);
+        mSearchMenuItem.expandActionView();
+    }
+
+    @Override
+    public boolean onMenuItemActionExpand(MenuItem item) {
+        return true;
+    }
+
+    @Override
+    public boolean onMenuItemActionCollapse(MenuItem item) {
+        // Return false to prevent the search box from collapsing.
+        return false;
+    }
+
+    @Override
+    public boolean onQueryTextChange(String query) {
+        if (query == null || query.equals(mQuery)) {
+            return false;
+        }
+
+        mQuery = query;
+        clearLoaders();
+
+        final LoaderManager loaderManager = getLoaderManager();
+        loaderManager.restartLoader(DATABASE_LOADER_ID, null, this);
+
+        return true;
+    }
+
+    @Override
+    public boolean onQueryTextSubmit(String query) {
+        return false;
+    }
+
+    @Override
+    public Loader<List<SearchResult>> onCreateLoader(int id, Bundle args) {
+        final Activity activity = getActivity();
+
+        switch (id) {
+            case DATABASE_LOADER_ID:
+                mSearchLoader = new DatabaseResultLoader(activity, mQuery);
+                return mSearchLoader;
+            default:
+                return null;
+        }
+    }
+
+    @Override
+    public void onLoadFinished(Loader<List<SearchResult>> loader, List<SearchResult> data) {
+        if (data == null) {
+            return;
+        }
+
+        mSearchAdapter.mergeResults(data, loader.getClass().getName());
+    }
+
+    @Override
+    public void onLoaderReset(Loader<List<SearchResult>> loader) { }
+
+    @Override
+    public int getMetricsCategory() {
+        return MetricsProto.MetricsEvent.DASHBOARD_SEARCH_RESULTS;
+    }
+
+    private void clearLoaders() {
+        if (mSearchLoader != null) {
+            mSearchLoader.cancelLoad();
+            mSearchLoader = null;
+        }
+    }
+}
diff --git a/src/com/android/settings/search2/SearchResult.java b/src/com/android/settings/search2/SearchResult.java
new file mode 100644
index 0000000..e483df3
--- /dev/null
+++ b/src/com/android/settings/search2/SearchResult.java
@@ -0,0 +1,146 @@
+/*
+ * 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.search2;
+
+import android.graphics.drawable.Drawable;
+
+import java.util.ArrayList;
+
+/**
+ * Dataclass as an interface for all Search Results.
+ */
+public class SearchResult implements Comparable<SearchResult> {
+    @Override
+    public int compareTo(SearchResult searchResult) {
+        if (searchResult == null) {
+            return -1;
+        }
+        return this.rank - searchResult.rank;
+    }
+
+    public static class Builder {
+        protected String mTitle;
+        protected String mSummary;
+        protected ArrayList<String> mBreadcrumbs;
+        protected int mRank = -1;
+        protected ResultPayload mResultPayload;
+        protected Drawable mIcon;
+
+        public Builder addTitle(String title) {
+            mTitle = title;
+            return this;
+        }
+
+        public Builder addSummary(String summary) {
+            mSummary = summary;
+            return this;
+        }
+
+        public Builder addBreadcrumbs(ArrayList<String> breadcrumbs) {
+            mBreadcrumbs = breadcrumbs;
+            return this;
+        }
+
+        public Builder addRank(int rank) {
+            if (rank < 0 || rank > 9) {
+                rank = 42;
+            }
+            mRank = rank;
+            return this;
+        }
+
+        public Builder addIcon(Drawable icon) {
+            mIcon = icon;
+            return this;
+        }
+
+        public Builder addPayload(ResultPayload payload) {
+            mResultPayload = payload;
+            return this;
+        }
+
+        public SearchResult build() {
+            // Check that all of the mandatory fields are set.
+            if (mTitle == null) {
+                throw new IllegalArgumentException("SearchResult missing title argument");
+            } else if (mSummary == null ) {
+                throw new IllegalArgumentException("SearchResult missing summary argument");
+            } else if (mBreadcrumbs == null){
+                throw new IllegalArgumentException("SearchResult missing breadcrumbs argument");
+            } else if (mRank == -1) {
+                throw new IllegalArgumentException("SearchResult missing rank argument");
+            } else if (mIcon == null) {
+                throw new IllegalArgumentException("SearchResult missing icon argument");
+            } else if (mResultPayload == null) {
+                throw new IllegalArgumentException("SearchResult missing Payload argument");
+            }
+            return new SearchResult(this);
+        }
+    }
+
+    /**
+     * The title of the result and main text displayed.
+     * Intent Results: Displays as the primary
+     */
+    public final String title;
+
+    /**
+     * Summary / subtitle text
+     * Intent Results: Displays the text underneath the title
+     */
+    final public String summary;
+
+    /**
+     * An ordered list of the information hierarchy.
+     * Intent Results: Displayed a hierarchy of selections to reach the setting from the home screen
+     */
+    public final ArrayList<String> breadcrumbs;
+
+    /**
+     * A suggestion for the ranking of the result.
+     * Based on Settings Rank:
+     * 1 is a near perfect match
+     * 9 is the weakest match
+     * TODO subject to change
+     */
+    public final int rank;
+
+    /**
+     * Identifier for the recycler view adapter.
+     */
+    @ResultPayload.PayloadType public final int viewType;
+
+    /**
+     * Metadata for the specific result types.
+     */
+    public final ResultPayload payload;
+
+    /**
+     * Result's icon.
+     */
+    public final Drawable icon;
+
+    private SearchResult(Builder builder) {
+        title = builder.mTitle;
+        summary = builder.mSummary;
+        breadcrumbs = builder.mBreadcrumbs;
+        rank = builder.mRank;
+        icon = builder.mIcon;
+        payload = builder.mResultPayload;
+        viewType = payload.getType();
+    }
+}
diff --git a/src/com/android/settings/search2/SearchResultsAdapter.java b/src/com/android/settings/search2/SearchResultsAdapter.java
new file mode 100644
index 0000000..22f106b
--- /dev/null
+++ b/src/com/android/settings/search2/SearchResultsAdapter.java
@@ -0,0 +1,101 @@
+/*
+ * 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.search2;
+
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.widget.RecyclerView.Adapter;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.settings.R;
+import com.android.settings.search2.ResultPayload.PayloadType;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class SearchResultsAdapter extends Adapter<SearchViewHolder> {
+    private ArrayList<SearchResult> mSearchResults;
+    private HashMap<String, List<SearchResult>> mResultsMap;
+
+    public SearchResultsAdapter() {
+        mSearchResults = new ArrayList<>();
+        mResultsMap = new HashMap<>();
+
+        setHasStableIds(true);
+    }
+
+    public void mergeResults(List<SearchResult> freshResults, String loaderClassName) {
+        if (freshResults == null) {
+            return;
+        }
+        mResultsMap.put(loaderClassName, freshResults);
+        mSearchResults = mergeMappedResults();
+        notifyDataSetChanged();
+    }
+
+    private ArrayList<SearchResult> mergeMappedResults() {
+        ArrayList<SearchResult> mergedResults = new ArrayList<>();
+        for(String key : mResultsMap.keySet()) {
+            mergedResults.addAll(mResultsMap.get(key));
+        }
+        return mergedResults;
+    }
+
+    @Override
+    public SearchViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+        switch(viewType) {
+            case PayloadType.INTENT:
+                View view = inflater.inflate(R.layout.search_intent_item, parent, false);
+                return new IntentSearchViewHolder(view);
+            case PayloadType.INLINE_SLIDER:
+                return null;
+            case PayloadType.INLINE_SWITCH:
+                return null;
+            default:
+                return null;
+        }
+    }
+
+    @Override
+    public void onBindViewHolder(SearchViewHolder holder, int position) {
+        SearchResult result = mSearchResults.get(position);
+        holder.onBind(result);
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return super.getItemId(position);
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        return mSearchResults.get(position).viewType;
+    }
+
+    @Override
+    public int getItemCount() {
+        return mSearchResults.size();
+    }
+
+    @VisibleForTesting
+    public ArrayList<SearchResult> getSearchResults() {
+        return mSearchResults;
+    }
+}
diff --git a/src/com/android/settings/search2/SearchViewHolder.java b/src/com/android/settings/search2/SearchViewHolder.java
new file mode 100644
index 0000000..2f500fb
--- /dev/null
+++ b/src/com/android/settings/search2/SearchViewHolder.java
@@ -0,0 +1,33 @@
+/*
+ * 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.search2;
+
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+
+/**
+ * The ViewHolder for the Search RecyclerView.
+ * There are multiple search result types in the same Recycler view with different UI requirements.
+ * Some examples include Intent results, Inline results, and Help articles.
+ */
+public abstract class SearchViewHolder extends RecyclerView.ViewHolder {
+
+    public SearchViewHolder(View view) {
+        super(view);
+    }
+
+    public abstract void onBind(SearchResult result);
+}
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/DevelopmentSettingsTest.java b/tests/robotests/src/com/android/settings/DevelopmentSettingsTest.java
new file mode 100644
index 0000000..e518620
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/DevelopmentSettingsTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.app.Activity;
+import android.content.Context;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceManager;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settingslib.drawer.CategoryKey;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.mockito.Answers.RETURNS_DEEP_STUBS;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+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 DevelopmentSettingsTest {
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Activity mActivity;
+    @Mock(answer = RETURNS_DEEP_STUBS)
+    private PreferenceScreen mScreen;
+    @Mock
+    private PreferenceManager mPreferenceManager;
+
+    private FakeFeatureFactory mFeatureFactory;
+    private DevelopmentSettings mSettings;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        FakeFeatureFactory.setupForTest(mContext);
+        mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
+        mSettings = spy(new DevelopmentSettings());
+    }
+
+    @Test
+    public void addDashboardCategoryPreference_shouldAddToScreen() {
+        final List<Preference> preferences = new ArrayList<>();
+        preferences.add(new Preference(ShadowApplication.getInstance().getApplicationContext()));
+        preferences.add(new Preference(ShadowApplication.getInstance().getApplicationContext()));
+        doReturn(mScreen).when(mSettings).getPreferenceScreen();
+        doReturn(mPreferenceManager).when(mSettings).getPreferenceManager();
+        doReturn(mActivity).when(mSettings).getActivity();
+        when(mPreferenceManager.getContext()).thenReturn(mContext);
+        when(mFeatureFactory.dashboardFeatureProvider.getPreferencesForCategory(
+                mActivity, mContext, CategoryKey.CATEGORY_SYSTEM_DEVELOPMENT))
+                .thenReturn(preferences);
+
+        mSettings.onAttach(mContext);
+        mSettings.addDashboardCategoryPreferences();
+
+        verify(mScreen, times(2)).addPreference(any(Preference.class));
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/SettingsRobolectricTestRunner.java b/tests/robotests/src/com/android/settings/SettingsRobolectricTestRunner.java
index 1a7647d..d6d6963 100644
--- a/tests/robotests/src/com/android/settings/SettingsRobolectricTestRunner.java
+++ b/tests/robotests/src/com/android/settings/SettingsRobolectricTestRunner.java
@@ -64,6 +64,10 @@
                         getPackageName(),
                         Fs.fileFromPath("./frameworks/base/packages/SettingsLib/res"),
                         null));
+                paths.add(new ResourcePath(
+                        getPackageName(),
+                        Fs.fileFromPath("./frameworks/base/core/res/res"),
+                        null));
                 return paths;
             }
         };
diff --git a/tests/robotests/src/com/android/settings/ZonePickerTest.java b/tests/robotests/src/com/android/settings/ZonePickerTest.java
new file mode 100644
index 0000000..344eea3
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/ZonePickerTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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 static com.google.common.truth.Truth.assertThat;
+
+import android.text.Spanned;
+import android.text.style.TtsSpan;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.SimpleAdapter;
+import android.widget.TextView;
+
+import com.android.settings.testutils.shadow.ShadowLibcoreTimeZoneNames;
+import com.android.settings.testutils.shadow.ShadowTimeZoneNames;
+
+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,
+        shadows = {
+                ShadowLibcoreTimeZoneNames.class,
+                ShadowLibcoreTimeZoneNames.ShadowZoneStringsCache.class,
+                ShadowTimeZoneNames.class
+        }
+)
+public class ZonePickerTest {
+
+    @Test
+    public void testConstructTimeZoneAdapter() {
+        final SimpleAdapter adapter =
+                ZonePicker.constructTimezoneAdapter(RuntimeEnvironment.application, true);
+        assertThat(adapter).isNotNull();
+
+        ViewGroup parent = new FrameLayout(RuntimeEnvironment.application);
+        ViewGroup convertView = new FrameLayout(RuntimeEnvironment.application);
+        TextView text1 = new TextView(RuntimeEnvironment.application);
+        text1.setId(android.R.id.text1);
+        convertView.addView(text1);
+        TextView text2 = new TextView(RuntimeEnvironment.application);
+        text2.setId(android.R.id.text2);
+        convertView.addView(text2);
+
+        adapter.getView(0, convertView, parent);
+        final CharSequence text = text2.getText();
+        assertThat(text).isInstanceOf(Spanned.class);
+        final TtsSpan[] spans = ((Spanned) text).getSpans(0, text.length(), TtsSpan.class);
+        // GMT offset label should have TTS spans
+        assertThat(spans.length).isGreaterThan(0);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/accounts/AccountPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accounts/AccountPreferenceControllerTest.java
new file mode 100644
index 0000000..6e9083b
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/accounts/AccountPreferenceControllerTest.java
@@ -0,0 +1,268 @@
+/*
+ * 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.accounts;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorDescription;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.UserManager;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceGroup;
+import android.support.v7.preference.PreferenceScreen;
+import android.support.v14.preference.PreferenceFragment;
+import android.util.SparseArray;
+
+import com.android.settings.AccessiblePreferenceCategory;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.search.SearchIndexableRaw;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.shadow.ShadowAccountManager;
+import com.android.settings.testutils.shadow.ShadowContentResolver;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+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.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class AccountPreferenceControllerTest {
+
+    @Mock(answer = RETURNS_DEEP_STUBS)
+    private PreferenceScreen mScreen;
+
+    @Mock(answer = RETURNS_DEEP_STUBS)
+    private UserManager mUserManager;
+    @Mock(answer = RETURNS_DEEP_STUBS)
+    private PreferenceFragment mFragment;
+    @Mock(answer = RETURNS_DEEP_STUBS)
+    private AccountManager mAccountManager;
+    @Mock(answer = RETURNS_DEEP_STUBS)
+    private AccountRestrictionHelper mAccountHelper;
+
+    private FakeFeatureFactory mFactory;
+    private Context mContext;
+    private AccountPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowApplication shadowContext = ShadowApplication.getInstance();
+        shadowContext.setSystemService(Context.USER_SERVICE, mUserManager);
+        shadowContext.setSystemService(Context.ACCOUNT_SERVICE, mAccountManager);
+        mContext = spy(shadowContext.getApplicationContext());
+        FakeFeatureFactory.setupForTest(mContext);
+        mFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
+
+        when(mFactory.dashboardFeatureProvider.isEnabled()).thenReturn(true);
+        when(mFragment.getPreferenceScreen()).thenReturn(mScreen);
+        when(mFragment.getPreferenceManager().getContext()).thenReturn(mContext);
+        when(mAccountManager.getAuthenticatorTypesAsUser(anyInt())).thenReturn(
+            new AuthenticatorDescription[0]);
+        when(mAccountManager.getAccountsAsUser(anyInt())).thenReturn(new Account[0]);
+        mController = new AccountPreferenceController(mContext, mFragment, null, mAccountHelper);
+    }
+
+    @Test
+    public void onResume_managedProfile_shouldNotAddAccountCategory() {
+        when(mUserManager.isManagedProfile()).thenReturn(true);
+        mController.onResume();
+
+        verify(mScreen, never()).addPreference(any(Preference.class));
+    }
+
+    @Test
+    @Config(shadows = {ShadowAccountManager.class, ShadowContentResolver.class})
+    public void onResume_linkedUser_shouldAddOneAccountCategory() {
+        final UserInfo info = new UserInfo(1, "user 1", 0);
+        when(mUserManager.isManagedProfile()).thenReturn(false);
+        when(mUserManager.isLinkedUser()).thenReturn(true);
+        when(mUserManager.getUserInfo(anyInt())).thenReturn(info);
+
+        mController.onResume();
+
+        verify(mScreen, times(1)).addPreference(any(PreferenceGroup.class));
+    }
+
+    @Test
+    @Config(shadows = {ShadowAccountManager.class, ShadowContentResolver.class})
+    public void onResume_oneProfile_shouldAddOneAccountCategory() {
+        final List<UserInfo> infos = new ArrayList<>();
+        infos.add(new UserInfo(1, "user 1", 0));
+        when(mUserManager.isManagedProfile()).thenReturn(false);
+        when(mUserManager.isLinkedUser()).thenReturn(false);
+        when(mUserManager.getProfiles(anyInt())).thenReturn(infos);
+
+        mController.onResume();
+
+        verify(mScreen, times(1)).addPreference(any(PreferenceGroup.class));
+    }
+
+    @Test
+    @Config(shadows = {ShadowAccountManager.class, ShadowContentResolver.class})
+    public void onResume_twoProfiles_shouldAddTwoAccountCategory() {
+        final List<UserInfo> infos = new ArrayList<>();
+        infos.add(new UserInfo(1, "user 1", 0));
+        infos.add(new UserInfo(2, "user 2", UserInfo.FLAG_MANAGED_PROFILE));
+        when(mUserManager.isManagedProfile()).thenReturn(false);
+        when(mUserManager.isLinkedUser()).thenReturn(false);
+        when(mUserManager.getProfiles(anyInt())).thenReturn(infos);
+
+        mController.onResume();
+
+        verify(mScreen, times(2)).addPreference(any(PreferenceGroup.class));
+    }
+
+    @Test
+    @Config(shadows = {ShadowAccountManager.class, ShadowContentResolver.class})
+    public void onResume_oneProfiles_shouldRemoveOneAccountCategory() {
+        final List<UserInfo> infos = new ArrayList<>();
+        infos.add(new UserInfo(1, "user 1", UserInfo.FLAG_MANAGED_PROFILE));
+        when(mUserManager.isManagedProfile()).thenReturn(false);
+        when(mUserManager.isLinkedUser()).thenReturn(false);
+        when(mUserManager.getProfiles(anyInt())).thenReturn(infos);
+        AccessiblePreferenceCategory preferenceGroup = mock(AccessiblePreferenceCategory.class);
+        when(mAccountHelper.createAccessiblePreferenceCategory(any(Context.class))).thenReturn(
+            preferenceGroup);
+
+        // First time resume will build the UI, 2nd time will refresh the UI
+        mController.onResume();
+        mController.onResume();
+
+        verify(mScreen, times(1)).removePreference(any(PreferenceGroup.class));
+        verify(mScreen).removePreference(preferenceGroup);
+    }
+
+    @Test
+    @Config(shadows = {ShadowAccountManager.class, ShadowContentResolver.class})
+    public void onResume_twoProfiles_shouldRemoveTwoAccountCategory() {
+        final List<UserInfo> infos = new ArrayList<>();
+        infos.add(new UserInfo(1, "user 1", 0));
+        infos.add(new UserInfo(2, "user 2", UserInfo.FLAG_MANAGED_PROFILE));
+        when(mUserManager.isManagedProfile()).thenReturn(false);
+        when(mUserManager.isLinkedUser()).thenReturn(false);
+        when(mUserManager.getProfiles(anyInt())).thenReturn(infos);
+        AccessiblePreferenceCategory preferenceGroup = mock(AccessiblePreferenceCategory.class);
+        when(mAccountHelper.createAccessiblePreferenceCategory(any(Context.class))).thenReturn(
+            preferenceGroup);
+
+        // First time resume will build the UI, 2nd time will refresh the UI
+        mController.onResume();
+        mController.onResume();
+
+        verify(mScreen, times(2)).removePreference(any(PreferenceGroup.class));
+        verify(mScreen, times(2)).removePreference(preferenceGroup);
+    }
+
+    @Test
+    public void updateRawDataToIndex_ManagedProfile_shouldNotUpdate() {
+        final List<SearchIndexableRaw> data = new ArrayList<>();
+        when(mUserManager.isManagedProfile()).thenReturn(true);
+
+        mController.updateRawDataToIndex(data);
+
+        assertThat(data).isEmpty();
+    }
+
+    @Test
+    public void updateRawDataToIndex_DisabledUser_shouldNotUpdate() {
+        final List<SearchIndexableRaw> data = new ArrayList<>();
+        final List<UserInfo> infos = new ArrayList<>();
+        infos.add(new UserInfo(1, "user 1", UserInfo.FLAG_DISABLED));
+        when(mUserManager.isManagedProfile()).thenReturn(false);
+        when(mUserManager.getProfiles(anyInt())).thenReturn(infos);
+        mController.updateRawDataToIndex(data);
+
+        assertThat(data).isEmpty();
+    }
+
+    @Test
+    public void updateRawDataToIndex_EnabledUser_shouldAddOne() {
+        final List<SearchIndexableRaw> data = new ArrayList<>();
+        final List<UserInfo> infos = new ArrayList<>();
+        infos.add(new UserInfo(1, "user 1", 0));
+        when(mUserManager.isManagedProfile()).thenReturn(false);
+        when(mUserManager.getProfiles(anyInt())).thenReturn(infos);
+
+        mController.updateRawDataToIndex(data);
+
+        assertThat(data.size()).isEqualTo(1);
+    }
+
+    @Test
+    public void updateRawDataToIndex_ManagedUser_shouldAddThree() {
+        final List<SearchIndexableRaw> data = new ArrayList<>();
+        final List<UserInfo> infos = new ArrayList<>();
+        infos.add(new UserInfo(1, "user 1", UserInfo.FLAG_MANAGED_PROFILE));
+        when(mUserManager.isManagedProfile()).thenReturn(false);
+        when(mUserManager.getProfiles(anyInt())).thenReturn(infos);
+
+        mController.updateRawDataToIndex(data);
+
+        assertThat(data.size()).isEqualTo(3);
+    }
+
+    @Test
+    public void updateRawDataToIndex_DisallowRemove_shouldAddTwo() {
+        final List<SearchIndexableRaw> data = new ArrayList<>();
+        final List<UserInfo> infos = new ArrayList<>();
+        infos.add(new UserInfo(1, "user 1", UserInfo.FLAG_MANAGED_PROFILE));
+        when(mUserManager.isManagedProfile()).thenReturn(false);
+        when(mUserManager.getProfiles(anyInt())).thenReturn(infos);
+        when(mAccountHelper.hasBaseUserRestriction(eq(UserManager.DISALLOW_REMOVE_USER), anyInt()))
+            .thenReturn(true);
+
+        mController.updateRawDataToIndex(data);
+
+        assertThat(data.size()).isEqualTo(2);
+    }
+
+    @Test
+    public void updateRawDataToIndex_DisallowModify_shouldAddTwo() {
+        final List<SearchIndexableRaw> data = new ArrayList<>();
+        final List<UserInfo> infos = new ArrayList<>();
+        infos.add(new UserInfo(1, "user 1", UserInfo.FLAG_MANAGED_PROFILE));
+        when(mUserManager.isManagedProfile()).thenReturn(false);
+        when(mUserManager.getProfiles(anyInt())).thenReturn(infos);
+        when(mAccountHelper.hasBaseUserRestriction(
+            eq(UserManager.DISALLOW_MODIFY_ACCOUNTS), anyInt())).thenReturn(true);
+
+        mController.updateRawDataToIndex(data);
+
+        assertThat(data.size()).isEqualTo(2);
+    }
+
+}
diff --git a/tests/robotests/src/com/android/settings/applications/ApplicationFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/applications/ApplicationFeatureProviderImplTest.java
new file mode 100644
index 0000000..dc177b8
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/ApplicationFeatureProviderImplTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.applications;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.os.UserManager;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.ApplicationTestUtils;
+import com.android.settings.testutils.shadow.ShadowUserManager;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+import java.util.Arrays;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.when;
+import org.robolectric.shadows.ShadowApplication;
+/**
+ * Tests for {@link ApplicationFeatureProviderImpl}.
+ */
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
+        shadows = {ShadowUserManager.class})
+public final class ApplicationFeatureProviderImplTest {
+
+    private final int MAIN_USER_ID = 0;
+    private final int MANAGED_PROFILE_ID = 10;
+
+    private @Mock UserManager mUserManager;
+    private @Mock Context mContext;
+    private @Mock PackageManagerWrapper mPackageManager;
+
+    private ApplicationFeatureProvider mProvider;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        when(mContext.getApplicationContext()).thenReturn(mContext);
+        when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+
+        mProvider = new ApplicationFeatureProviderImpl(mContext, mPackageManager);
+    }
+
+    @Test
+    public void testCalculateNumberOfInstalledApps() {
+        final Integer[] numberOfInstalledApps = new Integer[1];
+        numberOfInstalledApps[0] = null;
+
+        when(mUserManager.getUsers(true)).thenReturn(Arrays.asList(
+                new UserInfo(MAIN_USER_ID, "main", UserInfo.FLAG_ADMIN),
+                new UserInfo(MANAGED_PROFILE_ID, "managed profile", 0)));
+
+        when(mPackageManager.getInstalledApplicationsAsUser(PackageManager.GET_DISABLED_COMPONENTS
+                | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS
+                | PackageManager.GET_UNINSTALLED_PACKAGES,
+                MAIN_USER_ID)).thenReturn(Arrays.asList(
+                        ApplicationTestUtils.buildInfo(MAIN_USER_ID, "app1", 0 /* flags */)));
+
+        when(mPackageManager.getInstalledApplicationsAsUser(PackageManager.GET_DISABLED_COMPONENTS
+                | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
+                MANAGED_PROFILE_ID)).thenReturn(Arrays.asList(
+                        ApplicationTestUtils.buildInfo(MANAGED_PROFILE_ID, "app2", 0 /* flags */)));
+
+        mProvider.calculateNumberOfInstalledApps(
+                new ApplicationFeatureProvider.NumberOfInstalledAppsCallback() {
+                    @Override
+                    public void onNumberOfInstalledAppsResult(int num) {
+                        numberOfInstalledApps[0] = num;
+                    }
+                });
+        ShadowApplication.runBackgroundTasks();
+
+        assertThat(numberOfInstalledApps[0]).isEqualTo(2);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/InstalledAppCounterTest.java b/tests/robotests/src/com/android/settings/applications/InstalledAppCounterTest.java
new file mode 100644
index 0000000..0f0ada5
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/InstalledAppCounterTest.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.applications;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
+import android.os.UserManager;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.shadow.ShadowUserManager;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+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.Matchers.anyInt;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link InstalledAppCounter}.
+ */
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
+        shadows = {ShadowUserManager.class})
+public final class InstalledAppCounterTest {
+
+    private final int MAIN_USER_ID = 0;
+    private final int MANAGED_PROFILE_ID = 10;
+
+    @Mock private UserManager mUserManager;
+    @Mock private Context mContext;
+    @Mock private PackageManagerWrapper mPackageManager;
+    private List<UserInfo> mUsersToCount;
+
+    private int mInstalledAppCount = -1;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+    }
+
+    private void expectQueryIntentActivities(int userId, String packageName, boolean launchable) {
+        when(mPackageManager.queryIntentActivitiesAsUser(
+                argThat(new IsLaunchIntentFor(packageName)),
+                eq(PackageManager.GET_DISABLED_COMPONENTS | PackageManager.MATCH_DIRECT_BOOT_AWARE
+                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE),
+                eq(userId))).thenReturn(launchable ? Arrays.asList(new ResolveInfo())
+                        : new ArrayList<ResolveInfo>());
+    }
+
+    @Test
+    public void testCountInstalledAppsAcrossAllUsers() {
+        // There are two users.
+        mUsersToCount = Arrays.asList(
+                new UserInfo(MAIN_USER_ID, "main", UserInfo.FLAG_ADMIN),
+                new UserInfo(MANAGED_PROFILE_ID, "managed profile", 0));
+
+        // The first user has four apps installed:
+        // * app1 is an updated system app. It should be counted.
+        // * app2 is a user-installed app. It should be counted.
+        // * app3 is a system app that provides a launcher icon. It should be counted.
+        // * app4 is a system app that provides no launcher icon. It should not be counted.
+        when(mPackageManager.getInstalledApplicationsAsUser(PackageManager.GET_DISABLED_COMPONENTS
+                | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS
+                | PackageManager.GET_UNINSTALLED_PACKAGES,
+                MAIN_USER_ID)).thenReturn(Arrays.asList(
+                        buildInfo(MAIN_USER_ID, "app1", ApplicationInfo.FLAG_UPDATED_SYSTEM_APP),
+                        buildInfo(MAIN_USER_ID, "app2", 0 /* flags */),
+                        buildInfo(MAIN_USER_ID, "app3", ApplicationInfo.FLAG_SYSTEM),
+                        buildInfo(MAIN_USER_ID, "app4", ApplicationInfo.FLAG_SYSTEM)));
+        // For system apps, InstalledAppCounter checks whether they handle the default launcher
+        // intent to decide whether to include them in the count of installed apps or not.
+        expectQueryIntentActivities(MAIN_USER_ID, "app3", true /* launchable */);
+        expectQueryIntentActivities(MAIN_USER_ID, "app4", false /* launchable */);
+
+        // The second user has four apps installed:
+        // * app5 is a user-installed app. It should be counted.
+        // * app6 is a system app that provides a launcher icon. It should be counted.
+        when(mPackageManager.getInstalledApplicationsAsUser(PackageManager.GET_DISABLED_COMPONENTS
+                | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
+                MANAGED_PROFILE_ID)).thenReturn(Arrays.asList(
+                        buildInfo(MANAGED_PROFILE_ID, "app5", 0 /* flags */),
+                        buildInfo(MANAGED_PROFILE_ID, "app6", ApplicationInfo.FLAG_SYSTEM)));
+        expectQueryIntentActivities(MANAGED_PROFILE_ID, "app6", true /* launchable */);
+
+        // Count the number of apps installed. Wait for the background task to finish.
+        (new InstalledAppCounterTestable()).execute();
+        ShadowApplication.runBackgroundTasks();
+
+        assertThat(mInstalledAppCount).isEqualTo(5);
+
+        // Verify that installed packages were retrieved for the users returned by
+        // InstalledAppCounterTestable.getUsersToCount() only.
+        verify(mPackageManager).getInstalledApplicationsAsUser(anyInt(), eq(MAIN_USER_ID));
+        verify(mPackageManager).getInstalledApplicationsAsUser(anyInt(),
+                eq(MANAGED_PROFILE_ID));
+        verify(mPackageManager, atLeast(0)).queryIntentActivitiesAsUser(anyObject(), anyInt(),
+                anyInt());
+        verifyNoMoreInteractions(mPackageManager);
+    }
+
+    private class InstalledAppCounterTestable extends InstalledAppCounter {
+        public InstalledAppCounterTestable() {
+            super(mContext, mPackageManager);
+        }
+
+        @Override
+        protected void onCountComplete(int num) {
+            mInstalledAppCount = num;
+        }
+
+        @Override
+        protected List<UserInfo> getUsersToCount() {
+            return mUsersToCount;
+        }
+    }
+
+    private class IsLaunchIntentFor extends ArgumentMatcher<Intent> {
+        private final String mPackageName;
+
+        IsLaunchIntentFor(String packageName) {
+            mPackageName = packageName;
+        }
+
+        @Override
+        public boolean matches(Object i) {
+            final Intent intent = (Intent) i;
+            if (intent == null) {
+                return false;
+            }
+            if (intent.getAction() != Intent.ACTION_MAIN) {
+                return false;
+            }
+            final Set<String> categories = intent.getCategories();
+            if (categories == null || categories.size() != 1 ||
+                    !categories.contains(Intent.CATEGORY_LAUNCHER)) {
+                return false;
+            }
+            if (!mPackageName.equals(intent.getPackage())) {
+                return false;
+            }
+            return true;
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/ProcessStatsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/ProcessStatsPreferenceControllerTest.java
deleted file mode 100644
index d4df28c..0000000
--- a/tests/robotests/src/com/android/settings/applications/ProcessStatsPreferenceControllerTest.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.applications;
-
-import android.content.Context;
-
-import com.android.settings.SettingsRobolectricTestRunner;
-import com.android.settings.TestConfig;
-import com.android.settings.testutils.FakeFeatureFactory;
-
-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 ProcessStatsPreferenceControllerTest {
-    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
-    private Context mContext;
-
-    private FakeFeatureFactory mFactory;
-    private ProcessStatsPreferenceController mController;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        FakeFeatureFactory.setupForTest(mContext);
-
-        mFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
-
-        mController = new ProcessStatsPreferenceController(mContext);
-    }
-
-    @Test
-    public void testIsAvailble_dashboardFeatureEnabled_shouldReturnTrue() {
-        when(mFactory.dashboardFeatureProvider.isEnabled()).thenReturn(true);
-
-        assertThat(mController.isAvailable()).isTrue();
-    }
-
-    @Test
-    public void testIsAvailble_dashboardFeatureDisabled_shouldReturnFalse() {
-        when(mFactory.dashboardFeatureProvider.isEnabled()).thenReturn(false);
-
-        assertThat(mController.isAvailable()).isFalse();
-    }
-}
diff --git a/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java
index fabb438..c0bfad5 100644
--- a/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java
+++ b/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java
@@ -30,6 +30,9 @@
 import com.android.settings.SettingsActivity;
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
+import com.android.settingslib.drawer.CategoryKey;
+import com.android.settingslib.drawer.CategoryManager;
+import com.android.settingslib.drawer.DashboardCategory;
 import com.android.settingslib.drawer.Tile;
 
 import org.junit.Before;
@@ -45,6 +48,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -54,9 +58,10 @@
 
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private Activity mActivity;
-
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private UserManager mUserManager;
+    @Mock
+    private CategoryManager mCategoryManager;
 
     private DashboardFeatureProviderImpl mImpl;
 
@@ -151,4 +156,53 @@
 
         assertThat(preference.getOrder()).isEqualTo(-tile.priority + baseOrder);
     }
+
+    @Test
+    public void getPreferences_notEnabled_shouldReturnNull() {
+        final DashboardFeatureProviderImpl mSpy = spy(mImpl);
+        when(mSpy.isEnabled()).thenReturn(false);
+
+        assertThat(mSpy.getPreferencesForCategory(null, null, CategoryKey.CATEGORY_HOMEPAGE))
+                .isNull();
+    }
+
+    @Test
+    public void getPreferences_noCategory_shouldReturnNull() {
+        mImpl = new DashboardFeatureProviderImpl(mActivity, mCategoryManager);
+        final DashboardFeatureProviderImpl mSpy = spy(mImpl);
+        when(mSpy.isEnabled()).thenReturn(true);
+        when(mCategoryManager.getTilesByCategory(mActivity, CategoryKey.CATEGORY_HOMEPAGE))
+                .thenReturn(null);
+
+        assertThat(mSpy.getPreferencesForCategory(null, null, CategoryKey.CATEGORY_HOMEPAGE))
+                .isNull();
+    }
+
+    @Test
+    public void getPreferences_noTileForCategory_shouldReturnNull() {
+        mImpl = new DashboardFeatureProviderImpl(mActivity, mCategoryManager);
+        final DashboardFeatureProviderImpl mSpy = spy(mImpl);
+        when(mSpy.isEnabled()).thenReturn(true);
+        when(mCategoryManager.getTilesByCategory(mActivity, CategoryKey.CATEGORY_HOMEPAGE))
+                .thenReturn(new DashboardCategory());
+
+        assertThat(mSpy.getPreferencesForCategory(null, null, CategoryKey.CATEGORY_HOMEPAGE))
+                .isNull();
+    }
+
+    @Test
+    public void getPreferences_hasTileForCategory_shouldReturnPrefList() {
+        mImpl = new DashboardFeatureProviderImpl(mActivity, mCategoryManager);
+        final DashboardFeatureProviderImpl mSpy = spy(mImpl);
+        when(mSpy.isEnabled()).thenReturn(true);
+        final DashboardCategory category = new DashboardCategory();
+        category.tiles.add(new Tile());
+        when(mCategoryManager.getTilesByCategory(mActivity, CategoryKey.CATEGORY_HOMEPAGE))
+                .thenReturn(category);
+
+        assertThat(mSpy.getPreferencesForCategory(mActivity,
+                ShadowApplication.getInstance().getApplicationContext(),
+                CategoryKey.CATEGORY_HOMEPAGE).isEmpty())
+                .isFalse();
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/dashboard/SummaryLoaderTest.java b/tests/robotests/src/com/android/settings/dashboard/SummaryLoaderTest.java
new file mode 100644
index 0000000..547792b
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/dashboard/SummaryLoaderTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.dashboard;
+
+import android.app.Activity;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settingslib.drawer.DashboardCategory;
+import com.android.settingslib.drawer.Tile;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.annotation.Config;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.google.common.truth.Truth.assertThat;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SummaryLoaderTest {
+    private static final String SUMMARY_1 = "summary1";
+    private static final String SUMMARY_2 = "summary2";
+    private SummaryLoader mSummaryLoader;
+    private boolean mCallbackInvoked;
+    private Tile mTile;
+
+    @Before
+    public void SetUp() {
+        mTile = new Tile();
+        mTile.summary = SUMMARY_1;
+        mCallbackInvoked = false;
+
+        final Activity activity = Robolectric.buildActivity(Activity.class).get();
+        final List<DashboardCategory> categories = new ArrayList<>();
+        mSummaryLoader = new SummaryLoader(activity, categories);
+        mSummaryLoader.setSummaryConsumer(new SummaryLoader.SummaryConsumer() {
+            @Override
+            public void notifySummaryChanged(Tile tile) {
+                mCallbackInvoked = true;
+            }
+        });
+    }
+
+    @Test
+    public void testUpdateSummaryIfNeeded_SummaryIdentical_NoCallback() {
+        mSummaryLoader.updateSummaryIfNeeded(mTile, SUMMARY_1);
+
+        assertThat(mCallbackInvoked).isFalse();
+    }
+
+    @Test
+    public void testUpdateSummaryIfNeeded_SummaryChanged_HasCallback() {
+        mSummaryLoader.updateSummaryIfNeeded(mTile, SUMMARY_2);
+
+        assertThat(mCallbackInvoked).isTrue();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/datetime/TimeFormatPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/datetime/TimeFormatPreferenceControllerTest.java
new file mode 100644
index 0000000..91c6a44
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/datetime/TimeFormatPreferenceControllerTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.datetime;
+
+
+import android.content.Context;
+import android.provider.Settings;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Answers.RETURNS_DEEP_STUBS;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class TimeFormatPreferenceControllerTest {
+
+    @Mock(answer = RETURNS_DEEP_STUBS)
+    private PreferenceScreen mScreen;
+    @Mock
+    private UpdateTimeAndDateCallback mCallback;
+
+    private Context mContext;
+    private SwitchPreference mPreference;
+    private TimeFormatPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = ShadowApplication.getInstance().getApplicationContext();
+    }
+
+    @Test
+    public void isCalledFromSUW_NotAvailable() {
+        mController = new TimeFormatPreferenceController(mContext, mCallback, true);
+
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void notCalledFromSUW_shouldBeAvailable() {
+        mController = new TimeFormatPreferenceController(mContext, mCallback, false);
+
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void updateState_24HourSet_shouldCheckPreference() {
+        mController = new TimeFormatPreferenceController(mContext, mCallback, false);
+        mPreference = new SwitchPreference(mContext);
+        mPreference.setKey(mController.getPreferenceKey());
+        Settings.System.putString(mContext.getContentResolver(), Settings.System.TIME_12_24,
+                TimeFormatPreferenceController.HOURS_24);
+
+        mController.updateState(mPreference);
+
+        assertThat(mPreference.isChecked()).isTrue();
+    }
+
+    @Test
+    public void updateState_12HourSet_shouldNotCheckPreference() {
+        mController = new TimeFormatPreferenceController(mContext, mCallback, false);
+        mPreference = new SwitchPreference(mContext);
+        mPreference.setKey(mController.getPreferenceKey());
+        Settings.System.putString(mContext.getContentResolver(), Settings.System.TIME_12_24,
+                TimeFormatPreferenceController.HOURS_12);
+
+        mController.updateState(mPreference);
+
+        assertThat(mPreference.isChecked()).isFalse();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/deviceinfo/BuildNumberPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/BuildNumberPreferenceControllerTest.java
index e3a4ba3..81372cc 100644
--- a/tests/robotests/src/com/android/settings/deviceinfo/BuildNumberPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/deviceinfo/BuildNumberPreferenceControllerTest.java
@@ -26,6 +26,7 @@
 import android.support.v7.preference.PreferenceScreen;
 
 import com.android.settings.DevelopmentSettings;
+import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
 
 import org.junit.Before;
@@ -34,7 +35,6 @@
 import org.mockito.Answers;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
 import org.robolectric.annotation.Config;
 import org.robolectric.shadows.ShadowApplication;
 
@@ -46,7 +46,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-@RunWith(RobolectricTestRunner.class)
+@RunWith(SettingsRobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
 public class BuildNumberPreferenceControllerTest {
 
diff --git a/tests/robotests/src/com/android/settings/deviceinfo/SerialNumberPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/SerialNumberPreferenceControllerTest.java
new file mode 100644
index 0000000..72d6609
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/deviceinfo/SerialNumberPreferenceControllerTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.deviceinfo;
+
+import android.content.Context;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+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.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SerialNumberPreferenceControllerTest {
+
+    @Mock(answer = RETURNS_DEEP_STUBS)
+    private Context mContext;
+    @Mock(answer = RETURNS_DEEP_STUBS)
+    private PreferenceScreen mScreen;
+
+    private SerialNumberPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testIsAvaiable_noSerial_shouldReturnFalse() {
+        mController = new SerialNumberPreferenceController(mContext, null);
+
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void testIsAvaiable_hasSerial_shouldReturnTrue() {
+        mController = new SerialNumberPreferenceController(mContext, "123");
+
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void testDisplay_noSerial_shouldHidePreference() {
+        when(mScreen.findPreference(anyString())).thenReturn(mock(Preference.class));
+
+        mController = new SerialNumberPreferenceController(mContext, null);
+        mController.displayPreference(mScreen);
+
+        verify(mScreen).removePreference(any(Preference.class));
+    }
+
+    @Test
+    public void testDisplay_hasSerial_shouldSummary() {
+        final String serial = "123";
+        final Preference preference = mock(Preference.class);
+        when(mScreen.findPreference(anyString())).thenReturn(preference);
+
+        mController = new SerialNumberPreferenceController(mContext, serial);
+        mController.displayPreference(mScreen);
+
+        verify(mScreen, never()).removePreference(any(Preference.class));
+        verify(preference).setSummary(serial);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacyFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacyFeatureProviderImplTest.java
index afcd484..be43d0e 100644
--- a/tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacyFeatureProviderImplTest.java
+++ b/tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacyFeatureProviderImplTest.java
@@ -22,6 +22,7 @@
 
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
+import com.android.settings.applications.PackageManagerWrapper;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -41,9 +42,8 @@
 
     private final ComponentName DEVICE_OWNER = new ComponentName("dummy", "component");
 
-    private @Mock PackageManager mPackageManager;
-    private @Mock Context mContext;
     private @Mock DevicePolicyManagerWrapper mDevicePolicyManager;
+    private @Mock PackageManagerWrapper mPackageManager;
 
     private EnterprisePrivacyFeatureProvider mProvider;
 
@@ -51,12 +51,10 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        when(mContext.getApplicationContext()).thenReturn(mContext);
-        when(mContext.getPackageManager()).thenReturn(mPackageManager);
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN))
                 .thenReturn(true);
 
-        mProvider = new EnterprisePrivacyFeatureProviderImpl(mContext, mDevicePolicyManager);
+        mProvider = new EnterprisePrivacyFeatureProviderImpl(mDevicePolicyManager, mPackageManager);
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacySettingsTest.java b/tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacySettingsTest.java
index 20393ee..28db647 100644
--- a/tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacySettingsTest.java
+++ b/tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacySettingsTest.java
@@ -20,6 +20,8 @@
 import com.android.settings.R;
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
+import com.android.settings.core.PreferenceController;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -28,6 +30,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import java.util.List;
+
 /**
  * Tests for {@link EnterprisePrivacySettings}.
  */
@@ -66,7 +70,10 @@
 
     @Test
     public void getPreferenceControllers() {
-        assertThat(mSettings.getPreferenceControllers(
-                ShadowApplication.getInstance().getApplicationContext())).isNull();
+        final List<PreferenceController> controllers = mSettings.getPreferenceControllers(
+                ShadowApplication.getInstance().getApplicationContext());
+        assertThat(controllers).isNotNull();
+        assertThat(controllers.size()).isEqualTo(1);
+        assertThat(controllers.get(0)).isInstanceOf(InstalledPackagesPreferenceController.class);
     }
 }
diff --git a/tests/robotests/src/com/android/settings/enterprise/InstalledPackagesPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/enterprise/InstalledPackagesPreferenceControllerTest.java
new file mode 100644
index 0000000..fe41faa
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/enterprise/InstalledPackagesPreferenceControllerTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.enterprise;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.support.v7.preference.Preference;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settings.R;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.applications.ApplicationFeatureProvider;
+import com.android.settings.testutils.FakeFeatureFactory;
+
+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.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.robolectric.annotation.Config;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.anyObject;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link InstalledPackagesPreferenceController}.
+ */
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public final class InstalledPackagesPreferenceControllerTest {
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+    private FakeFeatureFactory mFeatureFactory;
+
+    private InstalledPackagesPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        FakeFeatureFactory.setupForTest(mContext);
+        mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
+        mController = new InstalledPackagesPreferenceController(mContext);
+    }
+
+    @Test
+    public void testUpdateState() {
+        final Preference preference = new Preference(mContext, null, 0, 0);
+        doAnswer(new Answer() {
+            public Object answer(InvocationOnMock invocation) {
+                ((ApplicationFeatureProvider.NumberOfInstalledAppsCallback)
+                        invocation.getArguments()[0]).onNumberOfInstalledAppsResult(20);
+                return null;
+            }}).when(mFeatureFactory.applicationFeatureProvider)
+                    .calculateNumberOfInstalledApps(anyObject());
+        when(mContext.getResources().getQuantityString(R.plurals.number_installed_packages, 20, 20))
+                .thenReturn("20 packages");
+        mController.updateState(preference);
+        assertThat(preference.getTitle()).isEqualTo("20 packages");
+    }
+
+    @Test
+    public void testIsAvailable() {
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void testHandlePreferenceTreeClick() {
+        assertThat(mController.handlePreferenceTreeClick(new Preference(mContext, null, 0, 0)))
+                .isFalse();
+    }
+
+    @Test
+    public void testGetPreferenceKey() {
+        assertThat(mController.getPreferenceKey()).isEqualTo("number_installed_packages");
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java b/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java
new file mode 100644
index 0000000..a744bb7
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java
@@ -0,0 +1,160 @@
+/*
+ * 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.search;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.database.MatrixCursor;
+import android.graphics.drawable.Drawable;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.search2.DatabaseResultLoader;
+import com.android.settings.search2.IntentPayload;
+import com.android.settings.search2.ResultPayload;
+import com.android.settings.search2.ResultPayload.PayloadType;
+import com.android.settings.search2.SearchResult;
+import com.android.settings.R;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.robolectric.annotation.Config;
+import org.robolectric.Robolectric;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.google.common.truth.Truth.assertThat;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class DatabaseResultLoaderTest {
+    private DatabaseResultLoader mLoader;
+
+    private static final String[] TITLES = new String[] {"title1", "title2", "title3"};
+    private static final String SUMMARY = "SUMMARY";
+    private static final int EXAMPLES = 3;
+    private static final Intent mIntent = new Intent("com.android.settings");
+    private static final int mIcon = R.drawable.ic_search_history;
+
+    private Drawable mDrawable;
+
+    @Before
+    public void setUp() {
+        Context context = Robolectric.buildActivity(Activity.class).get();
+        mDrawable = context.getDrawable(mIcon);
+        mLoader = new DatabaseResultLoader(context, "");
+    }
+
+    @Test
+    public void testParseNullResults_ReturnsNull() {
+        List<SearchResult> results = mLoader.parseCursorForSearch(null);
+        assertThat(results).isNull();
+    }
+
+    @Test
+    public void testParseCursor_NotNull() {
+        List<SearchResult> results = mLoader.parseCursorForSearch(getDummyCursor());
+        assertThat(results).isNotNull();
+    }
+
+    @Test
+    public void testParseCursor_MatchesRank() {
+        List<SearchResult> results = mLoader.parseCursorForSearch(getDummyCursor());
+        for (int i = 0; i < EXAMPLES; i++) {
+            assertThat(results.get(i).rank).isEqualTo(i);
+        }
+    }
+
+    @Test
+    public void testParseCursor_MatchesTitle() {
+        List<SearchResult> results = mLoader.parseCursorForSearch(getDummyCursor());
+        for (int i = 0; i < EXAMPLES; i++) {
+            assertThat(results.get(i).title).isEqualTo(TITLES[i]);
+        }
+    }
+
+    @Test
+    public void testParseCursor_MatchesSummary() {
+        List<SearchResult> results = mLoader.parseCursorForSearch(getDummyCursor());
+        for (int i = 0; i < EXAMPLES; i++) {
+            assertThat(results.get(i).summary).isEqualTo(SUMMARY);
+        }
+    }
+
+    @Test
+    public void testParseCursor_MatchesIcon() {
+        List<SearchResult> results = mLoader.parseCursorForSearch(getDummyCursor());
+        for (int i = 0; i < EXAMPLES; i++) {
+            Drawable resultDrawable = results.get(i).icon;
+            assertThat(resultDrawable.toString()).isEqualTo(mDrawable.toString());
+        }
+    }
+
+    @Test
+    public void testParseCursor_MatchesPayloadType() {
+        List<SearchResult> results = mLoader.parseCursorForSearch(getDummyCursor());
+        ResultPayload payload;
+        for (int i = 0; i < EXAMPLES; i++) {
+            payload = results.get(i).payload;
+            assertThat(payload.getType()).isEqualTo(PayloadType.INTENT);
+        }
+    }
+
+    @Test
+    public void testParseCursor_MatchesIntentPayload() {
+        List<SearchResult> results = mLoader.parseCursorForSearch(getDummyCursor());
+        IntentPayload payload;
+        for (int i = 0; i < EXAMPLES; i++) {
+            payload = (IntentPayload) results.get(i).payload;
+            Intent intent = payload.intent;
+            assertThat(intent.getAction()).isEqualTo(mIntent.getAction());
+        }
+    }
+
+    private MatrixCursor getDummyCursor() {
+        String[] columns = new String[] {"rank", "title",  "summary_on", "summary off", "entries",
+                "keywords", "class name", "screen title", "icon", "intent action",
+                "target package", "target class", "enabled", "key", "user id"};
+        MatrixCursor cursor = new MatrixCursor(columns);
+        final String BLANK = "";
+
+        for (int i = 0; i < EXAMPLES; i++) {
+            ArrayList<String> item = new ArrayList<>(columns.length);
+            item.add(Integer.toString(i));
+            item.add(TITLES[i]);
+            item.add(SUMMARY);
+            item.add(BLANK); // summary off
+            item.add(BLANK); // entries
+            item.add(BLANK); // keywords
+            item.add(BLANK); // classname
+            item.add(BLANK); // screen title
+            item.add(Integer.toString(mIcon));
+            item.add(mIntent.getAction());
+            item.add(BLANK); // target package
+            item.add(BLANK); // target class
+            item.add(BLANK); // enabled
+            item.add(BLANK); // key
+            item.add(BLANK); // user id
+
+            cursor.addRow(item);
+        }
+        return cursor;
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/search/InlineSliderPayloadTest.java b/tests/robotests/src/com/android/settings/search/InlineSliderPayloadTest.java
new file mode 100644
index 0000000..d52eb94
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/search/InlineSliderPayloadTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.search;
+
+import android.net.Uri;
+import android.os.Parcel;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.search2.InlineSliderPayload;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.robolectric.annotation.Config;
+
+import static com.google.common.truth.Truth.assertThat;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class InlineSliderPayloadTest {
+    private InlineSliderPayload mPayload;
+
+    @Test
+    public void testParcelOrdering_StaysValid() {
+        Uri uri = Uri.parse("http://www.TESTURI.com");
+        Parcel parcel = Parcel.obtain();
+
+        mPayload = new InlineSliderPayload(uri);
+        mPayload.writeToParcel(parcel, 0);
+        // Reset parcel for reading
+        parcel.setDataPosition(0);
+        InlineSliderPayload newPayload = InlineSliderPayload.CREATOR.createFromParcel(parcel);
+
+        String originalUri = mPayload.uri.toString();
+        String copiedUri = newPayload.uri.toString();
+        assertThat(originalUri).isEqualTo(copiedUri);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/search/IntentPayloadTest.java b/tests/robotests/src/com/android/settings/search/IntentPayloadTest.java
new file mode 100644
index 0000000..6f42622
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/search/IntentPayloadTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.search;
+
+import android.content.Intent;
+import android.os.Parcel;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.search2.IntentPayload;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.robolectric.annotation.Config;
+
+import static com.google.common.truth.Truth.assertThat;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class IntentPayloadTest {
+    private IntentPayload mPayload;
+
+    private final String EXTRA_KEY = "key";
+    private final String EXTRA_VALUE = "value";
+
+    @Test
+    public void testParcelOrdering_StaysValid() {
+        Intent intent = new Intent();
+        intent.putExtra(EXTRA_KEY, EXTRA_VALUE);
+        Parcel parcel = Parcel.obtain();
+
+        mPayload = new IntentPayload(intent);
+        mPayload.writeToParcel(parcel, 0);
+        // Reset parcel for reading
+        parcel.setDataPosition(0);
+        IntentPayload newPayload = IntentPayload.CREATOR.createFromParcel(parcel);
+
+        String originalIntentExtra = mPayload.intent.getStringExtra(EXTRA_KEY);
+        String copiedIntentExtra = newPayload.intent.getStringExtra(EXTRA_KEY);
+        assertThat(originalIntentExtra).isEqualTo(copiedIntentExtra);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/search/IntentSearchViewHolderTest.java b/tests/robotests/src/com/android/settings/search/IntentSearchViewHolderTest.java
new file mode 100644
index 0000000..2534c0b
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/search/IntentSearchViewHolderTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.search;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import com.android.settings.R;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.search2.IntentPayload;
+import com.android.settings.search2.IntentSearchViewHolder;
+import com.android.settings.search2.SearchResult.Builder;
+import com.android.settings.search2.SearchResult;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+import java.util.ArrayList;
+
+import static com.google.common.truth.Truth.assertThat;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class IntentSearchViewHolderTest {
+    private IntentSearchViewHolder mHolder;
+    private static Drawable mIcon;
+
+    private static final String TITLE = "title";
+    private static final String SUMMARY = "summary";
+
+
+    @Before
+    public void setUp() {
+        final Context context = ShadowApplication.getInstance().getApplicationContext();
+        View view = LayoutInflater.from(context).inflate(R.layout.search_intent_item, null);
+        mHolder = new IntentSearchViewHolder(view);
+
+        mIcon = context.getDrawable(R.drawable.ic_search_history);
+    }
+
+    @Test
+    public void testConstructor_MembersNotNull() {
+        assertThat(mHolder.titleView).isNotNull();
+        assertThat(mHolder.summaryView).isNotNull();
+        assertThat(mHolder.iconView).isNotNull();
+    }
+
+    @Test
+    public void testBindViewElements_AllUpdated() {
+        SearchResult result = getSearchResult();
+        mHolder.onBind(result);
+
+        assertThat(mHolder.titleView.getText()).isEqualTo(TITLE);
+        assertThat(mHolder.summaryView.getText()).isEqualTo(SUMMARY);
+        assertThat(mHolder.iconView.getDrawable()).isEqualTo(mIcon);
+    }
+
+    private SearchResult getSearchResult() {
+        Builder builder = new Builder();
+        builder.addTitle(TITLE)
+                .addSummary(SUMMARY)
+                .addRank(1)
+                .addPayload(new IntentPayload(null))
+                .addBreadcrumbs(new ArrayList<String>())
+                .addIcon(mIcon);
+
+        return builder.build();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/search/SearchAdapterTest.java b/tests/robotests/src/com/android/settings/search/SearchAdapterTest.java
new file mode 100644
index 0000000..b3da4eb
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/search/SearchAdapterTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.search;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.search2.*;
+import com.android.settings.search2.SearchResult.Builder;
+import com.android.settings.R;
+
+import java.util.ArrayList;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.robolectric.annotation.Config;
+import org.robolectric.Robolectric;
+
+import static com.google.common.truth.Truth.assertThat;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SearchAdapterTest {
+
+    private SearchResultsAdapter mAdapter;
+    private Context mContext;
+    private String mLoaderClassName;
+
+    @Before
+    public void setUp() {
+        mContext = Robolectric.buildActivity(Activity.class).get();
+        mAdapter = new SearchResultsAdapter();
+        mLoaderClassName = DatabaseResultLoader.class.getName();
+    }
+
+    private ArrayList<SearchResult> getIntentSampleResults() {
+        ArrayList<SearchResult> sampleResults = new ArrayList<>();
+        ArrayList<String> breadcrumbs = new ArrayList<>();
+        final Drawable icon = mContext.getDrawable(R.drawable.ic_search_history);
+        final ResultPayload payload = new IntentPayload(null);
+
+        SearchResult.Builder builder = new Builder();
+        builder.addTitle("title")
+                .addSummary("summary")
+                .addRank(1)
+                .addBreadcrumbs(breadcrumbs)
+                .addIcon(icon)
+                .addPayload(payload);
+        sampleResults.add(builder.build());
+
+        builder.addRank(2);
+        sampleResults.add(builder.build());
+
+        builder.addRank(3);
+        sampleResults.add(builder.build());
+        return sampleResults;
+    }
+
+
+    @Test
+    public void testNoResultsAdded_EmptyListReturned() {
+        ArrayList<SearchResult> updatedResults = mAdapter.getSearchResults();
+        assertThat(updatedResults).isEmpty();
+    }
+
+
+    @Test
+    public void testSingleSourceMerge_ExactCopyReturned() {
+        ArrayList<SearchResult> intentResults = getIntentSampleResults();
+        mAdapter.mergeResults(intentResults, mLoaderClassName);
+
+        ArrayList<SearchResult> updatedResults = mAdapter.getSearchResults();
+        assertThat(updatedResults).containsAllIn(intentResults);
+    }
+
+    @Test
+    public void testDuplicateSourceMerge_ExactCopyReturned() {
+        ArrayList<SearchResult> intentResults = getIntentSampleResults();
+        mAdapter.mergeResults(intentResults, mLoaderClassName);
+        mAdapter.mergeResults(intentResults, mLoaderClassName);
+
+        ArrayList<SearchResult> updatedResults = mAdapter.getSearchResults();
+        assertThat(updatedResults).containsAllIn(intentResults);
+    }
+}
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java
new file mode 100644
index 0000000..06f4322
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.search;
+
+import android.app.Activity;
+import android.view.Menu;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.search2.SearchFeatureProviderImpl;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+
+import org.mockito.MockitoAnnotations;
+import org.robolectric.Robolectric;
+import org.robolectric.annotation.Config;
+
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.verify;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SearchFeatureProviderImplTest {
+    private SearchFeatureProviderImpl mProvider;
+    private Activity mActivity;
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Menu menu;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mActivity = Robolectric.buildActivity(Activity.class).create().visible().get();
+        mProvider = (SearchFeatureProviderImpl) FeatureFactory.getFactory(mActivity)
+                .getSearchFeatureProvider(mActivity);
+    }
+
+    @Test
+    public void testPassNull_NoError() {
+        mProvider.setUpSearchMenu(null,null);
+    }
+
+    @Test
+    public void testSetUpMenu_HasItemAdded() {
+        mProvider.setUpSearchMenu(menu, mActivity);
+
+        verify(menu).add(anyInt(),anyInt(), anyInt(), anyString());
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/search/SearchResultBuilderTest.java b/tests/robotests/src/com/android/settings/search/SearchResultBuilderTest.java
new file mode 100644
index 0000000..c2ec49c
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/search/SearchResultBuilderTest.java
@@ -0,0 +1,188 @@
+/*
+ * 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.search;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.search2.IntentPayload;
+import com.android.settings.search2.ResultPayload;
+import com.android.settings.search2.SearchResult;
+import com.android.settings.search2.SearchResult.Builder;
+import com.android.settings.R;
+
+import java.util.ArrayList;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+import static com.google.common.truth.Truth.assertThat;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SearchResultBuilderTest {
+
+    private Builder mBuilder;
+    private String mTitle;
+    private String mSummary;
+    private ArrayList<String> mBreadcrumbs;
+    private int mRank;
+    private ResultPayload mResultPayload;
+    private Drawable mIcon;
+
+    @Before
+    public void setUp() {
+        mBuilder = new Builder();
+        mTitle = "title";
+        mSummary = "summary";
+        mBreadcrumbs = new ArrayList<>();
+        mRank = 3;
+        mResultPayload = new IntentPayload(null);
+
+        final Context context = ShadowApplication.getInstance().getApplicationContext();
+        mIcon = context.getDrawable(R.drawable.ic_search_history);
+    }
+
+    @Test
+    public void testAllInfo_BuildSearchResult() {
+        mBuilder.addTitle(mTitle)
+                .addSummary(mSummary)
+                .addRank(mRank)
+                .addBreadcrumbs(mBreadcrumbs)
+                .addIcon(mIcon)
+                .addPayload(mResultPayload);
+        SearchResult result = mBuilder.build();
+
+        assertThat(result).isNotNull();
+        assertThat(result.title).isEqualTo(mTitle);
+        assertThat(result.summary).isEqualTo(mSummary);
+        assertThat(result.rank).isEqualTo(mRank);
+        assertThat(result.breadcrumbs).isEqualTo(mBreadcrumbs);
+        assertThat(result.icon).isEqualTo(mIcon);
+        assertThat(result.payload).isEqualTo(mResultPayload);
+    }
+
+    @Test
+    public void testNoTitle_BuildSearchResultException() {
+        mBuilder.addSummary(mSummary)
+                .addRank(mRank)
+                .addBreadcrumbs(mBreadcrumbs)
+                .addIcon(mIcon)
+                .addPayload(mResultPayload);
+
+        SearchResult result = null;
+        try {
+            result = mBuilder.build();
+        } catch (IllegalArgumentException e) {
+            // passes.
+        }
+        assertThat(result).isNull();
+    }
+
+    @Test
+    public void testNoSummary_BuildSearchResultException() {
+        mBuilder.addTitle(mTitle)
+                .addRank(mRank)
+                .addBreadcrumbs(mBreadcrumbs)
+                .addIcon(mIcon)
+                .addPayload(mResultPayload);
+
+        SearchResult result = null;
+        try {
+            result = mBuilder.build();
+        } catch (IllegalArgumentException e) {
+            // passes.
+        }
+        assertThat(result).isNull();
+    }
+
+    @Test
+    public void testNoRank_BuildSearchResultException() {
+        mBuilder.addTitle(mTitle)
+                .addSummary(mSummary)
+                .addBreadcrumbs(mBreadcrumbs)
+                .addIcon(mIcon)
+                .addPayload(mResultPayload);
+
+        SearchResult result = null;
+        try {
+            result = mBuilder.build();
+        } catch (IllegalArgumentException e) {
+            // passes.
+        }
+        assertThat(result).isNull();
+    }
+
+    @Test
+    public void testNoBreadcrumbs_BuildSearchResultException() {
+        mBuilder.addTitle(mTitle)
+                .addSummary(mSummary)
+                .addRank(mRank)
+                .addIcon(mIcon)
+                .addPayload(mResultPayload);
+
+        SearchResult result = null;
+        try {
+            result = mBuilder.build();
+        } catch (IllegalArgumentException e) {
+            // passes.
+        }
+        assertThat(result).isNull();
+    }
+
+    @Test
+    public void testNoIcon_BuildSearchResultException() {
+        mBuilder.addTitle(mTitle)
+                .addSummary(mSummary)
+                .addRank(mRank)
+                .addBreadcrumbs(mBreadcrumbs)
+                .addPayload(mResultPayload);
+
+        SearchResult result = null;
+        try {
+            result = mBuilder.build();
+        } catch (IllegalArgumentException e) {
+            // passes.
+        }
+        assertThat(result).isNull();
+    }
+
+    @Test
+    public void testNoPayload_BuildSearchResultException() {
+        mBuilder.addTitle(mTitle)
+                .addSummary(mSummary)
+                .addRank(mRank)
+                .addBreadcrumbs(mBreadcrumbs)
+                .addIcon(mIcon);
+
+        SearchResult result = null;
+        try {
+            result = mBuilder.build();
+        } catch (IllegalArgumentException e) {
+            // passes.
+        }
+        assertThat(result).isNull();
+    }
+}
+
+
diff --git a/tests/robotests/src/com/android/settings/testutils/ApplicationTestUtils.java b/tests/robotests/src/com/android/settings/testutils/ApplicationTestUtils.java
new file mode 100644
index 0000000..8789928
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/testutils/ApplicationTestUtils.java
@@ -0,0 +1,43 @@
+/*
+ * 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.testutils;
+
+import android.content.pm.ApplicationInfo;
+import android.os.UserHandle;
+
+/**
+ * Helper for mocking installed applications.
+ */
+public class ApplicationTestUtils {
+    /**
+     * Create and populate an {@link android.content.pm.ApplicationInfo} object that describes an
+     * installed app.
+     *
+     * @param userId The user id that this app is installed for. Typical values are 0 for the
+     *         system user and 10, 11, 12... for secondary users.
+     * @param packageName The app's package name.
+     * @param flags Flags describing the app. See {@link android.content.pm.ApplicationInfo#flags}
+     *         for possible values.
+     *
+     * @see android.content.pm.ApplicationInfo
+     */
+    public static ApplicationInfo buildInfo(int userId, String packageName, int flags) {
+        final ApplicationInfo info = new ApplicationInfo();
+        info.uid = UserHandle.getUid(userId, 1);
+        info.packageName = packageName;
+        info.flags = flags;
+        return info;
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java b/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java
index 3528863..f702cdc 100644
--- a/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java
+++ b/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java
@@ -25,6 +25,7 @@
 import com.android.settings.localepicker.LocaleFeatureProvider;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.overlay.SupportFeatureProvider;
+import com.android.settings.search2.SearchFeatureProvider;
 
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.mock;
@@ -43,6 +44,7 @@
     public final LocaleFeatureProvider localeFeatureProvider;
     public final ApplicationFeatureProvider applicationFeatureProvider;
     public final EnterprisePrivacyFeatureProvider enterprisePrivacyFeatureProvider;
+    public final SearchFeatureProvider searchFeatureProvider;
 
     /**
      * Call this in {@code @Before} method of the test class to use fake factory.
@@ -72,6 +74,7 @@
         localeFeatureProvider = mock(LocaleFeatureProvider.class);
         applicationFeatureProvider = mock(ApplicationFeatureProvider.class);
         enterprisePrivacyFeatureProvider = mock(EnterprisePrivacyFeatureProvider.class);
+        searchFeatureProvider = mock(SearchFeatureProvider.class);
     }
 
     @Override
@@ -108,4 +111,9 @@
     public EnterprisePrivacyFeatureProvider getEnterprisePrivacyFeatureProvider(Context context) {
         return enterprisePrivacyFeatureProvider;
     }
+
+    @Override
+    public SearchFeatureProvider getSearchFeatureProvider(Context context) {
+        return searchFeatureProvider;
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowAccountManager.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowAccountManager.java
new file mode 100644
index 0000000..2bccccc
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowAccountManager.java
@@ -0,0 +1,31 @@
+/*
+ * 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.testutils.shadow;
+
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorDescription;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+@Implements(AccountManager.class)
+public class ShadowAccountManager {
+    @Implementation
+    public AuthenticatorDescription[] getAuthenticatorTypesAsUser(int userId) {
+        return null;
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowContentResolver.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowContentResolver.java
new file mode 100644
index 0000000..bc43fc3
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowContentResolver.java
@@ -0,0 +1,33 @@
+/*
+ * 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.testutils.shadow;
+
+import android.content.ContentResolver;
+import android.content.SyncAdapterType;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+@Implements(ContentResolver.class)
+public class ShadowContentResolver {
+
+    @Implementation
+    public static SyncAdapterType[] getSyncAdapterTypesAsUser(int userId) {
+        return new SyncAdapterType[0];
+    }
+
+}
diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowLibcoreTimeZoneNames.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowLibcoreTimeZoneNames.java
new file mode 100644
index 0000000..7292234
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowLibcoreTimeZoneNames.java
@@ -0,0 +1,60 @@
+/*
+ * 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.testutils.shadow;
+
+import libcore.icu.TimeZoneNames;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * System.logI used by ZoneStringsCache.create is a method new in API 24 and not available in
+ * Robolectric's 6.0 jar. Create a shadow which removes that log call.
+ */
+@Implements(value = TimeZoneNames.class, isInAndroidSdk = false)
+public class ShadowLibcoreTimeZoneNames {
+
+    private static final String[] availableTimeZoneIds = TimeZone.getAvailableIDs();
+
+    @Implements(value = TimeZoneNames.ZoneStringsCache.class, isInAndroidSdk = false)
+    public static class ShadowZoneStringsCache {
+
+        @RealObject
+        private TimeZoneNames.ZoneStringsCache mRealObject;
+
+        @Implementation
+        public String[][] create(Locale locale) {
+            // Set up the 2D array used to hold the names. The first column contains the Olson ids.
+            String[][] result = new String[availableTimeZoneIds.length][5];
+            for (int i = 0; i < availableTimeZoneIds.length; ++i) {
+                result[i][0] = availableTimeZoneIds[i];
+            }
+
+            ReflectionHelpers.callInstanceMethod(TimeZoneNames.class,
+                    mRealObject, "fillZoneStrings",
+                    ReflectionHelpers.ClassParameter.from(String.class, locale.toLanguageTag()),
+                    ReflectionHelpers.ClassParameter.from(String[][].class, result));
+
+            return result;
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowTimeZoneNames.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowTimeZoneNames.java
new file mode 100644
index 0000000..2bcc92a
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowTimeZoneNames.java
@@ -0,0 +1,35 @@
+/*
+ * 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.testutils.shadow;
+
+import android.icu.text.TimeZoneNames;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+/**
+ * TimeZoneNames.getDisplayName tries to access files which doesn't exist for Robolectric. Stub it
+ * out for a naive implementation.
+ */
+@Implements(TimeZoneNames.class)
+public class ShadowTimeZoneNames {
+
+    @Implementation
+    public String getDisplayName(String tzID, TimeZoneNames.NameType type, long date) {
+        return "[DisplayName]" + tzID;
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUserManager.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUserManager.java
new file mode 100644
index 0000000..c67ad36
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUserManager.java
@@ -0,0 +1,35 @@
+/*
+ * 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.testutils.shadow;
+
+import android.content.Context;
+import android.os.UserManager;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+/**
+ * This class provides the API 24 implementation of UserManager.get(Context).
+ */
+@Implements(UserManager.class)
+public class ShadowUserManager {
+
+    @Implementation
+    public static UserManager get(Context context) {
+        return (UserManager) context.getSystemService(Context.USER_SERVICE);
+    }
+}