Merge "Convert DeviceInfoSettings into a dashboard fragment."
diff --git a/res/layout-land/choose_lock_pattern.xml b/res/layout-land/choose_lock_pattern.xml
index b4d5fce..0743577 100644
--- a/res/layout-land/choose_lock_pattern.xml
+++ b/res/layout-land/choose_lock_pattern.xml
@@ -39,6 +39,8 @@
             android:layout_weight="1.0"
             android:layout_marginStart="?attr/suwMarginSides"
             android:layout_marginBottom="@dimen/suw_content_frame_padding_bottom"
+            android:clipChildren="false"
+            android:clipToPadding="false"
             android:orientation="vertical">
 
             <ImageView
@@ -86,7 +88,9 @@
             <RelativeLayout
                 android:layout_width="match_parent"
                 android:layout_height="0dip"
-                android:layout_weight="1.0">
+                android:layout_weight="1.0"
+                android:clipChildren="false"
+                android:clipToPadding="false">
 
                 <!-- confirm / restart buttons -->
                 <LinearLayout android:id="@+id/buttonContainer"
@@ -94,6 +98,8 @@
                     android:layout_height="wrap_content"
                     android:layout_centerHorizontal="true"
                     android:layout_alignParentBottom="true"
+                    android:clipChildren="false"
+                    android:clipToPadding="false"
                     android:orientation="horizontal">
 
                     <!-- left / top button: skip, or re-try -->
diff --git a/res/layout-land/fingerprint_enroll_enrolling.xml b/res/layout-land/fingerprint_enroll_enrolling.xml
index 27af681..c89bc42 100644
--- a/res/layout-land/fingerprint_enroll_enrolling.xml
+++ b/res/layout-land/fingerprint_enroll_enrolling.xml
@@ -40,6 +40,8 @@
             android:layout_height="match_parent"
             android:layout_marginStart="?attr/suwMarginSides"
             android:layout_marginBottom="@dimen/suw_content_frame_padding_bottom"
+            android:clipChildren="false"
+            android:clipToPadding="false"
             android:orientation="vertical">
 
             <ImageView
diff --git a/res/layout-land/fingerprint_enroll_find_sensor.xml b/res/layout-land/fingerprint_enroll_find_sensor.xml
index b64b27f..3dadece 100644
--- a/res/layout-land/fingerprint_enroll_find_sensor.xml
+++ b/res/layout-land/fingerprint_enroll_find_sensor.xml
@@ -36,12 +36,16 @@
             android:layout_height="match_parent"
             android:layout_marginStart="?attr/suwMarginSides"
             android:layout_marginBottom="@dimen/suw_content_frame_padding_bottom"
+            android:clipChildren="false"
+            android:clipToPadding="false"
             android:orientation="vertical">
 
             <LinearLayout
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:orientation="vertical">
+                android:orientation="vertical"
+                android:clipChildren="false"
+                android:clipToPadding="false">
 
                 <ImageView
                     android:id="@+id/suw_layout_icon"
diff --git a/res/layout-land/fingerprint_enroll_finish.xml b/res/layout-land/fingerprint_enroll_finish.xml
index 664123d..5fc0d73 100644
--- a/res/layout-land/fingerprint_enroll_finish.xml
+++ b/res/layout-land/fingerprint_enroll_finish.xml
@@ -36,6 +36,8 @@
             android:layout_weight="1"
             android:layout_height="wrap_content"
             android:layout_gravity="center_vertical"
+            android:clipChildren="false"
+            android:clipToPadding="false"
             android:orientation="vertical">
 
             <TextView
@@ -59,6 +61,8 @@
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:layout_marginTop="8dp"
+                android:clipChildren="false"
+                android:clipToPadding="false"
                 android:orientation="horizontal">
 
                 <Button
diff --git a/res/layout/choose_lock_password.xml b/res/layout/choose_lock_password.xml
index 4b9f3e5..15fb7db 100644
--- a/res/layout/choose_lock_password.xml
+++ b/res/layout/choose_lock_password.xml
@@ -26,6 +26,8 @@
         style="@style/SuwContentFrame"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
+        android:clipChildren="false"
+        android:clipToPadding="false"
         android:gravity="center_horizontal"
         android:orientation="vertical">
 
@@ -54,6 +56,8 @@
                 android:id="@+id/bottom_container"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
+                android:clipChildren="false"
+                android:clipToPadding="false"
                 android:orientation="vertical">
 
             <android.support.v7.widget.RecyclerView
diff --git a/res/layout/choose_lock_pattern_common.xml b/res/layout/choose_lock_pattern_common.xml
index c50aa5b..050479b 100644
--- a/res/layout/choose_lock_pattern_common.xml
+++ b/res/layout/choose_lock_pattern_common.xml
@@ -29,6 +29,8 @@
         style="@style/SuwContentFrame"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
+        android:clipChildren="false"
+        android:clipToPadding="false"
         android:orientation="vertical">
 
         <!-- takes up all space above button bar at bottom -->
@@ -37,6 +39,8 @@
             android:layout_height="0dip"
             android:layout_weight="1"
             android:gravity="center"
+            android:clipChildren="false"
+            android:clipToPadding="false"
             android:orientation="vertical">
 
             <TextView android:id="@+id/headerText"
@@ -56,6 +60,8 @@
             <LinearLayout
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
+                android:clipChildren="false"
+                android:clipToPadding="false"
                 android:orientation="horizontal">
 
                 <!-- left : cancel, or re-try -->
diff --git a/res/layout/fingerprint_enroll_finish_base.xml b/res/layout/fingerprint_enroll_finish_base.xml
index bcbbadf..99c96ad 100644
--- a/res/layout/fingerprint_enroll_finish_base.xml
+++ b/res/layout/fingerprint_enroll_finish_base.xml
@@ -75,6 +75,8 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_marginBottom="4dp"
+            android:clipChildren="false"
+            android:clipToPadding="false"
             android:orientation="horizontal">
 
             <Button
diff --git a/res/layout/fingerprint_enroll_introduction.xml b/res/layout/fingerprint_enroll_introduction.xml
index ccd1f62..6d20756 100644
--- a/res/layout/fingerprint_enroll_introduction.xml
+++ b/res/layout/fingerprint_enroll_introduction.xml
@@ -26,6 +26,8 @@
         style="@style/SuwContentFrame"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
+        android:clipChildren="false"
+        android:clipToPadding="false"
         android:orientation="vertical">
 
         <com.android.setupwizardlib.view.RichTextView
@@ -44,6 +46,8 @@
         <LinearLayout
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:clipChildren="false"
+            android:clipToPadding="false"
             android:orientation="horizontal">
 
             <Button
diff --git a/res/layout/redaction_interstitial.xml b/res/layout/redaction_interstitial.xml
index d1ce0dc..e48f20f 100644
--- a/res/layout/redaction_interstitial.xml
+++ b/res/layout/redaction_interstitial.xml
@@ -28,6 +28,8 @@
         style="@style/SuwContentFrame"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
+        android:clipChildren="false"
+        android:clipToPadding="false"
         android:orientation="vertical">
 
         <TextView
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 6885dcb..dfcbc96 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -6229,13 +6229,13 @@
     <string name="work_sound_same_as_personal">Same as personal profile</string>
 
     <!-- Work Sound: Title for dialog shown when enabling sync with personal sounds. [CHAR LIMIT=60] -->
-    <string name="work_sync_dialog_title">Replace work profile sounds?</string>
+    <string name="work_sync_dialog_title">Use personal profile sounds?</string>
 
-    <!-- Work Sound: Confirm action text for dialog shown when enabling sync with personal sounds. [CHAR LIMIT=30] -->
+    <!-- Work Sound: Confirm action text for dialog shown when overriding work notification sounds with personal sounds. [CHAR LIMIT=30] -->
     <string name="work_sync_dialog_yes">Replace</string>
 
-    <!-- Work Sound: Message for dialog shown when enabling sync with personal sounds. [CHAR LIMIT=none] -->
-    <string name="work_sync_dialog_message">Your current work profile sounds will be replaced with your personal profile sounds</string>
+    <!-- Work Sound: Message for dialog shown when using the same sounds for work events as for personal events (notifications / ringtones / alarms). [CHAR LIMIT=none] -->
+    <string name="work_sync_dialog_message">Your personal profile sounds will be used instead of your current work profile sounds</string>
 
     <!-- Ringtones preference category: Title for the Ringotnes preference categories. [CHAR LIMIT=none] -->
     <string name="ringtones_category_preference_title">Ringtones</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 3f36255..790b726 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -259,13 +259,13 @@
         <item name="android:singleLine">true</item>
     </style>
 
-    <style name="SetupWizardButton.Negative" parent="@android:style/Widget.Material.Button.Borderless.Colored">
-        <item name="android:minWidth">0dp</item>
-        <item name="android:textAllCaps">false</item>
-        <item name="android:theme">@style/AccentColorHighlightBorderlessButton</item>
+    <style name="SetupWizardButton.Negative" parent="@style/SuwGlifButton.Secondary">
+        <!-- Negative margin to offset for padding of the button itself. We want the label to be
+             aligned with the text above it -->
+        <item name="android:layout_marginStart">-16dp</item>
     </style>
 
-    <style name="SetupWizardButton.Positive" parent="@android:style/Widget.Material.Button.Colored" />
+    <style name="SetupWizardButton.Positive" parent="@style/SuwGlifButton.Primary" />
 
     <style name="AccentColorHighlightBorderlessButton">
         <item name="android:colorControlHighlight">?android:attr/colorAccent</item>
diff --git a/res/xml/storage_dashboard_fragment.xml b/res/xml/storage_dashboard_fragment.xml
index 271d31b..fedc77f 100644
--- a/res/xml/storage_dashboard_fragment.xml
+++ b/res/xml/storage_dashboard_fragment.xml
@@ -16,40 +16,50 @@
 
 <PreferenceScreen
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:title="@string/storage_settings">
+    android:title="@string/storage_settings"
+    android:orderingFromXml="false">
     <com.android.settings.deviceinfo.storage.StorageSummaryDonutPreference
-        android:key="pref_summary" />
+        android:key="pref_summary"
+        android:order="0" />
     <com.android.settings.deviceinfo.storage.StorageItemPreferenceAlternate
         android:key="pref_photos_videos"
-        android:title="@string/storage_photos_videos">
+        android:title="@string/storage_photos_videos"
+        android:order="1" >
     </com.android.settings.deviceinfo.storage.StorageItemPreferenceAlternate>
     <com.android.settings.deviceinfo.storage.StorageItemPreferenceAlternate
         android:key="pref_music_audio"
-        android:title="@string/storage_music_audio">
+        android:title="@string/storage_music_audio"
+        android:order="2" >
     </com.android.settings.deviceinfo.storage.StorageItemPreferenceAlternate>
     <com.android.settings.deviceinfo.storage.StorageItemPreferenceAlternate
         android:key="pref_games"
-        android:title="@string/storage_games">
+        android:title="@string/storage_games"
+        android:order="3" >
     </com.android.settings.deviceinfo.storage.StorageItemPreferenceAlternate>
     <com.android.settings.deviceinfo.storage.StorageItemPreferenceAlternate
         android:key="pref_other_apps"
-        android:title="@string/storage_other_apps">
+        android:title="@string/storage_other_apps"
+        android:order="4" >
     </com.android.settings.deviceinfo.storage.StorageItemPreferenceAlternate>
     <com.android.settings.deviceinfo.storage.StorageItemPreferenceAlternate
         android:key="pref_files"
-        android:title="@string/storage_files">
+        android:title="@string/storage_files"
+        android:order="5" >
     </com.android.settings.deviceinfo.storage.StorageItemPreferenceAlternate>
     <com.android.settings.deviceinfo.storage.StorageItemPreferenceAlternate
         android:key="pref_system"
-        android:title="@string/storage_detail_system">
+        android:title="@string/storage_detail_system"
+        android:order="100" >
     </com.android.settings.deviceinfo.storage.StorageItemPreferenceAlternate>
     <PreferenceCategory
         android:key="pref_secondary_users"
-        android:title="@string/storage_other_users" />
+        android:title="@string/storage_other_users"
+        android:order="200" />
     <Preference
         android:key="manage_storage"
         android:title="@string/storage_menu_manage"
         android:icon="@drawable/ic_settings_storage"
-        android:fragment="com.android.settings.deletionhelper.AutomaticStorageManagerSettings">
+        android:fragment="com.android.settings.deletionhelper.AutomaticStorageManagerSettings"
+        android:order="300" >
     </Preference>
 </PreferenceScreen>
\ No newline at end of file
diff --git a/res/xml/storage_profile_fragment.xml b/res/xml/storage_profile_fragment.xml
new file mode 100644
index 0000000..d0c5c3a
--- /dev/null
+++ b/res/xml/storage_profile_fragment.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- Layout for the storage breakdown for a profile of the primary user. -->
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:title="@string/storage_settings">
+    <com.android.settings.deviceinfo.storage.StorageItemPreferenceAlternate
+        android:key="pref_photos_videos"
+        android:title="@string/storage_photos_videos">
+    </com.android.settings.deviceinfo.storage.StorageItemPreferenceAlternate>
+    <com.android.settings.deviceinfo.storage.StorageItemPreferenceAlternate
+        android:key="pref_music_audio"
+        android:title="@string/storage_music_audio">
+    </com.android.settings.deviceinfo.storage.StorageItemPreferenceAlternate>
+    <com.android.settings.deviceinfo.storage.StorageItemPreferenceAlternate
+        android:key="pref_games"
+        android:title="@string/storage_games">
+    </com.android.settings.deviceinfo.storage.StorageItemPreferenceAlternate>
+    <com.android.settings.deviceinfo.storage.StorageItemPreferenceAlternate
+        android:key="pref_other_apps"
+        android:title="@string/storage_other_apps">
+    </com.android.settings.deviceinfo.storage.StorageItemPreferenceAlternate>
+    <com.android.settings.deviceinfo.storage.StorageItemPreferenceAlternate
+        android:key="pref_files"
+        android:title="@string/storage_files">
+    </com.android.settings.deviceinfo.storage.StorageItemPreferenceAlternate>
+</PreferenceScreen>
diff --git a/src/com/android/settings/SmsDefaultDialog.java b/src/com/android/settings/SmsDefaultDialog.java
index 830187c..e4b49ee 100644
--- a/src/com/android/settings/SmsDefaultDialog.java
+++ b/src/com/android/settings/SmsDefaultDialog.java
@@ -125,6 +125,10 @@
             p.mOnClickListener = this;
             p.mNegativeButtonText = getString(R.string.cancel);
             p.mNegativeButtonListener = this;
+            if (p.mAdapter.isEmpty()) {
+                // If there is nothing to choose from, don't build the dialog.
+                return false;
+            }
         }
         setupAlert();
 
diff --git a/src/com/android/settings/TetherService.java b/src/com/android/settings/TetherService.java
index 6d359f2..610d320 100644
--- a/src/com/android/settings/TetherService.java
+++ b/src/com/android/settings/TetherService.java
@@ -252,7 +252,8 @@
         Intent intent = new Intent(provisionAction);
         int type = mCurrentTethers.get(index);
         intent.putExtra(TETHER_CHOICE, type);
-        intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND
+                | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
 
         return intent;
     }
diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java
index 124c441..350ab9c 100644
--- a/src/com/android/settings/Utils.java
+++ b/src/com/android/settings/Utils.java
@@ -65,6 +65,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
 import android.preference.PreferenceFrameLayout;
 import android.provider.ContactsContract.CommonDataKinds;
 import android.provider.ContactsContract.Contacts;
@@ -1258,4 +1259,21 @@
                 (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
                         && user.profileGroupId == profile.profileGroupId);
     }
+
+    /**
+     * Tries to initalize a volume with the given bundle. If it is a valid, private, and readable
+     * {@link VolumeInfo}, it is returned. If it is not valid, null is returned.
+     */
+    @Nullable
+    public static VolumeInfo maybeInitializeVolume(StorageManager sm, Bundle bundle) {
+        final String volumeId = bundle.getString(VolumeInfo.EXTRA_VOLUME_ID,
+                VolumeInfo.ID_PRIVATE_INTERNAL);
+        VolumeInfo volume = sm.findVolumeById(volumeId);
+        return isVolumeValid(volume) ? volume : null;
+    }
+
+    private static boolean isVolumeValid(VolumeInfo volume) {
+        return (volume != null) && (volume.getType() == VolumeInfo.TYPE_PRIVATE)
+                && volume.isMountedReadable();
+    }
 }
diff --git a/src/com/android/settings/applications/ManageApplications.java b/src/com/android/settings/applications/ManageApplications.java
index 878a044..db72fce 100644
--- a/src/com/android/settings/applications/ManageApplications.java
+++ b/src/com/android/settings/applications/ManageApplications.java
@@ -71,7 +71,6 @@
 import com.android.settings.applications.AppStateUsageBridge.UsageState;
 import com.android.settings.core.InstrumentedPreferenceFragment;
 import com.android.settings.dashboard.SummaryLoader;
-import com.android.settings.deviceinfo.storage.StorageStatsSource;
 import com.android.settings.fuelgauge.HighPowerDetail;
 import com.android.settings.fuelgauge.PowerWhitelistBackend;
 import com.android.settings.notification.AppNotificationSettings;
@@ -85,6 +84,7 @@
 import com.android.settingslib.applications.ApplicationsState.AppFilter;
 import com.android.settingslib.applications.ApplicationsState.CompoundFilter;
 import com.android.settingslib.applications.ApplicationsState.VolumeFilter;
+import com.android.settingslib.applications.StorageStatsSource;
 
 import java.util.ArrayList;
 import java.util.Collections;
diff --git a/src/com/android/settings/applications/MusicViewHolderController.java b/src/com/android/settings/applications/MusicViewHolderController.java
index fd7e320..d7c142f 100644
--- a/src/com/android/settings/applications/MusicViewHolderController.java
+++ b/src/com/android/settings/applications/MusicViewHolderController.java
@@ -26,7 +26,7 @@
 
 import com.android.settings.R;
 import com.android.settings.Utils;
-import com.android.settings.deviceinfo.storage.StorageStatsSource;
+import com.android.settingslib.applications.StorageStatsSource;
 
 /**
  * MusicViewHolderController controls an Audio/Music file view in the ManageApplications view.
diff --git a/src/com/android/settings/applications/defaultapps/DefaultBrowserPreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultBrowserPreferenceController.java
index 7077912..9a5c224 100644
--- a/src/com/android/settings/applications/defaultapps/DefaultBrowserPreferenceController.java
+++ b/src/com/android/settings/applications/defaultapps/DefaultBrowserPreferenceController.java
@@ -39,7 +39,8 @@
 
     @Override
     public boolean isAvailable() {
-        return true;
+        final List<ResolveInfo> candidates = getCandidates();
+        return candidates != null && !candidates.isEmpty();
     }
 
     @Override
@@ -61,16 +62,6 @@
         }
     }
 
-    private String getOnlyAppLabel() {
-        // Resolve that intent and check that the handleAllWebDataURI boolean is set
-        final List<ResolveInfo> list = mPackageManager.queryIntentActivitiesAsUser(BROWSE_PROBE,
-                PackageManager.MATCH_ALL, mUserId);
-        if (list != null && list.size() == 1) {
-            return list.get(0).loadLabel(mPackageManager.getPackageManager()).toString();
-        }
-        return null;
-    }
-
     @Override
     protected DefaultAppInfo getDefaultAppInfo() {
         try {
@@ -81,6 +72,20 @@
         }
     }
 
+    private List<ResolveInfo> getCandidates() {
+        return mPackageManager.queryIntentActivitiesAsUser(BROWSE_PROBE, PackageManager.MATCH_ALL,
+                mUserId);
+    }
+
+    private String getOnlyAppLabel() {
+        // Resolve that intent and check that the handleAllWebDataURI boolean is set
+        final List<ResolveInfo> list = getCandidates();
+        if (list != null && list.size() == 1) {
+            return list.get(0).loadLabel(mPackageManager.getPackageManager()).toString();
+        }
+        return null;
+    }
+
     /**
      * Whether or not the pkg contains browser capability
      */
diff --git a/src/com/android/settings/applications/defaultapps/DefaultPhonePreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultPhonePreferenceController.java
index fa0d28f..8a73468 100644
--- a/src/com/android/settings/applications/defaultapps/DefaultPhonePreferenceController.java
+++ b/src/com/android/settings/applications/defaultapps/DefaultPhonePreferenceController.java
@@ -42,7 +42,11 @@
         final boolean hasUserRestriction =
                 um.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS);
 
-        return !hasUserRestriction;
+        if (hasUserRestriction) {
+            return false;
+        }
+        final List<String> candidates = getCandidates();
+        return candidates != null && !candidates.isEmpty();
     }
 
     @Override
@@ -60,6 +64,10 @@
         }
     }
 
+    private List<String> getCandidates() {
+        return DefaultDialerManager.getInstalledDialerApplications(mContext, mUserId);
+    }
+
     public static boolean hasPhonePreference(String pkg, Context context) {
         List<String> dialerPackages =
                 DefaultDialerManager.getInstalledDialerApplications(context, UserHandle.myUserId());
diff --git a/src/com/android/settings/applications/defaultapps/DefaultWorkBrowserPreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultWorkBrowserPreferenceController.java
index 0cee3c5..0792ff9 100644
--- a/src/com/android/settings/applications/defaultapps/DefaultWorkBrowserPreferenceController.java
+++ b/src/com/android/settings/applications/defaultapps/DefaultWorkBrowserPreferenceController.java
@@ -23,21 +23,26 @@
 
 public class DefaultWorkBrowserPreferenceController extends DefaultBrowserPreferenceController {
 
+    private final UserHandle mUserHandle;
+
     public DefaultWorkBrowserPreferenceController(Context context) {
         super(context);
-        final UserHandle managedProfile = Utils.getManagedProfile(mUserManager);
-        if (managedProfile != null) {
-            mUserId = managedProfile.getIdentifier();
+        mUserHandle = Utils.getManagedProfile(mUserManager);
+        if (mUserHandle != null) {
+            mUserId = mUserHandle.getIdentifier();
         }
     }
 
     @Override
-    public boolean isAvailable() {
-        return Utils.getManagedProfile(mUserManager) != null;
-    }
-
-    @Override
     public String getPreferenceKey() {
         return "work_default_browser";
     }
+
+    @Override
+    public boolean isAvailable() {
+        if (mUserHandle == null) {
+            return false;
+        }
+        return super.isAvailable();
+    }
 }
diff --git a/src/com/android/settings/applications/defaultapps/DefaultWorkPhonePreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultWorkPhonePreferenceController.java
index f793fa3..c6d04fa 100644
--- a/src/com/android/settings/applications/defaultapps/DefaultWorkPhonePreferenceController.java
+++ b/src/com/android/settings/applications/defaultapps/DefaultWorkPhonePreferenceController.java
@@ -17,11 +17,28 @@
 package com.android.settings.applications.defaultapps;
 
 import android.content.Context;
+import android.os.UserHandle;
+
+import com.android.settings.Utils;
 
 public class DefaultWorkPhonePreferenceController extends DefaultPhonePreferenceController {
 
+    private final UserHandle mUserHandle;
+
     public DefaultWorkPhonePreferenceController(Context context) {
         super(context);
+        mUserHandle = Utils.getManagedProfile(mUserManager);
+        if (mUserHandle != null) {
+            mUserId = mUserHandle.getIdentifier();
+        }
+    }
+
+    @Override
+    public boolean isAvailable() {
+        if (mUserHandle == null) {
+            return false;
+        }
+        return super.isAvailable();
     }
 
     @Override
diff --git a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
index d65eb75..72e1493 100644
--- a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
+++ b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
@@ -25,11 +25,11 @@
 import android.os.storage.StorageManager;
 import android.os.storage.VolumeInfo;
 import android.provider.SearchIndexableResource;
-import android.support.annotation.VisibleForTesting;
 import android.util.SparseArray;
 
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.settings.R;
+import com.android.settings.Utils;
 import com.android.settings.applications.PackageManagerWrapperImpl;
 import com.android.settings.applications.UserManagerWrapper;
 import com.android.settings.applications.UserManagerWrapperImpl;
@@ -48,7 +48,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.Map;
 
 public class StorageDashboardFragment extends DashboardFragment
     implements LoaderManager.LoaderCallbacks<SparseArray<StorageAsyncLoader.AppsStorageResult>> {
@@ -61,11 +60,6 @@
     private StorageItemPreferenceController mPreferenceController;
     private List<PreferenceController> mSecondaryUsers;
 
-    private boolean isVolumeValid() {
-        return (mVolume != null) && (mVolume.getType() == VolumeInfo.TYPE_PRIVATE)
-                && mVolume.isMountedReadable();
-    }
-
     @Override
     public void onResume() {
         super.onResume();
@@ -101,7 +95,8 @@
         // Initialize the storage sizes that we can quickly calc.
         final Context context = getActivity();
         StorageManager sm = context.getSystemService(StorageManager.class);
-        if (!initializeVolume(sm, getArguments())) {
+        mVolume = Utils.maybeInitializeVolume(sm, getArguments());
+        if (mVolume == null) {
             getActivity().finish();
             return;
         }
@@ -157,30 +152,16 @@
     }
 
     /**
-     * Initializes the volume with a given bundle and returns if the volume is valid.
-     */
-    @VisibleForTesting
-    boolean initializeVolume(StorageManager sm, Bundle bundle) {
-        String volumeId = bundle.getString(VolumeInfo.EXTRA_VOLUME_ID,
-                VolumeInfo.ID_PRIVATE_INTERNAL);
-        mVolume = sm.findVolumeById(volumeId);
-        return isVolumeValid();
-    }
-
-    /**
      * Updates the secondary user controller sizes.
      */
     private void updateSecondaryUserControllers(List<PreferenceController> controllers,
             SparseArray<StorageAsyncLoader.AppsStorageResult> stats) {
         for (int i = 0, size = controllers.size(); i < size; i++) {
             PreferenceController controller = controllers.get(i);
-            if (controller instanceof SecondaryUserController) {
-                SecondaryUserController userController = (SecondaryUserController) controller;
-                int userId = userController.getUser().id;
-                StorageAsyncLoader.AppsStorageResult result = stats.get(userId);
-                if (result != null) {
-                    userController.setSize(result.externalStats.totalBytes);
-                }
+            if (controller instanceof StorageAsyncLoader.ResultHandler) {
+                StorageAsyncLoader.ResultHandler userController =
+                        (StorageAsyncLoader.ResultHandler) controller;
+                userController.handleResult(stats);
             }
         }
     }
diff --git a/src/com/android/settings/deviceinfo/StorageProfileFragment.java b/src/com/android/settings/deviceinfo/StorageProfileFragment.java
new file mode 100644
index 0000000..6ae03da
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/StorageProfileFragment.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.deviceinfo;
+
+import android.app.LoaderManager;
+import android.content.Context;
+import android.content.Loader;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+import android.util.SparseArray;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.R;
+import com.android.settings.Utils;
+import com.android.settings.applications.PackageManagerWrapperImpl;
+import com.android.settings.applications.UserManagerWrapperImpl;
+import com.android.settings.core.PreferenceController;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.deviceinfo.storage.StorageAsyncLoader;
+import com.android.settings.deviceinfo.storage.StorageAsyncLoader.AppsStorageResult;
+import com.android.settings.deviceinfo.storage.StorageItemPreferenceController;
+import com.android.settingslib.applications.StorageStatsSource;
+import com.android.settingslib.deviceinfo.StorageManagerVolumeProvider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * StorageProfileFragment is a fragment which shows the storage results for a profile of the
+ * primary user.
+ */
+public class StorageProfileFragment extends DashboardFragment
+        implements LoaderManager.LoaderCallbacks<SparseArray<AppsStorageResult>> {
+    private static final String TAG = "StorageProfileFragment";
+    public static final String USER_ID_EXTRA = "userId";
+    private static final int APPS_JOB_ID = 0;
+
+    private VolumeInfo mVolume;
+    private int mUserId;
+    private StorageItemPreferenceController mPreferenceController;
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        final Bundle args = getArguments();
+
+        // Initialize the storage sizes that we can quickly calc.
+        final Context context = getActivity();
+        final StorageManager sm = context.getSystemService(StorageManager.class);
+        mVolume = Utils.maybeInitializeVolume(sm, args);
+        if (mVolume == null) {
+            getActivity().finish();
+            return;
+        }
+
+        mPreferenceController.setVolume(mVolume);
+        mUserId = args.getInt(USER_ID_EXTRA, UserHandle.myUserId());
+        mPreferenceController.setUserId(mUserId);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        getLoaderManager().initLoader(APPS_JOB_ID, Bundle.EMPTY, this);
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        return MetricsProto.MetricsEvent.SETTINGS_STORAGE_PROFILE;
+    }
+
+    @Override
+    protected String getLogTag() {
+        return TAG;
+    }
+
+    @Override
+    protected int getPreferenceScreenResId() {
+        return R.xml.storage_profile_fragment;
+    }
+
+    @Override
+    protected List<PreferenceController> getPreferenceControllers(Context context) {
+        final List<PreferenceController> controllers = new ArrayList<>();
+        final StorageManager sm = context.getSystemService(StorageManager.class);
+        mPreferenceController = new StorageItemPreferenceController(context, this,
+                mVolume, new StorageManagerVolumeProvider(sm));
+        controllers.add(mPreferenceController);
+        return controllers;
+    }
+
+    @Override
+    public Loader<SparseArray<AppsStorageResult>> onCreateLoader(int id, Bundle args) {
+        Context context = getContext();
+        return new StorageAsyncLoader(context,
+                new UserManagerWrapperImpl(context.getSystemService(UserManager.class)),
+                mVolume.fsUuid,
+                new StorageStatsSource(context),
+                new PackageManagerWrapperImpl(context.getPackageManager()));
+    }
+
+    @Override
+    public void onLoadFinished(Loader<SparseArray<AppsStorageResult>> loader,
+            SparseArray<AppsStorageResult> result) {
+        mPreferenceController.onLoadFinished(result.get(mUserId));
+    }
+
+    @Override
+    public void onLoaderReset(Loader<SparseArray<AppsStorageResult>> loader) {
+    }
+}
diff --git a/src/com/android/settings/deviceinfo/storage/SecondaryUserController.java b/src/com/android/settings/deviceinfo/storage/SecondaryUserController.java
index d45c6e3..a5e8373 100644
--- a/src/com/android/settings/deviceinfo/storage/SecondaryUserController.java
+++ b/src/com/android/settings/deviceinfo/storage/SecondaryUserController.java
@@ -23,6 +23,7 @@
 import android.support.annotation.VisibleForTesting;
 import android.support.v7.preference.PreferenceGroup;
 import android.support.v7.preference.PreferenceScreen;
+import android.util.SparseArray;
 
 import com.android.settings.Utils;
 import com.android.settings.applications.UserManagerWrapper;
@@ -35,10 +36,12 @@
  * SecondaryUserController controls the preferences on the Storage screen which had to do with
  * secondary users.
  */
-public class SecondaryUserController extends PreferenceController {
+public class SecondaryUserController extends PreferenceController implements
+        StorageAsyncLoader.ResultHandler {
     // PreferenceGroupKey to try to add our preference onto.
     private static final String TARGET_PREFERENCE_GROUP_KEY = "pref_secondary_users";
     private static final String PREFERENCE_KEY_BASE = "pref_user_";
+    private static final int USER_PROFILE_INSERTION_LOCATION = 6;
     private static final int SIZE_NOT_SET = -1;
 
     private @NonNull UserInfo mUser;
@@ -59,7 +62,13 @@
         List<UserInfo> infos = userManager.getUsers();
         for (int i = 0, size = infos.size(); i < size; i++) {
             UserInfo info = infos.get(i);
+            if (info.equals(primaryUser)) {
+                continue;
+            }
+
             if (info == null || Utils.isProfileOf(primaryUser, info)) {
+                controllers.add(new UserProfileController(context, info,
+                        USER_PROFILE_INSERTION_LOCATION));
                 continue;
             }
 
@@ -131,6 +140,14 @@
         }
     }
 
+    public void handleResult(SparseArray<StorageAsyncLoader.AppsStorageResult> stats) {
+        int userId = getUser().id;
+        StorageAsyncLoader.AppsStorageResult result = stats.get(userId);
+        if (result != null) {
+            setSize(result.externalStats.totalBytes);
+        }
+    }
+
     private static class NoSecondaryUserController extends PreferenceController {
         public NoSecondaryUserController(Context context) {
             super(context);
diff --git a/src/com/android/settings/deviceinfo/storage/StorageAsyncLoader.java b/src/com/android/settings/deviceinfo/storage/StorageAsyncLoader.java
index a60831b..d5d96a5 100644
--- a/src/com/android/settings/deviceinfo/storage/StorageAsyncLoader.java
+++ b/src/com/android/settings/deviceinfo/storage/StorageAsyncLoader.java
@@ -23,10 +23,9 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.UserInfo;
 import android.os.UserHandle;
-import android.util.ArrayMap;
 import android.util.ArraySet;
-import android.util.SparseArray;
 import android.util.Log;
+import android.util.SparseArray;
 
 import com.android.settings.applications.PackageManagerWrapper;
 import com.android.settings.applications.UserManagerWrapper;
@@ -34,7 +33,6 @@
 import com.android.settingslib.applications.StorageStatsSource;
 
 import java.util.List;
-import java.util.Map;
 
 /**
  * StorageAsyncLoader is a Loader which loads categorized app information and external stats for all
@@ -118,4 +116,12 @@
         public long otherAppsSize;
         public StorageStatsSource.ExternalStorageStats externalStats;
     }
+
+    /**
+     * ResultHandler defines a destination of data which can handle a result from
+     * {@link StorageAsyncLoader}.
+     */
+    public interface ResultHandler {
+        void handleResult(SparseArray<AppsStorageResult> result);
+    }
 }
diff --git a/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java b/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java
index 88ba152..e31d968 100644
--- a/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java
+++ b/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java
@@ -22,7 +22,6 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.UserHandle;
-import android.os.UserManager;
 import android.os.storage.VolumeInfo;
 import android.support.annotation.VisibleForTesting;
 import android.support.v7.preference.Preference;
@@ -36,12 +35,12 @@
 import com.android.settings.applications.ManageApplications;
 import com.android.settings.core.PreferenceController;
 import com.android.settings.core.instrumentation.MetricsFeatureProvider;
-
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settingslib.deviceinfo.StorageMeasurement;
 import com.android.settingslib.deviceinfo.StorageVolumeProvider;
-import com.android.settingslib.applications.StorageStatsSource;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -70,7 +69,7 @@
     private final  MetricsFeatureProvider mMetricsFeatureProvider;
     private final StorageVolumeProvider mSvp;
     private VolumeInfo mVolume;
-    private final int mUserId;
+    private int mUserId;
     private long mSystemSize;
 
     private StorageItemPreferenceAlternate mPhotoPreference;
@@ -89,8 +88,7 @@
         mVolume = volume;
         mSvp = svp;
         mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
-        UserManager um = mContext.getSystemService(UserManager.class);
-        mUserId = um.getUserHandle();
+        mUserId = UserHandle.myUserId();
     }
 
     @Override
@@ -157,6 +155,13 @@
         mVolume = volume;
     }
 
+    /**
+     * Sets the user id for which this preference controller is handling.
+     */
+    public void setUserId(int userId) {
+        mUserId = userId;
+    }
+
     @Override
     public void displayPreference(PreferenceScreen screen) {
         mPhotoPreference = (StorageItemPreferenceAlternate) screen.findPreference(PHOTO_KEY);
@@ -173,7 +178,9 @@
         mAudioPreference.setStorageSize(data.musicAppsSize + data.externalStats.audioBytes);
         mGamePreference.setStorageSize(data.gamesSize);
         mAppPreference.setStorageSize(data.otherAppsSize);
-        mSystemPreference.setStorageSize(mSystemSize);
+        if (mSystemPreference != null) {
+            mSystemPreference.setStorageSize(mSystemSize);
+        }
 
         long unattributedBytes = data.externalStats.totalBytes - data.externalStats.audioBytes
                 - data.externalStats.videoBytes - data.externalStats.imageBytes;
@@ -188,6 +195,20 @@
         mSystemSize = systemSize;
     }
 
+    /**
+     * Returns a list of keys used by this preference controller.
+     */
+    public static List<String> getUsedKeys() {
+        List<String> list = new ArrayList<>();
+        list.add(PHOTO_KEY);
+        list.add(AUDIO_KEY);
+        list.add(GAME_KEY);
+        list.add(OTHER_APPS_KEY);
+        list.add(SYSTEM_KEY);
+        list.add(FILES_KEY);
+        return list;
+    }
+
     private Intent getPhotosIntent() {
         Intent intent = new Intent();
         intent.setAction(android.content.Intent.ACTION_VIEW);
diff --git a/src/com/android/settings/deviceinfo/storage/StorageStatsSource.java b/src/com/android/settings/deviceinfo/storage/StorageStatsSource.java
deleted file mode 100644
index 98038fd..0000000
--- a/src/com/android/settings/deviceinfo/storage/StorageStatsSource.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.settings.deviceinfo.storage;
-
-import android.app.usage.StorageStats;
-import android.app.usage.StorageStatsManager;
-import android.content.Context;
-import android.os.UserHandle;
-
-/**
- * StorageStatsSource wraps the StorageStatsManager for testability purposes.
- */
-public class StorageStatsSource {
-    private StorageStatsManager mStorageStatsManager;
-
-    public StorageStatsSource(Context context) {
-        mStorageStatsManager = context.getSystemService(StorageStatsManager.class);
-    }
-
-    public ExternalStorageStats getExternalStorageStats(String volumeUuid, UserHandle user) {
-        return new ExternalStorageStats(
-                mStorageStatsManager.queryExternalStatsForUser(volumeUuid, user));
-    }
-
-    public AppStorageStats getStatsForUid(String volumeUuid, int uid) {
-        return new AppStorageStatsImpl(mStorageStatsManager.queryStatsForUid(volumeUuid, uid));
-    }
-
-    public static class ExternalStorageStats {
-        public long totalBytes;
-        public long audioBytes;
-        public long videoBytes;
-        public long imageBytes;
-
-        public ExternalStorageStats(long totalBytes, long audioBytes, long videoBytes,
-                long imageBytes) {
-            this.totalBytes = totalBytes;
-            this.audioBytes = audioBytes;
-            this.videoBytes = videoBytes;
-            this.imageBytes = imageBytes;
-        }
-
-        public ExternalStorageStats(android.app.usage.ExternalStorageStats stats) {
-            totalBytes = stats.getTotalBytes();
-            audioBytes = stats.getAudioBytes();
-            videoBytes = stats.getVideoBytes();
-            imageBytes = stats.getImageBytes();
-        }
-    }
-
-    public interface AppStorageStats {
-        long getCodeBytes();
-        long getDataBytes();
-        long getCacheBytes();
-    }
-
-    public static class AppStorageStatsImpl implements AppStorageStats {
-        private StorageStats mStats;
-
-        public AppStorageStatsImpl(StorageStats stats) {
-            mStats = stats;
-        }
-
-        public long getCodeBytes() {
-            return mStats.getCodeBytes();
-        }
-
-        public long getDataBytes() {
-            return mStats.getDataBytes();
-        }
-
-        public long getCacheBytes() {
-            return mStats.getCacheBytes();
-        }
-    }
-}
diff --git a/src/com/android/settings/deviceinfo/storage/UserProfileController.java b/src/com/android/settings/deviceinfo/storage/UserProfileController.java
new file mode 100644
index 0000000..5da9fec
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/storage/UserProfileController.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.deviceinfo.storage;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.os.Bundle;
+import android.os.storage.VolumeInfo;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.util.SparseArray;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.util.Preconditions;
+import com.android.settings.Utils;
+import com.android.settings.core.PreferenceController;
+import com.android.settings.deviceinfo.StorageProfileFragment;
+import com.android.settingslib.drawer.SettingsDrawerActivity;
+
+/**
+ * Defines a {@link PreferenceController} which handles a single profile of the primary user.
+ */
+public class UserProfileController extends PreferenceController implements
+        StorageAsyncLoader.ResultHandler {
+    private static final String PREFERENCE_KEY_BASE = "pref_profile_";
+    private StorageItemPreferenceAlternate mStoragePreference;
+    private UserInfo mUser;
+    private final int mPreferenceOrder;
+
+    public UserProfileController(Context context, UserInfo info, int preferenceOrder) {
+        super(context);
+        mUser = Preconditions.checkNotNull(info);
+        mPreferenceOrder = preferenceOrder;
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return PREFERENCE_KEY_BASE + mUser.id;
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        mStoragePreference = new StorageItemPreferenceAlternate(mContext);
+        mStoragePreference.setOrder(mPreferenceOrder);
+        mStoragePreference.setKey(PREFERENCE_KEY_BASE + mUser.id);
+        mStoragePreference.setTitle(mUser.name);
+        screen.addPreference(mStoragePreference);
+    }
+
+    @Override
+    public boolean handlePreferenceTreeClick(Preference preference) {
+        if (preference != null && mStoragePreference == preference) {
+            Bundle args = new Bundle(2);
+            args.putInt(StorageProfileFragment.USER_ID_EXTRA, mUser.id);
+            args.putString(VolumeInfo.EXTRA_VOLUME_ID, VolumeInfo.ID_PRIVATE_INTERNAL);
+            Intent intent = Utils.onBuildStartFragmentIntent(mContext,
+                    StorageProfileFragment.class.getName(), args, null, 0,
+                    mUser.name, false, MetricsProto.MetricsEvent.DEVICEINFO_STORAGE);
+            intent.putExtra(SettingsDrawerActivity.EXTRA_SHOW_MENU, true);
+            mContext.startActivity(intent);
+            return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public void handleResult(SparseArray<StorageAsyncLoader.AppsStorageResult> stats) {
+        Preconditions.checkNotNull(stats);
+
+        int userId = mUser.id;
+        StorageAsyncLoader.AppsStorageResult result = stats.get(userId);
+        if (result != null) {
+            setSize(result.externalStats.totalBytes);
+        }
+    }
+
+    /**
+     * Sets the size for the preference using a byte count.
+     */
+    public void setSize(long size) {
+        if (mStoragePreference != null) {
+            mStoragePreference.setStorageSize(size);
+        }
+    }
+}
diff --git a/src/com/android/settings/fuelgauge/PowerUsageBase.java b/src/com/android/settings/fuelgauge/PowerUsageBase.java
index 3d27d22..0236a30 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageBase.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageBase.java
@@ -101,27 +101,6 @@
         }
     }
 
-    @Override
-    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
-        super.onCreateOptionsMenu(menu, inflater);
-        MenuItem refresh = menu.add(0, MENU_STATS_REFRESH, 0, R.string.menu_stats_refresh)
-                .setIcon(com.android.internal.R.drawable.ic_menu_refresh)
-                .setAlphabeticShortcut('r');
-        refresh.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM |
-                MenuItem.SHOW_AS_ACTION_WITH_TEXT);
-    }
-
-    public boolean onOptionsItemSelected(MenuItem item) {
-        switch (item.getItemId()) {
-            case MENU_STATS_REFRESH:
-                mStatsHelper.clearStats();
-                refreshStats();
-                mHandler.removeMessages(MSG_REFRESH_STATS);
-                return true;
-        }
-        return super.onOptionsItemSelected(item);
-    }
-
     protected void refreshStats() {
         mStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, mUm.getUserProfiles());
     }
diff --git a/src/com/android/settings/search/SearchIndexableResources.java b/src/com/android/settings/search/SearchIndexableResources.java
index 07e1459..956ac0b 100644
--- a/src/com/android/settings/search/SearchIndexableResources.java
+++ b/src/com/android/settings/search/SearchIndexableResources.java
@@ -45,6 +45,7 @@
 import com.android.settings.datausage.DataUsageMeteredSettings;
 import com.android.settings.datausage.DataUsageSummary;
 import com.android.settings.deviceinfo.StorageDashboardFragment;
+import com.android.settings.deviceinfo.StorageProfileFragment;
 import com.android.settings.deviceinfo.StorageSettings;
 import com.android.settings.display.ScreenZoomSettings;
 import com.android.settings.enterprise.EnterprisePrivacySettings;
diff --git a/src/com/android/settings/search2/DatabaseResultLoader.java b/src/com/android/settings/search2/DatabaseResultLoader.java
index b7f5dd7..ad1e6a9 100644
--- a/src/com/android/settings/search2/DatabaseResultLoader.java
+++ b/src/com/android/settings/search2/DatabaseResultLoader.java
@@ -144,7 +144,7 @@
         results.addAll(secondaryResults);
         results.addAll(tertiaryResults);
 
-        return results;
+        return removeDuplicates(results);
     }
 
     @Override
@@ -300,4 +300,55 @@
         }
         return selection;
     }
+
+    /**
+     * Goes through the list of search results and verifies that none of the results are duplicates.
+     * A duplicate is quantified by a result with the same Title and the same non-empty Summary.
+     *
+     * The method walks through the results starting with the highest priority result. It removes
+     * the duplicates by doing the first rule that applies below:
+     * - If a result is inline, remove the intent result.
+     * - Remove the lower rank item.
+     * @param results A list of results with potential duplicates
+     * @return The list of results with duplicates removed.
+     */
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    List<SearchResult> removeDuplicates(List<SearchResult> results) {
+        SearchResult primaryResult, secondaryResult;
+
+        // We accept the O(n^2) solution because the number of results is small.
+        for (int i = results.size() - 1; i >= 0; i--) {
+            secondaryResult = results.get(i);
+
+            for (int j = i - 1; j >= 0; j--) {
+                primaryResult = results.get(j);
+                if (areDuplicateResults(primaryResult, secondaryResult)) {
+
+                    if (primaryResult.viewType != ResultPayload.PayloadType.INTENT) {
+                        // Case where both payloads are inline
+                        results.remove(i);
+                        break;
+                    } else if (secondaryResult.viewType != ResultPayload.PayloadType.INTENT) {
+                        // Case where only second result is inline
+                        results.remove(j);
+                        i--; // shift the top index to reflect the lower element being removed
+                    } else {
+                        // Case where both payloads are intent
+                        results.remove(i);
+                    }
+                }
+            }
+        }
+        return results;
+    }
+
+    /**
+     * @return True when the two {@link SearchResult SearchResults} have the same title, and the same
+     * non-empty summary.
+     */
+    private boolean areDuplicateResults(SearchResult primary, SearchResult secondary) {
+        return TextUtils.equals(primary.title, secondary.title)
+                && TextUtils.equals(primary.summary, secondary.summary)
+                && !TextUtils.isEmpty(primary.summary);
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/settings/wifi/WifiSettings.java b/src/com/android/settings/wifi/WifiSettings.java
index a903c09..d97b461 100644
--- a/src/com/android/settings/wifi/WifiSettings.java
+++ b/src/com/android/settings/wifi/WifiSettings.java
@@ -194,7 +194,6 @@
 
         mConnectedAccessPointPreferenceCategory =
                 (PreferenceCategory) findPreference(PREF_KEY_CONNECTED_ACCESS_POINTS);
-        mConnectedAccessPointPreferenceCategory.setVisible(false); // initially hidden
 
         mAccessPointsPreferenceCategory =
                 (PreferenceCategory) findPreference(PREF_KEY_ACCESS_POINTS);
@@ -799,6 +798,7 @@
         if (pref == null) {
             pref = createLongPressActionPointPreference(connectedAp);
         }
+        pref.refresh();
         mConnectedAccessPointPreferenceCategory.addPreference(pref);
         mConnectedAccessPointPreferenceCategory.setVisible(true);
     }
diff --git a/tests/robotests/assets/grandfather_not_implementing_index_provider b/tests/robotests/assets/grandfather_not_implementing_index_provider
index 5e134bf..76d1013 100644
--- a/tests/robotests/assets/grandfather_not_implementing_index_provider
+++ b/tests/robotests/assets/grandfather_not_implementing_index_provider
@@ -4,4 +4,5 @@
 com.android.settings.inputmethod.InputAndGestureSettings
 com.android.settings.accounts.AccountDetailDashboardFragment
 com.android.settings.gestures.GestureSettings
-com.android.settings.fuelgauge.PowerUsageDetail
\ No newline at end of file
+com.android.settings.fuelgauge.PowerUsageDetail
+com.android.settings.deviceinfo.StorageProfileFragment
diff --git a/tests/robotests/src/com/android/settings/UtilsTest.java b/tests/robotests/src/com/android/settings/UtilsTest.java
index 90a6e05..324e751 100644
--- a/tests/robotests/src/com/android/settings/UtilsTest.java
+++ b/tests/robotests/src/com/android/settings/UtilsTest.java
@@ -1,13 +1,25 @@
 package com.android.settings;
 
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.os.storage.DiskInfo;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
 import android.text.format.DateUtils;
-import java.net.InetAddress;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -16,9 +28,7 @@
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
+import java.net.InetAddress;
 
 @RunWith(SettingsRobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
@@ -97,4 +107,13 @@
 
         assertThat(Utils.formatElapsedTime(mContext, testMillis, false)).isEqualTo(expectedTime);
     }
+
+    @Test
+    public void testInitializeVolumeDoesntBreakOnNullVolume() {
+        VolumeInfo info = new VolumeInfo("id", 0, new DiskInfo("id", 0), "");
+        StorageManager storageManager = mock(StorageManager.class, RETURNS_DEEP_STUBS);
+        when(storageManager.findVolumeById(anyString())).thenReturn(info);
+
+        Utils.maybeInitializeVolume(storageManager, new Bundle());
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/applications/MusicViewHolderControllerTest.java b/tests/robotests/src/com/android/settings/applications/MusicViewHolderControllerTest.java
index 4af0707..592293d 100644
--- a/tests/robotests/src/com/android/settings/applications/MusicViewHolderControllerTest.java
+++ b/tests/robotests/src/com/android/settings/applications/MusicViewHolderControllerTest.java
@@ -32,8 +32,8 @@
 
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
-import com.android.settings.deviceinfo.storage.StorageStatsSource;
 import com.android.settingslib.deviceinfo.StorageVolumeProvider;
+import com.android.settingslib.applications.StorageStatsSource;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultBrowserPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultBrowserPreferenceControllerTest.java
index 10cfba0..644a942 100644
--- a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultBrowserPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultBrowserPreferenceControllerTest.java
@@ -36,7 +36,9 @@
 import org.robolectric.annotation.Config;
 import org.robolectric.util.ReflectionHelpers;
 
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 
 import static com.google.common.truth.Truth.assertThat;
 import static org.mockito.Matchers.any;
@@ -68,7 +70,18 @@
     }
 
     @Test
-    public void isAlwaysAvailable() {
+    public void isAvailable_noBrowser_shouldReturnFalse() {
+        when(mPackageManager.queryIntentActivitiesAsUser(any(Intent.class), anyInt(), anyInt()))
+                .thenReturn(null);
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void isAvailable_hasBrowser_shouldReturnTrue() {
+        final List<ResolveInfo> candidates = new ArrayList<>();
+        candidates.add(new ResolveInfo());
+        when(mPackageManager.queryIntentActivitiesAsUser(any(Intent.class), anyInt(), anyInt()))
+                .thenReturn(candidates);
         assertThat(mController.isAvailable()).isTrue();
     }
 
diff --git a/tests/robotests/src/com/android/settings/deviceinfo/StorageDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/deviceinfo/StorageDashboardFragmentTest.java
index f16304e..e2a46de 100644
--- a/tests/robotests/src/com/android/settings/deviceinfo/StorageDashboardFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/deviceinfo/StorageDashboardFragmentTest.java
@@ -79,11 +79,4 @@
         assertThat(indexRes).isNotNull();
         assertThat(indexRes.get(0).xmlResId).isEqualTo(mFragment.getPreferenceScreenResId());
     }
-
-    @Test
-    public void testInitializeVolumeDoesntBreakOnNullVolume() {
-        VolumeInfo info = new VolumeInfo("id", 0, new DiskInfo("id", 0), "");
-        when(mStorageManager.findVolumeById(anyString())).thenReturn(info);
-        mFragment.initializeVolume(mStorageManager, new Bundle());
-    }
 }
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/deviceinfo/storage/SecondaryUserControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/storage/SecondaryUserControllerTest.java
index b11132d..7222f53 100644
--- a/tests/robotests/src/com/android/settings/deviceinfo/storage/SecondaryUserControllerTest.java
+++ b/tests/robotests/src/com/android/settings/deviceinfo/storage/SecondaryUserControllerTest.java
@@ -28,11 +28,13 @@
 import android.support.v7.preference.Preference;
 import android.support.v7.preference.PreferenceGroup;
 import android.support.v7.preference.PreferenceScreen;
+import android.util.SparseArray;
 
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
 import com.android.settings.applications.UserManagerWrapper;
 import com.android.settings.core.PreferenceController;
+import com.android.settingslib.applications.StorageStatsSource;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -130,7 +132,7 @@
     }
 
     @Test
-    public void profilesOfPrimaryUserAreIgnored() throws Exception {
+    public void profilesOfPrimaryUserAreNotIgnored() throws Exception {
         ArrayList<UserInfo> userInfos = new ArrayList<>();
         UserInfo secondaryUser = new UserInfo();
         secondaryUser.id = mPrimaryUser.id;
@@ -142,7 +144,31 @@
         List<PreferenceController> controllers =
                 SecondaryUserController.getSecondaryUserControllers(mContext, mUserManager);
 
-        assertThat(controllers).hasSize(1);
-        assertThat(controllers.get(0) instanceof SecondaryUserController).isFalse();
+        assertThat(controllers).hasSize(2);
+        assertThat(controllers.get(0) instanceof UserProfileController).isTrue();
+        assertThat(controllers.get(1) instanceof SecondaryUserController).isFalse();
+    }
+
+    @Test
+    public void controllerUpdatesPreferenceOnAcceptingResult() throws Exception {
+        mPrimaryUser.name = TEST_NAME;
+        mPrimaryUser.id = 10;
+        PreferenceScreen screen = mock(PreferenceScreen.class);
+        PreferenceGroup group = mock(PreferenceGroup.class);
+        when(screen.findPreference(anyString())).thenReturn(group);
+        when(group.getKey()).thenReturn(TARGET_PREFERENCE_GROUP_KEY);
+        mController.displayPreference(screen);
+        StorageAsyncLoader.AppsStorageResult userResult =
+                new StorageAsyncLoader.AppsStorageResult();
+        SparseArray<StorageAsyncLoader.AppsStorageResult> result = new SparseArray<>();
+        userResult.externalStats = new StorageStatsSource.ExternalStorageStats(99, 33, 33, 33);
+        result.put(10, userResult);
+
+        mController.handleResult(result);
+        final ArgumentCaptor<Preference> argumentCaptor = ArgumentCaptor.forClass(Preference.class);
+        verify(group).addPreference(argumentCaptor.capture());
+        Preference preference = argumentCaptor.getValue();
+
+        assertThat(preference.getSummary()).isEqualTo("99.00B");
     }
 }
diff --git a/tests/robotests/src/com/android/settings/deviceinfo/storage/UserProfileControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/storage/UserProfileControllerTest.java
new file mode 100644
index 0000000..2cd4f76
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/deviceinfo/storage/UserProfileControllerTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.deviceinfo.storage;
+
+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 android.content.Context;
+import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.util.SparseArray;
+
+import com.android.settings.SettingsActivity;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.SubSettings;
+import com.android.settings.TestConfig;
+import com.android.settings.applications.UserManagerWrapper;
+import com.android.settings.deviceinfo.StorageProfileFragment;
+import com.android.settingslib.applications.StorageStatsSource;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class UserProfileControllerTest {
+    private static final String TEST_NAME = "Fred";
+
+    @Mock
+    private UserManagerWrapper mUserManager;
+
+    private Context mContext;
+    private UserProfileController mController;
+    private UserInfo mPrimaryProfile;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mContext = spy(RuntimeEnvironment.application);
+        mPrimaryProfile = new UserInfo();
+        mController = new UserProfileController(mContext, mPrimaryProfile, 0);
+    }
+
+    @Test
+    public void controllerAddsPrimaryProfilePreference() throws Exception {
+        mPrimaryProfile.name = TEST_NAME;
+        mPrimaryProfile.id = 10;
+        PreferenceScreen screen = mock(PreferenceScreen.class);
+        mController.displayPreference(screen);
+
+        final ArgumentCaptor<Preference> argumentCaptor = ArgumentCaptor.forClass(Preference.class);
+        verify(screen).addPreference(argumentCaptor.capture());
+        Preference preference = argumentCaptor.getValue();
+
+        assertThat(preference.getTitle()).isEqualTo(TEST_NAME);
+        assertThat(preference.getKey()).isEqualTo("pref_profile_10");
+    }
+
+    @Test
+    public void tappingProfilePreferenceSendsToStorageProfileFragment() throws Exception {
+        mPrimaryProfile.name = TEST_NAME;
+        mPrimaryProfile.id = 10;
+        PreferenceScreen screen = mock(PreferenceScreen.class);
+        mController.displayPreference(screen);
+
+        final ArgumentCaptor<Preference> argumentCaptor = ArgumentCaptor.forClass(Preference.class);
+        verify(screen).addPreference(argumentCaptor.capture());
+        Preference preference = argumentCaptor.getValue();
+        assertThat(mController.handlePreferenceTreeClick(preference)).isTrue();
+        final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).startActivity(intentCaptor.capture());
+
+        Intent intent = intentCaptor.getValue();
+        assertThat(intent.getComponent().getClassName()).isEqualTo(SubSettings.class.getName());
+        assertThat(intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT)).isEqualTo(
+                StorageProfileFragment.class.getName());
+    }
+
+    @Test
+    public void acceptingResultUpdatesPreferenceSize() throws Exception {
+        mPrimaryProfile.name = TEST_NAME;
+        mPrimaryProfile.id = 10;
+        PreferenceScreen screen = mock(PreferenceScreen.class);
+        mController.displayPreference(screen);
+        SparseArray<StorageAsyncLoader.AppsStorageResult> result = new SparseArray<>();
+        StorageAsyncLoader.AppsStorageResult userResult =
+                new StorageAsyncLoader.AppsStorageResult();
+        userResult.externalStats = new StorageStatsSource.ExternalStorageStats(99, 33, 33, 33);
+        result.put(10, userResult);
+
+        mController.handleResult(result);
+        final ArgumentCaptor<Preference> argumentCaptor = ArgumentCaptor.forClass(Preference.class);
+        verify(screen).addPreference(argumentCaptor.capture());
+        Preference preference = argumentCaptor.getValue();
+
+        assertThat(preference.getSummary()).isEqualTo("99.00B");
+    }
+}
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java
index 72f6a87..dab39e4 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java
@@ -88,8 +88,6 @@
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private Menu mMenu;
     @Mock
-    private MenuItem mRefreshMenu;
-    @Mock
     private MenuItem mAdditionalBatteryInfoMenu;
     @Mock
     private MenuItem mToggleAppsMenu;
@@ -142,11 +140,6 @@
 
         mFragment = new TestFragment(mContext);
 
-        when(mMenu.add(Menu.NONE, MENU_STATS_REFRESH, Menu.NONE,
-                R.string.menu_stats_refresh)
-                .setIcon(com.android.internal.R.drawable.ic_menu_refresh)
-                .setAlphabeticShortcut('r'))
-                .thenReturn(mRefreshMenu);
         when(mAdditionalBatteryInfoMenu.getItemId())
                 .thenReturn(MENU_ADDITIONAL_BATTERY_INFO);
         when(mToggleAppsMenu.getItemId()).thenReturn(MENU_TOGGLE_APPS);
diff --git a/tests/robotests/src/com/android/settings/search2/DatabaseResultLoaderTest.java b/tests/robotests/src/com/android/settings/search2/DatabaseResultLoaderTest.java
index 3a21bb2..72c658f 100644
--- a/tests/robotests/src/com/android/settings/search2/DatabaseResultLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/search2/DatabaseResultLoaderTest.java
@@ -19,6 +19,7 @@
 
 import android.content.ContentValues;
 import android.content.Context;
+import android.content.Intent;
 import android.database.sqlite.SQLiteDatabase;
 
 import com.android.settings.SettingsRobolectricTestRunner;
@@ -38,6 +39,7 @@
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 
+import java.util.ArrayList;
 import java.util.List;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -58,6 +60,15 @@
     private Context mContext;
     private DatabaseResultLoader loader;
 
+    private final String titleOne = "titleOne";
+    private final String titleTwo = "titleTwo";
+    private final String titleThree = "titleThree";
+    private final String titleFour = "titleFour";
+    private final String summaryOne = "summaryOne";
+    private final String summaryTwo = "summaryTwo";
+    private final String summaryThree = "summaryThree";
+    private final String summaryFour = "summaryFour";
+
     SQLiteDatabase mDb;
 
     @Before
@@ -104,49 +115,49 @@
     }
 
     @Test
-    public void testSpecialCaseWord_MatchesNonPrefix() {
+    public void testSpecialCaseWord_matchesNonPrefix() {
         insertSpecialCase("Data usage");
         loader = new DatabaseResultLoader(mContext, "usage", mSiteMapManager);
         assertThat(loader.loadInBackground().size()).isEqualTo(1);
     }
 
     @Test
-    public void testSpecialCaseSpace_Matches() {
+    public void testSpecialCaseSpace_matches() {
         insertSpecialCase("space");
         loader = new DatabaseResultLoader(mContext, " space ", mSiteMapManager);
         assertThat(loader.loadInBackground().size()).isEqualTo(1);
     }
 
     @Test
-    public void testSpecialCaseDash_MatchesWordNoDash() {
+    public void testSpecialCaseDash_matchesWordNoDash() {
         insertSpecialCase("wi-fi calling");
         loader = new DatabaseResultLoader(mContext, "wifi", mSiteMapManager);
         assertThat(loader.loadInBackground().size()).isEqualTo(1);
     }
 
     @Test
-    public void testSpecialCaseDash_MatchesWordWithDash() {
+    public void testSpecialCaseDash_matchesWordWithDash() {
         insertSpecialCase("priorités seulment");
         loader = new DatabaseResultLoader(mContext, "priorités", mSiteMapManager);
         assertThat(loader.loadInBackground().size()).isEqualTo(1);
     }
 
     @Test
-    public void testSpecialCaseDash_MatchesWordWithoutDash() {
+    public void testSpecialCaseDash_matchesWordWithoutDash() {
         insertSpecialCase("priorités seulment");
         loader = new DatabaseResultLoader(mContext, "priorites", mSiteMapManager);
         assertThat(loader.loadInBackground().size()).isEqualTo(1);
     }
 
     @Test
-    public void testSpecialCaseDash_MatchesEntireQueryWithoutDash() {
+    public void testSpecialCaseDash_matchesEntireQueryWithoutDash() {
         insertSpecialCase("wi-fi calling");
         loader = new DatabaseResultLoader(mContext, "wifi calling", mSiteMapManager);
         assertThat(loader.loadInBackground().size()).isEqualTo(1);
     }
 
     @Test
-    public void testSpecialCasePrefix_MatchesPrefixOfEntry() {
+    public void testSpecialCasePrefix_matchesPrefixOfEntry() {
         insertSpecialCase("Photos");
         loader = new DatabaseResultLoader(mContext, "pho", mSiteMapManager);
         assertThat(loader.loadInBackground().size()).isEqualTo(1);
@@ -160,14 +171,14 @@
     }
 
     @Test
-    public void testSpecialCaseMultiWordPrefix_MatchesPrefixOfEntry() {
+    public void testSpecialCaseMultiWordPrefix_matchesPrefixOfEntry() {
         insertSpecialCase("Apps Notifications");
         loader = new DatabaseResultLoader(mContext, "Apps", mSiteMapManager);
         assertThat(loader.loadInBackground().size()).isEqualTo(1);
     }
 
     @Test
-    public void testSpecialCaseMultiWordPrefix_MatchesSecondWordPrefixOfEntry() {
+    public void testSpecialCaseMultiWordPrefix_matchesSecondWordPrefixOfEntry() {
         insertSpecialCase("Apps Notifications");
         loader = new DatabaseResultLoader(mContext, "Not", mSiteMapManager);
         assertThat(loader.loadInBackground().size()).isEqualTo(1);
@@ -188,21 +199,188 @@
     }
 
     @Test
-    public void testSpecialCaseMultiWordPrefixWithSpecial_MatchesPrefixOfEntry() {
+    public void testSpecialCaseMultiWordPrefixWithSpecial_matchesPrefixOfEntry() {
         insertSpecialCase("Apps & Notifications");
         loader = new DatabaseResultLoader(mContext, "App", mSiteMapManager);
         assertThat(loader.loadInBackground().size()).isEqualTo(1);
     }
 
     @Test
-    public void testSpecialCaseMultiWordPrefixWithSpecial_MatchesPrefixOfSecondEntry() {
+    public void testSpecialCaseMultiWordPrefixWithSpecial_matchesPrefixOfSecondEntry() {
         insertSpecialCase("Apps & Notifications");
         loader = new DatabaseResultLoader(mContext, "No", mSiteMapManager);
         assertThat(loader.loadInBackground().size()).isEqualTo(1);
     }
 
     @Test
-    public void testSpecialCaseTwoWords_FirstWordMatches_RanksHigher() {
+    public void testDeDupe_noDuplicates_originalListReturn() {
+        // Three elements with unique titles and summaries
+        List<SearchResult> results = new ArrayList();
+        IntentPayload intentPayload = new IntentPayload(new Intent());
+
+        SearchResult.Builder builder = new SearchResult.Builder();
+        builder.addTitle(titleOne)
+                .addSummary(summaryOne)
+                .addPayload(intentPayload);
+        SearchResult resultOne = builder.build();
+        results.add(resultOne);
+
+        builder.addTitle(titleTwo)
+                .addSummary(summaryTwo);
+        SearchResult resultTwo = builder.build();
+        results.add(resultTwo);
+
+        builder.addTitle(titleThree)
+                .addSummary(summaryThree);
+        SearchResult resultThree = builder.build();
+        results.add(resultThree);
+
+        loader = new DatabaseResultLoader(mContext, "", null);
+        loader.removeDuplicates(results);
+        assertThat(results.size()).isEqualTo(3);
+        assertThat(results.get(0)).isEqualTo(resultOne);
+        assertThat(results.get(1)).isEqualTo(resultTwo);
+        assertThat(results.get(2)).isEqualTo(resultThree);
+    }
+
+    @Test
+    public void testDeDupe_oneDuplicate_duplicateRemoved() {
+        List<SearchResult> results = new ArrayList();
+        IntentPayload intentPayload = new IntentPayload(new Intent());
+
+        SearchResult.Builder builder = new SearchResult.Builder();
+        builder.addTitle(titleOne)
+                .addSummary(summaryOne)
+                .addRank(0)
+                .addPayload(intentPayload);
+        SearchResult resultOne = builder.build();
+        results.add(resultOne);
+
+        // Duplicate of the first element
+        builder.addTitle(titleOne)
+                .addSummary(summaryOne)
+                .addRank(1);
+        SearchResult resultTwo = builder.build();
+        results.add(resultTwo);
+
+        // Unique
+        builder.addTitle(titleThree)
+                .addSummary(summaryThree);
+        SearchResult resultThree = builder.build();
+        results.add(resultThree);
+
+        loader = new DatabaseResultLoader(mContext, "", null);
+        loader.removeDuplicates(results);
+        assertThat(results.size()).isEqualTo(2);
+        assertThat(results.get(0)).isEqualTo(resultOne);
+        assertThat(results.get(1)).isEqualTo(resultThree);
+    }
+
+    @Test
+    public void testDeDupe_firstDupeInline_secondDuplicateRemoved() {
+        List<SearchResult> results = new ArrayList();
+        InlineSwitchPayload inlinePayload = new InlineSwitchPayload("", 0,
+                null);
+        IntentPayload intentPayload = new IntentPayload(new Intent());
+
+        SearchResult.Builder builder = new SearchResult.Builder();
+        // Inline result
+        builder.addTitle(titleOne)
+                .addSummary(summaryOne)
+                .addRank(0)
+                .addPayload(inlinePayload);
+        SearchResult resultOne = builder.build();
+        results.add(resultOne);
+
+        // Duplicate of first result, but Intent Result. Should be removed.
+        builder.addTitle(titleOne)
+                .addSummary(summaryOne)
+                .addRank(1)
+                .addPayload(intentPayload);
+        SearchResult resultTwo = builder.build();
+        results.add(resultTwo);
+
+        // Unique
+        builder.addTitle(titleThree)
+                .addSummary(summaryThree);
+        SearchResult resultThree = builder.build();
+        results.add(resultThree);
+
+        loader = new DatabaseResultLoader(mContext, "", null);
+        loader.removeDuplicates(results);
+        assertThat(results.size()).isEqualTo(2);
+        assertThat(results.get(0)).isEqualTo(resultOne);
+        assertThat(results.get(1)).isEqualTo(resultThree);
+    }
+
+    @Test
+    public void testDeDupe_secondDupeInline_firstDuplicateRemoved() {
+        /*
+         * Create a list as follows:
+         * (5) Intent Four
+         * (4) Inline Two
+         * (3) Intent Three
+         * (2) Intent Two
+         * (1) Intent One
+         *
+         * After removing duplicates:
+         * (4) Intent Four
+         * (3) Inline Two
+         * (2) Intent Three
+         * (1) Intent One
+         */
+        List<SearchResult> results = new ArrayList();
+        InlineSwitchPayload inlinePayload = new InlineSwitchPayload("", 0,
+                null);
+        IntentPayload intentPayload = new IntentPayload(new Intent());
+
+
+        SearchResult.Builder builder = new SearchResult.Builder();
+        // Intent One
+        builder.addTitle(titleOne)
+                .addSummary(summaryOne)
+                .addPayload(intentPayload);
+        SearchResult resultOne = builder.build();
+        results.add(resultOne);
+
+        // Intent Two
+        builder.addTitle(titleTwo)
+                .addSummary(summaryTwo)
+                .addPayload(intentPayload);
+        SearchResult resultTwo = builder.build();
+        results.add(resultTwo);
+
+        // Intent Three
+        builder.addTitle(titleThree)
+                .addSummary(summaryThree);
+        SearchResult resultThree = builder.build();
+        results.add(resultThree);
+
+        // Inline Two
+        builder.addTitle(titleTwo)
+                .addSummary(summaryTwo)
+                .addPayload(inlinePayload);
+        SearchResult resultFour = builder.build();
+        results.add(resultFour);
+
+        // Intent Four
+        builder.addTitle(titleFour)
+                .addSummary(summaryOne)
+                .addPayload(intentPayload);
+        SearchResult resultFive = builder.build();
+        results.add(resultFive);
+
+        loader = new DatabaseResultLoader(mContext, "", null);
+        loader.removeDuplicates(results);
+        assertThat(results.size()).isEqualTo(4);
+        assertThat(results.get(0)).isEqualTo(resultOne);
+        assertThat(results.get(1)).isEqualTo(resultThree);
+        assertThat(results.get(2)).isEqualTo(resultFour);
+        assertThat(results.get(3)).isEqualTo(resultFive);
+    }
+
+    @Test
+    public void testSpecialCaseTwoWords_firstWordMatches_ranksHigher() {
         final String caseOne = "Apple pear";
         final String caseTwo = "Banana apple";
         insertSpecialCase(caseOne);
diff --git a/tests/unit/src/com/android/settings/TetherServiceTest.java b/tests/unit/src/com/android/settings/TetherServiceTest.java
index bec3e7e..899ea7a 100644
--- a/tests/unit/src/com/android/settings/TetherServiceTest.java
+++ b/tests/unit/src/com/android/settings/TetherServiceTest.java
@@ -415,6 +415,7 @@
 
         private void sendResponse(int response, Context context) {
             Intent responseIntent = new Intent(TEST_RESPONSE_ACTION);
+            responseIntent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
             responseIntent.putExtra(TetherService.EXTRA_RESULT, response);
             context.sendBroadcast(
                     responseIntent, android.Manifest.permission.CONNECTIVITY_INTERNAL);