Support for non-purgeable downloads through the public API.
Steve Howard [Tue, 20 Jul 2010 23:32:31 +0000 (16:32 -0700)]
This change adds a permission,
android.permission.DOWNLOAD_CACHE_NON_PURGEABLE.  When an app has this
permission, any downloads it requests through the public API to the
download cache will automatically become non-purgeable, i.e. they'll
never be automatically deleted by the download manager to free up
space.  This is intended for use only by the system updater.

Change-Id: I35cdd44f7e5d46bc70443d1a9743f61a51395ddb

AndroidManifest.xml
res/values/strings.xml
src/com/android/providers/downloads/DownloadProvider.java

index 20fe6ca..d6c85d3 100644 (file)
         android:description="@string/permdesc_downloadCompletedIntent"
         android:protectionLevel="signature" />
 
+    <!-- Allows to download non-purgeable files to the cache partition through the public API -->
+    <permission android:name="android.permission.DOWNLOAD_CACHE_NON_PURGEABLE"
+        android:label="@string/permlab_downloadCacheNonPurgeable"
+        android:description="@string/permdesc_downloadCacheNonPurgeable"
+        android:protectionLevel="dangerous"/>
+
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
     <uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER" />
     <uses-permission android:name="android.permission.ACCESS_DRM" />
index 7e40b60..0bc5c09 100644 (file)
     <string name="permdesc_seeAllExternal">Allows the application to see all
         downloads to the SD card, regardless of which application downloaded
         them.</string>
+
+    <string name="permlab_downloadCacheNonPurgeable">Download non-purgeable
+      files to the cache</string>
+    <string name="permdesc_downloadCacheNonPurgeable">Allows the application to
+    download files to the download cache through the public API which will not
+    be automatically deleted when the download manager needs more space.
+    Malicious applications can use this to block other applications from using
+    the download cache.</string>
+
     <!-- This is the title that is used when displaying the notification
     for a download that doesn't have a title associated with it. -->
     <string name="download_unknown_title">&lt;Untitled&gt;</string>
index 2c0a264..a116f87 100644 (file)
@@ -337,6 +337,11 @@ public final class DownloadProvider extends ContentProvider {
         copyBoolean(Downloads.Impl.COLUMN_NO_INTEGRITY, values, filteredValues);
         copyString(Downloads.Impl.COLUMN_FILE_NAME_HINT, values, filteredValues);
         copyString(Downloads.Impl.COLUMN_MIME_TYPE, values, filteredValues);
+
+        copyBoolean(Downloads.Impl.COLUMN_IS_PUBLIC_API, values, filteredValues);
+        boolean isPublicApi =
+                values.getAsBoolean(Downloads.Impl.COLUMN_IS_PUBLIC_API) == Boolean.TRUE;
+
         Integer dest = values.getAsInteger(Downloads.Impl.COLUMN_DESTINATION);
         if (dest != null) {
             if (getContext().checkCallingPermission(Downloads.Impl.PERMISSION_ACCESS_ADVANCED)
@@ -346,6 +351,16 @@ public final class DownloadProvider extends ContentProvider {
                     && dest != Downloads.Impl.DESTINATION_FILE_URI) {
                 throw new SecurityException("unauthorized destination code");
             }
+            // for public API behavior, if an app has CACHE_NON_PURGEABLE permission, automatically
+            // switch to non-purgeable download
+            boolean hasNonPurgeablePermission =
+                    getContext().checkCallingPermission(
+                            Downloads.Impl.PERMISSION_CACHE_NON_PURGEABLE)
+                            == PackageManager.PERMISSION_GRANTED;
+            if (isPublicApi && dest == Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE
+                    && hasNonPurgeablePermission) {
+                dest = Downloads.Impl.DESTINATION_CACHE_PARTITION;
+            }
             if (dest == Downloads.Impl.DESTINATION_FILE_URI) {
                 getContext().enforcePermission(
                         android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
@@ -372,10 +387,6 @@ public final class DownloadProvider extends ContentProvider {
         filteredValues.put(Downloads.Impl.COLUMN_LAST_MODIFICATION,
                            mSystemFacade.currentTimeMillis());
 
-        copyBoolean(Downloads.Impl.COLUMN_IS_PUBLIC_API, values, filteredValues);
-        boolean isPublicApi =
-                values.getAsBoolean(Downloads.Impl.COLUMN_IS_PUBLIC_API) == Boolean.TRUE;
-
         String pckg = values.getAsString(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE);
         String clazz = values.getAsString(Downloads.Impl.COLUMN_NOTIFICATION_CLASS);
         if (pckg != null && (clazz != null || isPublicApi)) {
@@ -846,7 +857,7 @@ public final class DownloadProvider extends ContentProvider {
         getContext().getContentResolver().notifyChange(uri, null);
         return count;
     }
-    
+
     private void appendClause(StringBuilder whereClause, String newClause) {
         if (whereClause.length() != 0) {
             whereClause.append(" AND ");