From 27de30d31c3e79bc429cb71aed9681c55243f18d Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Sat, 18 Apr 2015 16:20:27 -0700 Subject: [PATCH] Wire up non-visible volumes, more states. Adds logic to ExternalStorageProvider to scan non-visible volumes, such as USB OTG devices. We use internal paths when surfacing these volumes, which also optimizes around the FUSE daemon for public devices. Also dumps internal state when requested. VolumeInfo now directly contains DiskInfo, which means it's snapshotted when sending events, avoiding teardown races. Switch notifications to use this DiskInfo directly. Finish wiring up new volume state, including helper methods to make it readable/writable state clearer. Handle disks and volumes with spaces in their labels. Bug: 19993667 Change-Id: I5c75e5658a6415976811477aebafee7694bde0f4 --- .../android/os/storage/StorageManager.java | 17 +- core/java/android/os/storage/VolumeInfo.java | 56 ++++++- .../internal/content/PackageHelper.java | 2 +- .../ExternalStorageProvider.java | 153 ++++++++++-------- .../systemui/usb/StorageNotification.java | 15 +- .../java/com/android/server/MountService.java | 97 ++++++----- 6 files changed, 215 insertions(+), 125 deletions(-) diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index b49c14e98ef24..efa3ef2c17778 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -505,6 +505,16 @@ public class StorageManager { return null; } + /** {@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 getVolumes() { return getVolumes(0); @@ -523,10 +533,9 @@ public class StorageManager { 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 4177380c80bbe..375a86a9f983f 100644 --- a/core/java/android/os/storage/VolumeInfo.java +++ b/core/java/android/os/storage/VolumeInfo.java @@ -101,23 +101,27 @@ public class VolumeInfo implements Parcelable { 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 class VolumeInfo implements Parcelable { 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 @@ public class VolumeInfo implements Parcelable { 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 @@ public class VolumeInfo implements Parcelable { 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 class VolumeInfo implements Parcelable { } } + 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 @@ public class VolumeInfo implements Parcelable { } } + /** + * 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 @@ public class VolumeInfo implements Parcelable { 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 class VolumeInfo implements Parcelable { 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 255f1fddc6c76..b04ddf4eab8e4 100644 --- a/core/java/com/android/internal/content/PackageHelper.java +++ b/core/java/com/android/internal/content/PackageHelper.java @@ -361,7 +361,7 @@ public class PackageHelper { 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 8f7311853e447..aff57bf7ed79a 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.database.MatrixCursor.RowBuilder; 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 class ExternalStorageProvider extends DocumentsProvider { 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 @@ public class ExternalStorageProvider extends DocumentsProvider { private final Object mRootsLock = new Object(); @GuardedBy("mRootsLock") - private ArrayList mRoots; - @GuardedBy("mRootsLock") - private HashMap mIdToRoot; - @GuardedBy("mRootsLock") - private HashMap mIdToPath; + private ArrayMap mRoots = new ArrayMap<>(); @GuardedBy("mObservers") - private Map mObservers = Maps.newHashMap(); + private ArrayMap 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 @@ public class ExternalStorageProvider extends DocumentsProvider { 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 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 @@ public class ExternalStorageProvider extends DocumentsProvider { String path = file.getAbsolutePath(); // Find the most-specific root path - Map.Entry mostSpecific = null; + String mostSpecificId = null; + String mostSpecificPath = null; synchronized (mRootsLock) { - for (Map.Entry 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 @@ public class ExternalStorageProvider extends DocumentsProvider { 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 class ExternalStorageProvider extends DocumentsProvider { 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 @@ public class ExternalStorageProvider extends DocumentsProvider { final File parent; synchronized (mRootsLock) { - parent = mIdToPath.get(rootId); + parent = mRoots.get(rootId).path; } final LinkedList pending = new LinkedList(); @@ -494,8 +498,10 @@ public class ExternalStorageProvider extends DocumentsProvider { 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 class ExternalStorageProvider extends DocumentsProvider { 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 @@ public class ExternalStorageProvider extends DocumentsProvider { 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 4a441c7bf3512..ecd1a2fbbc0a6 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 @@ public class StorageNotification extends SystemUI { // 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 @@ public class StorageNotification extends SystemUI { onVolumeChecking(vol); break; case VolumeInfo.STATE_MOUNTED: + case VolumeInfo.STATE_MOUNTED_READ_ONLY: onVolumeMounted(vol); break; case VolumeInfo.STATE_FORMATTING: @@ -136,7 +137,7 @@ public class StorageNotification extends SystemUI { } 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 @@ public class StorageNotification extends SystemUI { // 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 @@ public class StorageNotification extends SystemUI { } 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 @@ public class StorageNotification extends SystemUI { } 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 @@ public class StorageNotification extends SystemUI { 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 @@ public class StorageNotification extends SystemUI { 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 34a9559986248..cc27cfcc2d4cf 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.InvalidKeySpecException; 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 @@ class MountService extends IMountService.Stub 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 @@ class MountService extends IMountService.Stub 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 @@ class MountService extends IMountService.Stub 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 @@ class MountService extends IMountService.Stub 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 @@ class MountService extends IMountService.Stub 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 @@ class MountService extends IMountService.Stub 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>> binders = mObbMounts.entrySet() @@ -2743,36 +2784,6 @@ class MountService extends IMountService.Stub 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();