summaryrefslogtreecommitdiffstats
path: root/src/com/android/providers/downloads
diff options
context:
space:
mode:
authorLinux Build Service Account <lnxbuild@localhost>2016-12-15 03:01:11 -0700
committerLinux Build Service Account <lnxbuild@localhost>2016-12-15 03:01:11 -0700
commit659df283363c0119f9789572bd3567a91b610ece (patch)
treec19b111aef28a52efc0147ef2bcd56dc423cc4b6 /src/com/android/providers/downloads
parent5ed6236eaa0aa5408cd3ae356b7b9519cc940d18 (diff)
parenta4ade1bc82cd526539e35dec99f923410161a35b (diff)
downloadandroid_packages_providers_DownloadProvider-659df283363c0119f9789572bd3567a91b610ece.tar.gz
android_packages_providers_DownloadProvider-659df283363c0119f9789572bd3567a91b610ece.tar.bz2
android_packages_providers_DownloadProvider-659df283363c0119f9789572bd3567a91b610ece.zip
Merge a4ade1bc82cd526539e35dec99f923410161a35b on remote branch
Change-Id: Id8324baa63058cb8118ce852484f89635d51efd9
Diffstat (limited to 'src/com/android/providers/downloads')
-rw-r--r--src/com/android/providers/downloads/DownloadNotifier.java4
-rw-r--r--src/com/android/providers/downloads/DownloadProvider.java67
-rw-r--r--src/com/android/providers/downloads/DownloadStorageProvider.java145
-rw-r--r--src/com/android/providers/downloads/DownloadThread.java1
-rw-r--r--src/com/android/providers/downloads/OpenHelper.java17
5 files changed, 174 insertions, 60 deletions
diff --git a/src/com/android/providers/downloads/DownloadNotifier.java b/src/com/android/providers/downloads/DownloadNotifier.java
index d5808690..d13bb5e2 100644
--- a/src/com/android/providers/downloads/DownloadNotifier.java
+++ b/src/com/android/providers/downloads/DownloadNotifier.java
@@ -213,7 +213,9 @@ public class DownloadNotifier {
downloadIds);
builder.setContentIntent(PendingIntent.getBroadcast(mContext,
0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
- builder.setOngoing(true);
+ if (type == TYPE_ACTIVE) {
+ builder.setOngoing(true);
+ }
// Add a Cancel action
final Uri cancelUri = new Uri.Builder().scheme("cancel-dl").appendPath(tag).build();
diff --git a/src/com/android/providers/downloads/DownloadProvider.java b/src/com/android/providers/downloads/DownloadProvider.java
index d50eedba..72975582 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;
@@ -1077,10 +1077,14 @@ public final class DownloadProvider extends ContentProvider {
Helpers.validateSelection(where, sAppReadableColumnsSet);
- SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ final Context context = getContext();
+ final ContentResolver resolver = context.getContentResolver();
+
+ final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
int count;
boolean updateSchedule = false;
+ boolean isCompleting = false;
ContentValues filteredValues;
if (Binder.getCallingPid() != Process.myPid()) {
@@ -1121,6 +1125,7 @@ public final class DownloadProvider extends ContentProvider {
if (isRestart || isUserBypassingSizeLimit) {
updateSchedule = true;
}
+ isCompleting = status != null && Downloads.Impl.isStatusCompleted(status);
}
int match = sURIMatcher.match(uri);
@@ -1137,14 +1142,20 @@ public final class DownloadProvider extends ContentProvider {
final SqlSelection selection = getWhereClause(uri, where, whereArgs, match);
count = db.update(DB_TABLE, filteredValues, selection.getSelection(),
selection.getParameters());
- if (updateSchedule) {
+ if (updateSchedule || isCompleting) {
final long token = Binder.clearCallingIdentity();
- try {
- try (Cursor cursor = db.query(DB_TABLE, new String[] { _ID },
- selection.getSelection(), selection.getParameters(),
- null, null, null)) {
- while (cursor.moveToNext()) {
- Helpers.scheduleJob(getContext(), cursor.getInt(0));
+ 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()) {
+ reader.updateFromDatabase(info);
+ if (updateSchedule) {
+ Helpers.scheduleJob(context, info);
+ }
+ if (isCompleting) {
+ info.sendIntentIfRequested();
}
}
} finally {
@@ -1207,7 +1218,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,16 +1233,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);
- revokeAllDownloadsPermission(id);
- DownloadStorageProvider.onDownloadProviderDelete(getContext(), id);
+ reader.updateFromDatabase(info);
+ scheduler.cancel((int) info.mId);
+
+ 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();
@@ -1241,7 +1257,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 {
@@ -1251,6 +1267,13 @@ public final class DownloadProvider extends ContentProvider {
Binder.restoreCallingIdentity(token);
}
}
+
+ // If the download wasn't completed yet, we're
+ // effectively completing it now, and we need to send
+ // any requested broadcasts
+ if (!Downloads.Impl.isStatusCompleted(info.mStatus)) {
+ info.sendIntentIfRequested();
+ }
}
}
@@ -1262,6 +1285,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 e0bb7cd1..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;
}
@@ -325,6 +347,10 @@ public class DownloadStorageProvider extends DocumentsProvider {
Document.FLAG_DIR_PREFERS_LAST_MODIFIED | Document.FLAG_DIR_SUPPORTS_CREATE);
}
+ /**
+ * Adds the entry from the cursor to the result only if the entry is valid. That is,
+ * if the file exists in the file system.
+ */
private void includeDownloadFromCursor(MatrixCursor result, Cursor cursor) {
final long id = cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_ID));
final String docId = String.valueOf(id);
@@ -344,12 +370,20 @@ public class DownloadStorageProvider extends DocumentsProvider {
if (size == -1) {
size = null;
}
+ String localFilePath = cursor.getString(
+ cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_FILENAME));
int extraFlags = Document.FLAG_PARTIAL;
final int status = cursor.getInt(
cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS));
switch (status) {
case DownloadManager.STATUS_SUCCESSFUL:
+ // Verify that the document still exists in external storage. This is necessary
+ // because files can be deleted from the file system without their entry being
+ // removed from DownloadsManager.
+ if (localFilePath == null || !new File(localFilePath).exists()) {
+ return;
+ }
extraFlags = Document.FLAG_SUPPORTS_RENAME; // only successful is non-partial
break;
case DownloadManager.STATUS_PAUSED:
@@ -400,38 +434,97 @@ public class DownloadStorageProvider extends DocumentsProvider {
row.add(Document.COLUMN_LAST_MODIFIED, lastModified);
}
- final String localFilePath = cursor.getString(
- cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_FILENAME));
if (localFilePath != null) {
row.add(DocumentArchiveHelper.COLUMN_LOCAL_FILE_PATH, localFilePath);
}
}
/**
- * 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..c6b4c71a 100644
--- a/src/com/android/providers/downloads/DownloadThread.java
+++ b/src/com/android/providers/downloads/DownloadThread.java
@@ -382,7 +382,6 @@ public class DownloadThread extends Thread {
}
if (Downloads.Impl.isStatusCompleted(mInfoDelta.mStatus)) {
- mInfo.sendIntentIfRequested();
if (mInfo.shouldScanFile(mInfoDelta.mStatus)) {
DownloadScanner.requestScanBlocking(mContext, mInfo.mId, mInfoDelta.mFileName,
mInfoDelta.mMimeType);
diff --git a/src/com/android/providers/downloads/OpenHelper.java b/src/com/android/providers/downloads/OpenHelper.java
index 27ab86b9..69a44922 100644
--- a/src/com/android/providers/downloads/OpenHelper.java
+++ b/src/com/android/providers/downloads/OpenHelper.java
@@ -17,10 +17,10 @@
package com.android.providers.downloads;
import static android.app.DownloadManager.COLUMN_LOCAL_FILENAME;
-import static android.app.DownloadManager.COLUMN_LOCAL_URI;
import static android.app.DownloadManager.COLUMN_MEDIA_TYPE;
import static android.app.DownloadManager.COLUMN_URI;
import static android.provider.Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI;
+
import static com.android.providers.downloads.Constants.TAG;
import android.app.DownloadManager;
@@ -30,7 +30,6 @@ import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
-import android.os.StrictMode;
import android.provider.DocumentsContract;
import android.provider.Downloads.Impl.RequestHeaders;
import android.util.Log;
@@ -51,14 +50,11 @@ public class OpenHelper {
intent.addFlags(intentFlags);
try {
- StrictMode.disableDeathOnFileUriExposure();
context.startActivity(intent);
return true;
} catch (ActivityNotFoundException e) {
Log.w(TAG, "Failed to start " + intent + ": " + e);
return false;
- } finally {
- StrictMode.enableDeathOnFileUriExposure();
}
}
@@ -78,7 +74,6 @@ public class OpenHelper {
return null;
}
- final Uri localUri = getCursorUri(cursor, COLUMN_LOCAL_URI);
final File file = getCursorFile(cursor, COLUMN_LOCAL_FILENAME);
String mimeType = getCursorString(cursor, COLUMN_MEDIA_TYPE);
mimeType = DownloadDrmHelper.getOriginalMimeType(context, file, mimeType);
@@ -87,20 +82,16 @@ public class OpenHelper {
Constants.STORAGE_AUTHORITY, String.valueOf(id));
final Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setDataAndType(documentUri, mimeType);
+ intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
if ("application/vnd.android.package-archive".equals(mimeType)) {
- // PackageInstaller doesn't like content URIs, so open file
- intent.setDataAndType(localUri, mimeType);
-
// Also splice in details about where it came from
final Uri remoteUri = getCursorUri(cursor, COLUMN_URI);
intent.putExtra(Intent.EXTRA_ORIGINATING_URI, remoteUri);
intent.putExtra(Intent.EXTRA_REFERRER, getRefererUri(context, id));
intent.putExtra(Intent.EXTRA_ORIGINATING_UID, getOriginatingUid(context, id));
- } else {
- intent.setDataAndType(documentUri, mimeType);
- intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
- | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
return intent;