diff options
author | Ricardo Cerqueira <cyanogenmod@cerqueira.org> | 2013-11-05 20:08:34 +0000 |
---|---|---|
committer | Ricardo Cerqueira <cyanogenmod@cerqueira.org> | 2013-11-05 20:08:34 +0000 |
commit | 40a32f628089f338f823b34ed832e18a164aec23 (patch) | |
tree | 301569d0a81c340425d54787155120c03c320c11 /src/com/android/providers | |
parent | 907bcd0c7cf8a44e07b3cf426b605ca7694ee24d (diff) | |
parent | a839ee4c19bfebac961f4b0aaa8aecaba114bac9 (diff) | |
download | android_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/providers')
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))); } |