Progress towards dynamic storage support.

Storage devices are no longer hard-coded, and instead bubble up from
whatever Disk and VolumeBase that vold uncovered, turning into
sibling Java objects in MountService.  We now treat vold events as
the source-of-truth for state, and synchronize our state by asking
vold to "reset" whenever we reconnect.

We've now moved to a model where all storage devices are mounted in
the root mount namespace (user boundaries protected with GIDs), so
we no longer need app-to-vold path translation.  This also means that
zygote only needs to bind mount the user-specific /mnt/user/n/ path
onto /storage/self/ to make legacy paths like /sdcard work.  This
grealy simplifies a lot of system code.

Many parts of the platform depend on a primary storage device always
being present, so we hack together a stub StorageVolume when vold
doesn't have a volume ready yet.

StorageVolume isn't really a volume anymore; it's the user-specific
view onto a volume, so MountService now filters and builds them
based on the calling user.  StorageVolume is now immutable, making
it easier to reason about.

Environment now builds all of its paths dynamically based on active
volumes.  Adds utility methods to turn int types and flags into
user-readable strings for debugging purposes.

Remove UMS sharing support for now, since no current devices support
it; MTP is the recommended solution going forward because it offers
better multi-user support.

Simplify unmount logic, since vold will now gladly trigger EJECTING
broadcast and kill stubborn processes.

Bug: 19993667
Change-Id: I9842280e61974c91bae15d764e386969aedcd338
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 59fe490..a0f40f6 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -402,13 +402,7 @@
                 new CachedServiceFetcher<StorageManager>() {
             @Override
             public StorageManager createService(ContextImpl ctx) {
-                try {
-                    return new StorageManager(
-                            ctx.getContentResolver(), ctx.mMainThread.getHandler().getLooper());
-                } catch (RemoteException rex) {
-                    Log.e(TAG, "Failed to create StorageManager", rex);
-                    return null;
-                }
+                return new StorageManager(ctx, ctx.mMainThread.getHandler().getLooper());
             }});
 
         registerService(Context.TELEPHONY_SERVICE, TelephonyManager.class,
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index 3f18519..ff3de2b 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -51,6 +51,8 @@
     public static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
     /** @hide */
     public static final long GB_IN_BYTES = MB_IN_BYTES * 1024;
+    /** @hide */
+    public static final long TB_IN_BYTES = GB_IN_BYTES * 1024;
 
     /**
      * Special UID value used when collecting {@link NetworkStatsHistory} for
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 975bfc2..2db976e 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -18,16 +18,11 @@
 
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
-import android.os.storage.IMountService;
+import android.os.storage.StorageManager;
 import android.os.storage.StorageVolume;
-import android.text.TextUtils;
 import android.util.Log;
 
-import com.google.android.collect.Lists;
-
 import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
 
 /**
  * Provides access to environment variables.
@@ -36,11 +31,9 @@
     private static final String TAG = "Environment";
 
     private static final String ENV_EXTERNAL_STORAGE = "EXTERNAL_STORAGE";
-    private static final String ENV_EMULATED_STORAGE_SOURCE = "EMULATED_STORAGE_SOURCE";
-    private static final String ENV_EMULATED_STORAGE_TARGET = "EMULATED_STORAGE_TARGET";
-    private static final String ENV_MEDIA_STORAGE = "MEDIA_STORAGE";
-    private static final String ENV_SECONDARY_STORAGE = "SECONDARY_STORAGE";
     private static final String ENV_ANDROID_ROOT = "ANDROID_ROOT";
+    private static final String ENV_ANDROID_DATA = "ANDROID_DATA";
+    private static final String ENV_ANDROID_STORAGE = "ANDROID_STORAGE";
     private static final String ENV_OEM_ROOT = "OEM_ROOT";
     private static final String ENV_VENDOR_ROOT = "VENDOR_ROOT";
 
@@ -57,12 +50,10 @@
     public static final String DIRECTORY_ANDROID = DIR_ANDROID;
 
     private static final File DIR_ANDROID_ROOT = getDirectory(ENV_ANDROID_ROOT, "/system");
+    private static final File DIR_ANDROID_DATA = getDirectory(ENV_ANDROID_DATA, "/data");
+    private static final File DIR_ANDROID_STORAGE = getDirectory(ENV_ANDROID_STORAGE, "/storage");
     private static final File DIR_OEM_ROOT = getDirectory(ENV_OEM_ROOT, "/oem");
     private static final File DIR_VENDOR_ROOT = getDirectory(ENV_VENDOR_ROOT, "/vendor");
-    private static final File DIR_MEDIA_STORAGE = getDirectory(ENV_MEDIA_STORAGE, "/data/media");
-
-    private static final String CANONCIAL_EMULATED_STORAGE_TARGET = getCanonicalPathOrNull(
-            ENV_EMULATED_STORAGE_TARGET);
 
     private static final String SYSTEM_PROPERTY_EFS_ENABLED = "persist.security.efs.enabled";
 
@@ -81,73 +72,24 @@
 
     /** {@hide} */
     public static class UserEnvironment {
-        // TODO: generalize further to create package-specific environment
-
-        /** External storage dirs, as visible to vold */
-        private final File[] mExternalDirsForVold;
-        /** External storage dirs, as visible to apps */
-        private final File[] mExternalDirsForApp;
-        /** Primary emulated storage dir for direct access */
-        private final File mEmulatedDirForDirect;
+        private final int mUserId;
 
         public UserEnvironment(int userId) {
-            // See storage config details at http://source.android.com/tech/storage/
-            String rawExternalStorage = System.getenv(ENV_EXTERNAL_STORAGE);
-            String rawEmulatedSource = System.getenv(ENV_EMULATED_STORAGE_SOURCE);
-            String rawEmulatedTarget = System.getenv(ENV_EMULATED_STORAGE_TARGET);
+            mUserId = userId;
+        }
 
-            String rawMediaStorage = System.getenv(ENV_MEDIA_STORAGE);
-            if (TextUtils.isEmpty(rawMediaStorage)) {
-                rawMediaStorage = "/data/media";
+        public File[] getExternalDirs() {
+            final StorageVolume[] volumes = StorageManager.getVolumeList(mUserId);
+            final File[] dirs = new File[volumes.length];
+            for (int i = 0; i < volumes.length; i++) {
+                dirs[i] = volumes[i].getPathFile();
             }
-
-            ArrayList<File> externalForVold = Lists.newArrayList();
-            ArrayList<File> externalForApp = Lists.newArrayList();
-
-            if (!TextUtils.isEmpty(rawEmulatedTarget)) {
-                // Device has emulated storage; external storage paths should have
-                // userId burned into them.
-                final String rawUserId = Integer.toString(userId);
-                final File emulatedSourceBase = new File(rawEmulatedSource);
-                final File emulatedTargetBase = new File(rawEmulatedTarget);
-                final File mediaBase = new File(rawMediaStorage);
-
-                // /storage/emulated/0
-                externalForVold.add(buildPath(emulatedSourceBase, rawUserId));
-                externalForApp.add(buildPath(emulatedTargetBase, rawUserId));
-                // /data/media/0
-                mEmulatedDirForDirect = buildPath(mediaBase, rawUserId);
-
-            } else {
-                // Device has physical external storage; use plain paths.
-                if (TextUtils.isEmpty(rawExternalStorage)) {
-                    Log.w(TAG, "EXTERNAL_STORAGE undefined; falling back to default");
-                    rawExternalStorage = "/storage/sdcard0";
-                }
-
-                // /storage/sdcard0
-                externalForVold.add(new File(rawExternalStorage));
-                externalForApp.add(new File(rawExternalStorage));
-                // /data/media
-                mEmulatedDirForDirect = new File(rawMediaStorage);
-            }
-
-            // Splice in any secondary storage paths, but only for owner
-            final String rawSecondaryStorage = System.getenv(ENV_SECONDARY_STORAGE);
-            if (!TextUtils.isEmpty(rawSecondaryStorage) && userId == UserHandle.USER_OWNER) {
-                for (String secondaryPath : rawSecondaryStorage.split(":")) {
-                    externalForVold.add(new File(secondaryPath));
-                    externalForApp.add(new File(secondaryPath));
-                }
-            }
-
-            mExternalDirsForVold = externalForVold.toArray(new File[externalForVold.size()]);
-            mExternalDirsForApp = externalForApp.toArray(new File[externalForApp.size()]);
+            return dirs;
         }
 
         @Deprecated
         public File getExternalStorageDirectory() {
-            return mExternalDirsForApp[0];
+            return getExternalDirs()[0];
         }
 
         @Deprecated
@@ -155,60 +97,36 @@
             return buildExternalStoragePublicDirs(type)[0];
         }
 
-        public File[] getExternalDirsForVold() {
-            return mExternalDirsForVold;
-        }
-
-        public File[] getExternalDirsForApp() {
-            return mExternalDirsForApp;
-        }
-
-        public File getMediaDir() {
-            return mEmulatedDirForDirect;
-        }
-
         public File[] buildExternalStoragePublicDirs(String type) {
-            return buildPaths(mExternalDirsForApp, type);
+            return buildPaths(getExternalDirs(), type);
         }
 
         public File[] buildExternalStorageAndroidDataDirs() {
-            return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA);
+            return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_DATA);
         }
 
         public File[] buildExternalStorageAndroidObbDirs() {
-            return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_OBB);
+            return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_OBB);
         }
 
         public File[] buildExternalStorageAppDataDirs(String packageName) {
-            return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA, packageName);
-        }
-
-        public File[] buildExternalStorageAppDataDirsForVold(String packageName) {
-            return buildPaths(mExternalDirsForVold, DIR_ANDROID, DIR_DATA, packageName);
+            return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_DATA, packageName);
         }
 
         public File[] buildExternalStorageAppMediaDirs(String packageName) {
-            return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_MEDIA, packageName);
-        }
-
-        public File[] buildExternalStorageAppMediaDirsForVold(String packageName) {
-            return buildPaths(mExternalDirsForVold, DIR_ANDROID, DIR_MEDIA, packageName);
+            return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_MEDIA, packageName);
         }
 
         public File[] buildExternalStorageAppObbDirs(String packageName) {
-            return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_OBB, packageName);
-        }
-
-        public File[] buildExternalStorageAppObbDirsForVold(String packageName) {
-            return buildPaths(mExternalDirsForVold, DIR_ANDROID, DIR_OBB, packageName);
+            return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_OBB, packageName);
         }
 
         public File[] buildExternalStorageAppFilesDirs(String packageName) {
-            return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA, packageName, DIR_FILES);
+            return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_DATA, packageName, DIR_FILES);
         }
 
         public File[] buildExternalStorageAppCacheDirs(String packageName) {
-            return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA, packageName, DIR_CACHE);
+            return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_DATA, packageName, DIR_CACHE);
         }
     }
 
@@ -220,6 +138,11 @@
         return DIR_ANDROID_ROOT;
     }
 
+    /** {@hide} */
+    public static File getStorageDirectory() {
+        return DIR_ANDROID_STORAGE;
+    }
+
     /**
      * Return root directory of the "oem" partition holding OEM customizations,
      * if any. If present, the partition is mounted read-only.
@@ -270,17 +193,6 @@
     }
 
     /**
-     * Return directory used for internal media storage, which is protected by
-     * {@link android.Manifest.permission#WRITE_MEDIA_STORAGE}.
-     *
-     * @hide
-     */
-    public static File getMediaStorageDirectory() {
-        throwIfUserRequired();
-        return sCurrentUser.getMediaDir();
-    }
-
-    /**
      * Return the system directory for a user. This is for use by system services to store
      * files relating to the user. This directory will be automatically deleted when the user
      * is removed.
@@ -389,7 +301,7 @@
      */
     public static File getExternalStorageDirectory() {
         throwIfUserRequired();
-        return sCurrentUser.getExternalDirsForApp()[0];
+        return sCurrentUser.getExternalDirs()[0];
     }
 
     /** {@hide} */
@@ -402,18 +314,6 @@
         return buildPath(getLegacyExternalStorageDirectory(), DIR_ANDROID, DIR_OBB);
     }
 
-    /** {@hide} */
-    public static File getEmulatedStorageSource(int userId) {
-        // /mnt/shell/emulated/0
-        return new File(System.getenv(ENV_EMULATED_STORAGE_SOURCE), String.valueOf(userId));
-    }
-
-    /** {@hide} */
-    public static File getEmulatedStorageObbSource() {
-        // /mnt/shell/emulated/obb
-        return new File(System.getenv(ENV_EMULATED_STORAGE_SOURCE), DIR_OBB);
-    }
-
     /**
      * Standard directory in which to place any audio files that should be
      * in the regular list of music for the user.
@@ -683,6 +583,13 @@
     public static final String MEDIA_UNMOUNTABLE = "unmountable";
 
     /**
+     * Storage state if the media is in the process of being ejected.
+     *
+     * @see #getExternalStorageState(File)
+     */
+    public static final String MEDIA_EJECTING = "ejecting";
+
+    /**
      * Returns the current state of the primary "external" storage device.
      * 
      * @see #getExternalStorageDirectory()
@@ -693,7 +600,7 @@
      *         {@link #MEDIA_BAD_REMOVAL}, or {@link #MEDIA_UNMOUNTABLE}.
      */
     public static String getExternalStorageState() {
-        final File externalDir = sCurrentUser.getExternalDirsForApp()[0];
+        final File externalDir = sCurrentUser.getExternalDirs()[0];
         return getExternalStorageState(externalDir);
     }
 
@@ -716,17 +623,12 @@
      *         {@link #MEDIA_BAD_REMOVAL}, or {@link #MEDIA_UNMOUNTABLE}.
      */
     public static String getExternalStorageState(File path) {
-        final StorageVolume volume = getStorageVolume(path);
+        final StorageVolume volume = StorageManager.getStorageVolume(path, UserHandle.myUserId());
         if (volume != null) {
-            final IMountService mountService = IMountService.Stub.asInterface(
-                    ServiceManager.getService("mount"));
-            try {
-                return mountService.getVolumeState(volume.getPath());
-            } catch (RemoteException e) {
-            }
+            return volume.getState();
+        } else {
+            return MEDIA_UNKNOWN;
         }
-
-        return Environment.MEDIA_UNKNOWN;
     }
 
     /**
@@ -738,7 +640,7 @@
      */
     public static boolean isExternalStorageRemovable() {
         if (isStorageDisabled()) return false;
-        final File externalDir = sCurrentUser.getExternalDirsForApp()[0];
+        final File externalDir = sCurrentUser.getExternalDirs()[0];
         return isExternalStorageRemovable(externalDir);
     }
 
@@ -753,7 +655,7 @@
      *             device.
      */
     public static boolean isExternalStorageRemovable(File path) {
-        final StorageVolume volume = getStorageVolume(path);
+        final StorageVolume volume = StorageManager.getStorageVolume(path, UserHandle.myUserId());
         if (volume != null) {
             return volume.isRemovable();
         } else {
@@ -771,7 +673,7 @@
      */
     public static boolean isExternalStorageEmulated() {
         if (isStorageDisabled()) return false;
-        final File externalDir = sCurrentUser.getExternalDirsForApp()[0];
+        final File externalDir = sCurrentUser.getExternalDirs()[0];
         return isExternalStorageEmulated(externalDir);
     }
 
@@ -784,7 +686,7 @@
      *             device.
      */
     public static boolean isExternalStorageEmulated(File path) {
-        final StorageVolume volume = getStorageVolume(path);
+        final StorageVolume volume = StorageManager.getStorageVolume(path, UserHandle.myUserId());
         if (volume != null) {
             return volume.isEmulated();
         } else {
@@ -797,19 +699,6 @@
         return path == null ? new File(defaultPath) : new File(path);
     }
 
-    private static String getCanonicalPathOrNull(String variableName) {
-        String path = System.getenv(variableName);
-        if (path == null) {
-            return null;
-        }
-        try {
-            return new File(path).getCanonicalPath();
-        } catch (IOException e) {
-            Log.w(TAG, "Unable to resolve canonical path for " + path);
-            return null;
-        }
-    }
-
     /** {@hide} */
     public static void setUserRequired(boolean userRequired) {
         sUserRequired = userRequired;
@@ -856,28 +745,6 @@
         return SystemProperties.getBoolean("config.disable_storage", false);
     }
 
-    private static StorageVolume getStorageVolume(File path) {
-        try {
-            path = path.getCanonicalFile();
-        } catch (IOException e) {
-            return null;
-        }
-
-        try {
-            final IMountService mountService = IMountService.Stub.asInterface(
-                    ServiceManager.getService("mount"));
-            final StorageVolume[] volumes = mountService.getVolumeList();
-            for (StorageVolume volume : volumes) {
-                if (FileUtils.contains(volume.getPathFile(), path)) {
-                    return volume;
-                }
-            }
-        } catch (RemoteException e) {
-        }
-
-        return null;
-    }
-
     /**
      * If the given path exists on emulated external storage, return the
      * translated backing path hosted on internal storage. This bypasses any
@@ -891,26 +758,7 @@
      * @hide
      */
     public static File maybeTranslateEmulatedPathToInternal(File path) {
-        // Fast return if not emulated, or missing variables
-        if (!Environment.isExternalStorageEmulated()
-                || CANONCIAL_EMULATED_STORAGE_TARGET == null) {
-            return path;
-        }
-
-        try {
-            final String rawPath = path.getCanonicalPath();
-            if (rawPath.startsWith(CANONCIAL_EMULATED_STORAGE_TARGET)) {
-                final File internalPath = new File(DIR_MEDIA_STORAGE,
-                        rawPath.substring(CANONCIAL_EMULATED_STORAGE_TARGET.length()));
-                if (internalPath.exists()) {
-                    return internalPath;
-                }
-            }
-        } catch (IOException e) {
-            Log.w(TAG, "Failed to resolve canonical path for " + path);
-        }
-
-        // Unable to translate to internal path; use original
+        // TODO: bring back this optimization
         return path;
     }
 }
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 0a724a1..b302f95 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -369,6 +369,23 @@
      * {@link File#getCanonicalFile()} to avoid symlink or path traversal
      * attacks.
      */
+    public static boolean contains(File[] dirs, File file) {
+        for (File dir : dirs) {
+            if (contains(dir, file)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Test if a file lives under the given directory, either as a direct child
+     * or a distant grandchild.
+     * <p>
+     * Both files <em>must</em> have been resolved using
+     * {@link File#getCanonicalFile()} to avoid symlink or path traversal
+     * attacks.
+     */
     public static boolean contains(File dir, File file) {
         if (file == null) return false;
 
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 0de9c70..355ec8c 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -637,10 +637,8 @@
             if ((debugFlags & Zygote.DEBUG_ENABLE_ASSERT) != 0) {
                 argsForZygote.add("--enable-assert");
             }
-            if (mountExternal == Zygote.MOUNT_EXTERNAL_MULTIUSER) {
-                argsForZygote.add("--mount-external-multiuser");
-            } else if (mountExternal == Zygote.MOUNT_EXTERNAL_MULTIUSER_ALL) {
-                argsForZygote.add("--mount-external-multiuser-all");
+            if (mountExternal == Zygote.MOUNT_EXTERNAL_DEFAULT) {
+                argsForZygote.add("--mount-external-default");
             }
             argsForZygote.add("--target-sdk-version=" + targetSdkVersion);
 
diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java
index 6209c2a..fef12d1 100644
--- a/core/java/android/os/storage/IMountService.java
+++ b/core/java/android/os/storage/IMountService.java
@@ -757,12 +757,13 @@
                 return _result;
             }
 
-            public StorageVolume[] getVolumeList() throws RemoteException {
+            public StorageVolume[] getVolumeList(int userId) throws RemoteException {
                 Parcel _data = Parcel.obtain();
                 Parcel _reply = Parcel.obtain();
                 StorageVolume[] _result;
                 try {
                     _data.writeInterfaceToken(DESCRIPTOR);
+                    _data.writeInt(userId);
                     mRemote.transact(Stub.TRANSACTION_getVolumeList, _data, _reply, 0);
                     _reply.readException();
                     _result = _reply.createTypedArray(StorageVolume.CREATOR);
@@ -1308,7 +1309,8 @@
                 }
                 case TRANSACTION_getVolumeList: {
                     data.enforceInterface(DESCRIPTOR);
-                    StorageVolume[] result = getVolumeList();
+                    int userId = data.readInt();
+                    StorageVolume[] result = getVolumeList(userId);
                     reply.writeNoException();
                     reply.writeTypedArray(result, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                     return true;
@@ -1630,7 +1632,7 @@
     /**
      * Returns list of all mountable volumes.
      */
-    public StorageVolume[] getVolumeList() throws RemoteException;
+    public StorageVolume[] getVolumeList(int userId) throws RemoteException;
 
     /**
      * Gets the path on the filesystem for the ASEC container itself.
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 2785ee8..532bf2c 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -18,19 +18,23 @@
 
 import static android.net.TrafficStats.MB_IN_BYTES;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.os.Environment;
+import android.os.FileUtils;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.provider.Settings;
 import android.util.Log;
 import android.util.SparseArray;
 
+import libcore.util.EmptyArray;
+
 import com.android.internal.util.Preconditions;
 
 import java.io.File;
@@ -60,6 +64,7 @@
 public class StorageManager {
     private static final String TAG = "StorageManager";
 
+    private final Context mContext;
     private final ContentResolver mResolver;
 
     /*
@@ -311,8 +316,9 @@
      *
      * @hide
      */
-    public StorageManager(ContentResolver resolver, Looper tgtLooper) throws RemoteException {
-        mResolver = resolver;
+    public StorageManager(Context context, Looper tgtLooper) {
+        mContext = context;
+        mResolver = context.getContentResolver();
         mTgtLooper = tgtLooper;
         mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount"));
         if (mMountService == null) {
@@ -548,17 +554,46 @@
         return null;
     }
 
+    /** {@hide} */
+    public @Nullable StorageVolume getStorageVolume(File file) {
+        return getStorageVolume(getVolumeList(), file);
+    }
+
+    /** {@hide} */
+    public static @Nullable StorageVolume getStorageVolume(File file, int userId) {
+        return getStorageVolume(getVolumeList(userId), file);
+    }
+
+    /** {@hide} */
+    private static @Nullable StorageVolume getStorageVolume(StorageVolume[] volumes, File file) {
+        File canonicalFile = null;
+        try {
+            canonicalFile = file.getCanonicalFile();
+        } catch (IOException ignored) {
+            canonicalFile = null;
+        }
+        for (StorageVolume volume : volumes) {
+            if (volume.getPathFile().equals(file)) {
+                return volume;
+            }
+            if (FileUtils.contains(volume.getPathFile(), canonicalFile)) {
+                return volume;
+            }
+        }
+        return null;
+    }
+
     /**
      * Gets the state of a volume via its mountpoint.
      * @hide
      */
-    public String getVolumeState(String mountPoint) {
-         if (mMountService == null) return Environment.MEDIA_REMOVED;
-        try {
-            return mMountService.getVolumeState(mountPoint);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to get volume state", e);
-            return null;
+    @Deprecated
+    public @NonNull String getVolumeState(String mountPoint) {
+        final StorageVolume vol = getStorageVolume(new File(mountPoint));
+        if (vol != null) {
+            return vol.getState();
+        } else {
+            return Environment.MEDIA_UNKNOWN;
         }
     }
 
@@ -566,20 +601,22 @@
      * Returns list of all mountable volumes.
      * @hide
      */
-    public StorageVolume[] getVolumeList() {
-        if (mMountService == null) return new StorageVolume[0];
+    public @NonNull StorageVolume[] getVolumeList() {
         try {
-            Parcelable[] list = mMountService.getVolumeList();
-            if (list == null) return new StorageVolume[0];
-            int length = list.length;
-            StorageVolume[] result = new StorageVolume[length];
-            for (int i = 0; i < length; i++) {
-                result[i] = (StorageVolume)list[i];
-            }
-            return result;
+            return mMountService.getVolumeList(mContext.getUserId());
         } catch (RemoteException e) {
-            Log.e(TAG, "Failed to get volume list", e);
-            return null;
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /** {@hide} */
+    public static @NonNull StorageVolume[] getVolumeList(int userId) {
+        final IMountService mountService = IMountService.Stub.asInterface(
+                ServiceManager.getService("mount"));
+        try {
+            return mountService.getVolumeList(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
         }
     }
 
@@ -587,9 +624,9 @@
      * Returns list of paths for all mountable volumes.
      * @hide
      */
-    public String[] getVolumePaths() {
+    @Deprecated
+    public @NonNull String[] getVolumePaths() {
         StorageVolume[] volumes = getVolumeList();
-        if (volumes == null) return null;
         int count = volumes.length;
         String[] paths = new String[count];
         for (int i = 0; i < count; i++) {
@@ -599,21 +636,21 @@
     }
 
     /** {@hide} */
-    public StorageVolume getPrimaryVolume() {
+    public @NonNull StorageVolume getPrimaryVolume() {
         return getPrimaryVolume(getVolumeList());
     }
 
     /** {@hide} */
-    public static StorageVolume getPrimaryVolume(StorageVolume[] volumes) {
+    public static @NonNull StorageVolume getPrimaryVolume(StorageVolume[] volumes) {
         for (StorageVolume volume : volumes) {
             if (volume.isPrimary()) {
                 return volume;
             }
         }
-        Log.w(TAG, "No primary storage defined");
-        return null;
+        throw new IllegalStateException("Missing primary storage");
     }
 
+    /** {@hide} */
     private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10;
     private static final long DEFAULT_THRESHOLD_MAX_BYTES = 500 * MB_IN_BYTES;
     private static final long DEFAULT_FULL_THRESHOLD_BYTES = MB_IN_BYTES;
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index 06565f1..0c391ca 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -17,6 +17,7 @@
 package android.os.storage;
 
 import android.content.Context;
+import android.net.TrafficStats;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
@@ -34,52 +35,58 @@
  */
 public class StorageVolume implements Parcelable {
 
-    // TODO: switch to more durable token
-    private int mStorageId;
+    private final String mId;
+    private final int mStorageId;
 
     private final File mPath;
     private final int mDescriptionId;
     private final boolean mPrimary;
     private final boolean mRemovable;
     private final boolean mEmulated;
-    private final int mMtpReserveSpace;
+    private final long mMtpReserveSize;
     private final boolean mAllowMassStorage;
     /** Maximum file size for the storage, or zero for no limit */
     private final long mMaxFileSize;
     /** When set, indicates exclusive ownership of this volume */
     private final UserHandle mOwner;
 
-    private String mUuid;
-    private String mUserLabel;
-    private String mState;
+    private final String mUuid;
+    private final String mUserLabel;
+    private final String mState;
 
     // StorageVolume extra for ACTION_MEDIA_REMOVED, ACTION_MEDIA_UNMOUNTED, ACTION_MEDIA_CHECKING,
     // ACTION_MEDIA_NOFS, ACTION_MEDIA_MOUNTED, ACTION_MEDIA_SHARED, ACTION_MEDIA_UNSHARED,
     // ACTION_MEDIA_BAD_REMOVAL, ACTION_MEDIA_UNMOUNTABLE and ACTION_MEDIA_EJECT broadcasts.
     public static final String EXTRA_STORAGE_VOLUME = "storage_volume";
 
-    public StorageVolume(File path, int descriptionId, boolean primary, boolean removable,
-            boolean emulated, int mtpReserveSpace, boolean allowMassStorage, long maxFileSize,
-            UserHandle owner) {
+    public StorageVolume(String id, int storageId, File path, int descriptionId, boolean primary,
+            boolean removable, boolean emulated, long mtpReserveSize, boolean allowMassStorage,
+            long maxFileSize, UserHandle owner, String uuid, String userLabel, String state) {
+        mId = id;
+        mStorageId = storageId;
         mPath = path;
         mDescriptionId = descriptionId;
         mPrimary = primary;
         mRemovable = removable;
         mEmulated = emulated;
-        mMtpReserveSpace = mtpReserveSpace;
+        mMtpReserveSize = mtpReserveSize;
         mAllowMassStorage = allowMassStorage;
         mMaxFileSize = maxFileSize;
         mOwner = owner;
+        mUuid = uuid;
+        mUserLabel = userLabel;
+        mState = state;
     }
 
     private StorageVolume(Parcel in) {
+        mId = in.readString();
         mStorageId = in.readInt();
         mPath = new File(in.readString());
         mDescriptionId = in.readInt();
         mPrimary = in.readInt() != 0;
         mRemovable = in.readInt() != 0;
         mEmulated = in.readInt() != 0;
-        mMtpReserveSpace = in.readInt();
+        mMtpReserveSize = in.readLong();
         mAllowMassStorage = in.readInt() != 0;
         mMaxFileSize = in.readLong();
         mOwner = in.readParcelable(null);
@@ -88,10 +95,8 @@
         mState = in.readString();
     }
 
-    public static StorageVolume fromTemplate(StorageVolume template, File path, UserHandle owner) {
-        return new StorageVolume(path, template.mDescriptionId, template.mPrimary,
-                template.mRemovable, template.mEmulated, template.mMtpReserveSpace,
-                template.mAllowMassStorage, template.mMaxFileSize, owner);
+    public String getId() {
+        return mId;
     }
 
     /**
@@ -153,15 +158,6 @@
     }
 
     /**
-     * Do not call this unless you are MountService
-     */
-    public void setStorageId(int index) {
-        // storage ID is 0x00010001 for primary storage,
-        // then 0x00020001, 0x00030001, etc. for secondary storages
-        mStorageId = ((index + 1) << 16) + 1;
-    }
-
-    /**
      * Number of megabytes of space to leave unallocated by MTP.
      * MTP will subtract this value from the free space it reports back
      * to the host via GetStorageInfo, and will not allow new files to
@@ -174,7 +170,7 @@
      * @return MTP reserve space
      */
     public int getMtpReserveSpace() {
-        return mMtpReserveSpace;
+        return (int) (mMtpReserveSize / TrafficStats.MB_IN_BYTES);
     }
 
     /**
@@ -199,10 +195,6 @@
         return mOwner;
     }
 
-    public void setUuid(String uuid) {
-        mUuid = uuid;
-    }
-
     public String getUuid() {
         return mUuid;
     }
@@ -222,18 +214,10 @@
         }
     }
 
-    public void setUserLabel(String userLabel) {
-        mUserLabel = userLabel;
-    }
-
     public String getUserLabel() {
         return mUserLabel;
     }
 
-    public void setState(String state) {
-        mState = state;
-    }
-
     public String getState() {
         return mState;
     }
@@ -262,13 +246,14 @@
     public void dump(IndentingPrintWriter pw) {
         pw.println("StorageVolume:");
         pw.increaseIndent();
+        pw.printPair("mId", mId);
         pw.printPair("mStorageId", mStorageId);
         pw.printPair("mPath", mPath);
         pw.printPair("mDescriptionId", mDescriptionId);
         pw.printPair("mPrimary", mPrimary);
         pw.printPair("mRemovable", mRemovable);
         pw.printPair("mEmulated", mEmulated);
-        pw.printPair("mMtpReserveSpace", mMtpReserveSpace);
+        pw.printPair("mMtpReserveSize", mMtpReserveSize);
         pw.printPair("mAllowMassStorage", mAllowMassStorage);
         pw.printPair("mMaxFileSize", mMaxFileSize);
         pw.printPair("mOwner", mOwner);
@@ -297,13 +282,14 @@
 
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeString(mId);
         parcel.writeInt(mStorageId);
         parcel.writeString(mPath.toString());
         parcel.writeInt(mDescriptionId);
         parcel.writeInt(mPrimary ? 1 : 0);
         parcel.writeInt(mRemovable ? 1 : 0);
         parcel.writeInt(mEmulated ? 1 : 0);
-        parcel.writeInt(mMtpReserveSpace);
+        parcel.writeLong(mMtpReserveSize);
         parcel.writeInt(mAllowMassStorage ? 1 : 0);
         parcel.writeLong(mMaxFileSize);
         parcel.writeParcelable(mOwner, flags);
diff --git a/core/java/android/util/DebugUtils.java b/core/java/android/util/DebugUtils.java
index 84d9ce8..c44f42b 100644
--- a/core/java/android/util/DebugUtils.java
+++ b/core/java/android/util/DebugUtils.java
@@ -17,8 +17,10 @@
 package android.util;
 
 import java.io.PrintWriter;
-import java.lang.reflect.Method;
+import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
 import java.util.Locale;
 
 /**
@@ -203,4 +205,57 @@
         outBuilder.append(suffix);
         return outBuilder.toString();
     }
+
+    /**
+     * Use prefixed constants (static final values) on given class to turn value
+     * into human-readable string.
+     *
+     * @hide
+     */
+    public static String valueToString(Class<?> clazz, String prefix, int value) {
+        for (Field field : clazz.getDeclaredFields()) {
+            final int modifiers = field.getModifiers();
+            if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)
+                    && field.getType().equals(int.class) && field.getName().startsWith(prefix)) {
+                try {
+                    if (value == field.getInt(null)) {
+                        return field.getName().substring(prefix.length());
+                    }
+                } catch (IllegalAccessException ignored) {
+                }
+            }
+        }
+        return Integer.toString(value);
+    }
+
+    /**
+     * Use prefixed constants (static final values) on given class to turn flags
+     * into human-readable string.
+     *
+     * @hide
+     */
+    public static String flagsToString(Class<?> clazz, String prefix, int flags) {
+        final StringBuilder res = new StringBuilder();
+
+        for (Field field : clazz.getDeclaredFields()) {
+            final int modifiers = field.getModifiers();
+            if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)
+                    && field.getType().equals(int.class) && field.getName().startsWith(prefix)) {
+                try {
+                    final int value = field.getInt(null);
+                    if ((flags & value) != 0) {
+                        flags &= ~value;
+                        res.append(field.getName().substring(prefix.length())).append('|');
+                    }
+                } catch (IllegalAccessException ignored) {
+                }
+            }
+        }
+        if (flags != 0 || res.length() == 0) {
+            res.append(Integer.toHexString(flags));
+        } else {
+            res.deleteCharAt(res.length() - 1);
+        }
+        return res.toString();
+    }
 }
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 8674a21..75b6446 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -41,15 +41,10 @@
     /** enable the JIT compiler */
     public static final int DEBUG_ENABLE_JIT         = 1 << 5;
 
-
     /** No external storage should be mounted. */
     public static final int MOUNT_EXTERNAL_NONE = 0;
-    /** Single-user external storage should be mounted. */
-    public static final int MOUNT_EXTERNAL_SINGLEUSER = 1;
-    /** Multi-user external storage should be mounted. */
-    public static final int MOUNT_EXTERNAL_MULTIUSER = 2;
-    /** All multi-user external storage should be mounted. */
-    public static final int MOUNT_EXTERNAL_MULTIUSER_ALL = 3;
+    /** Default user-specific external storage should be mounted. */
+    public static final int MOUNT_EXTERNAL_DEFAULT = 1;
 
     private static final ZygoteHooks VM_HOOKS = new ZygoteHooks();
 
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 4d405b2..9106ccd 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -514,10 +514,8 @@
                                 "Duplicate arg specified");
                     }
                     niceName = arg.substring(arg.indexOf('=') + 1);
-                } else if (arg.equals("--mount-external-multiuser")) {
-                    mountExternal = Zygote.MOUNT_EXTERNAL_MULTIUSER;
-                } else if (arg.equals("--mount-external-multiuser-all")) {
-                    mountExternal = Zygote.MOUNT_EXTERNAL_MULTIUSER_ALL;
+                } else if (arg.equals("--mount-external-default")) {
+                    mountExternal = Zygote.MOUNT_EXTERNAL_DEFAULT;
                 } else if (arg.equals("--query-abi-list")) {
                     abiListQuery = true;
                 } else if (arg.startsWith("--instruction-set=")) {