diff options
author | Steve Howard <showard@google.com> | 2010-07-12 17:24:17 -0700 |
---|---|---|
committer | Steve Howard <showard@google.com> | 2010-07-14 11:33:17 -0700 |
commit | 6d9b98282c817b86a00f9c19a705da4cb19bc3a6 (patch) | |
tree | d262513c211424ffcefa26440f3ee0d63e3b0849 /src/com/android/providers/downloads | |
parent | f413bd5422d918872e576c1c0c3e0b9227f15304 (diff) | |
download | android_packages_providers_DownloadProvider-6d9b98282c817b86a00f9c19a705da4cb19bc3a6.tar.gz android_packages_providers_DownloadProvider-6d9b98282c817b86a00f9c19a705da4cb19bc3a6.tar.bz2 android_packages_providers_DownloadProvider-6d9b98282c817b86a00f9c19a705da4cb19bc3a6.zip |
Support for file URI destinations + last modified timestamp
File URI destinations:
* permission checking in DownloadProvider
* implementation in Helpers.generateSaveFile(). it's a fairly small
amount of logic added here, but I did some significant method
extraction to simplify this change and clean up the code in general.
* added test case
Last modified timestamp:
* made DownloadProvider use a SystemFacade for getting system time, so I could properly test timestamps
* updated test cases to cover last modified time + handle new ordering
Diffstat (limited to 'src/com/android/providers/downloads')
-rw-r--r-- | src/com/android/providers/downloads/DownloadProvider.java | 23 | ||||
-rw-r--r-- | src/com/android/providers/downloads/Helpers.java | 146 |
2 files changed, 114 insertions, 55 deletions
diff --git a/src/com/android/providers/downloads/DownloadProvider.java b/src/com/android/providers/downloads/DownloadProvider.java index d7c24f9a..7070f31c 100644 --- a/src/com/android/providers/downloads/DownloadProvider.java +++ b/src/com/android/providers/downloads/DownloadProvider.java @@ -16,6 +16,8 @@ package com.android.providers.downloads; +import com.google.common.annotations.VisibleForTesting; + import android.content.ContentProvider; import android.content.ContentValues; import android.content.Context; @@ -110,6 +112,9 @@ public final class DownloadProvider extends ContentProvider { private int mSystemUid = -1; private int mDefContainerUid = -1; + @VisibleForTesting + SystemFacade mSystemFacade; + /** * Creates and updated database on demand when opening it. * Helper class to create database the first time the provider is @@ -172,6 +177,10 @@ public final class DownloadProvider extends ContentProvider { */ @Override public boolean onCreate() { + if (mSystemFacade == null) { + mSystemFacade = new RealSystemFacade(); + } + mOpenHelper = new DatabaseHelper(getContext()); // Initialize the system uid mSystemUid = Process.SYSTEM_UID; @@ -294,9 +303,16 @@ public final class DownloadProvider extends ContentProvider { if (getContext().checkCallingPermission(Downloads.Impl.PERMISSION_ACCESS_ADVANCED) != PackageManager.PERMISSION_GRANTED && dest != Downloads.Impl.DESTINATION_EXTERNAL - && dest != Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE) { + && dest != Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE + && dest != Downloads.Impl.DESTINATION_FILE_URI) { throw new SecurityException("unauthorized destination code"); } + if (dest == Downloads.Impl.DESTINATION_FILE_URI) { + getContext().enforcePermission( + android.Manifest.permission.WRITE_EXTERNAL_STORAGE, + Binder.getCallingPid(), Binder.getCallingUid(), + "need WRITE_EXTERNAL_STORAGE permission to use DESTINATION_FILE_URI"); + } filteredValues.put(Downloads.Impl.COLUMN_DESTINATION, dest); } Integer vis = values.getAsInteger(Downloads.Impl.COLUMN_VISIBILITY); @@ -313,7 +329,8 @@ public final class DownloadProvider extends ContentProvider { } copyInteger(Downloads.Impl.COLUMN_CONTROL, values, filteredValues); filteredValues.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING); - filteredValues.put(Downloads.Impl.COLUMN_LAST_MODIFICATION, System.currentTimeMillis()); + filteredValues.put(Downloads.Impl.COLUMN_LAST_MODIFICATION, + mSystemFacade.currentTimeMillis()); String pckg = values.getAsString(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE); String clazz = values.getAsString(Downloads.Impl.COLUMN_NOTIFICATION_CLASS); if (pckg != null && clazz != null) { @@ -733,7 +750,7 @@ public final class DownloadProvider extends ContentProvider { throw new FileNotFoundException("couldn't open file"); } else { ContentValues values = new ContentValues(); - values.put(Downloads.Impl.COLUMN_LAST_MODIFICATION, System.currentTimeMillis()); + values.put(Downloads.Impl.COLUMN_LAST_MODIFICATION, mSystemFacade.currentTimeMillis()); update(uri, values, null, null); } return ret; diff --git a/src/com/android/providers/downloads/Helpers.java b/src/com/android/providers/downloads/Helpers.java index 6bb5093a..2705a7cb 100644 --- a/src/com/android/providers/downloads/Helpers.java +++ b/src/com/android/providers/downloads/Helpers.java @@ -35,7 +35,7 @@ import android.util.Config; import android.util.Log; import android.webkit.MimeTypeMap; -import java.io.File; +import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.util.Random; @@ -76,6 +76,17 @@ public class Helpers { } /** + * Exception thrown from methods called by generateSaveFile() for any fatal error. + */ + private static class GenerateSaveFileError extends Exception { + int mStatus; + + public GenerateSaveFileError(int status) { + mStatus = status; + } + } + + /** * Creates a filename (where the file should be saved) from a uri. */ public static DownloadFileInfo generateSaveFile( @@ -88,16 +99,80 @@ public class Helpers { int destination, int contentLength) throws FileNotFoundException { - /* - * Don't download files that we won't be able to handle - */ + if (!canHandleDownload(context, mimeType, destination)) { + return new DownloadFileInfo(null, null, Downloads.Impl.STATUS_NOT_ACCEPTABLE); + } + + String fullFilename; + try { + if (destination == Downloads.Impl.DESTINATION_FILE_URI) { + fullFilename = getPathForFileUri(hint); + } else { + fullFilename = chooseFullPath(context, url, hint, contentDisposition, + contentLocation, mimeType, destination, + contentLength); + } + } catch (GenerateSaveFileError exc) { + return new DownloadFileInfo(null, null, exc.mStatus); + } + + return new DownloadFileInfo(fullFilename, new FileOutputStream(fullFilename), 0); + } + + private static String getPathForFileUri(String hint) throws GenerateSaveFileError { + Uri uri = Uri.parse(hint); + if (!uri.getScheme().equals("file")) { + Log.d(Constants.TAG, "Not a file URI: " + hint); + throw new GenerateSaveFileError(Downloads.Impl.STATUS_FILE_ERROR); + } + + String path = uri.getSchemeSpecificPart(); + if (new File(path).exists()) { + Log.d(Constants.TAG, "File already exists: " + path); + throw new GenerateSaveFileError(Downloads.Impl.STATUS_FILE_ERROR); + } + + return path; + } + + private static String chooseFullPath(Context context, String url, String hint, + String contentDisposition, String contentLocation, + String mimeType, int destination, int contentLength) + throws GenerateSaveFileError { + File base = locateDestinationDirectory(context, mimeType, destination, contentLength); + String filename = chooseFilename(url, hint, contentDisposition, contentLocation, + destination); + + // Split filename between base and extension + // Add an extension if filename does not have one + String extension = null; + int dotIndex = filename.indexOf('.'); + if (dotIndex < 0) { + extension = chooseExtensionFromMimeType(mimeType, true); + } else { + extension = chooseExtensionFromFilename(mimeType, destination, filename, dotIndex); + filename = filename.substring(0, dotIndex); + } + + boolean recoveryDir = Constants.RECOVERY_DIRECTORY.equalsIgnoreCase(filename + extension); + + filename = base.getPath() + File.separator + filename; + + if (Constants.LOGVV) { + Log.v(Constants.TAG, "target file: " + filename + extension); + } + + return chooseUniqueFilename(destination, filename, extension, recoveryDir); + } + + private static boolean canHandleDownload(Context context, String mimeType, int destination) { if (destination == Downloads.Impl.DESTINATION_EXTERNAL || destination == Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE) { if (mimeType == null) { if (Config.LOGD) { Log.d(Constants.TAG, "external download with no mime type not allowed"); } - return new DownloadFileInfo(null, null, Downloads.Impl.STATUS_NOT_ACCEPTABLE); + return false; } if (!DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING.equalsIgnoreCase(mimeType)) { // Check to see if we are allowed to download this file. Only files @@ -121,32 +196,19 @@ public class Helpers { if (Config.LOGD) { Log.d(Constants.TAG, "no handler found for type " + mimeType); } - return new DownloadFileInfo(null, null, Downloads.Impl.STATUS_NOT_ACCEPTABLE); + return false; } } } - String filename = chooseFilename( - url, hint, contentDisposition, contentLocation, destination); - - // Split filename between base and extension - // Add an extension if filename does not have one - String extension = null; - int dotIndex = filename.indexOf('.'); - if (dotIndex < 0) { - extension = chooseExtensionFromMimeType(mimeType, true); - } else { - extension = chooseExtensionFromFilename( - mimeType, destination, filename, dotIndex); - filename = filename.substring(0, dotIndex); - } - - /* - * Locate the directory where the file will be saved - */ + return true; + } + private static File locateDestinationDirectory(Context context, String mimeType, + int destination, int contentLength) + throws GenerateSaveFileError { File base = null; StatFs stat = null; - // DRM messages should be temporarily stored internally and then passed to + // DRM messages should be temporarily stored internally and then passed to // the DRM content provider if (destination == Downloads.Impl.DESTINATION_CACHE_PARTITION || destination == Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE @@ -170,8 +232,7 @@ public class Helpers { Log.d(Constants.TAG, "download aborted - not enough free space in internal storage"); } - return new DownloadFileInfo(null, null, - Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR); + throw new GenerateSaveFileError(Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR); } else { // Recalculate available space and try again. stat.restat(base.getPath()); @@ -192,8 +253,7 @@ public class Helpers { if (Config.LOGD) { Log.d(Constants.TAG, "download aborted - not enough free space"); } - return new DownloadFileInfo(null, null, - Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR); + throw new GenerateSaveFileError(Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR); } base = new File(root + Constants.DEFAULT_DL_SUBDIR); @@ -204,35 +264,17 @@ public class Helpers { Log.d(Constants.TAG, "download aborted - can't create base directory " + base.getPath()); } - return new DownloadFileInfo(null, null, Downloads.Impl.STATUS_FILE_ERROR); + throw new GenerateSaveFileError(Downloads.Impl.STATUS_FILE_ERROR); } } else { // No SD card found. if (Config.LOGD) { Log.d(Constants.TAG, "download aborted - no external storage"); } - return new DownloadFileInfo(null, null, - Downloads.Impl.STATUS_DEVICE_NOT_FOUND_ERROR); + throw new GenerateSaveFileError(Downloads.Impl.STATUS_DEVICE_NOT_FOUND_ERROR); } - boolean recoveryDir = Constants.RECOVERY_DIRECTORY.equalsIgnoreCase(filename + extension); - - filename = base.getPath() + File.separator + filename; - - /* - * Generate a unique filename, create the file, return it. - */ - if (Constants.LOGVV) { - Log.v(Constants.TAG, "target file: " + filename + extension); - } - - String fullFilename = chooseUniqueFilename( - destination, filename, extension, recoveryDir); - if (fullFilename != null) { - return new DownloadFileInfo(fullFilename, new FileOutputStream(fullFilename), 0); - } else { - return new DownloadFileInfo(null, null, Downloads.Impl.STATUS_FILE_ERROR); - } + return base; } private static String chooseFilename(String url, String hint, String contentDisposition, @@ -383,7 +425,7 @@ public class Helpers { } private static String chooseUniqueFilename(int destination, String filename, - String extension, boolean recoveryDir) { + String extension, boolean recoveryDir) throws GenerateSaveFileError { String fullFilename = filename + extension; if (!new File(fullFilename).exists() && (!recoveryDir || @@ -420,7 +462,7 @@ public class Helpers { sequence += sRandom.nextInt(magnitude) + 1; } } - return null; + throw new GenerateSaveFileError(Downloads.Impl.STATUS_FILE_ERROR); } /** |