diff options
author | Sudheer Shanka <sudheersai@google.com> | 2019-04-25 14:04:15 -0700 |
---|---|---|
committer | Sudheer Shanka <sudheersai@google.com> | 2019-04-28 15:11:36 -0700 |
commit | 178b6d89e9799473679a0d50417bda8bae2ec963 (patch) | |
tree | 74d93cb18ce5383527a76bec896cdae6788e2220 /src/com/android | |
parent | fca4711560d491fb0742e657487d5dcf4f0e57f5 (diff) | |
download | android_packages_providers_DownloadProvider-178b6d89e9799473679a0d50417bda8bae2ec963.tar.gz android_packages_providers_DownloadProvider-178b6d89e9799473679a0d50417bda8bae2ec963.tar.bz2 android_packages_providers_DownloadProvider-178b6d89e9799473679a0d50417bda8bae2ec963.zip |
Make downloads deletion on owner uninstall consistent.
When an app is uninstalled, it's downloads in top-level
download directory will be orphaned and the rest will be
deleted.
Bug: 126064843
Test: manual
Test: atest DownloadProviderTests
Test: atest cts/tests/app/src/android/app/cts/DownloadManagerTest.java
Change-Id: Iad430f7ec14f306ed859a3b30db9eea29a23c19d
Diffstat (limited to 'src/com/android')
-rw-r--r-- | src/com/android/providers/downloads/DownloadProvider.java | 100 | ||||
-rw-r--r-- | src/com/android/providers/downloads/DownloadReceiver.java | 17 |
2 files changed, 95 insertions, 22 deletions
diff --git a/src/com/android/providers/downloads/DownloadProvider.java b/src/com/android/providers/downloads/DownloadProvider.java index a6159ca7..a7f2093c 100644 --- a/src/com/android/providers/downloads/DownloadProvider.java +++ b/src/com/android/providers/downloads/DownloadProvider.java @@ -22,11 +22,14 @@ import static android.provider.Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI; import static android.provider.Downloads.Impl.COLUMN_MEDIASTORE_URI; import static android.provider.Downloads.Impl.COLUMN_MEDIA_SCANNED; import static android.provider.Downloads.Impl.COLUMN_OTHER_UID; +import static android.provider.Downloads.Impl.DESTINATION_EXTERNAL; +import static android.provider.Downloads.Impl.DESTINATION_FILE_URI; import static android.provider.Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD; import static android.provider.Downloads.Impl.MEDIA_NOT_SCANNABLE; import static android.provider.Downloads.Impl.MEDIA_NOT_SCANNED; import static android.provider.Downloads.Impl.MEDIA_SCANNED; import static android.provider.Downloads.Impl.PERMISSION_ACCESS_ALL; +import static android.provider.Downloads.Impl._DATA; import android.annotation.NonNull; import android.annotation.Nullable; @@ -56,6 +59,7 @@ import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.Environment; +import android.os.FileUtils; import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor.OnCloseListener; import android.os.Process; @@ -100,7 +104,7 @@ public final class DownloadProvider extends ContentProvider { /** Database filename */ private static final String DB_NAME = "downloads.db"; /** Current database version */ - private static final int DB_VERSION = 112; + private static final int DB_VERSION = 113; /** Name of table in the database */ private static final String DB_TABLE = "downloads"; /** Memory optimization - close idle connections after 30s of inactivity */ @@ -327,7 +331,11 @@ public final class DownloadProvider extends ContentProvider { break; case 112: - UpdateMediaStoreUrisFromFilesToDownloads(db); + updateMediaStoreUrisFromFilesToDownloads(db); + break; + + case 113: + canonicalizeDataPaths(db); break; default: @@ -423,7 +431,7 @@ public final class DownloadProvider extends ContentProvider { * MediaStore.Downloads specific columns. To avoid this, update the existing entries to * use MediaStore.Downloads based uris only. */ - private void UpdateMediaStoreUrisFromFilesToDownloads(SQLiteDatabase db) { + private void updateMediaStoreUrisFromFilesToDownloads(SQLiteDatabase db) { try (Cursor cursor = db.query(DB_TABLE, new String[] { Downloads.Impl._ID, COLUMN_MEDIASTORE_URI }, COLUMN_MEDIASTORE_URI + " IS NOT NULL", null, null, null, null)) { @@ -445,6 +453,30 @@ public final class DownloadProvider extends ContentProvider { } } + private void canonicalizeDataPaths(SQLiteDatabase db) { + try (Cursor cursor = db.query(DB_TABLE, + new String[] { Downloads.Impl._ID, Downloads.Impl._DATA}, + Downloads.Impl._DATA + " IS NOT NULL", null, null, null, null)) { + final ContentValues updateValues = new ContentValues(); + while (cursor.moveToNext()) { + final long id = cursor.getLong(0); + final String filePath = cursor.getString(1); + final String canonicalPath; + try { + canonicalPath = new File(filePath).getCanonicalPath(); + } catch (IOException e) { + Log.e(Constants.TAG, "Found invalid path='" + filePath + "' for id=" + id); + continue; + } + + updateValues.clear(); + updateValues.put(Downloads.Impl._DATA, canonicalPath); + db.update(DB_TABLE, updateValues, Downloads.Impl._ID + "=?", + new String[] { Long.toString(id) }); + } + } + } + /** * Add a column to a table using ALTER TABLE. * @param dbTable name of the table @@ -526,18 +558,38 @@ public final class DownloadProvider extends ContentProvider { mStorageManager = getContext().getSystemService(StorageManager.class); + reconcileRemovedUidEntries(); + return true; + } + + private void reconcileRemovedUidEntries() { // Grant access permissions for all known downloads to the owning apps final SQLiteDatabase db = mOpenHelper.getReadableDatabase(); - final Cursor cursor = db.query(DB_TABLE, new String[] { - Downloads.Impl._ID, Constants.UID }, null, null, null, null, null); + final Cursor cursor = db.query(DB_TABLE, + new String[] { Downloads.Impl._ID, Constants.UID, COLUMN_DESTINATION, _DATA }, + null, null, null, null, null); final ArrayList<Long> idsToDelete = new ArrayList<>(); + final ArrayList<Long> idsToOrphan = new ArrayList<>(); + final String downloadsDir = Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(); try { while (cursor.moveToNext()) { final long downloadId = cursor.getLong(0); final int uid = cursor.getInt(1); + final String ownerPackage = getPackageForUid(uid); if (ownerPackage == null) { - idsToDelete.add(downloadId); + final int destination = cursor.getInt(2); + final String filePath = cursor.getString(3); + + if ((destination == DESTINATION_EXTERNAL + || destination == DESTINATION_FILE_URI + || destination == DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD) + && FileUtils.contains(downloadsDir, filePath)) { + idsToOrphan.add(downloadId); + } else { + idsToDelete.add(downloadId); + } } else { grantAllDownloadsPermission(ownerPackage, downloadId); } @@ -545,25 +597,29 @@ public final class DownloadProvider extends ContentProvider { } finally { cursor.close(); } + if (idsToOrphan.size() > 0) { + Log.i(Constants.TAG, "Orphaning downloads with ids " + + Arrays.toString(idsToOrphan.toArray()) + " as owner package is missing"); + final ContentValues values = new ContentValues(); + values.putNull(Constants.UID); + update(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, values, + buildQueryWithIds(idsToOrphan), null); + } if (idsToDelete.size() > 0) { - Log.i(Constants.TAG, - "Deleting downloads with ids " + idsToDelete + " as owner package is missing"); - deleteDownloadsWithIds(idsToDelete); + Log.i(Constants.TAG, "Deleting downloads with ids " + + Arrays.toString(idsToDelete.toArray()) + " as owner package is missing"); + delete(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, buildQueryWithIds(idsToDelete), null); } - return true; } - private void deleteDownloadsWithIds(ArrayList<Long> downloadIds) { - final int N = downloadIds.size(); - if (N == 0) { - return; - } + private String buildQueryWithIds(ArrayList<Long> downloadIds) { final StringBuilder queryBuilder = new StringBuilder(Downloads.Impl._ID + " in ("); - for (int i = 0; i < N; i++) { + final int size = downloadIds.size(); + for (int i = 0; i < size; i++) { queryBuilder.append(downloadIds.get(i)); - queryBuilder.append((i == N - 1) ? ")" : ","); + queryBuilder.append((i == size - 1) ? ")" : ","); } - delete(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, queryBuilder.toString(), null); + return queryBuilder.toString(); } /** @@ -1002,6 +1058,7 @@ public final class DownloadProvider extends ContentProvider { final File file; try { file = new File(path).getCanonicalFile(); + values.put(Downloads.Impl.COLUMN_FILE_NAME_HINT, Uri.fromFile(file).toString()); } catch (IOException e) { throw new SecurityException(e); } @@ -1040,6 +1097,7 @@ public final class DownloadProvider extends ContentProvider { final File file; try { file = new File(path).getCanonicalFile(); + values.put(Downloads.Impl._DATA, file.getPath()); } catch (IOException e) { throw new SecurityException(e); } @@ -1516,6 +1574,12 @@ public final class DownloadProvider extends ContentProvider { filteredValues = values; String filename = values.getAsString(Downloads.Impl._DATA); if (filename != null) { + try { + filteredValues.put(Downloads.Impl._DATA, new File(filename).getCanonicalPath()); + } catch (IOException e) { + throw new IllegalStateException("Invalid path: " + filename); + } + Cursor c = null; try { c = query(uri, new String[] diff --git a/src/com/android/providers/downloads/DownloadReceiver.java b/src/com/android/providers/downloads/DownloadReceiver.java index 92d0bad4..6a0707bb 100644 --- a/src/com/android/providers/downloads/DownloadReceiver.java +++ b/src/com/android/providers/downloads/DownloadReceiver.java @@ -37,11 +37,14 @@ import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.provider.Downloads; +import android.provider.MediaStore; import android.text.TextUtils; import android.util.Log; import android.util.Slog; import android.widget.Toast; +import java.util.regex.Pattern; + /** * Receives system broadcasts (boot, network connectivity) */ @@ -144,11 +147,17 @@ public class DownloadReceiver extends BroadcastReceiver { // First, disown any downloads that live in shared storage final ContentValues values = new ContentValues(); values.putNull(Constants.UID); + + final StringBuilder queryString = new StringBuilder(Constants.UID + "=" + uid); + queryString.append(" AND ").append(Downloads.Impl.COLUMN_DESTINATION + " IN (" + + Downloads.Impl.DESTINATION_EXTERNAL + "," + + Downloads.Impl.DESTINATION_FILE_URI + "," + + Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD + ")"); + queryString.append(" AND ").append(Downloads.Impl._DATA + + " REGEXP '" + MediaStore.Downloads.PATTERN_DOWNLOADS_FILE.pattern() + "'"); + 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); + queryString.toString(), null); // Finally, delete any remaining downloads owned by UID final int deleted = resolver.delete(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, |