summaryrefslogtreecommitdiffstats
path: root/src/com/android/providers/downloads
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/providers/downloads')
-rw-r--r--src/com/android/providers/downloads/DownloadProvider.java35
-rw-r--r--src/com/android/providers/downloads/DownloadStorageProvider.java131
-rw-r--r--src/com/android/providers/downloads/DownloadThread.java6
3 files changed, 136 insertions, 36 deletions
diff --git a/src/com/android/providers/downloads/DownloadProvider.java b/src/com/android/providers/downloads/DownloadProvider.java
index 4a64743e..b2b2c08d 100644
--- a/src/com/android/providers/downloads/DownloadProvider.java
+++ b/src/com/android/providers/downloads/DownloadProvider.java
@@ -18,7 +18,6 @@ package com.android.providers.downloads;
import static android.provider.BaseColumns._ID;
import static android.provider.Downloads.Impl.COLUMN_DESTINATION;
-import static android.provider.Downloads.Impl.COLUMN_MEDIAPROVIDER_URI;
import static android.provider.Downloads.Impl.COLUMN_MEDIA_SCANNED;
import static android.provider.Downloads.Impl.COLUMN_MIME_TYPE;
import static android.provider.Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD;
@@ -29,6 +28,7 @@ import android.app.DownloadManager;
import android.app.DownloadManager.Request;
import android.app.job.JobScheduler;
import android.content.ContentProvider;
+import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
@@ -1207,7 +1207,10 @@ public final class DownloadProvider extends ContentProvider {
Helpers.validateSelection(where, sAppReadableColumnsSet);
}
- final JobScheduler scheduler = getContext().getSystemService(JobScheduler.class);
+ final Context context = getContext();
+ final ContentResolver resolver = context.getContentResolver();
+ final JobScheduler scheduler = context.getSystemService(JobScheduler.class);
+
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
int count;
int match = sURIMatcher.match(uri);
@@ -1219,17 +1222,18 @@ public final class DownloadProvider extends ContentProvider {
final SqlSelection selection = getWhereClause(uri, where, whereArgs, match);
deleteRequestHeaders(db, selection.getSelection(), selection.getParameters());
- try (Cursor cursor = db.query(DB_TABLE, new String[] {
- _ID, _DATA, COLUMN_MEDIAPROVIDER_URI
- }, selection.getSelection(), selection.getParameters(), null, null, null)) {
+ try (Cursor cursor = db.query(DB_TABLE, null, selection.getSelection(),
+ selection.getParameters(), null, null, null)) {
+ final DownloadInfo.Reader reader = new DownloadInfo.Reader(resolver, cursor);
+ final DownloadInfo info = new DownloadInfo(context);
while (cursor.moveToNext()) {
- final long id = cursor.getLong(0);
- scheduler.cancel((int) id);
+ reader.updateFromDatabase(info);
+ scheduler.cancel((int) info.mId);
- revokeAllDownloadsPermission(id);
- DownloadStorageProvider.onDownloadProviderDelete(getContext(), id);
+ revokeAllDownloadsPermission(info.mId);
+ DownloadStorageProvider.onDownloadProviderDelete(getContext(), info.mId);
- final String path = cursor.getString(1);
+ final String path = info.mFileName;
if (!TextUtils.isEmpty(path)) {
try {
final File file = new File(path).getCanonicalFile();
@@ -1242,7 +1246,7 @@ public final class DownloadProvider extends ContentProvider {
}
}
- final String mediaUri = cursor.getString(2);
+ final String mediaUri = info.mMediaProviderUri;
if (!TextUtils.isEmpty(mediaUri)) {
final long token = Binder.clearCallingIdentity();
try {
@@ -1252,6 +1256,9 @@ public final class DownloadProvider extends ContentProvider {
Binder.restoreCallingIdentity(token);
}
}
+
+ // Tell requester that download is finished
+ info.sendIntentIfRequested();
}
}
@@ -1263,6 +1270,12 @@ public final class DownloadProvider extends ContentProvider {
throw new UnsupportedOperationException("Cannot delete URI: " + uri);
}
notifyContentChanged(uri, match);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ Helpers.getDownloadNotifier(getContext()).update();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
return count;
}
diff --git a/src/com/android/providers/downloads/DownloadStorageProvider.java b/src/com/android/providers/downloads/DownloadStorageProvider.java
index 80d6ac79..4ec8e2d1 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,6 +308,8 @@ public class DownloadStorageProvider extends DocumentsProvider {
IoUtils.closeQuietly(cursor);
Binder.restoreCallingIdentity(token);
}
+
+ result.start();
return result;
}
@@ -418,30 +440,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;
}
}
diff --git a/src/com/android/providers/downloads/DownloadThread.java b/src/com/android/providers/downloads/DownloadThread.java
index 40194038..33436f57 100644
--- a/src/com/android/providers/downloads/DownloadThread.java
+++ b/src/com/android/providers/downloads/DownloadThread.java
@@ -382,7 +382,11 @@ public class DownloadThread extends Thread {
}
if (Downloads.Impl.isStatusCompleted(mInfoDelta.mStatus)) {
- mInfo.sendIntentIfRequested();
+ // If download was canceled, we already sent requested intent when
+ // deleted in the provider
+ if (mInfoDelta.mStatus != STATUS_CANCELED) {
+ mInfo.sendIntentIfRequested();
+ }
if (mInfo.shouldScanFile(mInfoDelta.mStatus)) {
DownloadScanner.requestScanBlocking(mContext, mInfo.mId, mInfoDelta.mFileName,
mInfoDelta.mMimeType);