Keep shared downloads when apps are uninstalled.

When an app downloads files to external storage, keep those downloads
around for the user to enjoy after the app is uninstalled.

We still end up deleting files stored in internal cache directories,
and under package-specific directories on external storage.

Test: builds, boots, downloads on external storage remain
Bug: 30868200
Change-Id: Ib70f42aa764a8252fe67c6fba9d60b3350f5d5a4
diff --git a/src/com/android/providers/downloads/DownloadProvider.java b/src/com/android/providers/downloads/DownloadProvider.java
index f3b7b6f..d50b394 100644
--- a/src/com/android/providers/downloads/DownloadProvider.java
+++ b/src/com/android/providers/downloads/DownloadProvider.java
@@ -1116,8 +1116,9 @@
     @Override
     public int update(final Uri uri, final ContentValues values,
             final String where, final String[] whereArgs) {
-
-        Helpers.validateSelection(where, sAppReadableColumnsSet);
+        if (shouldRestrictVisibility()) {
+            Helpers.validateSelection(where, sAppReadableColumnsSet);
+        }
 
         final Context context = getContext();
         final ContentResolver resolver = context.getContentResolver();
diff --git a/src/com/android/providers/downloads/DownloadReceiver.java b/src/com/android/providers/downloads/DownloadReceiver.java
index a0dc694..92d0bad 100644
--- a/src/com/android/providers/downloads/DownloadReceiver.java
+++ b/src/com/android/providers/downloads/DownloadReceiver.java
@@ -139,13 +139,24 @@
 
     private void handleUidRemoved(Context context, Intent intent) {
         final ContentResolver resolver = context.getContentResolver();
-
         final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
-        final int count = resolver.delete(
-                Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, Constants.UID + "=" + uid, null);
 
-        if (count > 0) {
-            Slog.d(TAG, "Deleted " + count + " downloads owned by UID " + uid);
+        // First, disown any downloads that live in shared storage
+        final ContentValues values = new ContentValues();
+        values.putNull(Constants.UID);
+        final int disowned = resolver.update(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, values,
+                Constants.UID + "=" + uid + " AND " + Downloads.Impl.COLUMN_DESTINATION + " IN ("
+                        + Downloads.Impl.DESTINATION_EXTERNAL + ","
+                        + Downloads.Impl.DESTINATION_FILE_URI + ")",
+                null);
+
+        // Finally, delete any remaining downloads owned by UID
+        final int deleted = resolver.delete(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI,
+                Constants.UID + "=" + uid, null);
+
+        if ((disowned + deleted) > 0) {
+            Slog.d(TAG, "Disowned " + disowned + " and deleted " + deleted
+                    + " downloads owned by UID " + uid);
         }
     }
 
diff --git a/src/com/android/providers/downloads/Helpers.java b/src/com/android/providers/downloads/Helpers.java
index e954905..7354076 100644
--- a/src/com/android/providers/downloads/Helpers.java
+++ b/src/com/android/providers/downloads/Helpers.java
@@ -796,7 +796,7 @@
                     mCurrentToken = TOKEN_COLUMN;
                     return;
                 }
-                throw new IllegalArgumentException("unrecognized column or keyword");
+                throw new IllegalArgumentException("unrecognized column or keyword: " + word);
             }
 
             // quoted strings