summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/com/android/providers/downloads/DownloadInfo.java1
-rw-r--r--src/com/android/providers/downloads/DownloadProvider.java12
-rw-r--r--src/com/android/providers/downloads/DownloadService.java20
-rw-r--r--src/com/android/providers/downloads/DownloadThread.java8
-rw-r--r--src/com/android/providers/downloads/Helpers.java46
-rw-r--r--tests/public_api_access/AndroidManifest.xml3
-rw-r--r--tests/src/com/android/providers/downloads/DownloadManagerFunctionalTest.java15
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 @@
</application>
<uses-permission android:name="android.permission.INTERNET"/>
+ <!-- The tests in this package can access /cache. so they need the following permission -->
+ <uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED"/>
+ <uses-permission android:name="android.permission.ACCESS_CACHE_FILESYSTEM"/>
<!--
The test declared in this instrumentation can be run via this command
diff --git a/tests/src/com/android/providers/downloads/DownloadManagerFunctionalTest.java b/tests/src/com/android/providers/downloads/DownloadManagerFunctionalTest.java
index 7a2bfdff..a5bae8ba 100644
--- a/tests/src/com/android/providers/downloads/DownloadManagerFunctionalTest.java
+++ b/tests/src/com/android/providers/downloads/DownloadManagerFunctionalTest.java
@@ -69,6 +69,21 @@ public class DownloadManagerFunctionalTest extends AbstractDownloadManagerFuncti
getDownloadFilename(downloadUri));
}
+ /**
+ * downloading to system cache should succeed because this tests package has
+ * the permission android.permission.ACCESS_CACHE_FILESYSTEM
+ */
+ public void testDownloadToSystemCache() throws Exception {
+ enqueueResponse(HTTP_OK, FILE_CONTENT);
+ Uri downloadUri = requestDownload("/path");
+ updateDownload(downloadUri, Downloads.Impl.COLUMN_DESTINATION,
+ Integer.toString(Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION));
+ runUntilStatus(downloadUri, Downloads.Impl.STATUS_SUCCESS);
+ assertEquals(FILE_CONTENT, getDownloadContents(downloadUri));
+ assertStartsWith(Environment.getDownloadCacheDirectory().getPath(),
+ getDownloadFilename(downloadUri));
+ }
+
public void testRoaming() throws Exception {
mSystemFacade.mActiveNetworkType = ConnectivityManager.TYPE_MOBILE;
mSystemFacade.mIsRoaming = true;