Loading core/java/android/os/storage/StorageManager.java +13 −4 Original line number Diff line number Diff line Loading @@ -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<VolumeInfo> getVolumes() { return getVolumes(0); Loading @@ -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(); } } Loading core/java/android/os/storage/VolumeInfo.java +49 −7 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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(); Loading Loading @@ -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() { Loading @@ -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); Loading @@ -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; } Loading Loading @@ -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; Loading Loading @@ -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)); Loading Loading @@ -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); Loading core/java/com/android/internal/content/PackageHelper.java +1 −1 Original line number Diff line number Diff line Loading @@ -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); Loading packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +90 −63 Original line number Diff line number Diff line Loading @@ -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 { Loading @@ -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"; Loading @@ -90,26 +93,17 @@ public class ExternalStorageProvider extends DocumentsProvider { 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; } Loading @@ -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<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); } Loading @@ -190,23 +185,26 @@ public class ExternalStorageProvider extends DocumentsProvider { 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("/")) { Loading @@ -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(); } Loading Loading @@ -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; Loading Loading @@ -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<File> pending = new LinkedList<File>(); Loading Loading @@ -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 { Loading @@ -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); } }); Loading @@ -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; Loading packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java +8 −7 Original line number Diff line number Diff line Loading @@ -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()); } } Loading Loading @@ -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: Loading @@ -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( Loading @@ -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(); Loading Loading @@ -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( Loading @@ -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( Loading @@ -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( Loading @@ -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( Loading Loading
core/java/android/os/storage/StorageManager.java +13 −4 Original line number Diff line number Diff line Loading @@ -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<VolumeInfo> getVolumes() { return getVolumes(0); Loading @@ -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(); } } Loading
core/java/android/os/storage/VolumeInfo.java +49 −7 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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(); Loading Loading @@ -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() { Loading @@ -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); Loading @@ -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; } Loading Loading @@ -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; Loading Loading @@ -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)); Loading Loading @@ -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); Loading
core/java/com/android/internal/content/PackageHelper.java +1 −1 Original line number Diff line number Diff line Loading @@ -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); Loading
packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +90 −63 Original line number Diff line number Diff line Loading @@ -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 { Loading @@ -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"; Loading @@ -90,26 +93,17 @@ public class ExternalStorageProvider extends DocumentsProvider { 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; } Loading @@ -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<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); } Loading @@ -190,23 +185,26 @@ public class ExternalStorageProvider extends DocumentsProvider { 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("/")) { Loading @@ -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(); } Loading Loading @@ -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; Loading Loading @@ -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<File> pending = new LinkedList<File>(); Loading Loading @@ -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 { Loading @@ -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); } }); Loading @@ -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; Loading
packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java +8 −7 Original line number Diff line number Diff line Loading @@ -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()); } } Loading Loading @@ -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: Loading @@ -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( Loading @@ -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(); Loading Loading @@ -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( Loading @@ -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( Loading @@ -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( Loading @@ -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( Loading