Add support for user profiles to the Storage Settings.

This adds new preferences for each profile (such as the work
profile) and defines a new view for viewing the storage
breakdown for the individual profile. The functionality closely
mimics the presentation on the main view, but without the system-wide
breakdown and without any additional users/profiles.

Bug: 34715777
Test: Settings Robotests

Change-Id: I19d449b648c6566331fd02e45c2e45f8c74ea7e7
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/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/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/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/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/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/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