From 9aadb4b3f2b3c914166ebfae8851fbecaf536f4f Mon Sep 17 00:00:00 2001 From: Vasu Nori Date: Mon, 13 Dec 2010 16:29:29 -0800 Subject: Download dir: /data/data/com.android.providers.downloads/cache NOT /cache bug:3264401 still to do: make sure only N bytes are taken up by downloads dir N = a value specific to each device. default = 100MB. Change-Id: I2a49f4b3831d3a8d7be13b5fd46d85d56e831e38 --- .../android/providers/downloads/DownloadInfo.java | 1 + .../providers/downloads/DownloadProvider.java | 12 +++++- .../providers/downloads/DownloadService.java | 20 +++++++--- .../providers/downloads/DownloadThread.java | 8 ++-- src/com/android/providers/downloads/Helpers.java | 46 +++++++++++++++------- tests/public_api_access/AndroidManifest.xml | 3 ++ .../downloads/DownloadManagerFunctionalTest.java | 15 +++++++ 7 files changed, 80 insertions(+), 25 deletions(-) diff --git a/src/com/android/providers/downloads/DownloadInfo.java b/src/com/android/providers/downloads/DownloadInfo.java index 363b68cd..d1ea43e2 100644 --- a/src/com/android/providers/downloads/DownloadInfo.java +++ b/src/com/android/providers/downloads/DownloadInfo.java @@ -451,6 +451,7 @@ public class DownloadInfo { public boolean isOnCache() { return (mDestination == Downloads.Impl.DESTINATION_CACHE_PARTITION + || mDestination == Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION || mDestination == Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING || mDestination == Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE); } diff --git a/src/com/android/providers/downloads/DownloadProvider.java b/src/com/android/providers/downloads/DownloadProvider.java index 9336b737..d848b65b 100644 --- a/src/com/android/providers/downloads/DownloadProvider.java +++ b/src/com/android/providers/downloads/DownloadProvider.java @@ -151,6 +151,7 @@ public final class DownloadProvider extends ContentProvider { /** List of uids that can access the downloads */ private int mSystemUid = -1; private int mDefContainerUid = -1; + private File mDownloadsDataDir; @VisibleForTesting SystemFacade mSystemFacade; @@ -421,6 +422,7 @@ public final class DownloadProvider extends ContentProvider { if (appInfo != null) { mDefContainerUid = appInfo.uid; } + mDownloadsDataDir = Helpers.getDownloadsDataDirectory(getContext()); return true; } @@ -490,7 +492,8 @@ public final class DownloadProvider extends ContentProvider { && dest != Downloads.Impl.DESTINATION_EXTERNAL && dest != Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE && dest != Downloads.Impl.DESTINATION_FILE_URI) { - throw new SecurityException("unauthorized destination code"); + throw new SecurityException("setting destination to : " + dest + + " not allowed, unless PERMISSION_ACCESS_ADVANCED is granted"); } // for public API behavior, if an app has CACHE_NON_PURGEABLE permission, automatically // switch to non-purgeable download @@ -508,6 +511,11 @@ public final class DownloadProvider extends ContentProvider { Binder.getCallingPid(), Binder.getCallingUid(), "need WRITE_EXTERNAL_STORAGE permission to use DESTINATION_FILE_URI"); checkFileUriDestination(values); + } else if (dest == Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION) { + getContext().enforcePermission( + android.Manifest.permission.ACCESS_CACHE_FILESYSTEM, + Binder.getCallingPid(), Binder.getCallingUid(), + "need ACCESS_CACHE_FILESYSTEM permission to use system cache"); } filteredValues.put(Downloads.Impl.COLUMN_DESTINATION, dest); } @@ -1068,7 +1076,7 @@ public final class DownloadProvider extends ContentProvider { if (path == null) { throw new FileNotFoundException("No filename found."); } - if (!Helpers.isFilenameValid(path)) { + if (!Helpers.isFilenameValid(path, mDownloadsDataDir)) { throw new FileNotFoundException("Invalid filename."); } if (!"r".equals(mode)) { diff --git a/src/com/android/providers/downloads/DownloadService.java b/src/com/android/providers/downloads/DownloadService.java index f93c5c2e..62e355c4 100644 --- a/src/com/android/providers/downloads/DownloadService.java +++ b/src/com/android/providers/downloads/DownloadService.java @@ -93,10 +93,14 @@ public class DownloadService extends Service { private boolean mMediaScannerConnecting; + private static final int LOCATION_SYSTEM_CACHE = 1; + private static final int LOCATION_DOWNLOAD_DATA_DIR = 2; + /** * The IPC interface to the Media Scanner */ private IMediaScannerService mMediaScannerService; + private File mDownloadsDataDir; @VisibleForTesting SystemFacade mSystemFacade; @@ -218,7 +222,7 @@ public class DownloadService extends Service { mNotifier = new DownloadNotification(this, mSystemFacade); mSystemFacade.cancelAllNotifications(); - + mDownloadsDataDir = Helpers.getDownloadsDataDirectory(getApplicationContext()); updateFromProvider(); } @@ -267,7 +271,10 @@ public class DownloadService extends Service { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); trimDatabase(); - removeSpuriousFiles(); + // remove spurious files from system cache + removeSpuriousFiles(LOCATION_SYSTEM_CACHE); + // remove spurious files from downloads dir + removeSpuriousFiles(LOCATION_DOWNLOAD_DATA_DIR); boolean keepService = false; // for each update from the database, remember which download is @@ -423,10 +430,13 @@ public class DownloadService extends Service { } /** - * Removes files that may have been left behind in the cache directory + * Removes files that may have been left behind in the systemcache or + * /data/downloads directory */ - private void removeSpuriousFiles() { - File[] files = Environment.getDownloadCacheDirectory().listFiles(); + private void removeSpuriousFiles(int location) { + File base = (location == LOCATION_SYSTEM_CACHE) ? + Environment.getDownloadCacheDirectory() : mDownloadsDataDir; + File[] files = base.listFiles(); if (files == null) { // The cache folder doesn't appear to exist (this is likely the case // when running the simulator). diff --git a/src/com/android/providers/downloads/DownloadThread.java b/src/com/android/providers/downloads/DownloadThread.java index a1d91019..c497d5cf 100644 --- a/src/com/android/providers/downloads/DownloadThread.java +++ b/src/com/android/providers/downloads/DownloadThread.java @@ -437,7 +437,8 @@ public class DownloadThread extends Thread { return; } catch (IOException ex) { if (mInfo.isOnCache()) { - if (Helpers.discardPurgeableFiles(mContext, Constants.BUFFER_SIZE)) { + if (Helpers.discardPurgeableFiles(mInfo.mDestination, mContext, + Constants.BUFFER_SIZE)) { continue; } } else if (!Helpers.isExternalMediaMounted()) { @@ -446,7 +447,7 @@ public class DownloadThread extends Thread { } long availableBytes = - Helpers.getAvailableBytes(Helpers.getFilesystemRoot(state.mFilename)); + Helpers.getAvailableBytes(Helpers.getFilesystemRoot(mContext, state.mFilename)); if (availableBytes < bytesRead) { throw new StopRequest(Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR, "insufficient space while writing destination file", ex); @@ -802,7 +803,8 @@ public class DownloadThread extends Thread { private void setupDestinationFile(State state, InnerState innerState) throws StopRequest { if (!TextUtils.isEmpty(state.mFilename)) { // only true if we've already run a thread for this download - if (!Helpers.isFilenameValid(state.mFilename)) { + if (!Helpers.isFilenameValid(state.mFilename, + Helpers.getDownloadsDataDirectory(mContext))) { // this should never happen throw new StopRequest(Downloads.Impl.STATUS_FILE_ERROR, "found invalid internal destination filename"); diff --git a/src/com/android/providers/downloads/Helpers.java b/src/com/android/providers/downloads/Helpers.java index 59cc97cf..052876fa 100644 --- a/src/com/android/providers/downloads/Helpers.java +++ b/src/com/android/providers/downloads/Helpers.java @@ -98,7 +98,7 @@ public class Helpers { boolean isPublicApi) throws GenerateSaveFileError { checkCanHandleDownload(context, mimeType, destination, isPublicApi); if (destination == Downloads.Impl.DESTINATION_FILE_URI) { - String path = verifyFileUri(hint, contentLength); + String path = verifyFileUri(context, hint, contentLength); String c = getFullPath(path, mimeType, destination, null); return c; } else { @@ -107,14 +107,14 @@ public class Helpers { } } - private static String verifyFileUri(String hint, long contentLength) + private static String verifyFileUri(Context context, String hint, long contentLength) throws GenerateSaveFileError { if (!isExternalMediaMounted()) { throw new GenerateSaveFileError(Downloads.Impl.STATUS_DEVICE_NOT_FOUND_ERROR, "external media not mounted"); } String path = Uri.parse(hint).getPath(); - if (getAvailableBytes(getFilesystemRoot(path)) < contentLength) { + if (getAvailableBytes(getFilesystemRoot(context, path)) < contentLength) { throw new GenerateSaveFileError(Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR, "insufficient space on external storage"); } @@ -125,11 +125,15 @@ public class Helpers { /** * @return the root of the filesystem containing the given path */ - public static File getFilesystemRoot(String path) { + static File getFilesystemRoot(Context context, String path) { File cache = Environment.getDownloadCacheDirectory(); if (path.startsWith(cache.getPath())) { return cache; } + File systemCache = Helpers.getDownloadsDataDirectory(context); + if (path.startsWith(systemCache.getPath())) { + return systemCache; + } File external = Environment.getExternalStorageDirectory(); if (path.startsWith(external.getPath())) { return external; @@ -221,8 +225,9 @@ public class Helpers { if (destination == Downloads.Impl.DESTINATION_CACHE_PARTITION || destination == Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE || destination == Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING + || destination == Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION || DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING.equalsIgnoreCase(mimeType)) { - return getCacheDestination(context, contentLength); + return getCacheDestination(context, contentLength, destination); } return getExternalDestination(contentLength); @@ -261,18 +266,20 @@ public class Helpers { return true; } - private static File getCacheDestination(Context context, long contentLength) + private static File getCacheDestination(Context context, long contentLength, int destination) throws GenerateSaveFileError { File base; - base = Environment.getDownloadCacheDirectory(); + base = (destination == Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION) ? + Environment.getDownloadCacheDirectory() : + Helpers.getDownloadsDataDirectory(context); long bytesAvailable = getAvailableBytes(base); while (bytesAvailable < contentLength) { // Insufficient space; try discarding purgeable files. - if (!discardPurgeableFiles(context, contentLength - bytesAvailable)) { + if (!discardPurgeableFiles(destination, context, contentLength - bytesAvailable)) { // No files to purge, give up. throw new GenerateSaveFileError(Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR, - "not enough free space in internal download storage, unable to free any " - + "more"); + "not enough free space in internal download storage: " + base + + ", unable to free any more"); } bytesAvailable = getAvailableBytes(base); } @@ -443,6 +450,7 @@ public class Helpers { if (!new File(fullFilename).exists() && (!recoveryDir || (destination != Downloads.Impl.DESTINATION_CACHE_PARTITION && + destination != Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION && destination != Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE && destination != Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING))) { return fullFilename; @@ -484,15 +492,19 @@ public class Helpers { * the matching database entries. Files are deleted in LRU order until * the total byte size is greater than targetBytes. */ - public static final boolean discardPurgeableFiles(Context context, long targetBytes) { + static final boolean discardPurgeableFiles(int destination, Context context, + long targetBytes) { + String destStr = (destination == Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION) ? + String.valueOf(destination) : + String.valueOf(Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE); + String[] bindArgs = new String[]{destStr}; Cursor cursor = context.getContentResolver().query( Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, null, "( " + Downloads.Impl.COLUMN_STATUS + " = '" + Downloads.Impl.STATUS_SUCCESS + "' AND " + - Downloads.Impl.COLUMN_DESTINATION + - " = '" + Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE + "' )", - null, + Downloads.Impl.COLUMN_DESTINATION + " = '?' )", + bindArgs, Downloads.Impl.COLUMN_LAST_MODIFICATION); if (cursor == null) { return false; @@ -536,9 +548,10 @@ public class Helpers { /** * Checks whether the filename looks legitimate */ - public static boolean isFilenameValid(String filename) { + 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()); } @@ -851,4 +864,7 @@ public class Helpers { } return sb.toString(); } + static final File getDownloadsDataDirectory(Context context) { + return context.getCacheDir(); + } } diff --git a/tests/public_api_access/AndroidManifest.xml b/tests/public_api_access/AndroidManifest.xml index 01048460..bec636be 100644 --- a/tests/public_api_access/AndroidManifest.xml +++ b/tests/public_api_access/AndroidManifest.xml @@ -23,6 +23,9 @@ + + +