diff options
author | Steve McKay <smckay@google.com> | 2016-08-30 16:27:20 -0700 |
---|---|---|
committer | Steve McKay <smckay@google.com> | 2016-09-06 10:29:12 -0700 |
commit | 88b93822c84ebd2b63d0645aa7442769bfaa1769 (patch) | |
tree | a1966aa0e708c0c1e8a152d9e3e37e273c9561d3 /src/com | |
parent | 5d279927353df65d1ca3530c87c84eafaf9f8413 (diff) | |
download | android_packages_providers_DownloadProvider-88b93822c84ebd2b63d0645aa7442769bfaa1769.tar.gz android_packages_providers_DownloadProvider-88b93822c84ebd2b63d0645aa7442769bfaa1769.tar.bz2 android_packages_providers_DownloadProvider-88b93822c84ebd2b63d0645aa7442769bfaa1769.zip |
Listen to file changes on Downloads dir.
And notify clients of Downloads when files on disk.
Cherrypicked from: e398c096892fe8aac0c46374aec6bbe016c41016 and
2b011b97f96463c1f4a4f6b69c5bbe18ec0fc572 (followup changes
due to autocommit commiting changes before completing nits).
Bug: 28430547
Change-Id: I42a42bfb5e585918fe6b384a862699445afefcbc
Diffstat (limited to 'src/com')
-rw-r--r-- | src/com/android/providers/downloads/DownloadStorageProvider.java | 137 |
1 files changed, 112 insertions, 25 deletions
diff --git a/src/com/android/providers/downloads/DownloadStorageProvider.java b/src/com/android/providers/downloads/DownloadStorageProvider.java index fb9392f9..0bacb88b 100644 --- a/src/com/android/providers/downloads/DownloadStorageProvider.java +++ b/src/com/android/providers/downloads/DownloadStorageProvider.java @@ -29,28 +29,36 @@ import android.net.Uri; import android.os.Binder; import android.os.CancellationSignal; import android.os.Environment; +import android.os.FileObserver; import android.os.FileUtils; import android.os.ParcelFileDescriptor; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Root; import android.provider.DocumentsProvider; +import android.provider.Downloads; import android.support.provider.DocumentArchiveHelper; import android.text.TextUtils; -import android.webkit.MimeTypeMap; - -import libcore.io.IoUtils; +import android.util.Log; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.text.NumberFormat; +import javax.annotation.Nullable; +import javax.annotation.concurrent.GuardedBy; + +import libcore.io.IoUtils; + /** * Presents a {@link DocumentsContract} view of {@link DownloadManager} * contents. */ public class DownloadStorageProvider extends DocumentsProvider { + private static final String TAG = "DownloadStorageProvider"; + private static final boolean DEBUG = false; + private static final String AUTHORITY = Constants.STORAGE_AUTHORITY; private static final String DOC_ID_ROOT = Constants.STORAGE_ROOT_ID; @@ -74,6 +82,7 @@ public class DownloadStorageProvider extends DocumentsProvider { mDm.setAccessAllDownloads(true); mDm.setAccessFilename(true); mArchiveHelper = new DocumentArchiveHelper(this, ':'); + return true; } @@ -179,7 +188,8 @@ public class DownloadStorageProvider extends DocumentsProvider { return mArchiveHelper.queryDocument(docId, projection); } - final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); + final DownloadsCursor result = + new DownloadsCursor(projection, getContext().getContentResolver()); if (DOC_ID_ROOT.equals(docId)) { includeDefaultDocument(result); @@ -200,6 +210,8 @@ public class DownloadStorageProvider extends DocumentsProvider { Binder.restoreCallingIdentity(token); } } + + result.start(); return result; } @@ -211,7 +223,8 @@ public class DownloadStorageProvider extends DocumentsProvider { return mArchiveHelper.queryChildDocuments(docId, projection, sortOrder); } - final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); + final DownloadsCursor result = + new DownloadsCursor(projection, getContext().getContentResolver()); // Delegate to real provider final long token = Binder.clearCallingIdentity(); @@ -227,6 +240,8 @@ public class DownloadStorageProvider extends DocumentsProvider { IoUtils.closeQuietly(cursor); Binder.restoreCallingIdentity(token); } + + result.start(); return result; } @@ -238,7 +253,8 @@ public class DownloadStorageProvider extends DocumentsProvider { return mArchiveHelper.queryDocument(parentDocumentId, projection); } - final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); + final DownloadsCursor result = + new DownloadsCursor(projection, getContext().getContentResolver()); // Delegate to real provider final long token = Binder.clearCallingIdentity(); @@ -254,13 +270,17 @@ public class DownloadStorageProvider extends DocumentsProvider { IoUtils.closeQuietly(cursor); Binder.restoreCallingIdentity(token); } + + result.start(); return result; } @Override public Cursor queryRecentDocuments(String rootId, String[] projection) throws FileNotFoundException { - final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); + + final DownloadsCursor result = + new DownloadsCursor(projection, getContext().getContentResolver()); // Delegate to real provider final long token = Binder.clearCallingIdentity(); @@ -288,13 +308,17 @@ public class DownloadStorageProvider extends DocumentsProvider { IoUtils.closeQuietly(cursor); Binder.restoreCallingIdentity(token); } + + result.start(); return result; } @Override public Cursor querySearchDocuments(String rootId, String query, String[] projection) throws FileNotFoundException { - final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); + + final DownloadsCursor result = + new DownloadsCursor(projection, getContext().getContentResolver()); // Delegate to real provider final long token = Binder.clearCallingIdentity(); @@ -310,6 +334,8 @@ public class DownloadStorageProvider extends DocumentsProvider { IoUtils.closeQuietly(cursor); Binder.restoreCallingIdentity(token); } + + result.start(); return result; } @@ -440,30 +466,91 @@ public class DownloadStorageProvider extends DocumentsProvider { } /** - * Remove file extension from name, but only if exact MIME type mapping - * exists. This means we can reapply the extension later. + * A MatrixCursor that spins up a file observer when the first instance is + * started ({@link #start()}, and stops the file observer when the last instance + * closed ({@link #close()}. When file changes are observed, a content change + * notification is sent on the Downloads content URI. + * + * <p>This is necessary as other processes, like ExternalStorageProvider, + * can access and modify files directly (without sending operations + * through DownloadStorageProvider). + * + * <p>Without this, contents accessible by one a Downloads cursor instance + * (like the Downloads root in Files app) can become state. */ - private static String removeExtension(String mimeType, String name) { - final int lastDot = name.lastIndexOf('.'); - if (lastDot >= 0) { - final String extension = name.substring(lastDot + 1); - final String nameMime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); - if (mimeType.equals(nameMime)) { - return name.substring(0, lastDot); + private static final class DownloadsCursor extends MatrixCursor { + + private static final Object mLock = new Object(); + @GuardedBy("mLock") + private static int mOpenCursorCount = 0; + @GuardedBy("mLock") + private static @Nullable ContentChangedRelay mFileWatcher; + + private final ContentResolver mResolver; + + DownloadsCursor(String[] projection, ContentResolver resolver) { + super(resolveDocumentProjection(projection)); + mResolver = resolver; + } + + void start() { + synchronized (mLock) { + if (mOpenCursorCount++ == 0) { + mFileWatcher = new ContentChangedRelay(mResolver); + mFileWatcher.startWatching(); + } + } + } + + @Override + public void close() { + super.close(); + synchronized (mLock) { + if (--mOpenCursorCount == 0) { + mFileWatcher.stopWatching(); + mFileWatcher = null; + } } } - return name; } /** - * Add file extension to name, but only if exact MIME type mapping exists. + * A file observer that notifies on the Downloads content URI(s) when + * files change on disk. */ - private static String addExtension(String mimeType, String name) { - final String extension = MimeTypeMap.getSingleton() - .getExtensionFromMimeType(mimeType); - if (extension != null) { - return name + "." + extension; + private static class ContentChangedRelay extends FileObserver { + private static final int NOTIFY_EVENTS = ATTRIB | CLOSE_WRITE | MOVED_FROM | MOVED_TO + | CREATE | DELETE | DELETE_SELF | MOVE_SELF; + + private static final String DOWNLOADS_PATH = + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + .getAbsolutePath(); + private final ContentResolver mResolver; + + public ContentChangedRelay(ContentResolver resolver) { + super(DOWNLOADS_PATH, NOTIFY_EVENTS); + mResolver = resolver; + } + + @Override + public void startWatching() { + super.startWatching(); + if (DEBUG) Log.d(TAG, "Started watching for file changes in: " + DOWNLOADS_PATH); + } + + @Override + public void stopWatching() { + super.stopWatching(); + if (DEBUG) Log.d(TAG, "Stopped watching for file changes in: " + DOWNLOADS_PATH); + } + + @Override + public void onEvent(int event, String path) { + if ((event & NOTIFY_EVENTS) != 0) { + if (DEBUG) Log.v(TAG, "Change detected at path: " + DOWNLOADS_PATH); + mResolver.notifyChange(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, null, false); + mResolver.notifyChange(Downloads.Impl.CONTENT_URI, null, false); + } } - return name; } } |