Parcelable objects for Disk/Volume.

Will eventually be used by SystemUI and/or Settings.

Also fix SettingsProvider NPE.

Bug: 19993667, 19909433
Change-Id: Ie326849ac5f43ee35f728d9cc0e332b72292db70
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 2db976e..2eb97f1 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -80,11 +80,11 @@
 
         public File[] getExternalDirs() {
             final StorageVolume[] volumes = StorageManager.getVolumeList(mUserId);
-            final File[] dirs = new File[volumes.length];
+            final File[] files = new File[volumes.length];
             for (int i = 0; i < volumes.length; i++) {
-                dirs[i] = volumes[i].getPathFile();
+                files[i] = volumes[i].getPathFile();
             }
-            return dirs;
+            return files;
         }
 
         @Deprecated
diff --git a/core/java/android/os/storage/DiskInfo.aidl b/core/java/android/os/storage/DiskInfo.aidl
new file mode 100644
index 0000000..5126c19
--- /dev/null
+++ b/core/java/android/os/storage/DiskInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2015 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 android.os.storage;
+
+parcelable DiskInfo;
diff --git a/core/java/android/os/storage/DiskInfo.java b/core/java/android/os/storage/DiskInfo.java
new file mode 100644
index 0000000..d676b1e
--- /dev/null
+++ b/core/java/android/os/storage/DiskInfo.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2015 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 android.os.storage;
+
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.DebugUtils;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
+
+/**
+ * Information about a physical disk which may contain one or more
+ * {@link VolumeInfo}.
+ *
+ * @hide
+ */
+public class DiskInfo implements Parcelable {
+    public static final int FLAG_ADOPTABLE = 1 << 0;
+    public static final int FLAG_DEFAULT_PRIMARY = 1 << 1;
+    public static final int FLAG_SD = 1 << 2;
+    public static final int FLAG_USB = 1 << 3;
+
+    public final String id;
+    public final int flags;
+    public long size;
+    public String label;
+    public String[] volumes;
+
+    public DiskInfo(String id, int flags) {
+        this.id = Preconditions.checkNotNull(id);
+        this.flags = flags;
+    }
+
+    public DiskInfo(Parcel parcel) {
+        id = parcel.readString();
+        flags = parcel.readInt();
+        size = parcel.readLong();
+        label = parcel.readString();
+        volumes = parcel.readStringArray();
+    }
+
+    public String getDescription(Context context) {
+        // TODO: splice vendor label into these strings
+        if ((flags & FLAG_SD) != 0) {
+            return context.getString(com.android.internal.R.string.storage_sd_card);
+        } else if ((flags & FLAG_USB) != 0) {
+            return context.getString(com.android.internal.R.string.storage_usb);
+        } else {
+            return null;
+        }
+    }
+
+//    public void partitionPublic() throws NativeDaemonConnectorException {
+//        mConnector.execute("volume", "partition", id, "public");
+//    }
+//
+//    public void partitionPrivate() throws NativeDaemonConnectorException {
+//        mConnector.execute("volume", "partition", id, "private");
+//    }
+//
+//    public void partitionMixed(int frac) throws NativeDaemonConnectorException {
+//        mConnector.execute("volume", "partition", id, "mixed", frac);
+//    }
+
+    public void dump(IndentingPrintWriter pw) {
+        pw.println("DiskInfo:");
+        pw.increaseIndent();
+        pw.printPair("id", id);
+        pw.printPair("flags", DebugUtils.flagsToString(getClass(), "FLAG_", flags));
+        pw.printPair("size", size);
+        pw.printPair("label", label);
+        pw.printPair("volumes", volumes);
+        pw.decreaseIndent();
+        pw.println();
+    }
+
+    public static final Creator<DiskInfo> CREATOR = new Creator<DiskInfo>() {
+        @Override
+        public DiskInfo createFromParcel(Parcel in) {
+            return new DiskInfo(in);
+        }
+
+        @Override
+        public DiskInfo[] newArray(int size) {
+            return new DiskInfo[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeString(id);
+        parcel.writeInt(flags);
+        parcel.writeLong(size);
+        parcel.writeString(label);
+        parcel.writeStringArray(volumes);
+    }
+}
diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java
index fef12d1..4209ad4 100644
--- a/core/java/android/os/storage/IMountService.java
+++ b/core/java/android/os/storage/IMountService.java
@@ -904,6 +904,40 @@
                 }
                 return;
             }
+
+            @Override
+            public DiskInfo[] getDisks() throws RemoteException {
+                Parcel _data = Parcel.obtain();
+                Parcel _reply = Parcel.obtain();
+                DiskInfo[] _result;
+                try {
+                    _data.writeInterfaceToken(DESCRIPTOR);
+                    mRemote.transact(Stub.TRANSACTION_getDisks, _data, _reply, 0);
+                    _reply.readException();
+                    _result = _reply.createTypedArray(DiskInfo.CREATOR);
+                } finally {
+                    _reply.recycle();
+                    _data.recycle();
+                }
+                return _result;
+            }
+
+            @Override
+            public VolumeInfo[] getVolumes() throws RemoteException {
+                Parcel _data = Parcel.obtain();
+                Parcel _reply = Parcel.obtain();
+                VolumeInfo[] _result;
+                try {
+                    _data.writeInterfaceToken(DESCRIPTOR);
+                    mRemote.transact(Stub.TRANSACTION_getDisks, _data, _reply, 0);
+                    _reply.readException();
+                    _result = _reply.createTypedArray(VolumeInfo.CREATOR);
+                } finally {
+                    _reply.recycle();
+                    _data.recycle();
+                }
+                return _result;
+            }
         }
 
         private static final String DESCRIPTOR = "IMountService";
@@ -996,6 +1030,10 @@
 
         static final int TRANSACTION_waitForAsecScan = IBinder.FIRST_CALL_TRANSACTION + 43;
 
+        static final int TRANSACTION_getDisks = IBinder.FIRST_CALL_TRANSACTION + 44;
+
+        static final int TRANSACTION_getVolumes = IBinder.FIRST_CALL_TRANSACTION + 45;
+
         /**
          * Cast an IBinder object into an IMountService interface, generating a
          * proxy if needed.
@@ -1421,6 +1459,20 @@
                     reply.writeNoException();
                     return true;
                 }
+                case TRANSACTION_getDisks: {
+                    data.enforceInterface(DESCRIPTOR);
+                    DiskInfo[] disks = getDisks();
+                    reply.writeNoException();
+                    reply.writeTypedArray(disks, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+                    return true;
+                }
+                case TRANSACTION_getVolumes: {
+                    data.enforceInterface(DESCRIPTOR);
+                    VolumeInfo[] volumes = getVolumes();
+                    reply.writeNoException();
+                    reply.writeTypedArray(volumes, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+                    return true;
+                }
             }
             return super.onTransact(code, data, reply, flags);
         }
@@ -1707,4 +1759,7 @@
     public void runMaintenance() throws RemoteException;
 
     public void waitForAsecScan() throws RemoteException;
+
+    public DiskInfo[] getDisks() throws RemoteException;
+    public VolumeInfo[] getVolumes() throws RemoteException;
 }
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 532bf2c..83559fa 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -33,14 +33,13 @@
 import android.util.Log;
 import android.util.SparseArray;
 
-import libcore.util.EmptyArray;
-
 import com.android.internal.util.Preconditions;
 
 import java.io.File;
 import java.io.IOException;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -64,6 +63,9 @@
 public class StorageManager {
     private static final String TAG = "StorageManager";
 
+    /** {@hide} */
+    public static final String PROP_PRIMARY_PHYSICAL = "ro.vold.primary_physical";
+
     private final Context mContext;
     private final ContentResolver mResolver;
 
@@ -555,6 +557,24 @@
     }
 
     /** {@hide} */
+    public @NonNull List<DiskInfo> getDisks() {
+        try {
+            return Arrays.asList(mMountService.getDisks());
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /** {@hide} */
+    public @NonNull List<VolumeInfo> getVolumes() {
+        try {
+            return Arrays.asList(mMountService.getVolumes());
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /** {@hide} */
     public @Nullable StorageVolume getStorageVolume(File file) {
         return getStorageVolume(getVolumeList(), file);
     }
@@ -597,16 +617,9 @@
         }
     }
 
-    /**
-     * Returns list of all mountable volumes.
-     * @hide
-     */
+    /** {@hide} */
     public @NonNull StorageVolume[] getVolumeList() {
-        try {
-            return mMountService.getVolumeList(mContext.getUserId());
-        } catch (RemoteException e) {
-            throw e.rethrowAsRuntimeException();
-        }
+        return getVolumeList(mContext.getUserId());
     }
 
     /** {@hide} */
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index 0c391ca..d66e228 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -23,13 +23,17 @@
 import android.os.UserHandle;
 
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
 
 import java.io.CharArrayWriter;
 import java.io.File;
 
 /**
- * Description of a storage volume and its capabilities, including the
- * filesystem path where it may be mounted.
+ * Information about a storage volume that may be mounted. This is a legacy
+ * specialization of {@link VolumeInfo} which describes the volume for a
+ * specific user.
+ * <p>
+ * This class may be deprecated in the future.
  *
  * @hide
  */
@@ -37,21 +41,16 @@
 
     private final String mId;
     private final int mStorageId;
-
     private final File mPath;
-    private final int mDescriptionId;
+    private final String mDescription;
     private final boolean mPrimary;
     private final boolean mRemovable;
     private final boolean mEmulated;
     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 final String mUuid;
-    private final String mUserLabel;
+    private final String mFsUuid;
     private final String mState;
 
     // StorageVolume extra for ACTION_MEDIA_REMOVED, ACTION_MEDIA_UNMOUNTED, ACTION_MEDIA_CHECKING,
@@ -59,30 +58,29 @@
     // ACTION_MEDIA_BAD_REMOVAL, ACTION_MEDIA_UNMOUNTABLE and ACTION_MEDIA_EJECT broadcasts.
     public static final String EXTRA_STORAGE_VOLUME = "storage_volume";
 
-    public StorageVolume(String id, int storageId, File path, int descriptionId, boolean primary,
+    public StorageVolume(String id, int storageId, File path, String description, boolean primary,
             boolean removable, boolean emulated, long mtpReserveSize, boolean allowMassStorage,
-            long maxFileSize, UserHandle owner, String uuid, String userLabel, String state) {
-        mId = id;
+            long maxFileSize, UserHandle owner, String fsUuid, String state) {
+        mId = Preconditions.checkNotNull(id);
         mStorageId = storageId;
-        mPath = path;
-        mDescriptionId = descriptionId;
+        mPath = Preconditions.checkNotNull(path);
+        mDescription = Preconditions.checkNotNull(description);
         mPrimary = primary;
         mRemovable = removable;
         mEmulated = emulated;
         mMtpReserveSize = mtpReserveSize;
         mAllowMassStorage = allowMassStorage;
         mMaxFileSize = maxFileSize;
-        mOwner = owner;
-        mUuid = uuid;
-        mUserLabel = userLabel;
-        mState = state;
+        mOwner = Preconditions.checkNotNull(owner);
+        mFsUuid = fsUuid;
+        mState = Preconditions.checkNotNull(state);
     }
 
     private StorageVolume(Parcel in) {
         mId = in.readString();
         mStorageId = in.readInt();
         mPath = new File(in.readString());
-        mDescriptionId = in.readInt();
+        mDescription = in.readString();
         mPrimary = in.readInt() != 0;
         mRemovable = in.readInt() != 0;
         mEmulated = in.readInt() != 0;
@@ -90,8 +88,7 @@
         mAllowMassStorage = in.readInt() != 0;
         mMaxFileSize = in.readLong();
         mOwner = in.readParcelable(null);
-        mUuid = in.readString();
-        mUserLabel = in.readString();
+        mFsUuid = in.readString();
         mState = in.readString();
     }
 
@@ -118,11 +115,7 @@
      * @return the volume description
      */
     public String getDescription(Context context) {
-        return context.getResources().getString(mDescriptionId);
-    }
-
-    public int getDescriptionId() {
-        return mDescriptionId;
+        return mDescription;
     }
 
     public boolean isPrimary() {
@@ -196,7 +189,7 @@
     }
 
     public String getUuid() {
-        return mUuid;
+        return mFsUuid;
     }
 
     /**
@@ -204,18 +197,18 @@
      * parse or UUID is unknown.
      */
     public int getFatVolumeId() {
-        if (mUuid == null || mUuid.length() != 9) {
+        if (mFsUuid == null || mFsUuid.length() != 9) {
             return -1;
         }
         try {
-            return (int)Long.parseLong(mUuid.replace("-", ""), 16);
+            return (int) Long.parseLong(mFsUuid.replace("-", ""), 16);
         } catch (NumberFormatException e) {
             return -1;
         }
     }
 
     public String getUserLabel() {
-        return mUserLabel;
+        return mDescription;
     }
 
     public String getState() {
@@ -249,7 +242,7 @@
         pw.printPair("mId", mId);
         pw.printPair("mStorageId", mStorageId);
         pw.printPair("mPath", mPath);
-        pw.printPair("mDescriptionId", mDescriptionId);
+        pw.printPair("mDescription", mDescription);
         pw.printPair("mPrimary", mPrimary);
         pw.printPair("mRemovable", mRemovable);
         pw.printPair("mEmulated", mEmulated);
@@ -257,8 +250,7 @@
         pw.printPair("mAllowMassStorage", mAllowMassStorage);
         pw.printPair("mMaxFileSize", mMaxFileSize);
         pw.printPair("mOwner", mOwner);
-        pw.printPair("mUuid", mUuid);
-        pw.printPair("mUserLabel", mUserLabel);
+        pw.printPair("mFsUuid", mFsUuid);
         pw.printPair("mState", mState);
         pw.decreaseIndent();
     }
@@ -285,7 +277,7 @@
         parcel.writeString(mId);
         parcel.writeInt(mStorageId);
         parcel.writeString(mPath.toString());
-        parcel.writeInt(mDescriptionId);
+        parcel.writeString(mDescription);
         parcel.writeInt(mPrimary ? 1 : 0);
         parcel.writeInt(mRemovable ? 1 : 0);
         parcel.writeInt(mEmulated ? 1 : 0);
@@ -293,8 +285,7 @@
         parcel.writeInt(mAllowMassStorage ? 1 : 0);
         parcel.writeLong(mMaxFileSize);
         parcel.writeParcelable(mOwner, flags);
-        parcel.writeString(mUuid);
-        parcel.writeString(mUserLabel);
+        parcel.writeString(mFsUuid);
         parcel.writeString(mState);
     }
 }
diff --git a/core/java/android/os/storage/VolumeInfo.aidl b/core/java/android/os/storage/VolumeInfo.aidl
new file mode 100644
index 0000000..32d12da
--- /dev/null
+++ b/core/java/android/os/storage/VolumeInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2015 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 android.os.storage;
+
+parcelable VolumeInfo;
diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java
new file mode 100644
index 0000000..22bf1e4
--- /dev/null
+++ b/core/java/android/os/storage/VolumeInfo.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2015 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 android.os.storage;
+
+import android.content.Context;
+import android.content.Intent;
+import android.mtp.MtpStorage;
+import android.os.Environment;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.DebugUtils;
+import android.util.SparseArray;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
+
+import java.io.File;
+
+/**
+ * Information about a storage volume that may be mounted. A volume may be a
+ * partition on a physical {@link DiskInfo}, an emulated volume above some other
+ * storage medium, or a standalone container like an ASEC or OBB.
+ *
+ * @hide
+ */
+public class VolumeInfo implements Parcelable {
+    public static final String ID_EMULATED_INTERNAL = "emulated";
+
+    public static final int TYPE_PUBLIC = 0;
+    public static final int TYPE_PRIVATE = 1;
+    public static final int TYPE_EMULATED = 2;
+    public static final int TYPE_ASEC = 3;
+    public static final int TYPE_OBB = 4;
+
+    public static final int STATE_UNMOUNTED = 0;
+    public static final int STATE_MOUNTING = 1;
+    public static final int STATE_MOUNTED = 2;
+    public static final int STATE_FORMATTING = 3;
+    public static final int STATE_UNMOUNTING = 4;
+
+    public static final int FLAG_PRIMARY = 1 << 0;
+    public static final int FLAG_VISIBLE = 1 << 1;
+
+    public static SparseArray<String> sStateToEnvironment = new SparseArray<>();
+    public static ArrayMap<String, String> sEnvironmentToBroadcast = new ArrayMap<>();
+
+    static {
+        sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTED, Environment.MEDIA_UNMOUNTED);
+        sStateToEnvironment.put(VolumeInfo.STATE_MOUNTING, Environment.MEDIA_CHECKING);
+        sStateToEnvironment.put(VolumeInfo.STATE_MOUNTED, Environment.MEDIA_MOUNTED);
+        sStateToEnvironment.put(VolumeInfo.STATE_FORMATTING, Environment.MEDIA_UNMOUNTED);
+        sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTING, Environment.MEDIA_EJECTING);
+
+        sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTED, Intent.ACTION_MEDIA_UNMOUNTED);
+        sEnvironmentToBroadcast.put(Environment.MEDIA_CHECKING, Intent.ACTION_MEDIA_CHECKING);
+        sEnvironmentToBroadcast.put(Environment.MEDIA_MOUNTED, Intent.ACTION_MEDIA_MOUNTED);
+        sEnvironmentToBroadcast.put(Environment.MEDIA_EJECTING, Intent.ACTION_MEDIA_EJECT);
+    }
+
+    /** vold state */
+    public final String id;
+    public final int type;
+    public int flags = 0;
+    public int userId = -1;
+    public int state = STATE_UNMOUNTED;
+    public String fsType;
+    public String fsUuid;
+    public String fsLabel;
+    public String path;
+
+    /** Framework state */
+    public final int mtpIndex;
+    public String nickname;
+
+    public DiskInfo disk;
+
+    public VolumeInfo(String id, int type, int mtpIndex) {
+        this.id = Preconditions.checkNotNull(id);
+        this.type = type;
+        this.mtpIndex = mtpIndex;
+    }
+
+    public VolumeInfo(Parcel parcel) {
+        id = parcel.readString();
+        type = parcel.readInt();
+        flags = parcel.readInt();
+        userId = parcel.readInt();
+        state = parcel.readInt();
+        fsType = parcel.readString();
+        fsUuid = parcel.readString();
+        fsLabel = parcel.readString();
+        path = parcel.readString();
+        mtpIndex = parcel.readInt();
+        nickname = parcel.readString();
+    }
+
+    public String getDescription(Context context) {
+        if (ID_EMULATED_INTERNAL.equals(id)) {
+            return context.getString(com.android.internal.R.string.storage_internal);
+        } else if (!TextUtils.isEmpty(nickname)) {
+            return nickname;
+        } else if (!TextUtils.isEmpty(fsLabel)) {
+            return fsLabel;
+        } else {
+            return null;
+        }
+    }
+
+    public boolean isPrimary() {
+        return (flags & FLAG_PRIMARY) != 0;
+    }
+
+    public boolean isVisible() {
+        return (flags & FLAG_VISIBLE) != 0;
+    }
+
+    public boolean isVisibleToUser(int userId) {
+        if (type == TYPE_PUBLIC && userId == this.userId) {
+            return isVisible();
+        } else if (type == TYPE_EMULATED) {
+            return isVisible();
+        } else {
+            return false;
+        }
+    }
+
+    public File getPathForUser(int userId) {
+        if (type == TYPE_PUBLIC && userId == this.userId) {
+            return new File(path);
+        } else if (type == TYPE_EMULATED) {
+            return new File(path, Integer.toString(userId));
+        } else {
+            return null;
+        }
+    }
+
+    public StorageVolume buildStorageVolume(Context context, int userId) {
+        final boolean removable;
+        final boolean emulated;
+        final boolean allowMassStorage = false;
+        final int mtpStorageId = MtpStorage.getStorageIdForIndex(mtpIndex);
+
+        File userPath = getPathForUser(userId);
+        if (userPath == null) {
+            userPath = new File("/dev/null");
+        }
+
+        String description = getDescription(context);
+        if (description == null) {
+            description = context.getString(android.R.string.unknownName);
+        }
+
+        String envState = sStateToEnvironment.get(state);
+        if (envState == null) {
+            envState = Environment.MEDIA_UNKNOWN;
+        }
+
+        long mtpReserveSize = 0;
+        long maxFileSize = 0;
+
+        if (type == TYPE_EMULATED) {
+            emulated = true;
+            mtpReserveSize = StorageManager.from(context).getStorageLowBytes(userPath);
+
+            if (ID_EMULATED_INTERNAL.equals(id)) {
+                removable = false;
+            } else {
+                removable = true;
+            }
+
+        } else if (type == TYPE_PUBLIC) {
+            emulated = false;
+            removable = true;
+
+            if ("vfat".equals(fsType)) {
+                maxFileSize = 4294967295L;
+            }
+
+        } else {
+            throw new IllegalStateException("Unexpected volume type " + type);
+        }
+
+        return new StorageVolume(id, mtpStorageId, userPath, description, isPrimary(), removable,
+                emulated, mtpReserveSize, allowMassStorage, maxFileSize, new UserHandle(userId),
+                fsUuid, envState);
+    }
+
+    public void dump(IndentingPrintWriter pw) {
+        pw.println("VolumeInfo:");
+        pw.increaseIndent();
+        pw.printPair("id", id);
+        pw.printPair("type", DebugUtils.valueToString(getClass(), "TYPE_", type));
+        pw.printPair("flags", DebugUtils.flagsToString(getClass(), "FLAG_", flags));
+        pw.printPair("userId", userId);
+        pw.printPair("state", DebugUtils.valueToString(getClass(), "STATE_", state));
+        pw.println();
+        pw.printPair("fsType", fsType);
+        pw.printPair("fsUuid", fsUuid);
+        pw.printPair("fsLabel", fsLabel);
+        pw.println();
+        pw.printPair("path", path);
+        pw.printPair("mtpIndex", mtpIndex);
+        pw.decreaseIndent();
+        pw.println();
+    }
+
+    public static final Creator<VolumeInfo> CREATOR = new Creator<VolumeInfo>() {
+        @Override
+        public VolumeInfo createFromParcel(Parcel in) {
+            return new VolumeInfo(in);
+        }
+
+        @Override
+        public VolumeInfo[] newArray(int size) {
+            return new VolumeInfo[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeString(id);
+        parcel.writeInt(type);
+        parcel.writeInt(flags);
+        parcel.writeInt(userId);
+        parcel.writeInt(state);
+        parcel.writeString(fsType);
+        parcel.writeString(fsUuid);
+        parcel.writeString(fsLabel);
+        parcel.writeString(path);
+        parcel.writeInt(mtpIndex);
+        parcel.writeString(nickname);
+    }
+}
diff --git a/core/java/com/android/internal/util/IndentingPrintWriter.java b/core/java/com/android/internal/util/IndentingPrintWriter.java
index 6fddd09..f1add27 100644
--- a/core/java/com/android/internal/util/IndentingPrintWriter.java
+++ b/core/java/com/android/internal/util/IndentingPrintWriter.java
@@ -18,6 +18,7 @@
 
 import java.io.PrintWriter;
 import java.io.Writer;
+import java.util.Arrays;
 
 /**
  * Lightweight wrapper around {@link PrintWriter} that automatically indents
@@ -68,6 +69,10 @@
         print(key + "=" + String.valueOf(value) + " ");
     }
 
+    public void printPair(String key, Object[] value) {
+        print(key + "=" + Arrays.toString(value) + " ");
+    }
+
     public void printHexPair(String key, int value) {
         print(key + "=0x" + Integer.toHexString(value) + " ");
     }
diff --git a/media/java/android/mtp/MtpStorage.java b/media/java/android/mtp/MtpStorage.java
index 7b8102b..3641ff5 100644
--- a/media/java/android/mtp/MtpStorage.java
+++ b/media/java/android/mtp/MtpStorage.java
@@ -38,7 +38,7 @@
     public MtpStorage(StorageVolume volume, Context context) {
         mStorageId = volume.getStorageId();
         mPath = volume.getPath();
-        mDescription = context.getResources().getString(volume.getDescriptionId());
+        mDescription = volume.getDescription(context);
         mReserveSpace = volume.getMtpReserveSpace() * 1024L * 1024L;
         mRemovable = volume.isRemovable();
         mMaxFileSize = volume.getMaxFileSize();
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 126b4aa..0d61606 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -1502,15 +1502,15 @@
         public void onPackageRemovedLocked(String packageName, int userId) {
             final int globalKey = makeKey(SETTINGS_TYPE_GLOBAL, UserHandle.USER_OWNER);
             SettingsState globalSettings = mSettingsStates.get(globalKey);
-            globalSettings.onPackageRemovedLocked(packageName);
+            if (globalSettings != null) globalSettings.onPackageRemovedLocked(packageName);
 
             final int secureKey = makeKey(SETTINGS_TYPE_SECURE, userId);
             SettingsState secureSettings = mSettingsStates.get(secureKey);
-            secureSettings.onPackageRemovedLocked(packageName);
+            if (secureSettings != null) secureSettings.onPackageRemovedLocked(packageName);
 
             final int systemKey = makeKey(SETTINGS_TYPE_SYSTEM, userId);
             SettingsState systemSettings = mSettingsStates.get(systemKey);
-            systemSettings.onPackageRemovedLocked(packageName);
+            if (systemSettings != null) systemSettings.onPackageRemovedLocked(packageName);
         }
 
         private SettingsState peekSettingsStateLocked(int key) {
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index 61286e8..4d8cb90 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -42,6 +42,7 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.storage.DiskInfo;
 import android.os.storage.IMountService;
 import android.os.storage.IMountServiceListener;
 import android.os.storage.IMountShutdownObserver;
@@ -50,11 +51,11 @@
 import android.os.storage.StorageManager;
 import android.os.storage.StorageResultCode;
 import android.os.storage.StorageVolume;
+import android.os.storage.VolumeInfo;
 import android.text.TextUtils;
 import android.util.ArrayMap;
-import android.util.DebugUtils;
+import android.util.Log;
 import android.util.Slog;
-import android.util.SparseArray;
 
 import libcore.util.EmptyArray;
 import libcore.util.HexEncoding;
@@ -68,7 +69,6 @@
 import com.android.server.NativeDaemonConnector.Command;
 import com.android.server.NativeDaemonConnector.SensitiveArg;
 import com.android.server.pm.PackageManagerService;
-import com.google.android.collect.Lists;
 
 import java.io.File;
 import java.io.FileDescriptor;
@@ -143,7 +143,6 @@
     }
 
     private static final boolean LOCAL_LOGD = false;
-    private static final boolean DEBUG_UNMOUNT = false;
     private static final boolean DEBUG_EVENTS = false;
     private static final boolean DEBUG_OBB = false;
 
@@ -157,8 +156,6 @@
     /** Maximum number of ASEC containers allowed to be mounted. */
     private static final int MAX_CONTAINERS = 250;
 
-    private static final String PROP_PRIMARY_PHYSICAL = "ro.vold.primary_physical";
-
     /*
      * Internal vold response code constants
      */
@@ -213,22 +210,6 @@
         public static final int FstrimCompleted                = 700;
     }
 
-    private static SparseArray<String> sStateToEnvironment = new SparseArray<>();
-    private static ArrayMap<String, String> sEnvironmentToBroadcast = new ArrayMap<>();
-
-    static {
-        sStateToEnvironment.put(Volume.STATE_UNMOUNTED, Environment.MEDIA_UNMOUNTED);
-        sStateToEnvironment.put(Volume.STATE_MOUNTING, Environment.MEDIA_CHECKING);
-        sStateToEnvironment.put(Volume.STATE_MOUNTED, Environment.MEDIA_MOUNTED);
-        sStateToEnvironment.put(Volume.STATE_FORMATTING, Environment.MEDIA_UNMOUNTED);
-        sStateToEnvironment.put(Volume.STATE_UNMOUNTING, Environment.MEDIA_EJECTING);
-
-        sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTED, Intent.ACTION_MEDIA_UNMOUNTED);
-        sEnvironmentToBroadcast.put(Environment.MEDIA_CHECKING, Intent.ACTION_MEDIA_CHECKING);
-        sEnvironmentToBroadcast.put(Environment.MEDIA_MOUNTED, Intent.ACTION_MEDIA_MOUNTED);
-        sEnvironmentToBroadcast.put(Environment.MEDIA_EJECTING, Intent.ACTION_MEDIA_EJECT);
-    }
-
     /**
      * <em>Never</em> hold the lock while performing downcalls into vold, since
      * unsolicited events can suddenly appear to update data structures.
@@ -238,14 +219,15 @@
     @GuardedBy("mLock")
     private int[] mStartedUsers = EmptyArray.INT;
     @GuardedBy("mLock")
-    private ArrayMap<String, Disk> mDisks = new ArrayMap<>();
+    private ArrayMap<String, DiskInfo> mDisks = new ArrayMap<>();
     @GuardedBy("mLock")
-    private ArrayMap<String, Volume> mVolumes = new ArrayMap<>();
+    private ArrayMap<String, VolumeInfo> mVolumes = new ArrayMap<>();
 
     @Deprecated
-    private Volume findVolumeByLegacyPath(String legacyPath) {
+    private VolumeInfo findVolumeByLegacyPath(String legacyPath) {
         synchronized (mLock) {
-            for (Volume vol : mVolumes.values()) {
+            for (int i = 0; i < mVolumes.size(); i++) {
+                final VolumeInfo vol = mVolumes.valueAt(i);
                 if (vol.path != null && legacyPath.startsWith(vol.path)) {
                     return vol;
                 }
@@ -255,198 +237,13 @@
         return null;
     }
 
-    /**
-     * Framework-side twin of android::vold::Disk
-     */
-    private class Disk {
-        public static final int FLAG_ADOPTABLE = 1 << 0;
-        public static final int FLAG_DEFAULT_PRIMARY = 1 << 1;
-        public static final int FLAG_SD = 1 << 2;
-        public static final int FLAG_USB = 1 << 3;
-
-        public final String id;
-        public final int flags;
-        public long size;
-        public String label;
-
-        public ArrayList<Volume> volumes = new ArrayList<>();
-
-        public Disk(String id, int flags) {
-            this.id = id;
-            this.flags = flags;
-        }
-
-        public void partitionPublic() throws NativeDaemonConnectorException {
-            mConnector.execute("volume", "partition", id, "public");
-        }
-
-        public void partitionPrivate() throws NativeDaemonConnectorException {
-            mConnector.execute("volume", "partition", id, "private");
-        }
-
-        public void partitionMixed(int frac) throws NativeDaemonConnectorException {
-            mConnector.execute("volume", "partition", id, "mixed", frac);
-        }
-
-        public void dump(IndentingPrintWriter pw) {
-            pw.println("Disk:");
-            pw.increaseIndent();
-            pw.printPair("id", id);
-            pw.printPair("flags", DebugUtils.flagsToString(getClass(), "FLAG_", flags));
-            pw.printPair("size", size);
-            pw.printPair("label", label);
-            pw.decreaseIndent();
-            pw.println();
-        }
-    }
-
     private static int sNextMtpIndex = 1;
 
-    /**
-     * Framework-side twin of android::vold::VolumeBase
-     */
-    private class Volume {
-        public static final String ID_EMULATED_INTERNAL = "emulated";
-
-        public static final int TYPE_PUBLIC = 0;
-        public static final int TYPE_PRIVATE = 1;
-        public static final int TYPE_EMULATED = 2;
-        public static final int TYPE_ASEC = 3;
-        public static final int TYPE_OBB = 4;
-
-        public static final int STATE_UNMOUNTED = 0;
-        public static final int STATE_MOUNTING = 1;
-        public static final int STATE_MOUNTED = 2;
-        public static final int STATE_FORMATTING = 3;
-        public static final int STATE_UNMOUNTING = 4;
-
-        public static final int FLAG_PRIMARY = 1 << 0;
-        public static final int FLAG_VISIBLE = 1 << 1;
-
-        /** vold state */
-        public final String id;
-        public final int type;
-        public int flags = 0;
-        public int userId = -1;
-        public int state = STATE_UNMOUNTED;
-        public String fsType;
-        public String fsUuid;
-        public String fsLabel;
-        public String path = "/dev/null";
-
-        /** Framework state */
-        public final int mtpIndex;
-
-        public Disk disk;
-
-        public Volume(String id, int type) {
-            this.id = id;
-            this.type = type;
-
-            if (ID_EMULATED_INTERNAL.equals(id)) {
-                mtpIndex = 0;
-            } else {
-                mtpIndex = sNextMtpIndex++;
-            }
-        }
-
-        public boolean isPrimary() {
-            return (flags & FLAG_PRIMARY) != 0;
-        }
-
-        public boolean isVisible() {
-            return (flags & FLAG_VISIBLE) != 0;
-        }
-
-        public boolean isVisibleToUser(int userId) {
-            if (type == TYPE_PUBLIC && this.userId == userId) {
-                return isVisible();
-            } else if (type == TYPE_EMULATED) {
-                return isVisible();
-            } else {
-                return false;
-            }
-        }
-
-        public void mount() throws NativeDaemonConnectorException {
-            mConnector.execute("volume", "mount", id, flags, userId);
-        }
-
-        public void unmount() throws NativeDaemonConnectorException {
-            mConnector.execute("volume", "unmount", id);
-        }
-
-        public void format() throws NativeDaemonConnectorException {
-            mConnector.execute("volume", "format", id);
-        }
-
-        public StorageVolume buildVolumeForUser(int userId) {
-            final File userPath;
-            final boolean removable;
-            final boolean emulated;
-            final boolean allowMassStorage = false;
-            final int mtpStorageId = MtpStorage.getStorageIdForIndex(mtpIndex);
-            final String envState = sStateToEnvironment.get(state);
-
-            int descriptionId = com.android.internal.R.string.unknownName;
-            long mtpReserveSize = 0;
-            long maxFileSize = 0;
-
-            if (type == TYPE_EMULATED) {
-                userPath = new File(path, Integer.toString(userId));
-                emulated = true;
-                mtpReserveSize = StorageManager.from(mContext).getStorageLowBytes(userPath);
-                descriptionId = com.android.internal.R.string.storage_internal;
-
-                if (ID_EMULATED_INTERNAL.equals(id)) {
-                    removable = false;
-                } else {
-                    removable = true;
-                }
-
-            } else if (type == TYPE_PUBLIC) {
-                userPath = new File(path);
-                emulated = false;
-                removable = true;
-
-                if (disk != null) {
-                    if ((disk.flags & Disk.FLAG_SD) != 0) {
-                        descriptionId = com.android.internal.R.string.storage_sd_card;
-                    } else if ((disk.flags & Disk.FLAG_USB) != 0) {
-                        descriptionId = com.android.internal.R.string.storage_usb;
-                    }
-                }
-
-                if ("vfat".equals(fsType)) {
-                    maxFileSize = 4294967295L;
-                }
-
-            } else {
-                throw new IllegalStateException("Unexpected volume type " + type);
-            }
-
-            return new StorageVolume(id, mtpStorageId, userPath, descriptionId, isPrimary(),
-                    removable, emulated, mtpReserveSize, allowMassStorage, maxFileSize,
-                    new UserHandle(userId), fsUuid, fsLabel, envState);
-        }
-
-        public void dump(IndentingPrintWriter pw) {
-            pw.println("Volume:");
-            pw.increaseIndent();
-            pw.printPair("id", id);
-            pw.printPair("type", DebugUtils.valueToString(getClass(), "TYPE_", type));
-            pw.printPair("flags", DebugUtils.flagsToString(getClass(), "FLAG_", flags));
-            pw.printPair("userId", userId);
-            pw.printPair("state", DebugUtils.valueToString(getClass(), "STATE_", state));
-            pw.println();
-            pw.printPair("fsType", fsType);
-            pw.printPair("fsUuid", fsUuid);
-            pw.printPair("fsLabel", fsLabel);
-            pw.println();
-            pw.printPair("path", path);
-            pw.printPair("mtpIndex", mtpIndex);
-            pw.decreaseIndent();
-            pw.println();
+    private static int allocateMtpIndex(String volId) {
+        if (VolumeInfo.ID_EMULATED_INTERNAL.equals(volId)) {
+            return 0;
+        } else {
+            return sNextMtpIndex++;
         }
     }
 
@@ -672,9 +469,9 @@
                     break;
                 }
                 case H_VOLUME_MOUNT: {
-                    final Volume vol = (Volume) msg.obj;
+                    final VolumeInfo vol = (VolumeInfo) msg.obj;
                     try {
-                        vol.mount();
+                        mConnector.execute("volume", "mount", vol.id, vol.flags, vol.userId);
                     } catch (NativeDaemonConnectorException ignored) {
                     }
                     break;
@@ -685,7 +482,7 @@
                     Slog.d(TAG, "Volume " + userVol.getId() + " broadcasting " + state + " to "
                             + userVol.getOwner());
 
-                    final String action = sEnvironmentToBroadcast.get(state);
+                    final String action = VolumeInfo.sEnvironmentToBroadcast.get(state);
                     if (action != null) {
                         final Intent intent = new Intent(action,
                                 Uri.fromFile(userVol.getPathFile()));
@@ -769,9 +566,10 @@
         // Record user as started so newly mounted volumes kick off events
         // correctly, then synthesize events for any already-mounted volumes.
         synchronized (mVolumes) {
-            for (Volume vol : mVolumes.values()) {
-                if (vol.isVisibleToUser(userId) && vol.state == Volume.STATE_MOUNTED) {
-                    final StorageVolume userVol = vol.buildVolumeForUser(userId);
+            for (int i = 0; i < mVolumes.size(); i++) {
+                final VolumeInfo vol = mVolumes.valueAt(i);
+                if (vol.isVisibleToUser(userId) && vol.state == VolumeInfo.STATE_MOUNTED) {
+                    final StorageVolume userVol = vol.buildStorageVolume(mContext, userId);
                     mHandler.obtainMessage(H_VOLUME_BROADCAST, userVol).sendToTarget();
                 }
             }
@@ -906,12 +704,12 @@
                 if (cooked.length != 3) break;
                 final String id = cooked[1];
                 final int flags = Integer.parseInt(cooked[2]);
-                mDisks.put(id, new Disk(id, flags));
+                mDisks.put(id, new DiskInfo(id, flags));
                 break;
             }
             case VoldResponseCode.DISK_SIZE_CHANGED: {
                 if (cooked.length != 3) break;
-                final Disk disk = mDisks.get(cooked[1]);
+                final DiskInfo disk = mDisks.get(cooked[1]);
                 if (disk != null) {
                     disk.size = Long.parseLong(cooked[2]);
                 }
@@ -919,7 +717,7 @@
             }
             case VoldResponseCode.DISK_LABEL_CHANGED: {
                 if (cooked.length != 3) break;
-                final Disk disk = mDisks.get(cooked[1]);
+                final DiskInfo disk = mDisks.get(cooked[1]);
                 if (disk != null) {
                     disk.label = cooked[2];
                 }
@@ -927,10 +725,10 @@
             }
             case VoldResponseCode.DISK_VOLUME_CREATED: {
                 if (cooked.length != 3) break;
-                final Disk disk = mDisks.get(cooked[1]);
-                final Volume vol = mVolumes.get(cooked[2]);
-                if (disk != null && vol != null) {
-                    disk.volumes.add(vol);
+                final DiskInfo disk = mDisks.get(cooked[1]);
+                final String volId = cooked[2];
+                if (disk != null) {
+                    disk.volumes = ArrayUtils.appendElement(String.class, disk.volumes, volId);
                 }
                 break;
             }
@@ -944,14 +742,15 @@
                 if (cooked.length != 3) break;
                 final String id = cooked[1];
                 final int type = Integer.parseInt(cooked[2]);
-                final Volume vol = new Volume(id, type);
+                final int mtpIndex = allocateMtpIndex(id);
+                final VolumeInfo vol = new VolumeInfo(id, type, mtpIndex);
                 mVolumes.put(id, vol);
                 onVolumeCreatedLocked(vol);
                 break;
             }
             case VoldResponseCode.VOLUME_STATE_CHANGED: {
                 if (cooked.length != 3) break;
-                final Volume vol = mVolumes.get(cooked[1]);
+                final VolumeInfo vol = mVolumes.get(cooked[1]);
                 if (vol != null) {
                     final int oldState = vol.state;
                     final int newState = Integer.parseInt(cooked[2]);
@@ -962,7 +761,7 @@
             }
             case VoldResponseCode.VOLUME_FS_TYPE_CHANGED: {
                 if (cooked.length != 3) break;
-                final Volume vol = mVolumes.get(cooked[1]);
+                final VolumeInfo vol = mVolumes.get(cooked[1]);
                 if (vol != null) {
                     vol.fsType = cooked[2];
                 }
@@ -970,7 +769,7 @@
             }
             case VoldResponseCode.VOLUME_FS_UUID_CHANGED: {
                 if (cooked.length != 3) break;
-                final Volume vol = mVolumes.get(cooked[1]);
+                final VolumeInfo vol = mVolumes.get(cooked[1]);
                 if (vol != null) {
                     vol.fsUuid = cooked[2];
                 }
@@ -978,7 +777,7 @@
             }
             case VoldResponseCode.VOLUME_FS_LABEL_CHANGED: {
                 if (cooked.length != 3) break;
-                final Volume vol = mVolumes.get(cooked[1]);
+                final VolumeInfo vol = mVolumes.get(cooked[1]);
                 if (vol != null) {
                     vol.fsLabel = cooked[2];
                 }
@@ -986,13 +785,14 @@
             }
             case VoldResponseCode.VOLUME_PATH_CHANGED: {
                 if (cooked.length != 3) break;
-                final Volume vol = mVolumes.get(cooked[1]);
+                final VolumeInfo vol = mVolumes.get(cooked[1]);
                 if (vol != null) {
                     vol.path = cooked[2];
                 }
                 break;
             }
             case VoldResponseCode.VOLUME_DESTROYED: {
+                // TODO: send ACTION_MEDIA_REMOVED broadcast
                 if (cooked.length != 2) break;
                 mVolumes.remove(cooked[1]);
                 break;
@@ -1010,18 +810,19 @@
         return true;
     }
 
-    private void onVolumeCreatedLocked(Volume vol) {
-        final boolean primaryPhysical = SystemProperties.getBoolean(PROP_PRIMARY_PHYSICAL, false);
-        if (vol.type == Volume.TYPE_EMULATED && !primaryPhysical) {
-            vol.flags |= Volume.FLAG_PRIMARY;
-            vol.flags |= Volume.FLAG_VISIBLE;
+    private void onVolumeCreatedLocked(VolumeInfo vol) {
+        final boolean primaryPhysical = SystemProperties.getBoolean(
+                StorageManager.PROP_PRIMARY_PHYSICAL, false);
+        if (vol.type == VolumeInfo.TYPE_EMULATED && !primaryPhysical) {
+            vol.flags |= VolumeInfo.FLAG_PRIMARY;
+            vol.flags |= VolumeInfo.FLAG_VISIBLE;
             mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
 
-        } else if (vol.type == Volume.TYPE_PUBLIC) {
+        } else if (vol.type == VolumeInfo.TYPE_PUBLIC) {
             if (primaryPhysical) {
-                vol.flags |= Volume.FLAG_PRIMARY;
+                vol.flags |= VolumeInfo.FLAG_PRIMARY;
             }
-            vol.flags |= Volume.FLAG_VISIBLE;
+            vol.flags |= VolumeInfo.FLAG_VISIBLE;
             vol.userId = UserHandle.USER_OWNER;
             mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
 
@@ -1030,24 +831,24 @@
         }
     }
 
-    private void onVolumeStateChangedLocked(Volume vol, int oldState, int newState) {
+    private void onVolumeStateChangedLocked(VolumeInfo vol, int oldState, int newState) {
         // Kick state changed event towards all started users. Any users
         // started after this point will trigger additional
         // user-specific broadcasts.
         for (int userId : mStartedUsers) {
             if (vol.isVisibleToUser(userId)) {
-                final StorageVolume userVol = vol.buildVolumeForUser(userId);
+                final StorageVolume userVol = vol.buildStorageVolume(mContext, userId);
                 mHandler.obtainMessage(H_VOLUME_BROADCAST, userVol).sendToTarget();
             }
         }
 
         // Tell PackageManager about changes to primary volume state, but only
         // when not emulated.
-        if (vol.isPrimary() && vol.type == Volume.TYPE_PUBLIC) {
-            if (vol.state == Volume.STATE_MOUNTED) {
+        if (vol.isPrimary() && vol.type == VolumeInfo.TYPE_PUBLIC) {
+            if (vol.state == VolumeInfo.STATE_MOUNTED) {
                 mPms.updateExternalMediaStatus(true, false);
 
-            } else if (vol.state == Volume.STATE_UNMOUNTING) {
+            } else if (vol.state == VolumeInfo.STATE_UNMOUNTING) {
                 mPms.updateExternalMediaStatus(false, false);
 
                 // TODO: this should eventually be handled by new ObbVolume state changes
@@ -1061,8 +862,8 @@
             }
         }
 
-        final String oldEnvState = sStateToEnvironment.get(oldState);
-        final String newEnvState = sStateToEnvironment.get(newState);
+        final String oldEnvState = VolumeInfo.sStateToEnvironment.get(oldState);
+        final String newEnvState = VolumeInfo.sStateToEnvironment.get(newState);
 
         synchronized (mListeners) {
             for (int i = mListeners.size() -1; i >= 0; i--) {
@@ -1203,22 +1004,16 @@
         return false;
     }
 
-    /**
-     * @return state of the volume at the specified mount point
-     */
     @Override
     @Deprecated
     public String getVolumeState(String mountPoint) {
-        // TODO: pretend that we're unmounted when encrypting?
-        // SystemProperties.get("vold.encrypt_progress")
-
-        final Volume vol = findVolumeByLegacyPath(mountPoint);
-        return sStateToEnvironment.get(vol.state);
+        throw new UnsupportedOperationException();
     }
 
     @Override
+    @Deprecated
     public boolean isExternalStorageEmulated() {
-        return Environment.isExternalStorageEmulated();
+        throw new UnsupportedOperationException();
     }
 
     @Override
@@ -1226,13 +1021,13 @@
         enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
         waitForReady();
 
-        final Volume vol = findVolumeByLegacyPath(path);
+        final VolumeInfo vol = findVolumeByLegacyPath(path);
         if (vol != null) {
-            if (vol.type == Volume.TYPE_PUBLIC || vol.type == Volume.TYPE_PRIVATE) {
+            if (vol.type == VolumeInfo.TYPE_PUBLIC || vol.type == VolumeInfo.TYPE_PRIVATE) {
                 enforceUserRestriction(UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA);
             }
             try {
-                vol.mount();
+                mConnector.execute("volume", "mount", vol.id, vol.flags, vol.userId);
                 return 0;
             } catch (NativeDaemonConnectorException ignored) {
             }
@@ -1247,7 +1042,7 @@
         enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
         waitForReady();
 
-        final Volume vol = findVolumeByLegacyPath(path);
+        final VolumeInfo vol = findVolumeByLegacyPath(path);
         if (vol != null) {
             // TODO: expand PMS to know about multiple volumes
             if (vol.isPrimary()) {
@@ -1260,7 +1055,7 @@
             }
 
             try {
-                vol.unmount();
+                mConnector.execute("volume", "unmount", vol.id);
             } catch (NativeDaemonConnectorException ignored) {
             }
         } else {
@@ -1273,10 +1068,10 @@
         enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
         waitForReady();
 
-        final Volume vol = findVolumeByLegacyPath(path);
+        final VolumeInfo vol = findVolumeByLegacyPath(path);
         if (vol != null) {
             try {
-                vol.format();
+                mConnector.execute("volume", "format", vol.id);
                 return 0;
             } catch (NativeDaemonConnectorException ignored) {
             }
@@ -1315,8 +1110,9 @@
 
     private void warnOnNotMounted() {
         synchronized (mLock) {
-            for (Volume vol : mVolumes.values()) {
-                if (vol.isPrimary() && vol.state == Volume.STATE_MOUNTED) {
+            for (int i = 0; i < mVolumes.size(); i++) {
+                final VolumeInfo vol = mVolumes.valueAt(i);
+                if (vol.isPrimary() && vol.state == VolumeInfo.STATE_MOUNTED) {
                     // Cool beans, we have a mounted primary volume
                     return;
                 }
@@ -2012,17 +1808,14 @@
 
     @Override
     public StorageVolume[] getVolumeList(int userId) {
-        if (UserHandle.getCallingUserId() != userId) {
-            mContext.enforceCallingOrSelfPermission(
-                    android.Manifest.permission.ACCESS_ALL_EXTERNAL_STORAGE, "getVolumeList");
-        }
-
-        final ArrayList<StorageVolume> res = Lists.newArrayList();
+        final ArrayList<StorageVolume> res = new ArrayList<>();
         boolean foundPrimary = false;
+
         synchronized (mLock) {
-            for (Volume vol : mVolumes.values()) {
+            for (int i = 0; i < mVolumes.size(); i++) {
+                final VolumeInfo vol = mVolumes.valueAt(i);
                 if (vol.isVisibleToUser(userId)) {
-                    final StorageVolume userVol = vol.buildVolumeForUser(userId);
+                    final StorageVolume userVol = vol.buildStorageVolume(mContext, userId);
                     if (vol.isPrimary()) {
                         res.add(0, userVol);
                         foundPrimary = true;
@@ -2034,14 +1827,14 @@
         }
 
         if (!foundPrimary) {
-            Slog.w(TAG, "No primary storage defined yet; hacking together a stub");
+            Log.w(TAG, "No primary storage defined yet; hacking together a stub");
 
             final boolean primaryPhysical = SystemProperties.getBoolean(
-                    PROP_PRIMARY_PHYSICAL, false);
+                    StorageManager.PROP_PRIMARY_PHYSICAL, false);
 
             final String id = "stub_primary";
             final File path = Environment.getLegacyExternalStorageDirectory();
-            final int descriptionId = android.R.string.unknownName;
+            final String description = mContext.getString(android.R.string.unknownName);
             final boolean primary = true;
             final boolean removable = primaryPhysical;
             final boolean emulated = !primaryPhysical;
@@ -2050,17 +1843,38 @@
             final long maxFileSize = 0L;
             final UserHandle owner = new UserHandle(userId);
             final String uuid = null;
-            final String userLabel = null;
             final String state = Environment.MEDIA_REMOVED;
 
             res.add(0, new StorageVolume(id, MtpStorage.getStorageIdForIndex(0), path,
-                    descriptionId, primary, removable, emulated, mtpReserveSize,
-                    allowMassStorage, maxFileSize, owner, uuid, userLabel, state));
+                    description, primary, removable, emulated, mtpReserveSize,
+                    allowMassStorage, maxFileSize, owner, uuid, state));
         }
 
         return res.toArray(new StorageVolume[res.size()]);
     }
 
+    @Override
+    public DiskInfo[] getDisks() {
+        synchronized (mLock) {
+            final DiskInfo[] res = new DiskInfo[mDisks.size()];
+            for (int i = 0; i < mDisks.size(); i++) {
+                res[i] = mDisks.valueAt(i);
+            }
+            return res;
+        }
+    }
+
+    @Override
+    public VolumeInfo[] getVolumes() {
+        synchronized (mLock) {
+            final VolumeInfo[] res = new VolumeInfo[mVolumes.size()];
+            for (int i = 0; i < mVolumes.size(); i++) {
+                res[i] = mVolumes.valueAt(i);
+            }
+            return res;
+        }
+    }
+
     private void addObbStateLocked(ObbState obbState) throws RemoteException {
         final IBinder binder = obbState.getBinder();
         List<ObbState> obbStates = mObbMounts.get(binder);
@@ -2601,7 +2415,8 @@
             pw.println();
             pw.println("Disks:");
             pw.increaseIndent();
-            for (Disk disk : mDisks.values()) {
+            for (int i = 0; i < mDisks.size(); i++) {
+                final DiskInfo disk = mDisks.valueAt(i);
                 disk.dump(pw);
             }
             pw.decreaseIndent();
@@ -2609,7 +2424,8 @@
             pw.println();
             pw.println("Volumes:");
             pw.increaseIndent();
-            for (Volume vol : mVolumes.values()) {
+            for (int i = 0; i < mVolumes.size(); i++) {
+                final VolumeInfo vol = mVolumes.valueAt(i);
                 vol.dump(pw);
             }
             pw.decreaseIndent();