We thought we could push everyone through sdcardfs, but secondary
devices mounted in a stable location don't give full write access to
apps holding WRITE_EXTERNAL_STORAGE, so system internals still need
to reach behind sdcardfs.
To keep sdcardfs in the loop about changes that we make behind its
back, we issue access(2) calls which should be enough for it to
invalidate any cached details.
Bug:
74132243
Test: manual
Change-Id: I727cd179a5a825b16ec4df6e2f41a079758d41c5
* {@link android.Manifest.permission#WRITE_MEDIA_STORAGE}.
*/
public File getInternalPathForUser(int userId) {
- if (type == TYPE_PUBLIC && !isVisible()) {
+ if (type == TYPE_PUBLIC) {
// TODO: plumb through cleaner path from vold
return new File(path.replace("/storage/", "/mnt/media_rw/"));
} else {
protected abstract Uri buildNotificationUri(String docId);
+ /**
+ * Callback indicating that the given document has been modified. This gives
+ * the provider a hook to invalidate cached data, such as {@code sdcardfs}.
+ */
+ protected void onDocIdChanged(String docId) {
+ // Default is no-op
+ }
+
@Override
public boolean onCreate() {
throw new UnsupportedOperationException(
throw new IllegalStateException("Failed to mkdir " + file);
}
childId = getDocIdForFile(file);
+ onDocIdChanged(childId);
addFolderToMediaStore(getFileForDocId(childId, true));
} else {
try {
throw new IllegalStateException("Failed to touch " + file);
}
childId = getDocIdForFile(file);
+ onDocIdChanged(childId);
} catch (IOException e) {
throw new IllegalStateException("Failed to touch " + file + ": " + e);
}
final File before = getFileForDocId(docId);
final File after = FileUtils.buildUniqueFile(before.getParentFile(), displayName);
- final File visibleFileBefore = getFileForDocId(docId, true);
if (!before.renameTo(after)) {
throw new IllegalStateException("Failed to rename to " + after);
}
final String afterDocId = getDocIdForFile(after);
- moveInMediaStore(visibleFileBefore, getFileForDocId(afterDocId, true));
+ onDocIdChanged(docId);
+ onDocIdChanged(afterDocId);
+
+ final File beforeVisibleFile = getFileForDocId(docId, true);
+ final File afterVisibleFile = getFileForDocId(afterDocId, true);
+ moveInMediaStore(beforeVisibleFile, afterVisibleFile);
if (!TextUtils.equals(docId, afterDocId)) {
- scanFile(after);
+ scanFile(afterVisibleFile);
return afterDocId;
} else {
return null;
}
final String docId = getDocIdForFile(after);
+ onDocIdChanged(sourceDocumentId);
+ onDocIdChanged(docId);
moveInMediaStore(visibleFileBefore, getFileForDocId(docId, true));
return docId;
throw new IllegalStateException("Failed to delete " + file);
}
+ onDocIdChanged(docId);
removeFromMediaStore(visibleFile, isDirectory);
}
try {
// When finished writing, kick off media scanner
return ParcelFileDescriptor.open(
- file, pfdMode, mHandler, (IOException e) -> scanFile(visibleFile));
+ file, pfdMode, mHandler, (IOException e) -> {
+ onDocIdChanged(documentId);
+ scanFile(visibleFile);
+ });
} catch (IOException e) {
throw new FileNotFoundException("Failed to open for writing: " + e);
}
import android.provider.DocumentsContract.Path;
import android.provider.DocumentsContract.Root;
import android.provider.Settings;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.DebugUtils;
@Override
protected File getFileForDocId(String docId, boolean visible) throws FileNotFoundException {
+ return getFileForDocId(docId, visible, true);
+ }
+
+ private File getFileForDocId(String docId, boolean visible, boolean mustExist)
+ throws FileNotFoundException {
RootInfo root = getRootFromDocId(docId);
- return buildFile(root, docId, visible);
+ return buildFile(root, docId, visible, mustExist);
}
private Pair<RootInfo, File> resolveDocId(String docId, boolean visible)
throws FileNotFoundException {
RootInfo root = getRootFromDocId(docId);
- return Pair.create(root, buildFile(root, docId, visible));
+ return Pair.create(root, buildFile(root, docId, visible, true));
}
private RootInfo getRootFromDocId(String docId) throws FileNotFoundException {
return root;
}
- private File buildFile(RootInfo root, String docId, boolean visible)
+ private File buildFile(RootInfo root, String docId, boolean visible, boolean mustExist)
throws FileNotFoundException {
final int splitIndex = docId.indexOf(':', 1);
final String path = docId.substring(splitIndex + 1);
target.mkdirs();
}
target = new File(target, path);
- if (!target.exists()) {
+ if (mustExist && !target.exists()) {
throw new FileNotFoundException("Missing file for " + docId + " at " + target);
}
return target;
}
@Override
+ protected void onDocIdChanged(String docId) {
+ try {
+ // Touch the visible path to ensure that any sdcardfs caches have
+ // been updated to reflect underlying changes on disk.
+ final File visiblePath = getFileForDocId(docId, true, false);
+ if (visiblePath != null) {
+ Os.access(visiblePath.getAbsolutePath(), OsConstants.F_OK);
+ }
+ } catch (FileNotFoundException | ErrnoException ignored) {
+ }
+ }
+
+ @Override
public Cursor queryRoots(String[] projection) throws FileNotFoundException {
final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
synchronized (mRootsLock) {