Merge "Wire up non-visible volumes, more states."
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index b49c14e..efa3ef2 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -506,6 +506,16 @@
     }
 
     /** {@hide} */
+    public @Nullable VolumeInfo findPrivateForEmulated(VolumeInfo emulatedVol) {
+        return findVolumeById(emulatedVol.getId().replace("emulated", "private"));
+    }
+
+    /** {@hide} */
+    public @Nullable VolumeInfo findEmulatedForPrivate(VolumeInfo privateVol) {
+        return findVolumeById(privateVol.getId().replace("private", "emulated"));
+    }
+
+    /** {@hide} */
     public @NonNull List<VolumeInfo> getVolumes() {
         return getVolumes(0);
     }
@@ -523,10 +533,9 @@
     public @Nullable String getBestVolumeDescription(VolumeInfo vol) {
         String descrip = vol.getDescription();
 
-        if (vol.diskId != null) {
-            final DiskInfo disk = findDiskById(vol.diskId);
-            if (disk != null && TextUtils.isEmpty(descrip)) {
-                descrip = disk.getDescription();
+        if (vol.disk != null) {
+            if (TextUtils.isEmpty(descrip)) {
+                descrip = vol.disk.getDescription();
             }
         }
 
diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java
index 4177380..375a86a 100644
--- a/core/java/android/os/storage/VolumeInfo.java
+++ b/core/java/android/os/storage/VolumeInfo.java
@@ -101,23 +101,27 @@
         sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTED, Environment.MEDIA_UNMOUNTED);
         sStateToEnvironment.put(VolumeInfo.STATE_CHECKING, Environment.MEDIA_CHECKING);
         sStateToEnvironment.put(VolumeInfo.STATE_MOUNTED, Environment.MEDIA_MOUNTED);
+        sStateToEnvironment.put(VolumeInfo.STATE_MOUNTED_READ_ONLY, Environment.MEDIA_MOUNTED_READ_ONLY);
         sStateToEnvironment.put(VolumeInfo.STATE_FORMATTING, Environment.MEDIA_UNMOUNTED);
         sStateToEnvironment.put(VolumeInfo.STATE_EJECTING, Environment.MEDIA_EJECTING);
         sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTABLE, Environment.MEDIA_UNMOUNTABLE);
         sStateToEnvironment.put(VolumeInfo.STATE_REMOVED, Environment.MEDIA_REMOVED);
+        sStateToEnvironment.put(VolumeInfo.STATE_BAD_REMOVAL, Environment.MEDIA_BAD_REMOVAL);
 
         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_MOUNTED_READ_ONLY, Intent.ACTION_MEDIA_MOUNTED);
         sEnvironmentToBroadcast.put(Environment.MEDIA_EJECTING, Intent.ACTION_MEDIA_EJECT);
         sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTABLE, Intent.ACTION_MEDIA_UNMOUNTABLE);
         sEnvironmentToBroadcast.put(Environment.MEDIA_REMOVED, Intent.ACTION_MEDIA_REMOVED);
+        sEnvironmentToBroadcast.put(Environment.MEDIA_BAD_REMOVAL, Intent.ACTION_MEDIA_BAD_REMOVAL);
     }
 
     /** vold state */
     public final String id;
     public final int type;
-    public final String diskId;
+    public final DiskInfo disk;
     public int mountFlags = 0;
     public int mountUserId = -1;
     public int state = STATE_UNMOUNTED;
@@ -131,17 +135,21 @@
     public String nickname;
     public int userFlags = 0;
 
-    public VolumeInfo(String id, int type, String diskId, int mtpIndex) {
+    public VolumeInfo(String id, int type, DiskInfo disk, int mtpIndex) {
         this.id = Preconditions.checkNotNull(id);
         this.type = type;
-        this.diskId = diskId;
+        this.disk = disk;
         this.mtpIndex = mtpIndex;
     }
 
     public VolumeInfo(Parcel parcel) {
         id = parcel.readString();
         type = parcel.readInt();
-        diskId = parcel.readString();
+        if (parcel.readInt() != 0) {
+            disk = DiskInfo.CREATOR.createFromParcel(parcel);
+        } else {
+            disk = null;
+        }
         mountFlags = parcel.readInt();
         mountUserId = parcel.readInt();
         state = parcel.readInt();
@@ -179,8 +187,12 @@
         return id;
     }
 
+    public @Nullable DiskInfo getDisk() {
+        return disk;
+    }
+
     public @Nullable String getDiskId() {
-        return diskId;
+        return (disk != null) ? disk.id : null;
     }
 
     public int getType() {
@@ -199,6 +211,10 @@
         return nickname;
     }
 
+    public int getMountUserId() {
+        return mountUserId;
+    }
+
     public @Nullable String getDescription() {
         if (ID_PRIVATE_INTERNAL.equals(id)) {
             return Resources.getSystem().getString(com.android.internal.R.string.storage_internal);
@@ -211,6 +227,14 @@
         }
     }
 
+    public boolean isMountedReadable() {
+        return state == STATE_MOUNTED || state == STATE_MOUNTED_READ_ONLY;
+    }
+
+    public boolean isMountedWritable() {
+        return state == STATE_MOUNTED;
+    }
+
     public boolean isPrimary() {
         return (mountFlags & MOUNT_FLAG_PRIMARY) != 0;
     }
@@ -253,6 +277,19 @@
         }
     }
 
+    /**
+     * Path which is accessible to apps holding
+     * {@link android.Manifest.permission#WRITE_MEDIA_STORAGE}.
+     */
+    public File getInternalPathForUser(int userId) {
+        if (type == TYPE_PUBLIC) {
+            // TODO: plumb through cleaner path from vold
+            return new File(path.replace("/storage/", "/mnt/media_rw/"));
+        } else {
+            return getPathForUser(userId);
+        }
+    }
+
     public StorageVolume buildStorageVolume(Context context, int userId) {
         final boolean removable;
         final boolean emulated;
@@ -339,7 +376,7 @@
         pw.println("VolumeInfo{" + id + "}:");
         pw.increaseIndent();
         pw.printPair("type", DebugUtils.valueToString(getClass(), "TYPE_", type));
-        pw.printPair("diskId", diskId);
+        pw.printPair("diskId", getDiskId());
         pw.printPair("mountFlags", DebugUtils.flagsToString(getClass(), "MOUNT_FLAG_", mountFlags));
         pw.printPair("mountUserId", mountUserId);
         pw.printPair("state", DebugUtils.valueToString(getClass(), "STATE_", state));
@@ -403,7 +440,12 @@
     public void writeToParcel(Parcel parcel, int flags) {
         parcel.writeString(id);
         parcel.writeInt(type);
-        parcel.writeString(diskId);
+        if (disk != null) {
+            parcel.writeInt(1);
+            disk.writeToParcel(parcel, flags);
+        } else {
+            parcel.writeInt(0);
+        }
         parcel.writeInt(mountFlags);
         parcel.writeInt(mountUserId);
         parcel.writeInt(state);
diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java
index 255f1fd..b04ddf4 100644
--- a/core/java/com/android/internal/content/PackageHelper.java
+++ b/core/java/com/android/internal/content/PackageHelper.java
@@ -361,7 +361,7 @@
         VolumeInfo bestCandidate = null;
         long bestCandidateAvailBytes = Long.MIN_VALUE;
         for (VolumeInfo vol : storageManager.getVolumes()) {
-            if (vol.type == VolumeInfo.TYPE_PRIVATE && vol.state == VolumeInfo.STATE_MOUNTED) {
+            if (vol.type == VolumeInfo.TYPE_PRIVATE && vol.isMountedWritable()) {
                 final long availBytes = storageManager.getStorageBytesUntilLow(new File(vol.path));
                 if (availBytes >= sizeBytes) {
                     allCandidates.add(vol.fsUuid);
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 8f73118..aff57bf 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -26,34 +26,35 @@
 import android.graphics.Point;
 import android.net.Uri;
 import android.os.CancellationSignal;
-import android.os.Environment;
 import android.os.FileObserver;
 import android.os.FileUtils;
 import android.os.Handler;
 import android.os.ParcelFileDescriptor;
 import android.os.ParcelFileDescriptor.OnCloseListener;
+import android.os.UserHandle;
 import android.os.storage.StorageManager;
-import android.os.storage.StorageVolume;
+import android.os.storage.VolumeInfo;
 import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.Document;
 import android.provider.DocumentsContract.Root;
 import android.provider.DocumentsProvider;
 import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.DebugUtils;
 import android.util.Log;
 import android.webkit.MimeTypeMap;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.google.android.collect.Lists;
-import com.google.android.collect.Maps;
+import com.android.internal.util.IndentingPrintWriter;
 
 import java.io.File;
+import java.io.FileDescriptor;
 import java.io.FileNotFoundException;
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
+import java.io.PrintWriter;
 import java.util.LinkedList;
-import java.util.Map;
+import java.util.List;
 import java.util.Objects;
 
 public class ExternalStorageProvider extends DocumentsProvider {
@@ -80,6 +81,8 @@
         public int flags;
         public String title;
         public String docId;
+        public File visiblePath;
+        public File path;
     }
 
     private static final String ROOT_ID_PRIMARY_EMULATED = "primary";
@@ -90,26 +93,17 @@
     private final Object mRootsLock = new Object();
 
     @GuardedBy("mRootsLock")
-    private ArrayList<RootInfo> mRoots;
-    @GuardedBy("mRootsLock")
-    private HashMap<String, RootInfo> mIdToRoot;
-    @GuardedBy("mRootsLock")
-    private HashMap<String, File> mIdToPath;
+    private ArrayMap<String, RootInfo> mRoots = new ArrayMap<>();
 
     @GuardedBy("mObservers")
-    private Map<File, DirectoryObserver> mObservers = Maps.newHashMap();
+    private ArrayMap<File, DirectoryObserver> mObservers = new ArrayMap<>();
 
     @Override
     public boolean onCreate() {
         mStorageManager = (StorageManager) getContext().getSystemService(Context.STORAGE_SERVICE);
         mHandler = new Handler();
 
-        mRoots = Lists.newArrayList();
-        mIdToRoot = Maps.newHashMap();
-        mIdToPath = Maps.newHashMap();
-
         updateVolumes();
-
         return true;
     }
 
@@ -121,52 +115,53 @@
 
     private void updateVolumesLocked() {
         mRoots.clear();
-        mIdToPath.clear();
-        mIdToRoot.clear();
 
-        final StorageVolume[] volumes = mStorageManager.getVolumeList();
-        for (StorageVolume volume : volumes) {
-            final boolean mounted = Environment.MEDIA_MOUNTED.equals(volume.getState())
-                    || Environment.MEDIA_MOUNTED_READ_ONLY.equals(volume.getState());
-            if (!mounted) continue;
+        final int userId = UserHandle.myUserId();
+        final List<VolumeInfo> volumes = mStorageManager.getVolumes();
+        for (VolumeInfo volume : volumes) {
+            if (!volume.isMountedReadable()) continue;
 
             final String rootId;
-            if (volume.isPrimary() && volume.isEmulated()) {
+            if (VolumeInfo.ID_EMULATED_INTERNAL.equals(volume.getId())) {
                 rootId = ROOT_ID_PRIMARY_EMULATED;
-            } else if (volume.getUuid() != null) {
-                rootId = volume.getUuid();
+            } else if (volume.getType() == VolumeInfo.TYPE_EMULATED) {
+                final VolumeInfo privateVol = mStorageManager.findPrivateForEmulated(volume);
+                rootId = privateVol.getFsUuid();
+            } else if (volume.getType() == VolumeInfo.TYPE_PUBLIC) {
+                rootId = volume.getFsUuid();
             } else {
-                Log.d(TAG, "Missing UUID for " + volume.getPath() + "; skipping");
+                // Unsupported volume; ignore
                 continue;
             }
 
-            if (mIdToPath.containsKey(rootId)) {
-                Log.w(TAG, "Duplicate UUID " + rootId + "; skipping");
+            if (TextUtils.isEmpty(rootId)) {
+                Log.d(TAG, "Missing UUID for " + volume.getId() + "; skipping");
+                continue;
+            }
+            if (mRoots.containsKey(rootId)) {
+                Log.w(TAG, "Duplicate UUID " + rootId + " for " + volume.getId() + "; skipping");
                 continue;
             }
 
             try {
-                final File path = volume.getPathFile();
-                mIdToPath.put(rootId, path);
-
                 final RootInfo root = new RootInfo();
+                mRoots.put(rootId, root);
+
                 root.rootId = rootId;
                 root.flags = Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED
                         | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD;
                 if (ROOT_ID_PRIMARY_EMULATED.equals(rootId)) {
                     root.title = getContext().getString(R.string.root_internal_storage);
                 } else {
-                    final String userLabel = volume.getUserLabel();
-                    if (!TextUtils.isEmpty(userLabel)) {
-                        root.title = userLabel;
-                    } else {
-                        root.title = volume.getDescription(getContext());
-                    }
+                    root.title = mStorageManager.getBestVolumeDescription(volume);
+                }
+                if (volume.getType() == VolumeInfo.TYPE_PUBLIC) {
                     root.flags |= Root.FLAG_HAS_SETTINGS;
                 }
-                root.docId = getDocIdForFile(path);
-                mRoots.add(root);
-                mIdToRoot.put(rootId, root);
+                root.visiblePath = volume.getPathForUser(userId);
+                root.path = volume.getInternalPathForUser(userId);
+                root.docId = getDocIdForFile(root.path);
+
             } catch (FileNotFoundException e) {
                 throw new IllegalStateException(e);
             }
@@ -190,23 +185,26 @@
         String path = file.getAbsolutePath();
 
         // Find the most-specific root path
-        Map.Entry<String, File> mostSpecific = null;
+        String mostSpecificId = null;
+        String mostSpecificPath = null;
         synchronized (mRootsLock) {
-            for (Map.Entry<String, File> root : mIdToPath.entrySet()) {
-                final String rootPath = root.getValue().getPath();
-                if (path.startsWith(rootPath) && (mostSpecific == null
-                        || rootPath.length() > mostSpecific.getValue().getPath().length())) {
-                    mostSpecific = root;
+            for (int i = 0; i < mRoots.size(); i++) {
+                final String rootId = mRoots.keyAt(i);
+                final String rootPath = mRoots.valueAt(i).path.getAbsolutePath();
+                if (path.startsWith(rootPath) && (mostSpecificPath == null
+                        || rootPath.length() > mostSpecificPath.length())) {
+                    mostSpecificId = rootId;
+                    mostSpecificPath = rootPath;
                 }
             }
         }
 
-        if (mostSpecific == null) {
+        if (mostSpecificPath == null) {
             throw new FileNotFoundException("Failed to find root that contains " + path);
         }
 
         // Start at first char of path under root
-        final String rootPath = mostSpecific.getValue().getPath();
+        final String rootPath = mostSpecificPath;
         if (rootPath.equals(path)) {
             path = "";
         } else if (rootPath.endsWith("/")) {
@@ -215,21 +213,30 @@
             path = path.substring(rootPath.length() + 1);
         }
 
-        return mostSpecific.getKey() + ':' + path;
+        return mostSpecificId + ':' + path;
     }
 
     private File getFileForDocId(String docId) throws FileNotFoundException {
+        return getFileForDocId(docId, false);
+    }
+
+    private File getFileForDocId(String docId, boolean visible) throws FileNotFoundException {
         final int splitIndex = docId.indexOf(':', 1);
         final String tag = docId.substring(0, splitIndex);
         final String path = docId.substring(splitIndex + 1);
 
-        File target;
+        RootInfo root;
         synchronized (mRootsLock) {
-            target = mIdToPath.get(tag);
+            root = mRoots.get(tag);
         }
-        if (target == null) {
+        if (root == null) {
             throw new FileNotFoundException("No root for " + tag);
         }
+
+        File target = visible ? root.visiblePath : root.path;
+        if (target == null) {
+            return null;
+        }
         if (!target.exists()) {
             target.mkdirs();
         }
@@ -286,16 +293,13 @@
     public Cursor queryRoots(String[] projection) throws FileNotFoundException {
         final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
         synchronized (mRootsLock) {
-            for (String rootId : mIdToPath.keySet()) {
-                final RootInfo root = mIdToRoot.get(rootId);
-                final File path = mIdToPath.get(rootId);
-
+            for (RootInfo root : mRoots.values()) {
                 final RowBuilder row = result.newRow();
                 row.add(Root.COLUMN_ROOT_ID, root.rootId);
                 row.add(Root.COLUMN_FLAGS, root.flags);
                 row.add(Root.COLUMN_TITLE, root.title);
                 row.add(Root.COLUMN_DOCUMENT_ID, root.docId);
-                row.add(Root.COLUMN_AVAILABLE_BYTES, path.getFreeSpace());
+                row.add(Root.COLUMN_AVAILABLE_BYTES, root.path.getFreeSpace());
             }
         }
         return result;
@@ -464,7 +468,7 @@
 
         final File parent;
         synchronized (mRootsLock) {
-            parent = mIdToPath.get(rootId);
+            parent = mRoots.get(rootId).path;
         }
 
         final LinkedList<File> pending = new LinkedList<File>();
@@ -494,8 +498,10 @@
             String documentId, String mode, CancellationSignal signal)
             throws FileNotFoundException {
         final File file = getFileForDocId(documentId);
+        final File visibleFile = getFileForDocId(documentId, true);
+
         final int pfdMode = ParcelFileDescriptor.parseMode(mode);
-        if (pfdMode == ParcelFileDescriptor.MODE_READ_ONLY) {
+        if (pfdMode == ParcelFileDescriptor.MODE_READ_ONLY || visibleFile == null) {
             return ParcelFileDescriptor.open(file, pfdMode);
         } else {
             try {
@@ -505,7 +511,7 @@
                     public void onClose(IOException e) {
                         final Intent intent = new Intent(
                                 Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
-                        intent.setData(Uri.fromFile(file));
+                        intent.setData(Uri.fromFile(visibleFile));
                         getContext().sendBroadcast(intent);
                     }
                 });
@@ -523,6 +529,27 @@
         return DocumentsContract.openImageThumbnail(file);
     }
 
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ", 160);
+        synchronized (mRootsLock) {
+            for (int i = 0; i < mRoots.size(); i++) {
+                final RootInfo root = mRoots.valueAt(i);
+                pw.println("Root{" + root.rootId + "}:");
+                pw.increaseIndent();
+                pw.printPair("flags", DebugUtils.flagsToString(Root.class, "FLAG_", root.flags));
+                pw.println();
+                pw.printPair("title", root.title);
+                pw.printPair("docId", root.docId);
+                pw.println();
+                pw.printPair("path", root.path);
+                pw.printPair("visiblePath", root.visiblePath);
+                pw.decreaseIndent();
+                pw.println();
+            }
+        }
+    }
+
     private static String getTypeForFile(File file) {
         if (file.isDirectory()) {
             return Document.MIME_TYPE_DIR;
diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
index 4a441c7..ecd1a2f 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
@@ -60,7 +60,7 @@
             // Avoid kicking notifications when getting early metadata before
             // mounted. If already mounted, we're being kicked because of a
             // nickname or init'ed change.
-            if (vol.getState() == VolumeInfo.STATE_MOUNTED) {
+            if (vol.isMountedReadable()) {
                 onVolumeStateChangedInternal(vol, vol.getState(), vol.getState());
             }
         }
@@ -111,6 +111,7 @@
                 onVolumeChecking(vol);
                 break;
             case VolumeInfo.STATE_MOUNTED:
+            case VolumeInfo.STATE_MOUNTED_READ_ONLY:
                 onVolumeMounted(vol);
                 break;
             case VolumeInfo.STATE_FORMATTING:
@@ -136,7 +137,7 @@
     }
 
     private void onVolumeChecking(VolumeInfo vol) {
-        final DiskInfo disk = mStorageManager.findDiskById(vol.getDiskId());
+        final DiskInfo disk = vol.getDisk();
         final CharSequence title = mContext.getString(
                 R.string.ext_media_checking_notification_title, disk.getDescription());
         final CharSequence text = mContext.getString(
@@ -156,7 +157,7 @@
         // Don't annoy when user dismissed in past
         if (vol.isSnoozed()) return;
 
-        final DiskInfo disk = mStorageManager.findDiskById(vol.getDiskId());
+        final DiskInfo disk = vol.getDisk();
         final Notification notif;
         if (disk.isAdoptable() && !vol.isInited()) {
             final CharSequence title = disk.getDescription();
@@ -198,7 +199,7 @@
     }
 
     private void onVolumeEjecting(VolumeInfo vol) {
-        final DiskInfo disk = mStorageManager.findDiskById(vol.getDiskId());
+        final DiskInfo disk = vol.getDisk();
         final CharSequence title = mContext.getString(
                 R.string.ext_media_unmounting_notification_title, disk.getDescription());
         final CharSequence text = mContext.getString(
@@ -215,7 +216,7 @@
     }
 
     private void onVolumeUnmountable(VolumeInfo vol) {
-        final DiskInfo disk = mStorageManager.findDiskById(vol.getDiskId());
+        final DiskInfo disk = vol.getDisk();
         final CharSequence title = mContext.getString(
                 R.string.ext_media_unmountable_notification_title, disk.getDescription());
         final CharSequence text = mContext.getString(
@@ -236,7 +237,7 @@
             return;
         }
 
-        final DiskInfo disk = mStorageManager.findDiskById(vol.getDiskId());
+        final DiskInfo disk = vol.getDisk();
         final CharSequence title = mContext.getString(
                 R.string.ext_media_nomedia_notification_title, disk.getDescription());
         final CharSequence text = mContext.getString(
@@ -256,7 +257,7 @@
             return;
         }
 
-        final DiskInfo disk = mStorageManager.findDiskById(vol.getDiskId());
+        final DiskInfo disk = vol.getDisk();
         final CharSequence title = mContext.getString(
                 R.string.ext_media_badremoval_notification_title, disk.getDescription());
         final CharSequence text = mContext.getString(
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index 34a9559..cc27cfc 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -103,7 +103,6 @@
 import java.security.spec.KeySpec;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -688,12 +687,12 @@
         synchronized (mVolumes) {
             for (int i = 0; i < mVolumes.size(); i++) {
                 final VolumeInfo vol = mVolumes.valueAt(i);
-                if (vol.isVisibleToUser(userId) && vol.state == VolumeInfo.STATE_MOUNTED) {
+                if (vol.isVisibleToUser(userId) && vol.isMountedReadable()) {
                     final StorageVolume userVol = vol.buildStorageVolume(mContext, userId);
                     mHandler.obtainMessage(H_VOLUME_BROADCAST, userVol).sendToTarget();
 
-                    mCallbacks.notifyStorageStateChanged(userVol.getPath(),
-                            Environment.MEDIA_MOUNTED, Environment.MEDIA_MOUNTED);
+                    final String envState = VolumeInfo.getEnvironmentForState(vol.getState());
+                    mCallbacks.notifyStorageStateChanged(userVol.getPath(), envState, envState);
                 }
             }
             mStartedUsers = ArrayUtils.appendInt(mStartedUsers, userId);
@@ -822,13 +821,13 @@
                 break;
             }
             case VoldResponseCode.DISK_LABEL_CHANGED: {
-                if (cooked.length != 3) break;
                 final DiskInfo disk = mDisks.get(cooked[1]);
                 if (disk != null) {
-                    disk.label = cooked[2];
-                }
-                if (disk.label != null) {
-                    disk.label = disk.label.trim();
+                    final StringBuilder builder = new StringBuilder();
+                    for (int i = 2; i < cooked.length; i++) {
+                        builder.append(cooked[i]).append(' ');
+                    }
+                    disk.label = builder.toString().trim();
                 }
                 break;
             }
@@ -848,8 +847,9 @@
                 final String id = cooked[1];
                 final int type = Integer.parseInt(cooked[2]);
                 final String diskId = (cooked.length == 4) ? cooked[3] : null;
+                final DiskInfo disk = mDisks.get(diskId);
                 final int mtpIndex = allocateMtpIndex(id);
-                final VolumeInfo vol = new VolumeInfo(id, type, diskId, mtpIndex);
+                final VolumeInfo vol = new VolumeInfo(id, type, disk, mtpIndex);
                 mVolumes.put(id, vol);
                 onVolumeCreatedLocked(vol);
                 break;
@@ -885,10 +885,13 @@
                 break;
             }
             case VoldResponseCode.VOLUME_FS_LABEL_CHANGED: {
-                if (cooked.length != 3) break;
                 final VolumeInfo vol = mVolumes.get(cooked[1]);
                 if (vol != null) {
-                    vol.fsLabel = cooked[2];
+                    final StringBuilder builder = new StringBuilder();
+                    for (int i = 2; i < cooked.length; i++) {
+                        builder.append(cooked[i]).append(' ');
+                    }
+                    vol.fsLabel = builder.toString().trim();
                 }
                 mCallbacks.notifyVolumeMetadataChanged(vol.clone());
                 break;
@@ -1351,7 +1354,7 @@
         synchronized (mLock) {
             for (int i = 0; i < mVolumes.size(); i++) {
                 final VolumeInfo vol = mVolumes.valueAt(i);
-                if (vol.isPrimary() && vol.state == VolumeInfo.STATE_MOUNTED) {
+                if (vol.isPrimary() && vol.isMountedWritable()) {
                     // Cool beans, we have a mounted primary volume
                     return;
                 }
@@ -2711,9 +2714,47 @@
     protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
 
+        for (String arg : args) {
+            if ("--clear-metadata".equals(arg)) {
+                synchronized (mLock) {
+                    mMetadata.clear();
+                    writeMetadataLocked();
+                }
+            }
+        }
+
         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ", 160);
+        synchronized (mLock) {
+            pw.println("Disks:");
+            pw.increaseIndent();
+            for (int i = 0; i < mDisks.size(); i++) {
+                final DiskInfo disk = mDisks.valueAt(i);
+                disk.dump(pw);
+            }
+            pw.decreaseIndent();
+
+            pw.println();
+            pw.println("Volumes:");
+            pw.increaseIndent();
+            for (int i = 0; i < mVolumes.size(); i++) {
+                final VolumeInfo vol = mVolumes.valueAt(i);
+                if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.id)) continue;
+                vol.dump(pw);
+            }
+            pw.decreaseIndent();
+
+            pw.println();
+            pw.println("Metadata:");
+            pw.increaseIndent();
+            for (int i = 0; i < mMetadata.size(); i++) {
+                final VolumeMetadata meta = mMetadata.valueAt(i);
+                meta.dump(pw);
+            }
+            pw.decreaseIndent();
+        }
 
         synchronized (mObbMounts) {
+            pw.println();
             pw.println("mObbMounts:");
             pw.increaseIndent();
             final Iterator<Entry<IBinder, List<ObbState>>> binders = mObbMounts.entrySet()
@@ -2743,36 +2784,6 @@
             pw.decreaseIndent();
         }
 
-        synchronized (mLock) {
-            pw.println();
-            pw.println("Disks:");
-            pw.increaseIndent();
-            for (int i = 0; i < mDisks.size(); i++) {
-                final DiskInfo disk = mDisks.valueAt(i);
-                disk.dump(pw);
-            }
-            pw.decreaseIndent();
-
-            pw.println();
-            pw.println("Volumes:");
-            pw.increaseIndent();
-            for (int i = 0; i < mVolumes.size(); i++) {
-                final VolumeInfo vol = mVolumes.valueAt(i);
-                if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.id)) continue;
-                vol.dump(pw);
-            }
-            pw.decreaseIndent();
-
-            pw.println();
-            pw.println("Metadata:");
-            pw.increaseIndent();
-            for (int i = 0; i < mMetadata.size(); i++) {
-                final VolumeMetadata meta = mMetadata.valueAt(i);
-                meta.dump(pw);
-            }
-            pw.decreaseIndent();
-        }
-
         pw.println();
         pw.println("mConnection:");
         pw.increaseIndent();