summaryrefslogtreecommitdiffstats
path: root/src/com/android
diff options
context:
space:
mode:
authorRicardo Cerqueira <cyanogenmod@cerqueira.org>2013-11-05 20:08:34 +0000
committerRicardo Cerqueira <cyanogenmod@cerqueira.org>2013-11-05 20:08:34 +0000
commit40a32f628089f338f823b34ed832e18a164aec23 (patch)
tree301569d0a81c340425d54787155120c03c320c11 /src/com/android
parent907bcd0c7cf8a44e07b3cf426b605ca7694ee24d (diff)
parenta839ee4c19bfebac961f4b0aaa8aecaba114bac9 (diff)
downloadandroid_packages_providers_DownloadProvider-40a32f628089f338f823b34ed832e18a164aec23.tar.gz
android_packages_providers_DownloadProvider-40a32f628089f338f823b34ed832e18a164aec23.tar.bz2
android_packages_providers_DownloadProvider-40a32f628089f338f823b34ed832e18a164aec23.zip
Merge tag 'android-4.4_r1' into cm-11.0
Android 4.4 Release 1.0 Change-Id: I6eadeafdb9d3219bebd28325b4e290b6d5282499
Diffstat (limited to 'src/com/android')
-rw-r--r--src/com/android/providers/downloads/Constants.java3
-rw-r--r--src/com/android/providers/downloads/DownloadProvider.java89
-rw-r--r--src/com/android/providers/downloads/DownloadReceiver.java10
-rw-r--r--src/com/android/providers/downloads/DownloadStorageProvider.java381
-rw-r--r--src/com/android/providers/downloads/DownloadThread.java13
-rw-r--r--src/com/android/providers/downloads/Helpers.java26
-rw-r--r--src/com/android/providers/downloads/OpenHelper.java40
7 files changed, 505 insertions, 57 deletions
diff --git a/src/com/android/providers/downloads/Constants.java b/src/com/android/providers/downloads/Constants.java
index 08ef4665..89210a25 100644
--- a/src/com/android/providers/downloads/Constants.java
+++ b/src/com/android/providers/downloads/Constants.java
@@ -174,4 +174,7 @@ public class Constants {
/** Enable super-verbose logging */
private static final boolean LOCAL_LOGVV = false;
public static final boolean LOGVV = LOCAL_LOGVV && LOGV;
+
+ public static final String STORAGE_AUTHORITY = "com.android.providers.downloads.documents";
+ public static final String STORAGE_ROOT_ID = "downloads";
}
diff --git a/src/com/android/providers/downloads/DownloadProvider.java b/src/com/android/providers/downloads/DownloadProvider.java
index e0b5842d..ad3cf7ac 100644
--- a/src/com/android/providers/downloads/DownloadProvider.java
+++ b/src/com/android/providers/downloads/DownloadProvider.java
@@ -35,7 +35,9 @@ import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.os.Binder;
import android.os.Environment;
+import android.os.Handler;
import android.os.ParcelFileDescriptor;
+import android.os.ParcelFileDescriptor.OnCloseListener;
import android.os.Process;
import android.os.SELinux;
import android.provider.BaseColumns;
@@ -49,6 +51,8 @@ import com.android.internal.util.IndentingPrintWriter;
import com.google.android.collect.Maps;
import com.google.common.annotations.VisibleForTesting;
+import libcore.io.IoUtils;
+
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
@@ -69,7 +73,7 @@ public final class DownloadProvider extends ContentProvider {
/** Database filename */
private static final String DB_NAME = "downloads.db";
/** Current database version */
- private static final int DB_VERSION = 108;
+ private static final int DB_VERSION = 109;
/** Name of table in the database */
private static final String DB_TABLE = "downloads";
@@ -166,6 +170,8 @@ public final class DownloadProvider extends ContentProvider {
private static final List<String> downloadManagerColumnsList =
Arrays.asList(DownloadManager.UNDERLYING_COLUMNS);
+ private Handler mHandler;
+
/** The database that lies underneath this content provider */
private SQLiteOpenHelper mOpenHelper = null;
@@ -319,6 +325,11 @@ public final class DownloadProvider extends ContentProvider {
"INTEGER NOT NULL DEFAULT 1");
break;
+ case 109:
+ addColumn(db, DB_TABLE, Downloads.Impl.COLUMN_ALLOW_WRITE,
+ "BOOLEAN NOT NULL DEFAULT 0");
+ break;
+
default:
throw new IllegalStateException("Don't know how to upgrade to " + version);
}
@@ -432,6 +443,8 @@ public final class DownloadProvider extends ContentProvider {
mSystemFacade = new RealSystemFacade(getContext());
}
+ mHandler = new Handler();
+
mOpenHelper = new DatabaseHelper(getContext());
// Initialize the system uid
mSystemUid = Process.SYSTEM_UID;
@@ -590,6 +603,7 @@ public final class DownloadProvider extends ContentProvider {
filteredValues.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0);
copyInteger(Downloads.Impl.COLUMN_MEDIA_SCANNED, values, filteredValues);
copyString(Downloads.Impl._DATA, values, filteredValues);
+ copyBoolean(Downloads.Impl.COLUMN_ALLOW_WRITE, values, filteredValues);
} else {
filteredValues.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING);
filteredValues.put(Downloads.Impl.COLUMN_TOTAL_BYTES, -1);
@@ -669,23 +683,12 @@ public final class DownloadProvider extends ContentProvider {
}
insertRequestHeaders(db, rowID, values);
- /*
- * requests coming from
- * DownloadManager.addCompletedDownload(String, String, String,
- * boolean, String, String, long) need special treatment
- */
- Context context = getContext();
- if (values.getAsInteger(Downloads.Impl.COLUMN_DESTINATION) ==
- Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD) {
- // When notification is requested, kick off service to process all
- // relevant downloads.
- if (Downloads.Impl.isNotificationToBeDisplayed(vis)) {
- context.startService(new Intent(context, DownloadService.class));
- }
- } else {
- context.startService(new Intent(context, DownloadService.class));
- }
notifyContentChanged(uri, match);
+
+ // Always start service to handle notifications and/or scanning
+ final Context context = getContext();
+ context.startService(new Intent(context, DownloadService.class));
+
return ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, rowID);
}
@@ -784,6 +787,7 @@ public final class DownloadProvider extends ContentProvider {
values.remove(Downloads.Impl.COLUMN_ALLOW_METERED);
values.remove(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI);
values.remove(Downloads.Impl.COLUMN_MEDIA_SCANNED);
+ values.remove(Downloads.Impl.COLUMN_ALLOW_WRITE);
Iterator<Map.Entry<String, Object>> iterator = values.valueSet().iterator();
while (iterator.hasNext()) {
String key = iterator.next().getKey();
@@ -1147,6 +1151,19 @@ public final class DownloadProvider extends ContentProvider {
case ALL_DOWNLOADS_ID:
SqlSelection selection = getWhereClause(uri, where, whereArgs, match);
deleteRequestHeaders(db, selection.getSelection(), selection.getParameters());
+
+ final Cursor cursor = db.query(DB_TABLE, new String[] {
+ Downloads.Impl._ID }, selection.getSelection(), selection.getParameters(),
+ null, null, null);
+ try {
+ while (cursor.moveToNext()) {
+ final long id = cursor.getLong(0);
+ DownloadStorageProvider.onDownloadProviderDelete(getContext(), id);
+ }
+ } finally {
+ IoUtils.closeQuietly(cursor);
+ }
+
count = db.delete(DB_TABLE, selection.getSelection(), selection.getParameters());
break;
@@ -1162,12 +1179,12 @@ public final class DownloadProvider extends ContentProvider {
* Remotely opens a file
*/
@Override
- public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+ public ParcelFileDescriptor openFile(final Uri uri, String mode) throws FileNotFoundException {
if (Constants.LOGVV) {
logVerboseOpenFileInfo(uri, mode);
}
- Cursor cursor = query(uri, new String[] {"_data"}, null, null, null);
+ final Cursor cursor = query(uri, new String[] { Downloads.Impl._DATA }, null, null, null);
String path;
try {
int count = (cursor != null) ? cursor.getCount() : 0;
@@ -1182,9 +1199,7 @@ public final class DownloadProvider extends ContentProvider {
cursor.moveToFirst();
path = cursor.getString(0);
} finally {
- if (cursor != null) {
- cursor.close();
- }
+ IoUtils.closeQuietly(cursor);
}
if (path == null) {
@@ -1193,20 +1208,28 @@ public final class DownloadProvider extends ContentProvider {
if (!Helpers.isFilenameValid(path, mDownloadsDataDir)) {
throw new FileNotFoundException("Invalid filename: " + path);
}
- if (!"r".equals(mode)) {
- throw new FileNotFoundException("Bad mode for " + uri + ": " + mode);
- }
-
- ParcelFileDescriptor ret = ParcelFileDescriptor.open(new File(path),
- ParcelFileDescriptor.MODE_READ_ONLY);
- if (ret == null) {
- if (Constants.LOGV) {
- Log.v(Constants.TAG, "couldn't open file");
+ final File file = new File(path);
+ if ("r".equals(mode)) {
+ return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
+ } else {
+ try {
+ // When finished writing, update size and timestamp
+ return ParcelFileDescriptor.open(file, ParcelFileDescriptor.parseMode(mode),
+ mHandler, new OnCloseListener() {
+ @Override
+ public void onClose(IOException e) {
+ final ContentValues values = new ContentValues();
+ values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, file.length());
+ values.put(Downloads.Impl.COLUMN_LAST_MODIFICATION,
+ System.currentTimeMillis());
+ update(uri, values, null, null);
+ }
+ });
+ } catch (IOException e) {
+ throw new FileNotFoundException("Failed to open for writing: " + e);
}
- throw new FileNotFoundException("couldn't open file");
}
- return ret;
}
@Override
diff --git a/src/com/android/providers/downloads/DownloadReceiver.java b/src/com/android/providers/downloads/DownloadReceiver.java
index 42f029a3..f3d23766 100644
--- a/src/com/android/providers/downloads/DownloadReceiver.java
+++ b/src/com/android/providers/downloads/DownloadReceiver.java
@@ -20,7 +20,6 @@ import static android.app.DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMP
import static android.app.DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION;
import android.app.DownloadManager;
-import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ContentUris;
import android.content.ContentValues;
@@ -162,13 +161,8 @@ public class DownloadReceiver extends BroadcastReceiver {
* {@link DownloadManager#COLUMN_ID}.
*/
private void openDownload(Context context, long id) {
- final Intent intent = OpenHelper.buildViewIntent(context, id);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- try {
- context.startActivity(intent);
- } catch (ActivityNotFoundException ex) {
- Log.d(Constants.TAG, "no activity for " + intent, ex);
- Toast.makeText(context, R.string.download_no_application_title, Toast.LENGTH_LONG)
+ if (!OpenHelper.startViewIntent(context, id, Intent.FLAG_ACTIVITY_NEW_TASK)) {
+ Toast.makeText(context, R.string.download_no_application_title, Toast.LENGTH_SHORT)
.show();
}
}
diff --git a/src/com/android/providers/downloads/DownloadStorageProvider.java b/src/com/android/providers/downloads/DownloadStorageProvider.java
new file mode 100644
index 00000000..ecef54e0
--- /dev/null
+++ b/src/com/android/providers/downloads/DownloadStorageProvider.java
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.downloads;
+
+import android.app.DownloadManager;
+import android.app.DownloadManager.Query;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.database.MatrixCursor.RowBuilder;
+import android.graphics.Point;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.CancellationSignal;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.provider.DocumentsContract;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract.Root;
+import android.provider.DocumentsProvider;
+import android.text.TextUtils;
+import android.webkit.MimeTypeMap;
+
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * Presents a {@link DocumentsContract} view of {@link DownloadManager}
+ * contents.
+ */
+public class DownloadStorageProvider extends DocumentsProvider {
+ private static final String AUTHORITY = Constants.STORAGE_AUTHORITY;
+ private static final String DOC_ID_ROOT = Constants.STORAGE_ROOT_ID;
+
+ private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
+ Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON,
+ Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID,
+ };
+
+ private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
+ Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, Document.COLUMN_DISPLAY_NAME,
+ Document.COLUMN_SUMMARY, Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS,
+ Document.COLUMN_SIZE,
+ };
+
+ private DownloadManager mDm;
+
+ @Override
+ public boolean onCreate() {
+ mDm = (DownloadManager) getContext().getSystemService(Context.DOWNLOAD_SERVICE);
+ mDm.setAccessAllDownloads(true);
+ return true;
+ }
+
+ private static String[] resolveRootProjection(String[] projection) {
+ return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
+ }
+
+ private static String[] resolveDocumentProjection(String[] projection) {
+ return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION;
+ }
+
+ private void copyNotificationUri(MatrixCursor result, Cursor cursor) {
+ result.setNotificationUri(getContext().getContentResolver(), cursor.getNotificationUri());
+ }
+
+ static void onDownloadProviderDelete(Context context, long id) {
+ final Uri uri = DocumentsContract.buildDocumentUri(AUTHORITY, Long.toString(id));
+ context.revokeUriPermission(uri, ~0);
+ }
+
+ @Override
+ public Cursor queryRoots(String[] projection) throws FileNotFoundException {
+ final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
+ final RowBuilder row = result.newRow();
+ row.add(Root.COLUMN_ROOT_ID, DOC_ID_ROOT);
+ row.add(Root.COLUMN_FLAGS,
+ Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_RECENTS | Root.FLAG_SUPPORTS_CREATE);
+ row.add(Root.COLUMN_ICON, R.mipmap.ic_launcher_download);
+ row.add(Root.COLUMN_TITLE, getContext().getString(R.string.root_downloads));
+ row.add(Root.COLUMN_DOCUMENT_ID, DOC_ID_ROOT);
+ return result;
+ }
+
+ @Override
+ public String createDocument(String docId, String mimeType, String displayName)
+ throws FileNotFoundException {
+ if (Document.MIME_TYPE_DIR.equals(mimeType)) {
+ throw new FileNotFoundException("Directory creation not supported");
+ }
+
+ final File parent = Environment.getExternalStoragePublicDirectory(
+ Environment.DIRECTORY_DOWNLOADS);
+ parent.mkdirs();
+
+ // Delegate to real provider
+ final long token = Binder.clearCallingIdentity();
+ try {
+ displayName = removeExtension(mimeType, displayName);
+ File file = new File(parent, addExtension(mimeType, displayName));
+
+ // If conflicting file, try adding counter suffix
+ int n = 0;
+ while (file.exists() && n++ < 32) {
+ file = new File(parent, addExtension(mimeType, displayName + " (" + n + ")"));
+ }
+
+ try {
+ if (!file.createNewFile()) {
+ throw new IllegalStateException("Failed to touch " + file);
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to touch " + file + ": " + e);
+ }
+
+ return Long.toString(mDm.addCompletedDownload(
+ file.getName(), file.getName(), false, mimeType, file.getAbsolutePath(), 0L,
+ false, true));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void deleteDocument(String docId) throws FileNotFoundException {
+ // Delegate to real provider
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (mDm.remove(Long.parseLong(docId)) != 1) {
+ throw new IllegalStateException("Failed to delete " + docId);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public Cursor queryDocument(String docId, String[] projection) throws FileNotFoundException {
+ final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
+
+ if (DOC_ID_ROOT.equals(docId)) {
+ includeDefaultDocument(result);
+ } else {
+ // Delegate to real provider
+ final long token = Binder.clearCallingIdentity();
+ Cursor cursor = null;
+ try {
+ cursor = mDm.query(new Query().setFilterById(Long.parseLong(docId)));
+ copyNotificationUri(result, cursor);
+ if (cursor.moveToFirst()) {
+ includeDownloadFromCursor(result, cursor);
+ }
+ } finally {
+ IoUtils.closeQuietly(cursor);
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public Cursor queryChildDocuments(String docId, String[] projection, String sortOrder)
+ throws FileNotFoundException {
+ final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
+
+ // Delegate to real provider
+ final long token = Binder.clearCallingIdentity();
+ Cursor cursor = null;
+ try {
+ cursor = mDm.query(new DownloadManager.Query().setOnlyIncludeVisibleInDownloadsUi(true)
+ .setFilterByStatus(DownloadManager.STATUS_SUCCESSFUL));
+ copyNotificationUri(result, cursor);
+ while (cursor.moveToNext()) {
+ includeDownloadFromCursor(result, cursor);
+ }
+ } finally {
+ IoUtils.closeQuietly(cursor);
+ Binder.restoreCallingIdentity(token);
+ }
+ return result;
+ }
+
+ @Override
+ public Cursor queryChildDocumentsForManage(
+ String parentDocumentId, String[] projection, String sortOrder)
+ throws FileNotFoundException {
+ final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
+
+ // Delegate to real provider
+ final long token = Binder.clearCallingIdentity();
+ Cursor cursor = null;
+ try {
+ cursor = mDm.query(
+ new DownloadManager.Query().setOnlyIncludeVisibleInDownloadsUi(true));
+ copyNotificationUri(result, cursor);
+ while (cursor.moveToNext()) {
+ includeDownloadFromCursor(result, cursor);
+ }
+ } finally {
+ IoUtils.closeQuietly(cursor);
+ Binder.restoreCallingIdentity(token);
+ }
+ return result;
+ }
+
+ @Override
+ public Cursor queryRecentDocuments(String rootId, String[] projection)
+ throws FileNotFoundException {
+ final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
+
+ // Delegate to real provider
+ final long token = Binder.clearCallingIdentity();
+ Cursor cursor = null;
+ try {
+ cursor = mDm.query(new DownloadManager.Query().setOnlyIncludeVisibleInDownloadsUi(true)
+ .setFilterByStatus(DownloadManager.STATUS_SUCCESSFUL));
+ copyNotificationUri(result, cursor);
+ while (cursor.moveToNext() && result.getCount() < 12) {
+ final String mimeType = cursor.getString(
+ cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_MEDIA_TYPE));
+ final String uri = cursor.getString(
+ cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_MEDIAPROVIDER_URI));
+
+ // Skip images that have been inserted into the MediaStore so we
+ // don't duplicate them in the recents list.
+ if (mimeType == null
+ || (mimeType.startsWith("image/") && !TextUtils.isEmpty(uri))) {
+ continue;
+ }
+
+ includeDownloadFromCursor(result, cursor);
+ }
+ } finally {
+ IoUtils.closeQuietly(cursor);
+ Binder.restoreCallingIdentity(token);
+ }
+ return result;
+ }
+
+ @Override
+ public ParcelFileDescriptor openDocument(String docId, String mode, CancellationSignal signal)
+ throws FileNotFoundException {
+ // Delegate to real provider
+ final long token = Binder.clearCallingIdentity();
+ try {
+ final long id = Long.parseLong(docId);
+ final ContentResolver resolver = getContext().getContentResolver();
+ return resolver.openFileDescriptor(mDm.getDownloadUri(id), mode, signal);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public AssetFileDescriptor openDocumentThumbnail(
+ String docId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
+ // TODO: extend ExifInterface to support fds
+ final ParcelFileDescriptor pfd = openDocument(docId, "r", signal);
+ return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH);
+ }
+
+ private void includeDefaultDocument(MatrixCursor result) {
+ final RowBuilder row = result.newRow();
+ row.add(Document.COLUMN_DOCUMENT_ID, DOC_ID_ROOT);
+ row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR);
+ row.add(Document.COLUMN_FLAGS,
+ Document.FLAG_DIR_PREFERS_LAST_MODIFIED | Document.FLAG_DIR_SUPPORTS_CREATE);
+ }
+
+ private void includeDownloadFromCursor(MatrixCursor result, Cursor cursor) {
+ final long id = cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_ID));
+ final String docId = String.valueOf(id);
+
+ final String displayName = cursor.getString(
+ cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TITLE));
+ String summary = cursor.getString(
+ cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_DESCRIPTION));
+ String mimeType = cursor.getString(
+ cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_MEDIA_TYPE));
+ if (mimeType == null) {
+ // Provide fake MIME type so it's openable
+ mimeType = "vnd.android.document/file";
+ }
+ Long size = cursor.getLong(
+ cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
+ if (size == -1) {
+ size = null;
+ }
+
+ final int status = cursor.getInt(
+ cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS));
+ switch (status) {
+ case DownloadManager.STATUS_SUCCESSFUL:
+ break;
+ case DownloadManager.STATUS_PAUSED:
+ summary = getContext().getString(R.string.download_queued);
+ break;
+ case DownloadManager.STATUS_PENDING:
+ summary = getContext().getString(R.string.download_queued);
+ break;
+ case DownloadManager.STATUS_RUNNING:
+ final long progress = cursor.getLong(cursor.getColumnIndexOrThrow(
+ DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
+ if (size != null) {
+ final long percent = progress * 100 / size;
+ summary = getContext().getString(R.string.download_running_percent, percent);
+ } else {
+ summary = getContext().getString(R.string.download_running);
+ }
+ break;
+ case DownloadManager.STATUS_FAILED:
+ default:
+ summary = getContext().getString(R.string.download_error);
+ break;
+ }
+
+ int flags = Document.FLAG_SUPPORTS_DELETE | Document.FLAG_SUPPORTS_WRITE;
+ if (mimeType != null && mimeType.startsWith("image/")) {
+ flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
+ }
+
+ final long lastModified = cursor.getLong(
+ cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_LAST_MODIFIED_TIMESTAMP));
+
+ final RowBuilder row = result.newRow();
+ row.add(Document.COLUMN_DOCUMENT_ID, docId);
+ row.add(Document.COLUMN_DISPLAY_NAME, displayName);
+ row.add(Document.COLUMN_SUMMARY, summary);
+ row.add(Document.COLUMN_SIZE, size);
+ row.add(Document.COLUMN_MIME_TYPE, mimeType);
+ row.add(Document.COLUMN_LAST_MODIFIED, lastModified);
+ row.add(Document.COLUMN_FLAGS, flags);
+ }
+
+ /**
+ * Remove file extension from name, but only if exact MIME type mapping
+ * exists. This means we can reapply the extension later.
+ */
+ 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);
+ }
+ }
+ return name;
+ }
+
+ /**
+ * Add file extension to name, but only if exact MIME type mapping exists.
+ */
+ private static String addExtension(String mimeType, String name) {
+ final String extension = MimeTypeMap.getSingleton()
+ .getExtensionFromMimeType(mimeType);
+ if (extension != null) {
+ return name + "." + extension;
+ }
+ return name;
+ }
+}
diff --git a/src/com/android/providers/downloads/DownloadThread.java b/src/com/android/providers/downloads/DownloadThread.java
index 6a0eb47e..93f8d650 100644
--- a/src/com/android/providers/downloads/DownloadThread.java
+++ b/src/com/android/providers/downloads/DownloadThread.java
@@ -20,6 +20,7 @@ import static android.provider.Downloads.Impl.STATUS_BAD_REQUEST;
import static android.provider.Downloads.Impl.STATUS_CANNOT_RESUME;
import static android.provider.Downloads.Impl.STATUS_FILE_ERROR;
import static android.provider.Downloads.Impl.STATUS_HTTP_DATA_ERROR;
+import static android.provider.Downloads.Impl.STATUS_SUCCESS;
import static android.provider.Downloads.Impl.STATUS_TOO_MANY_REDIRECTS;
import static android.provider.Downloads.Impl.STATUS_WAITING_FOR_NETWORK;
import static android.provider.Downloads.Impl.STATUS_WAITING_TO_RETRY;
@@ -47,6 +48,7 @@ import android.os.FileUtils;
import android.os.PowerManager;
import android.os.Process;
import android.os.SystemClock;
+import android.os.WorkSource;
import android.provider.Downloads;
import android.text.TextUtils;
import android.util.Log;
@@ -54,6 +56,8 @@ import android.util.Pair;
import com.android.providers.downloads.DownloadInfo.NetworkState;
+import libcore.io.IoUtils;
+
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
@@ -66,8 +70,6 @@ import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
-import libcore.io.IoUtils;
-
/**
* Task which executes a given {@link DownloadInfo}: making network requests,
* persisting data to disk, and updating {@link DownloadProvider}.
@@ -188,6 +190,7 @@ public class DownloadThread implements Runnable {
try {
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Constants.TAG);
+ wakeLock.setWorkSource(new WorkSource(mInfo.mUid));
wakeLock.acquire();
// while performing download, register for rules updates
@@ -263,6 +266,10 @@ public class DownloadThread implements Runnable {
finalStatus = Downloads.Impl.STATUS_UNKNOWN_ERROR;
// falls through to the code that reports an error
} finally {
+ if (finalStatus == STATUS_SUCCESS) {
+ TrafficStats.incrementOperationCount(1);
+ }
+
TrafficStats.clearThreadStatsTag();
TrafficStats.clearThreadStatsUid();
@@ -512,7 +519,7 @@ public class DownloadThread implements Runnable {
throw new StopRequestException(
Downloads.Impl.STATUS_PAUSED_BY_APP, "download paused by owner");
}
- if (mInfo.mStatus == Downloads.Impl.STATUS_CANCELED) {
+ if (mInfo.mStatus == Downloads.Impl.STATUS_CANCELED || mInfo.mDeleted) {
throw new StopRequestException(Downloads.Impl.STATUS_CANCELED, "download canceled");
}
}
diff --git a/src/com/android/providers/downloads/Helpers.java b/src/com/android/providers/downloads/Helpers.java
index 33205557..013faf27 100644
--- a/src/com/android/providers/downloads/Helpers.java
+++ b/src/com/android/providers/downloads/Helpers.java
@@ -16,6 +16,8 @@
package com.android.providers.downloads;
+import static com.android.providers.downloads.Constants.TAG;
+
import android.content.Context;
import android.net.Uri;
import android.os.Environment;
@@ -342,10 +344,26 @@ public class Helpers {
* Checks whether the filename looks legitimate
*/
static boolean isFilenameValid(String filename, File downloadsDataDir) {
- filename = filename.replaceFirst("/+", "/"); // normalize leading slashes
- return filename.startsWith(Environment.getDownloadCacheDirectory().toString())
- || filename.startsWith(downloadsDataDir.toString())
- || filename.startsWith(Environment.getExternalStorageDirectory().toString());
+ final String[] whitelist;
+ try {
+ filename = new File(filename).getCanonicalPath();
+ whitelist = new String[] {
+ downloadsDataDir.getCanonicalPath(),
+ Environment.getDownloadCacheDirectory().getCanonicalPath(),
+ Environment.getExternalStorageDirectory().getCanonicalPath(),
+ };
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to resolve canonical path: " + e);
+ return false;
+ }
+
+ for (String test : whitelist) {
+ if (filename.startsWith(test)) {
+ return true;
+ }
+ }
+
+ return false;
}
/**
diff --git a/src/com/android/providers/downloads/OpenHelper.java b/src/com/android/providers/downloads/OpenHelper.java
index 184cdb3d..58c8ed69 100644
--- a/src/com/android/providers/downloads/OpenHelper.java
+++ b/src/com/android/providers/downloads/OpenHelper.java
@@ -22,23 +22,47 @@ import static android.app.DownloadManager.COLUMN_MEDIAPROVIDER_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;
+import android.content.ActivityNotFoundException;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.provider.Downloads.Impl.RequestHeaders;
+import android.util.Log;
import java.io.File;
public class OpenHelper {
/**
- * Build an {@link Intent} to view the download at current {@link Cursor}
- * position, handling subtleties around installing packages.
+ * Build and start an {@link Intent} to view the download with given ID,
+ * handling subtleties around installing packages.
*/
- public static Intent buildViewIntent(Context context, long id) {
+ public static boolean startViewIntent(Context context, long id, int intentFlags) {
+ final Intent intent = OpenHelper.buildViewIntent(context, id);
+ if (intent == null) {
+ Log.w(TAG, "No intent built for " + id);
+ return false;
+ }
+
+ intent.addFlags(intentFlags);
+ try {
+ context.startActivity(intent);
+ return true;
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "Failed to start " + intent + ": " + e);
+ return false;
+ }
+ }
+
+ /**
+ * Build an {@link Intent} to view the download with given ID, handling
+ * subtleties around installing packages.
+ */
+ private static Intent buildViewIntent(Context context, long id) {
final DownloadManager downManager = (DownloadManager) context.getSystemService(
Context.DOWNLOAD_SERVICE);
downManager.setAccessAllDownloads(true);
@@ -46,7 +70,7 @@ public class OpenHelper {
final Cursor cursor = downManager.query(new DownloadManager.Query().setFilterById(id));
try {
if (!cursor.moveToFirst()) {
- throw new IllegalArgumentException("Missing download " + id);
+ return null;
}
final Uri localUri = getCursorUri(cursor, COLUMN_LOCAL_URI);
@@ -55,7 +79,6 @@ public class OpenHelper {
mimeType = DownloadDrmHelper.getOriginalMimeType(context, file, mimeType);
final Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
if ("application/vnd.android.package-archive".equals(mimeType)) {
// PackageInstaller doesn't like content URIs, so open file
@@ -71,9 +94,12 @@ public class OpenHelper {
intent.setDataAndType(mediaUri, mimeType);
intent.putExtra("SingleItemOnly", true);
} else if ("file".equals(localUri.getScheme())) {
+ intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.setDataAndType(
ContentUris.withAppendedId(ALL_DOWNLOADS_CONTENT_URI, id), mimeType);
} else {
+ intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(localUri, mimeType);
}
@@ -130,10 +156,6 @@ public class OpenHelper {
return Uri.parse(getCursorString(cursor, column));
}
- private static long getCursorLong(Cursor cursor, String column) {
- return cursor.getLong(cursor.getColumnIndexOrThrow(column));
- }
-
private static File getCursorFile(Cursor cursor, String column) {
return new File(cursor.getString(cursor.getColumnIndexOrThrow(column)));
}