From b9a0ad7182209d4aca708e13e876e9b1b43ffafc Mon Sep 17 00:00:00 2001 From: Steve Howard Date: Thu, 16 Sep 2010 12:04:17 -0700 Subject: Improve file error reporting + new detailed error messages in UI * support new error code for "destination file already exists" * improve error handling for various file error cases to return a more specific error code when appropriate * make UI support more detailed error messages for some cases * use Uri.getPath() instead of Uri.getSchemeSpecificPart() for file URIs Change-Id: Icb01d4d3b47c7776be3ddcd8347212e950cd023e --- .../providers/downloads/DownloadProvider.java | 8 +- .../providers/downloads/DownloadThread.java | 15 ++- src/com/android/providers/downloads/Helpers.java | 147 ++++++++++++--------- 3 files changed, 103 insertions(+), 67 deletions(-) (limited to 'src/com/android') diff --git a/src/com/android/providers/downloads/DownloadProvider.java b/src/com/android/providers/downloads/DownloadProvider.java index df7ca71d..102c611d 100644 --- a/src/com/android/providers/downloads/DownloadProvider.java +++ b/src/com/android/providers/downloads/DownloadProvider.java @@ -121,6 +121,7 @@ public final class DownloadProvider extends ContentProvider { Downloads.Impl.COLUMN_DESCRIPTION, Downloads.Impl.COLUMN_URI, Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, + Downloads.Impl.COLUMN_FILE_NAME_HINT, }; private static HashSet sAppReadableColumnsSet; @@ -508,9 +509,12 @@ public final class DownloadProvider extends ContentProvider { if (!uri.getScheme().equals("file")) { throw new IllegalArgumentException("Not a file URI: " + uri); } - File path = new File(uri.getSchemeSpecificPart()); + String path = uri.getPath(); + if (path == null) { + throw new IllegalArgumentException("Invalid file URI: " + uri); + } String externalPath = Environment.getExternalStorageDirectory().getAbsolutePath(); - if (!path.getPath().startsWith(externalPath)) { + if (!path.startsWith(externalPath)) { throw new SecurityException("Destination must be on external storage: " + uri); } } diff --git a/src/com/android/providers/downloads/DownloadThread.java b/src/com/android/providers/downloads/DownloadThread.java index b2353e16..57007f49 100644 --- a/src/com/android/providers/downloads/DownloadThread.java +++ b/src/com/android/providers/downloads/DownloadThread.java @@ -426,9 +426,18 @@ public class DownloadThread extends Thread { } return; } catch (IOException ex) { - if (mInfo.isOnCache() - && Helpers.discardPurgeableFiles(mContext, Constants.BUFFER_SIZE)) { - continue; + if (mInfo.isOnCache()) { + if (Helpers.discardPurgeableFiles(mContext, Constants.BUFFER_SIZE)) { + continue; + } + } else if (!Helpers.isExternalMediaMounted()) { + throw new StopRequest(Downloads.Impl.STATUS_DEVICE_NOT_FOUND_ERROR); + } + + long availableBytes = + Helpers.getAvailableBytes(Helpers.getFilesystemRoot(state.mFilename)); + if (availableBytes < bytesRead) { + throw new StopRequest(Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR, ex); } throw new StopRequest(Downloads.Impl.STATUS_FILE_ERROR, ex); } diff --git a/src/com/android/providers/downloads/Helpers.java b/src/com/android/providers/downloads/Helpers.java index f8900d9a..f1f23b17 100644 --- a/src/com/android/providers/downloads/Helpers.java +++ b/src/com/android/providers/downloads/Helpers.java @@ -104,7 +104,7 @@ public class Helpers { String fullFilename; try { if (destination == Downloads.Impl.DESTINATION_FILE_URI) { - fullFilename = getPathForFileUri(hint); + fullFilename = getPathForFileUri(hint, contentLength); } else { fullFilename = chooseFullPath(context, url, hint, contentDisposition, contentLocation, mimeType, destination, @@ -117,16 +117,38 @@ public class Helpers { return new DownloadFileInfo(fullFilename, new FileOutputStream(fullFilename), 0); } - private static String getPathForFileUri(String hint) throws GenerateSaveFileError { - String path = Uri.parse(hint).getSchemeSpecificPart(); + private static String getPathForFileUri(String hint, long contentLength) + throws GenerateSaveFileError { + if (!isExternalMediaMounted()) { + throw new GenerateSaveFileError(Downloads.Impl.STATUS_DEVICE_NOT_FOUND_ERROR); + } + String path = Uri.parse(hint).getPath(); if (new File(path).exists()) { Log.d(Constants.TAG, "File already exists: " + path); - throw new GenerateSaveFileError(Downloads.Impl.STATUS_FILE_ERROR); + throw new GenerateSaveFileError(Downloads.Impl.STATUS_FILE_ALREADY_EXISTS_ERROR); + } + if (getAvailableBytes(getFilesystemRoot(path)) < contentLength) { + throw new GenerateSaveFileError(Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR); } return path; } + /** + * @return the root of the filesystem containing the given path + */ + public static File getFilesystemRoot(String path) { + File cache = Environment.getDownloadCacheDirectory(); + if (path.startsWith(cache.getPath())) { + return cache; + } + File external = Environment.getExternalStorageDirectory(); + if (path.startsWith(external.getPath())) { + return external; + } + throw new IllegalArgumentException("Cannot determine filesystem root for " + path); + } + private static String chooseFullPath(Context context, String url, String hint, String contentDisposition, String contentLocation, String mimeType, int destination, long contentLength) @@ -203,77 +225,78 @@ public class Helpers { private static File locateDestinationDirectory(Context context, String mimeType, int destination, long contentLength) throws GenerateSaveFileError { - File base = null; - StatFs stat = null; // 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 || destination == Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING || DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING.equalsIgnoreCase(mimeType)) { - // Saving to internal storage. - base = Environment.getDownloadCacheDirectory(); - stat = new StatFs(base.getPath()); - - /* - * Check whether there's enough space on the target filesystem to save the file. - * Put a bit of margin (in case creating the file grows the system by a few blocks). - */ - int blockSize = stat.getBlockSize(); - long bytesAvailable = blockSize * ((long) stat.getAvailableBlocks() - 4); - while (bytesAvailable < contentLength) { - // Insufficient space; try discarding purgeable files. - if (!discardPurgeableFiles(context, contentLength - bytesAvailable)) { - // No files to purge, give up. - if (Config.LOGD) { - Log.d(Constants.TAG, - "download aborted - not enough free space in internal storage"); - } - throw new GenerateSaveFileError(Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR); - } else { - // Recalculate available space and try again. - stat.restat(base.getPath()); - bytesAvailable = blockSize * ((long) stat.getAvailableBlocks() - 4); - } - } - } else if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - // Saving to external storage (SD card). - String root = Environment.getExternalStorageDirectory().getPath(); - stat = new StatFs(root); - - /* - * Check whether there's enough space on the target filesystem to save the file. - * Put a bit of margin (in case creating the file grows the system by a few blocks). - */ - if (stat.getBlockSize() * ((long) stat.getAvailableBlocks() - 4) < contentLength) { - // Insufficient space. - if (Config.LOGD) { - Log.d(Constants.TAG, "download aborted - not enough free space"); - } - throw new GenerateSaveFileError(Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR); - } + return getCacheDestination(context, contentLength); + } - base = new File(root + Constants.DEFAULT_DL_SUBDIR); - if (!base.isDirectory() && !base.mkdir()) { - // Can't create download directory, e.g. because a file called "download" - // already exists at the root level, or the SD card filesystem is read-only. - if (Config.LOGD) { - Log.d(Constants.TAG, "download aborted - can't create base directory " - + base.getPath()); - } - 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 getExternalDestination(contentLength); + } + + private static File getExternalDestination(long contentLength) throws GenerateSaveFileError { + if (!isExternalMediaMounted()) { throw new GenerateSaveFileError(Downloads.Impl.STATUS_DEVICE_NOT_FOUND_ERROR); } + File root = Environment.getExternalStorageDirectory(); + if (getAvailableBytes(root) < contentLength) { + // Insufficient space. + Log.d(Constants.TAG, "download aborted - not enough free space"); + throw new GenerateSaveFileError(Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR); + } + + File base = new File(root.getPath() + Constants.DEFAULT_DL_SUBDIR); + if (!base.isDirectory() && !base.mkdir()) { + // Can't create download directory, e.g. because a file called "download" + // already exists at the root level, or the SD card filesystem is read-only. + Log.d(Constants.TAG, "download aborted - can't create base directory " + + base.getPath()); + throw new GenerateSaveFileError(Downloads.Impl.STATUS_FILE_ERROR); + } return base; } + public static boolean isExternalMediaMounted() { + if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + // No SD card found. + Log.d(Constants.TAG, "no external storage"); + return false; + } + return true; + } + + private static File getCacheDestination(Context context, long contentLength) + throws GenerateSaveFileError { + File base; + base = Environment.getDownloadCacheDirectory(); + long bytesAvailable = getAvailableBytes(base); + while (bytesAvailable < contentLength) { + // Insufficient space; try discarding purgeable files. + if (!discardPurgeableFiles(context, contentLength - bytesAvailable)) { + // No files to purge, give up. + Log.d(Constants.TAG, + "download aborted - not enough free space in internal storage"); + throw new GenerateSaveFileError(Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR); + } + bytesAvailable = getAvailableBytes(base); + } + return base; + } + + /** + * @return the number of bytes available on the filesystem rooted at the given File + */ + public static long getAvailableBytes(File root) { + StatFs stat = new StatFs(root.getPath()); + // put a bit of margin (in case creating the file grows the system by a few blocks) + long availableBlocks = (long) stat.getAvailableBlocks() - 4; + return stat.getBlockSize() * availableBlocks; + } + private static String chooseFilename(String url, String hint, String contentDisposition, String contentLocation, int destination) { String filename = null; -- cgit v1.2.3