Surface storage usage of other users.

Bring up separate StorageMeasurement sessions bound to other users
on device, and surface their total usage as teal colors.  Exclude
app code size when measuring secondary users.  Shift preferences to
using keys instead of fragile index ordering.

Bug: 7003520
Change-Id: I214d0b223e53955df71104502596743f049f2027
diff --git a/res/values/colors.xml b/res/values/colors.xml
index f81d614..d6e9150 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -25,6 +25,8 @@
     <color name="memory_dcim">#793A7F</color>
     <color name="memory_music">#8E562A</color>
     <color name="memory_misc">#7C3030</color>
+    <color name="memory_user_light">#479392</color>
+    <color name="memory_user_dark">#316665</color>
 
     <color name="crypt_keeper_clock_background">#ff9a9a9a</color>
     <color name="crypt_keeper_clock_foreground">#ff666666</color>
@@ -34,4 +36,4 @@
     <color name="title_color">@android:color/holo_blue_light</color>
     <color name="setup_divider_color">#333333</color>
 
-</resources>
\ No newline at end of file
+</resources>
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index 8e3987f..d5a90f6 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -103,6 +103,7 @@
             R.id.device_section,
             R.id.sound_settings,
             R.id.display_settings,
+            R.id.storage_settings,
             R.id.application_settings,
             R.id.personal_section,
             R.id.security_settings,
diff --git a/src/com/android/settings/deviceinfo/MiscFilesHandler.java b/src/com/android/settings/deviceinfo/MiscFilesHandler.java
index 2f9697f..5c803ad 100644
--- a/src/com/android/settings/deviceinfo/MiscFilesHandler.java
+++ b/src/com/android/settings/deviceinfo/MiscFilesHandler.java
@@ -20,6 +20,7 @@
 import android.app.ListActivity;
 import android.content.Context;
 import android.os.Bundle;
+import android.os.UserHandle;
 import android.os.storage.StorageVolume;
 import android.text.format.Formatter;
 import android.util.Log;
@@ -193,8 +194,8 @@
             mContext = activity;
             final StorageVolume storageVolume = activity.getIntent().getParcelableExtra(
                     StorageVolume.EXTRA_STORAGE_VOLUME);
-            StorageMeasurement mMeasurement = 
-                StorageMeasurement.getInstance(activity, storageVolume, false /*Unused as a key*/);
+            StorageMeasurement mMeasurement = StorageMeasurement.getInstance(
+                    activity, storageVolume, new UserHandle(UserHandle.USER_CURRENT), false);
             if (mMeasurement == null) return;
             mData = (ArrayList<StorageMeasurement.FileInfo>) mMeasurement.mFileInfoForMisc;
             if (mData != null) {
diff --git a/src/com/android/settings/deviceinfo/StorageItemPreference.java b/src/com/android/settings/deviceinfo/StorageItemPreference.java
new file mode 100644
index 0000000..6a76a86
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/StorageItemPreference.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.deviceinfo;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.RectShape;
+import android.preference.Preference;
+
+import com.android.settings.R;
+
+public class StorageItemPreference extends Preference {
+
+    private int mColor = Color.MAGENTA;
+
+    public StorageItemPreference(Context context, String key, int titleRes, int colorRes) {
+        this(context, key, context.getText(titleRes), colorRes);
+    }
+
+    public StorageItemPreference(Context context, String key, CharSequence title, int colorRes) {
+        super(context);
+        //setLayoutResource(R.layout.app_percentage_item);
+
+        if (colorRes != 0) {
+            mColor = context.getResources().getColor(colorRes);
+
+            final Resources res = context.getResources();
+            final int width = res.getDimensionPixelSize(R.dimen.device_memory_usage_button_width);
+            final int height = res.getDimensionPixelSize(R.dimen.device_memory_usage_button_height);
+            setIcon(createRectShape(width, height, mColor));
+        }
+
+        setKey(key);
+        setTitle(title);
+        setSummary(R.string.memory_calculating_size);
+    }
+
+    private static ShapeDrawable createRectShape(int width, int height, int color) {
+        ShapeDrawable shape = new ShapeDrawable(new RectShape());
+        shape.setIntrinsicHeight(height);
+        shape.setIntrinsicWidth(width);
+        shape.getPaint().setColor(color);
+        return shape;
+    }
+
+    public int getColor() {
+        return mColor;
+    }
+}
diff --git a/src/com/android/settings/deviceinfo/StorageMeasurement.java b/src/com/android/settings/deviceinfo/StorageMeasurement.java
index 2792d09..ccdd600 100644
--- a/src/com/android/settings/deviceinfo/StorageMeasurement.java
+++ b/src/com/android/settings/deviceinfo/StorageMeasurement.java
@@ -32,18 +32,21 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.os.storage.StorageVolume;
 import android.util.Log;
+import android.util.Pair;
 
 import com.android.internal.app.IMediaContainerService;
+import com.android.internal.util.Preconditions;
+import com.google.android.collect.Maps;
 
 import java.io.File;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * Measure the memory for various systems.
@@ -86,9 +89,8 @@
 
     private final MeasurementHandler mHandler;
 
-    private static Map<StorageVolume, StorageMeasurement> sInstances =
-        new ConcurrentHashMap<StorageVolume, StorageMeasurement>();
-    private static StorageMeasurement sInternalInstance;
+    private static HashMap<Pair<StorageVolume, UserHandle>, StorageMeasurement>
+            sInstances = Maps.newHashMap();
 
     private volatile WeakReference<MeasurementReceiver> mReceiver;
 
@@ -99,19 +101,24 @@
     private long mMiscSize;
     private long[] mMediaSizes = new long[StorageVolumePreferenceCategory.sMediaCategories.length];
 
-    final private StorageVolume mStorageVolume;
-    final private boolean mIsPrimary;
-    final private boolean mIsInternal;
+    private final StorageVolume mStorageVolume;
+    private final UserHandle mUser;
+    private final boolean mIsPrimary;
+    private final boolean mIsInternal;
+
+    private boolean mIncludeAppCodeSize = true;
 
     List<FileInfo> mFileInfoForMisc;
 
     public interface MeasurementReceiver {
-        public void updateApproximate(Bundle bundle);
-        public void updateExact(Bundle bundle);
+        public void updateApproximate(StorageMeasurement meas, Bundle bundle);
+        public void updateExact(StorageMeasurement meas, Bundle bundle);
     }
 
-    private StorageMeasurement(Context context, StorageVolume storageVolume, boolean isPrimary) {
+    private StorageMeasurement(
+            Context context, StorageVolume storageVolume, UserHandle user, boolean isPrimary) {
         mStorageVolume = storageVolume;
+        mUser = Preconditions.checkNotNull(user);
         mIsInternal = storageVolume == null;
         mIsPrimary = !mIsInternal && isPrimary;
 
@@ -121,29 +128,33 @@
         mHandler = new MeasurementHandler(context, handlerThread.getLooper());
     }
 
+    public void setIncludeAppCodeSize(boolean include) {
+        mIncludeAppCodeSize = include;
+    }
+
     /**
      * Get the singleton of the StorageMeasurement class. The application
      * context is used to avoid leaking activities.
      * @param storageVolume The {@link StorageVolume} that will be measured
      * @param isPrimary true when this storage volume is the primary volume
      */
-    public static StorageMeasurement getInstance(Context context, StorageVolume storageVolume,
-            boolean isPrimary) {
-        if (storageVolume == null) {
-            if (sInternalInstance == null) {
-                sInternalInstance =
-                    new StorageMeasurement(context.getApplicationContext(), storageVolume, isPrimary);
+    public static StorageMeasurement getInstance(
+            Context context, StorageVolume storageVolume, UserHandle user, boolean isPrimary) {
+        final Pair<StorageVolume, UserHandle> key = new Pair<StorageVolume, UserHandle>(
+                storageVolume, user);
+        synchronized (sInstances) {
+            StorageMeasurement value = sInstances.get(key);
+            if (value == null) {
+                value = new StorageMeasurement(
+                        context.getApplicationContext(), storageVolume, user, isPrimary);
+                sInstances.put(key, value);
             }
-            return sInternalInstance;
+            return value;
         }
-        if (sInstances.containsKey(storageVolume)) {
-            return sInstances.get(storageVolume);
-        } else {
-            StorageMeasurement storageMeasurement =
-                new StorageMeasurement(context.getApplicationContext(), storageVolume, isPrimary);
-            sInstances.put(storageVolume, storageMeasurement);
-            return storageMeasurement;
-        }
+    }
+
+    public UserHandle getUser() {
+        return mUser;
     }
 
     public void setReceiver(MeasurementReceiver receiver) {
@@ -178,7 +189,7 @@
         bundle.putLong(TOTAL_SIZE, mTotalSize);
         bundle.putLong(AVAIL_SIZE, mAvailSize);
 
-        receiver.updateApproximate(bundle);
+        receiver.updateApproximate(this, bundle);
     }
 
     private void sendExactUpdate() {
@@ -198,7 +209,7 @@
         bundle.putLong(MISC_SIZE, mMiscSize);
         bundle.putLongArray(MEDIA_SIZES, mMediaSizes);
 
-        receiver.updateExact(bundle);
+        receiver.updateExact(this, bundle);
     }
 
     private class MeasurementHandler extends Handler {
@@ -265,7 +276,7 @@
                         } else {
                             Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
                             context.bindService(service, mDefContainerConn,
-                                    Context.BIND_AUTO_CREATE);
+                                    Context.BIND_AUTO_CREATE, mUser.getIdentifier());
                         }
                     }
                     break;
@@ -327,13 +338,19 @@
 
                 if (succeeded) {
                     if (mIsInternal) {
-                        mAppsSizeForThisStatsObserver += stats.codeSize + stats.dataSize;
+                        if (mIncludeAppCodeSize) {
+                            mAppsSizeForThisStatsObserver += stats.codeSize;
+                        }
+                        mAppsSizeForThisStatsObserver += stats.dataSize;
                     } else if (!Environment.isExternalStorageEmulated()) {
                         mAppsSizeForThisStatsObserver += stats.externalObbSize +
                                 stats.externalCodeSize + stats.externalDataSize +
                                 stats.externalCacheSize + stats.externalMediaSize;
                     } else {
-                        mAppsSizeForThisStatsObserver += stats.codeSize + stats.dataSize +
+                        if (mIncludeAppCodeSize) {
+                            mAppsSizeForThisStatsObserver += stats.codeSize;
+                        }
+                        mAppsSizeForThisStatsObserver += stats.dataSize +
                                 stats.externalCodeSize + stats.externalDataSize +
                                 stats.externalCacheSize + stats.externalMediaSize +
                                 stats.externalObbSize;
diff --git a/src/com/android/settings/deviceinfo/StorageVolumePreferenceCategory.java b/src/com/android/settings/deviceinfo/StorageVolumePreferenceCategory.java
index 156184a..cb4d1c0 100644
--- a/src/com/android/settings/deviceinfo/StorageVolumePreferenceCategory.java
+++ b/src/com/android/settings/deviceinfo/StorageVolumePreferenceCategory.java
@@ -16,20 +16,22 @@
 
 package com.android.settings.deviceinfo;
 
+import android.app.ActivityManagerNative;
 import android.app.ActivityThread;
 import android.app.DownloadManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.IPackageManager;
+import android.content.pm.UserInfo;
 import android.content.res.Resources;
-import android.graphics.drawable.ShapeDrawable;
-import android.graphics.drawable.shapes.RectShape;
 import android.hardware.usb.UsbManager;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.Message;
 import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.storage.StorageManager;
 import android.os.storage.StorageVolume;
 import android.preference.Preference;
@@ -38,69 +40,55 @@
 
 import com.android.settings.R;
 import com.android.settings.deviceinfo.StorageMeasurement.MeasurementReceiver;
+import com.google.android.collect.Lists;
 
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
-public class StorageVolumePreferenceCategory extends PreferenceCategory implements
-        MeasurementReceiver {
+public class StorageVolumePreferenceCategory extends PreferenceCategory
+        implements MeasurementReceiver {
+    private static final String KEY_TOTAL_SIZE = "total_size";
+    private static final String KEY_APPLICATIONS = "applications";
+    private static final String KEY_DCIM = "dcim"; // Pictures and Videos
+    private static final String KEY_MUSIC = "music";
+    private static final String KEY_DOWNLOADS = "downloads";
+    private static final String KEY_MISC = "misc";
+    private static final String KEY_AVAILABLE = "available";
+    private static final String KEY_USER_PREFIX = "user";
 
-    static final int TOTAL_SIZE = 0;
-    static final int APPLICATIONS = 1;
-    static final int DCIM = 2; // Pictures and Videos
-    static final int MUSIC = 3;
-    static final int DOWNLOADS = 4;
-    static final int MISC = 5;
-    static final int AVAILABLE = 6;
+    private static final int ORDER_USAGE_BAR = -2;
+    private static final int ORDER_STORAGE_LOW = -1;
 
     private UsageBarPreference mUsageBarPreference;
-    private Preference[] mPreferences;
     private Preference mMountTogglePreference;
     private Preference mFormatPreference;
     private Preference mStorageLow;
-    private int[] mColors;
 
-    private Resources mResources;
+    private final Resources mResources;
 
-    private StorageVolume mStorageVolume;
+    private final StorageVolume mStorageVolume;
+    private final StorageManager mStorageManager;
 
-    private StorageManager mStorageManager = null;
-
-    private StorageMeasurement mMeasurement;
+    /** Measurement for local user. */
+    private StorageMeasurement mLocalMeasure;
+    /** All used measurements, including other users. */
+    private List<StorageMeasurement> mAllMeasures = Lists.newArrayList();
 
     private boolean mAllowFormat;
+    private final boolean mMeasureUsers;
 
     private boolean mUsbConnected;
     private String mUsbFunction;
-
-    static class CategoryInfo {
-        final int mTitle;
-        final int mColor;
-
-        public CategoryInfo(int title, int color) {
-            mTitle = title;
-            mColor = color;
-        }
-    }
-
-    static final CategoryInfo[] sCategoryInfos = new CategoryInfo[] {
-        new CategoryInfo(R.string.memory_size, 0),
-        new CategoryInfo(R.string.memory_apps_usage, R.color.memory_apps_usage),
-        new CategoryInfo(R.string.memory_dcim_usage, R.color.memory_dcim),
-        new CategoryInfo(R.string.memory_music_usage, R.color.memory_music),
-        new CategoryInfo(R.string.memory_downloads_usage, R.color.memory_downloads),
-        new CategoryInfo(R.string.memory_media_misc_usage, R.color.memory_misc),
-        new CategoryInfo(R.string.memory_available, R.color.memory_avail),
-    };
+    private boolean mShowingApprox;
 
     public static final Set<String> sPathsExcludedForMisc = new HashSet<String>();
 
     static class MediaCategory {
         final String[] mDirPaths;
-        final int mCategory;
-        //final int mMediaType;
+        final String mCategory;
 
-        public MediaCategory(int category, String... directories) {
+        public MediaCategory(String category, String... directories) {
             mCategory = category;
             final int length = directories.length;
             mDirPaths = new String[length];
@@ -115,9 +103,9 @@
     }
 
     static final MediaCategory[] sMediaCategories = new MediaCategory[] {
-        new MediaCategory(DCIM, Environment.DIRECTORY_DCIM, Environment.DIRECTORY_MOVIES,
+        new MediaCategory(KEY_DCIM, Environment.DIRECTORY_DCIM, Environment.DIRECTORY_MOVIES,
                 Environment.DIRECTORY_PICTURES),
-        new MediaCategory(MUSIC, Environment.DIRECTORY_MUSIC, Environment.DIRECTORY_ALARMS,
+        new MediaCategory(KEY_MUSIC, Environment.DIRECTORY_MUSIC, Environment.DIRECTORY_ALARMS,
                 Environment.DIRECTORY_NOTIFICATIONS, Environment.DIRECTORY_RINGTONES,
                 Environment.DIRECTORY_PODCASTS)
     };
@@ -142,22 +130,36 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_UI_UPDATE_APPROXIMATE: {
-                    Bundle bundle = msg.getData();
+                    final UserHandle user = (UserHandle) msg.obj;
+                    final Bundle bundle = msg.getData();
                     final long totalSize = bundle.getLong(StorageMeasurement.TOTAL_SIZE);
                     final long availSize = bundle.getLong(StorageMeasurement.AVAIL_SIZE);
-                    updateApproximate(totalSize, availSize);
+
+                    if (user.getIdentifier() == UserHandle.USER_CURRENT) {
+                        updateApproximate(totalSize, availSize);
+                    }
                     break;
                 }
                 case MSG_UI_UPDATE_EXACT: {
-                    Bundle bundle = msg.getData();
+                    final UserHandle user = (UserHandle) msg.obj;
+                    final Bundle bundle = msg.getData();
                     final long totalSize = bundle.getLong(StorageMeasurement.TOTAL_SIZE);
                     final long availSize = bundle.getLong(StorageMeasurement.AVAIL_SIZE);
                     final long appsUsed = bundle.getLong(StorageMeasurement.APPS_USED);
                     final long downloadsSize = bundle.getLong(StorageMeasurement.DOWNLOADS_SIZE);
                     final long miscSize = bundle.getLong(StorageMeasurement.MISC_SIZE);
                     final long[] mediaSizes = bundle.getLongArray(StorageMeasurement.MEDIA_SIZES);
-                    updateExact(totalSize, availSize, appsUsed, downloadsSize, miscSize,
-                            mediaSizes);
+
+                    if (user.getIdentifier() == UserHandle.USER_CURRENT) {
+                        updateExact(totalSize, availSize, appsUsed, downloadsSize, miscSize,
+                                mediaSizes);
+                    } else {
+                        long usedSize = appsUsed + downloadsSize + miscSize;
+                        for (long mediaSize : mediaSizes) {
+                            usedSize += mediaSize;
+                        }
+                        updateUserExact(user, totalSize, usedSize);
+                    }
                     break;
                 }
             }
@@ -172,54 +174,97 @@
         mStorageManager = storageManager;
         setTitle(storageVolume != null ? storageVolume.getDescription(context)
                 : resources.getText(R.string.internal_storage));
-        mMeasurement = StorageMeasurement.getInstance(context, storageVolume, isPrimary);
-        mMeasurement.setReceiver(this);
+        mLocalMeasure = StorageMeasurement.getInstance(
+                context, storageVolume, new UserHandle(UserHandle.USER_CURRENT), isPrimary);
+        mAllMeasures.add(mLocalMeasure);
 
         // Cannot format emulated storage
         mAllowFormat = mStorageVolume != null && !mStorageVolume.isEmulated();
         // For now we are disabling reformatting secondary external storage
         // until some interoperability problems with MTP are fixed
         if (!isPrimary) mAllowFormat = false;
+
+        // Measure other users when showing primary emulated storage
+        mMeasureUsers = (mStorageVolume != null && mStorageVolume.isEmulated()) && isPrimary;
+    }
+
+    private void addStorageItem(String key, int titleRes, int colorRes) {
+        addPreference(new StorageItemPreference(getContext(), key, titleRes, colorRes));
+    }
+
+    private static String buildUserKey(UserHandle user) {
+        return KEY_USER_PREFIX + user.getIdentifier();
     }
 
     public void init() {
-        mUsageBarPreference = new UsageBarPreference(getContext());
+        final Context context = getContext();
 
-        final int width = (int) mResources.getDimension(R.dimen.device_memory_usage_button_width);
-        final int height = (int) mResources.getDimension(R.dimen.device_memory_usage_button_height);
+        mUsageBarPreference = new UsageBarPreference(context);
+        mUsageBarPreference.setOrder(ORDER_USAGE_BAR);
+        addPreference(mUsageBarPreference);
 
-        final int numberOfCategories = sCategoryInfos.length;
-        mPreferences = new Preference[numberOfCategories];
-        mColors = new int[numberOfCategories];
-        for (int i = 0; i < numberOfCategories; i++) {
-            final Preference preference = new Preference(getContext());
-            mPreferences[i] = preference;
-            preference.setTitle(sCategoryInfos[i].mTitle);
-            preference.setSummary(R.string.memory_calculating_size);
-            if (i != TOTAL_SIZE) {
-                // TOTAL_SIZE has no associated color
-                mColors[i] = mResources.getColor(sCategoryInfos[i].mColor);
-                preference.setIcon(createRectShape(width, height, mColors[i]));
+        addStorageItem(KEY_TOTAL_SIZE, R.string.memory_size, 0);
+        addStorageItem(KEY_APPLICATIONS, R.string.memory_apps_usage, R.color.memory_apps_usage);
+        addStorageItem(KEY_DCIM, R.string.memory_dcim_usage, R.color.memory_dcim);
+        addStorageItem(KEY_MUSIC, R.string.memory_music_usage, R.color.memory_music);
+        addStorageItem(KEY_DOWNLOADS, R.string.memory_downloads_usage, R.color.memory_downloads);
+        addStorageItem(KEY_MISC, R.string.memory_media_misc_usage, R.color.memory_misc);
+        addStorageItem(KEY_AVAILABLE, R.string.memory_available, R.color.memory_avail);
+
+        if (mMeasureUsers) {
+            final UserManager userManager = (UserManager) context.getSystemService(
+                    Context.USER_SERVICE);
+
+            final UserInfo currentUser;
+            try {
+                currentUser = ActivityManagerNative.getDefault().getCurrentUser();
+            } catch (RemoteException e) {
+                throw new RuntimeException("Failed to get current user");
+            }
+
+            int count = 0;
+            for (UserInfo info : userManager.getUsers()) {
+                // Only measure other users
+                if (info.id == currentUser.id) {
+                    continue;
+                }
+
+                final UserHandle user = new UserHandle(info.id);
+                final String key = buildUserKey(user);
+
+                final StorageMeasurement measure = StorageMeasurement.getInstance(
+                        context, mStorageVolume, user, true);
+                measure.setIncludeAppCodeSize(false);
+                mAllMeasures.add(measure);
+
+                final int colorRes = count++ % 2 == 0 ? R.color.memory_user_light
+                        : R.color.memory_user_dark;
+                addPreference(new StorageItemPreference(getContext(), key, info.name, colorRes));
             }
         }
 
-        mMountTogglePreference = new Preference(getContext());
+        mMountTogglePreference = new Preference(context);
         mMountTogglePreference.setTitle(R.string.sd_eject);
         mMountTogglePreference.setSummary(R.string.sd_eject_summary);
+        addPreference(mMountTogglePreference);
 
         if (mAllowFormat) {
-            mFormatPreference = new Preference(getContext());
+            mFormatPreference = new Preference(context);
             mFormatPreference.setTitle(R.string.sd_format);
             mFormatPreference.setSummary(R.string.sd_format_summary);
+            addPreference(mFormatPreference);
         }
 
         final IPackageManager pm = ActivityThread.getPackageManager();
         try {
             if (pm.isStorageLow()) {
-                mStorageLow = new Preference(getContext());
+                mStorageLow = new Preference(context);
+                mStorageLow.setOrder(ORDER_STORAGE_LOW);
                 mStorageLow.setTitle(R.string.storage_low_title);
                 mStorageLow.setSummary(R.string.storage_low_summary);
-            } else {
+                addPreference(mStorageLow);
+            } else if (mStorageLow != null) {
+                removePreference(mStorageLow);
                 mStorageLow = null;
             }
         } catch (RemoteException e) {
@@ -230,39 +275,8 @@
         return mStorageVolume;
     }
 
-    /**
-     * Successive mounts can change the list of visible preferences.
-     * This makes sure all preferences are visible and displayed in the right order.
-     */
-    private void resetPreferences() {
-        final int numberOfCategories = sCategoryInfos.length;
-
-        removePreference(mUsageBarPreference);
-        for (int i = 0; i < numberOfCategories; i++) {
-            removePreference(mPreferences[i]);
-        }
-        removePreference(mMountTogglePreference);
-        if (mFormatPreference != null) {
-            removePreference(mFormatPreference);
-        }
-
-        addPreference(mUsageBarPreference);
-        if (mStorageLow != null) {
-            addPreference(mStorageLow);
-        }
-        for (int i = 0; i < numberOfCategories; i++) {
-            addPreference(mPreferences[i]);
-        }
-        addPreference(mMountTogglePreference);
-        if (mFormatPreference != null) {
-            addPreference(mFormatPreference);
-        }
-
-        mMountTogglePreference.setEnabled(true);
-    }
-
     private void updatePreferencesFromState() {
-        resetPreferences();
+        mMountTogglePreference.setEnabled(true);
 
         String state = mStorageVolume != null
                 ? mStorageManager.getVolumeState(mStorageVolume.getPath())
@@ -285,7 +299,8 @@
         }
 
         if (Environment.MEDIA_MOUNTED.equals(state)) {
-            mPreferences[AVAILABLE].setSummary(mPreferences[AVAILABLE].getSummary() + readOnly);
+            final Preference pref = findPreference(KEY_AVAILABLE);
+            pref.setSummary(pref.getSummary() + readOnly);
 
             mMountTogglePreference.setEnabled(true);
             mMountTogglePreference.setTitle(mResources.getString(R.string.sd_eject));
@@ -303,8 +318,8 @@
             }
 
             removePreference(mUsageBarPreference);
-            removePreference(mPreferences[TOTAL_SIZE]);
-            removePreference(mPreferences[AVAILABLE]);
+            removePreference(findPreference(KEY_TOTAL_SIZE));
+            removePreference(findPreference(KEY_AVAILABLE));
             if (mFormatPreference != null) {
                 removePreference(mFormatPreference);
             }
@@ -329,67 +344,98 @@
     }
 
     public void updateApproximate(long totalSize, long availSize) {
-        mPreferences[TOTAL_SIZE].setSummary(formatSize(totalSize));
-        mPreferences[AVAILABLE].setSummary(formatSize(availSize));
+        findPreference(KEY_TOTAL_SIZE).setSummary(formatSize(totalSize));
+        findPreference(KEY_AVAILABLE).setSummary(formatSize(availSize));
 
         final long usedSize = totalSize - availSize;
 
         mUsageBarPreference.clear();
         mUsageBarPreference.addEntry(usedSize / (float) totalSize, android.graphics.Color.GRAY);
         mUsageBarPreference.commit();
+        mShowingApprox = true;
 
         updatePreferencesFromState();
     }
 
     public void updateExact(long totalSize, long availSize, long appsSize, long downloadsSize,
             long miscSize, long[] mediaSizes) {
-        mUsageBarPreference.clear();
+        if (mShowingApprox) {
+            mUsageBarPreference.clear();
+            mShowingApprox = false;
+        }
 
-        mPreferences[TOTAL_SIZE].setSummary(formatSize(totalSize));
+        findPreference(KEY_TOTAL_SIZE).setSummary(formatSize(totalSize));
 
-        if (mMeasurement.isExternalSDCard()) {
+        if (mLocalMeasure.isExternalSDCard()) {
             // TODO FIXME: external SD card will not report any size. Show used space in bar graph
             final long usedSize = totalSize - availSize;
             mUsageBarPreference.addEntry(usedSize / (float) totalSize, android.graphics.Color.GRAY);
         }
 
-        updatePreference(appsSize, totalSize, APPLICATIONS);
+        updatePreference(appsSize, totalSize, KEY_APPLICATIONS);
 
         long totalMediaSize = 0;
         for (int i = 0; i < sMediaCategories.length; i++) {
-            final int category = sMediaCategories[i].mCategory;
+            final String category = sMediaCategories[i].mCategory;
             final long size = mediaSizes[i];
             updatePreference(size, totalSize, category);
             totalMediaSize += size;
         }
 
-        updatePreference(downloadsSize, totalSize, DOWNLOADS);
+        updatePreference(downloadsSize, totalSize, KEY_DOWNLOADS);
 
         // Note miscSize != totalSize - availSize - appsSize - downloadsSize - totalMediaSize
         // Block size is taken into account. That can be extra space from folders. TODO Investigate
-        updatePreference(miscSize, totalSize, MISC);
+        updatePreference(miscSize, totalSize, KEY_MISC);
 
-        updatePreference(availSize, totalSize, AVAILABLE);
+        updatePreference(availSize, totalSize, KEY_AVAILABLE, false);
 
         mUsageBarPreference.commit();
     }
 
-    private void updatePreference(long size, long totalSize, int category) {
-        if (size > 0) {
-            mPreferences[category].setSummary(formatSize(size));
-            mUsageBarPreference.addEntry(size / (float) totalSize, mColors[category]);
-        } else {
-            removePreference(mPreferences[category]);
+    public void updateUserExact(UserHandle user, long totalSize, long usedSize) {
+        if (mShowingApprox) {
+            mUsageBarPreference.clear();
+            mShowingApprox = false;
+        }
+
+        final String key = buildUserKey(user);
+
+        findPreference(key).setSummary(formatSize(usedSize));
+        updatePreference(usedSize, totalSize, key);
+
+        mUsageBarPreference.commit();
+    }
+
+    private void updatePreference(long size, long totalSize, String category) {
+        updatePreference(size, totalSize, category, true);
+    }
+
+    private void updatePreference(long size, long totalSize, String category, boolean showBar) {
+        final StorageItemPreference pref = (StorageItemPreference) findPreference(category);
+        if (pref != null) {
+            if (size > 0) {
+                pref.setSummary(formatSize(size));
+                if (showBar) {
+                    mUsageBarPreference.addEntry(size / (float) totalSize, pref.getColor());
+                }
+            } else {
+                removePreference(pref);
+            }
         }
     }
 
     private void measure() {
-        mMeasurement.invalidate();
-        mMeasurement.measure();
+        for (StorageMeasurement measure : mAllMeasures) {
+            measure.invalidate();
+            measure.measure();
+        }
     }
 
     public void onResume() {
-        mMeasurement.setReceiver(this);
+        for (StorageMeasurement measure : mAllMeasures) {
+            measure.setReceiver(this);
+        }
         measure();
     }
 
@@ -407,15 +453,9 @@
     }
 
     public void onPause() {
-        mMeasurement.cleanUp();
-    }
-
-    private static ShapeDrawable createRectShape(int width, int height, int color) {
-        ShapeDrawable shape = new ShapeDrawable(new RectShape());
-        shape.setIntrinsicHeight(height);
-        shape.setIntrinsicWidth(width);
-        shape.getPaint().setColor(color);
-        return shape;
+        for (StorageMeasurement measure : mAllMeasures) {
+            measure.cleanUp();
+        }
     }
 
     private String formatSize(long size) {
@@ -423,15 +463,17 @@
     }
 
     @Override
-    public void updateApproximate(Bundle bundle) {
+    public void updateApproximate(StorageMeasurement meas, Bundle bundle) {
         final Message message = mUpdateHandler.obtainMessage(MSG_UI_UPDATE_APPROXIMATE);
+        message.obj = meas.getUser();
         message.setData(bundle);
         mUpdateHandler.sendMessage(message);
     }
 
     @Override
-    public void updateExact(Bundle bundle) {
+    public void updateExact(StorageMeasurement meas, Bundle bundle) {
         final Message message = mUpdateHandler.obtainMessage(MSG_UI_UPDATE_EXACT);
+        message.obj = meas.getUser();
         message.setData(bundle);
         mUpdateHandler.sendMessage(message);
     }
@@ -440,34 +482,35 @@
         return preference == mMountTogglePreference;
     }
 
-    public Intent intentForClick(Preference preference) {
+    public Intent intentForClick(Preference pref) {
         Intent intent = null;
 
         // TODO The current "delete" story is not fully handled by the respective applications.
         // When it is done, make sure the intent types below are correct.
         // If that cannot be done, remove these intents.
-        if (preference == mFormatPreference) {
+        final String key = pref.getKey();
+        if (pref == mFormatPreference) {
             intent = new Intent(Intent.ACTION_VIEW);
             intent.setClass(getContext(), com.android.settings.MediaFormat.class);
             intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, mStorageVolume);
-        } else if (preference == mPreferences[APPLICATIONS]) {
+        } else if (KEY_APPLICATIONS.equals(key)) {
             intent = new Intent(Intent.ACTION_MANAGE_PACKAGE_STORAGE);
             intent.setClass(getContext(),
                     com.android.settings.Settings.ManageApplicationsActivity.class);
-        } else if (preference == mPreferences[DOWNLOADS]) {
+        } else if (KEY_DOWNLOADS.equals(key)) {
             intent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS).putExtra(
                     DownloadManager.INTENT_EXTRAS_SORT_BY_SIZE, true);
-        } else if (preference == mPreferences[MUSIC]) {
+        } else if (KEY_MUSIC.equals(key)) {
             intent = new Intent(Intent.ACTION_GET_CONTENT);
             intent.setType("audio/mp3");
-        } else if (preference == mPreferences[DCIM]) {
+        } else if (KEY_DCIM.equals(key)) {
             intent = new Intent(Intent.ACTION_VIEW);
             intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
             // TODO Create a Videos category, type = vnd.android.cursor.dir/video
             intent.setType("vnd.android.cursor.dir/image");
-        } else if (preference == mPreferences[MISC]) {
+        } else if (KEY_MISC.equals(key)) {
             Context context = getContext().getApplicationContext();
-            if (mMeasurement.getMiscSize() > 0) {
+            if (mLocalMeasure.getMiscSize() > 0) {
                 intent = new Intent(context, MiscFilesHandler.class);
                 intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, mStorageVolume);
             }