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/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;