Merge "Refactor duplicate code handling removed uid entries and add tests."
diff --git a/src/com/android/providers/downloads/DownloadProvider.java b/src/com/android/providers/downloads/DownloadProvider.java
index 550c8fb..5d39d56 100644
--- a/src/com/android/providers/downloads/DownloadProvider.java
+++ b/src/com/android/providers/downloads/DownloadProvider.java
@@ -22,17 +22,14 @@
 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;
 import android.app.AppOpsManager;
 import android.app.DownloadManager;
 import android.app.DownloadManager.Request;
@@ -50,7 +47,6 @@
 import android.database.Cursor;
 import android.database.DatabaseUtils;
 import android.database.SQLException;
-import android.database.TranslatingCursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.database.sqlite.SQLiteQueryBuilder;
@@ -59,7 +55,6 @@
 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;
@@ -71,11 +66,7 @@
 import android.provider.OpenableColumns;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
-import android.util.ArrayMap;
 import android.util.Log;
-import android.util.LongArray;
-import android.util.LongSparseArray;
-import android.util.SparseArray;
 
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.IndentingPrintWriter;
@@ -91,7 +82,6 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -565,36 +555,7 @@
     }
 
     private void reconcileRemovedUidEntries() {
-        // Grant access permissions for all known downloads to the owning apps
-        final ArrayList<Long> idsToDelete = new ArrayList<>();
-        final ArrayList<Long> idsToOrphan = new ArrayList<>();
-        final LongSparseArray<String> idsToGrantPermission = new LongSparseArray<>();
-        final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
-        try (Cursor cursor = db.query(DB_TABLE,
-                new String[] { Downloads.Impl._ID, Constants.UID, COLUMN_DESTINATION, _DATA },
-                Constants.UID + " IS NOT NULL", null, null, null, null)) {
-            Helpers.handleRemovedUidEntries(getContext(), cursor,
-                    idsToDelete, idsToOrphan, idsToGrantPermission);
-        }
-        for (int i = 0; i < idsToGrantPermission.size(); ++i) {
-            final long downloadId = idsToGrantPermission.keyAt(i);
-            final String ownerPackageName = idsToGrantPermission.valueAt(i);
-            grantAllDownloadsPermission(ownerPackageName, downloadId);
-        }
-        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,
-                    Helpers.buildQueryWithIds(idsToOrphan), null);
-        }
-        if (idsToDelete.size() > 0) {
-            Log.i(Constants.TAG, "Deleting downloads with ids "
-                    + Arrays.toString(idsToDelete.toArray()) + " as owner package is missing");
-            delete(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI,
-                    Helpers.buildQueryWithIds(idsToDelete), null);
-        }
+        Helpers.handleRemovedUidEntries(getContext(), this, -1, this::grantAllDownloadsPermission);
     }
 
     /**
diff --git a/src/com/android/providers/downloads/DownloadReceiver.java b/src/com/android/providers/downloads/DownloadReceiver.java
index f0b9de7..8038500 100644
--- a/src/com/android/providers/downloads/DownloadReceiver.java
+++ b/src/com/android/providers/downloads/DownloadReceiver.java
@@ -18,8 +18,7 @@
 
 import static android.app.DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED;
 import static android.app.DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION;
-import static android.provider.Downloads.Impl.COLUMN_DESTINATION;
-import static android.provider.Downloads.Impl._DATA;
+import static android.provider.Downloads.Impl.AUTHORITY;
 
 import static com.android.providers.downloads.Constants.TAG;
 import static com.android.providers.downloads.Helpers.getAsyncHandler;
@@ -31,6 +30,7 @@
 import android.app.DownloadManager;
 import android.app.NotificationManager;
 import android.content.BroadcastReceiver;
+import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
@@ -39,16 +39,10 @@
 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.ArrayList;
-import java.util.Arrays;
-import java.util.regex.Pattern;
-
 /**
  * Receives system broadcasts (boot, network connectivity)
  */
@@ -145,30 +139,14 @@
     }
 
     private void handleUidRemoved(Context context, Intent intent) {
-        final ContentResolver resolver = context.getContentResolver();
         final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
-
-        final ArrayList<Long> idsToDelete = new ArrayList<>();
-        final ArrayList<Long> idsToOrphan = new ArrayList<>();
-        try (Cursor cursor = resolver.query(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI,
-                new String[] { Downloads.Impl._ID, Constants.UID, COLUMN_DESTINATION, _DATA },
-                Constants.UID + "=" + uid, null, null)) {
-            Helpers.handleRemovedUidEntries(context, cursor, idsToDelete, idsToOrphan, null);
+        if (uid == -1) {
+            return;
         }
 
-        if (idsToOrphan.size() > 0) {
-            Log.i(Constants.TAG, "Orphaning downloads with ids "
-                    + Arrays.toString(idsToOrphan.toArray()) + " as owner package is removed");
-            final ContentValues values = new ContentValues();
-            values.putNull(Constants.UID);
-            resolver.update(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, values,
-                    Helpers.buildQueryWithIds(idsToOrphan), null);
-        }
-        if (idsToDelete.size() > 0) {
-            Log.i(Constants.TAG, "Deleting downloads with ids "
-                    + Arrays.toString(idsToDelete.toArray()) + " as owner package is removed");
-            resolver.delete(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI,
-                    Helpers.buildQueryWithIds(idsToDelete), null);
+        try (ContentProviderClient cpc = context.getContentResolver()
+                .acquireContentProviderClient(AUTHORITY)) {
+            Helpers.handleRemovedUidEntries(context, cpc.getLocalContentProvider(), uid, null);
         }
     }
 
diff --git a/src/com/android/providers/downloads/Helpers.java b/src/com/android/providers/downloads/Helpers.java
index 7b4cb4e..7ce269f 100644
--- a/src/com/android/providers/downloads/Helpers.java
+++ b/src/com/android/providers/downloads/Helpers.java
@@ -20,11 +20,13 @@
 import static android.os.Environment.buildExternalStorageAppMediaDirs;
 import static android.os.Environment.buildExternalStorageAppObbDirs;
 import static android.os.Environment.buildExternalStoragePublicDirs;
+import static android.provider.Downloads.Impl.COLUMN_DESTINATION;
 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.FLAG_REQUIRES_CHARGING;
 import static android.provider.Downloads.Impl.FLAG_REQUIRES_DEVICE_IDLE;
+import static android.provider.Downloads.Impl._DATA;
 
 import static com.android.providers.downloads.Constants.TAG;
 
@@ -33,6 +35,8 @@
 import android.app.job.JobInfo;
 import android.app.job.JobScheduler;
 import android.content.ComponentName;
+import android.content.ContentProvider;
+import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
@@ -48,20 +52,19 @@
 import android.provider.Downloads;
 import android.text.TextUtils;
 import android.util.Log;
-import android.util.LongSparseArray;
 import android.util.SparseArray;
-import android.util.SparseBooleanArray;
 import android.webkit.MimeTypeMap;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 
-import com.google.common.annotations.VisibleForTesting;
-
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Random;
 import java.util.Set;
+import java.util.function.BiConsumer;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -626,39 +629,63 @@
         }
     }
 
-    public static void handleRemovedUidEntries(@NonNull Context context, @NonNull Cursor cursor,
-            @NonNull ArrayList<Long> idsToDelete, @NonNull ArrayList<Long> idsToOrphan,
-            @Nullable LongSparseArray<String> idsToGrantPermission) {
+    @VisibleForTesting
+    public static void handleRemovedUidEntries(@NonNull Context context,
+            @NonNull ContentProvider downloadProvider, int removedUid,
+            @Nullable BiConsumer<String, Long> validEntryConsumer) {
         final SparseArray<String> knownUids = new SparseArray<>();
-        while (cursor.moveToNext()) {
-            final long downloadId = cursor.getLong(0);
-            final int uid = cursor.getInt(1);
+        final ArrayList<Long> idsToDelete = new ArrayList<>();
+        final ArrayList<Long> idsToOrphan = new ArrayList<>();
+        final String selection = removedUid == -1 ? Constants.UID + " IS NOT NULL"
+                : Constants.UID + "=" + removedUid;
+        try (Cursor cursor = downloadProvider.query(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI,
+                new String[] { Downloads.Impl._ID, Constants.UID, COLUMN_DESTINATION, _DATA },
+                selection, null, null)) {
+            while (cursor.moveToNext()) {
+                final long downloadId = cursor.getLong(0);
+                final int uid = cursor.getInt(1);
 
-            final String ownerPackageName;
-            final int index = knownUids.indexOfKey(uid);
-            if (index >= 0) {
-                ownerPackageName = knownUids.valueAt(index);
-            } else {
-                ownerPackageName = getPackageForUid(context, uid);
-                knownUids.put(uid, ownerPackageName);
-            }
-
-            if (ownerPackageName == null) {
-                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)
-                        && isFilenameValidInKnownPublicDir(filePath)) {
-                    idsToOrphan.add(downloadId);
+                final String ownerPackageName;
+                final int index = knownUids.indexOfKey(uid);
+                if (index >= 0) {
+                    ownerPackageName = knownUids.valueAt(index);
                 } else {
-                    idsToDelete.add(downloadId);
+                    ownerPackageName = getPackageForUid(context, uid);
+                    knownUids.put(uid, ownerPackageName);
                 }
-            } else if (idsToGrantPermission != null) {
-                idsToGrantPermission.put(downloadId, ownerPackageName);
+
+                if (ownerPackageName == null) {
+                    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)
+                            && isFilenameValidInKnownPublicDir(filePath)) {
+                        idsToOrphan.add(downloadId);
+                    } else {
+                        idsToDelete.add(downloadId);
+                    }
+                } else if (validEntryConsumer != null) {
+                    validEntryConsumer.accept(ownerPackageName, downloadId);
+                }
             }
         }
+
+        if (idsToOrphan.size() > 0) {
+            Log.i(Constants.TAG, "Orphaning downloads with ids "
+                    + Arrays.toString(idsToOrphan.toArray()) + " as owner package is removed");
+            final ContentValues values = new ContentValues();
+            values.putNull(Constants.UID);
+            downloadProvider.update(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, values,
+                    Helpers.buildQueryWithIds(idsToOrphan), null);
+        }
+        if (idsToDelete.size() > 0) {
+            Log.i(Constants.TAG, "Deleting downloads with ids "
+                    + Arrays.toString(idsToDelete.toArray()) + " as owner package is removed");
+            downloadProvider.delete(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI,
+                    Helpers.buildQueryWithIds(idsToDelete), null);
+        }
     }
 
     public static String buildQueryWithIds(ArrayList<Long> downloadIds) {
diff --git a/tests/src/com/android/providers/downloads/HelpersTest.java b/tests/src/com/android/providers/downloads/HelpersTest.java
index 65c5d36..9cb99ba 100644
--- a/tests/src/com/android/providers/downloads/HelpersTest.java
+++ b/tests/src/com/android/providers/downloads/HelpersTest.java
@@ -16,14 +16,45 @@
 
 package com.android.providers.downloads;
 
+import static android.provider.Downloads.Impl.COLUMN_DESTINATION;
+import static android.provider.Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE;
+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._DATA;
+import static android.provider.Downloads.Impl._ID;
+
+import static com.android.internal.util.ArrayUtils.contains;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ContentProvider;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.database.MatrixCursor;
 import android.net.Uri;
+import android.os.Environment;
 import android.provider.Downloads;
+
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.util.LongArray;
+import android.util.LongSparseArray;
 
 import libcore.io.IoUtils;
 
 import java.io.File;
+import java.util.Arrays;
+import java.util.function.BiConsumer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * This test exercises methods in the {@Helpers} utility class.
@@ -31,8 +62,22 @@
 @SmallTest
 public class HelpersTest extends AndroidTestCase {
 
+    private final static int TEST_UID1 = 11111;
+    private final static int TEST_UID2 = 11112;
+    private final static int TEST_UID3 = 11113;
+
+    private final MockitoHelper mMockitoHelper = new MockitoHelper();
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mMockitoHelper.setUp(getClass());
+    }
+
     @Override
     protected void tearDown() throws Exception {
+        mMockitoHelper.tearDown();
         IoUtils.deleteContents(getContext().getFilesDir());
         IoUtils.deleteContents(getContext().getCacheDir());
 
@@ -117,4 +162,164 @@
         assertFalse(Helpers.isFilenameValidInKnownPublicDir(
                 "/storage/emulated/0/Android/data/com.example/bar.jpg"));
     }
+
+    public void testHandleRemovedUidEntries() throws Exception {
+        // Prepare
+        final int[] testUids = {
+                TEST_UID1, TEST_UID2, TEST_UID3
+        };
+        final int[] unknownUids = {
+                TEST_UID1, TEST_UID2
+        };
+        final Context context = mock(Context.class);
+        final PackageManager packageManager = mock(PackageManager.class);
+        when(context.getPackageManager()).thenReturn(packageManager);
+        for (int uid : testUids) {
+            when(packageManager.getPackagesForUid(uid)).thenReturn(
+                    contains(unknownUids, uid) ? null : new String[] {"com.example" + uid}
+            );
+        }
+
+        final LongArray idsToRemove = new LongArray();
+        final LongArray idsToOrphan = new LongArray();
+        final LongSparseArray<String> validEntries = new LongSparseArray<>();
+        final MatrixCursor cursor = prepareData(testUids, unknownUids,
+                idsToOrphan, idsToRemove, validEntries);
+
+        final ContentProvider downloadProvider = mock(ContentProvider.class);
+        when(downloadProvider.query(eq(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI),
+                any(String[].class), any(String.class),isNull(), isNull())).thenReturn(cursor);
+        final BiConsumer<String, Long> validEntryConsumer = mock(BiConsumer.class);
+
+        // Call
+        Helpers.handleRemovedUidEntries(context, downloadProvider, -1, validEntryConsumer);
+
+        // Verify
+        verify(downloadProvider).update(eq(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI),
+                argThat(values -> values.get(Constants.UID) == null),
+                argThat(selection -> Arrays.equals(
+                        idsToOrphan.toArray(), extractIdsFromSelection(selection))),
+                isNull());
+        verify(downloadProvider).delete(eq(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI),
+                argThat(selection -> Arrays.equals(
+                        idsToRemove.toArray(), extractIdsFromSelection(selection))),
+                isNull());
+        for (int i = 0; i < validEntries.size(); ++i) {
+            verify(validEntryConsumer).accept(validEntries.valueAt(i), validEntries.keyAt(i));
+        }
+
+
+        // Reset
+        idsToOrphan.clear();
+        idsToRemove.clear();
+        validEntries.clear();
+        reset(downloadProvider);
+
+        // Prepare
+        final MatrixCursor cursor2 = prepareData(new int[] {TEST_UID2}, unknownUids,
+                idsToOrphan, idsToRemove, validEntries);
+        when(downloadProvider.query(eq(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI),
+                any(String[].class), any(String.class),isNull(), isNull())).thenReturn(cursor2);
+
+        // Call
+        Helpers.handleRemovedUidEntries(context, downloadProvider, TEST_UID2, null);
+
+        // Verify
+        verify(downloadProvider).update(eq(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI),
+                argThat(values -> values.get(Constants.UID) == null),
+                argThat(selection -> Arrays.equals(
+                        idsToOrphan.toArray(), extractIdsFromSelection(selection))),
+                isNull());
+        verify(downloadProvider).delete(eq(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI),
+                argThat(selection -> Arrays.equals(
+                        idsToRemove.toArray(), extractIdsFromSelection(selection))),
+                isNull());
+    }
+
+    private MatrixCursor prepareData(int[] uids, int[] unknownUids,
+            final LongArray idsToOrphan, final LongArray idsToRemove,
+            LongSparseArray<String> validEntries) {
+        final MatrixCursor cursor = new MatrixCursor(
+                new String[] {_ID, Constants.UID, COLUMN_DESTINATION, _DATA});
+        final int[] destinations = {
+                DESTINATION_EXTERNAL,
+                DESTINATION_FILE_URI,
+                DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD,
+                DESTINATION_CACHE_PARTITION_PURGEABLE
+        };
+        long counter = 0;
+        for (int uid : uids) {
+            for (int destination : destinations) {
+                final String fileName = uid + "_" + destination + ".txt";
+                switch (destination) {
+                    case DESTINATION_EXTERNAL: {
+                        final File file = new File(Environment.getExternalStoragePublicDirectory(
+                                Environment.DIRECTORY_DOWNLOADS), fileName);
+                        cursor.addRow(new Object[]{++counter, uid, destination, file.getPath()});
+                        if (contains(unknownUids, uid)) {
+                            idsToOrphan.add(counter);
+                        } else {
+                            validEntries.put(counter, "com.example" + uid);
+                        }
+                    } break;
+                    case DESTINATION_FILE_URI: {
+                        final File file1 = new File(Environment.getExternalStoragePublicDirectory(
+                                Environment.DIRECTORY_DOCUMENTS), fileName);
+                        cursor.addRow(new Object[]{++counter, uid, destination, file1.getPath()});
+                        if (contains(unknownUids, uid)) {
+                            idsToOrphan.add(counter);
+                        } else {
+                            validEntries.put(counter, "com.example" + uid);
+                        }
+                        final File file2 = new File(getContext().getExternalFilesDir(null),
+                                fileName);
+                        cursor.addRow(new Object[]{++counter, uid, destination, file2.getPath()});
+                        if (contains(unknownUids, uid)) {
+                            idsToRemove.add(counter);
+                        } else {
+                            validEntries.put(counter, "com.example" + uid);
+                        }
+                    } break;
+                    case DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD: {
+                        final File file1 = new File(Environment.getExternalStoragePublicDirectory(
+                                Environment.DIRECTORY_DOCUMENTS), fileName);
+                        cursor.addRow(new Object[]{++counter, uid, destination, file1.getPath()});
+                        if (contains(unknownUids, uid)) {
+                            idsToOrphan.add(counter);
+                        } else {
+                            validEntries.put(counter, "com.example" + uid);
+                        }
+                        final File file2 = new File(getContext().getExternalFilesDir(null),
+                                fileName);
+                        cursor.addRow(new Object[]{++counter, uid, destination, file2.getPath()});
+                        if (contains(unknownUids, uid)) {
+                            idsToRemove.add(counter);
+                        } else {
+                            validEntries.put(counter, "com.example" + uid);
+                        }
+                    } break;
+                    case DESTINATION_CACHE_PARTITION_PURGEABLE: {
+                        final File file = new File(getContext().getCacheDir(), fileName);
+                        final String filePath = file.getPath().replace(
+                                getContext().getPackageName(), "com.android.providers.downloads");
+                        cursor.addRow(new Object[]{++counter, uid, destination, filePath});
+                        if (contains(unknownUids, uid)) {
+                            idsToRemove.add(counter);
+                        } else {
+                            validEntries.put(counter, "com.example" + uid);
+                        }
+                    } break;
+                }
+            }
+        }
+        return cursor;
+    }
+
+    private long[] extractIdsFromSelection(String selection) {
+        final Pattern uidsListPattern = Pattern.compile(".*\\((.+)\\)");
+        final Matcher matcher = uidsListPattern.matcher(selection);
+        assertTrue(matcher.matches());
+        return Arrays.stream(matcher.group(1).split(","))
+                .mapToLong(Long::valueOf).sorted().toArray();
+    }
 }