From 27de30d31c3e79bc429cb71aed9681c55243f18d Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Sat, 18 Apr 2015 16:20:27 -0700 Subject: 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 --- .../externalstorage/ExternalStorageProvider.java | 153 ++++++++++++--------- 1 file changed, 90 insertions(+), 63 deletions(-) (limited to 'packages/ExternalStorageProvider') 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.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; -- cgit v1.1