1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.providers.media;
18 
19 import static com.android.providers.media.MediaGrants.FILE_ID_COLUMN;
20 import static com.android.providers.media.MediaGrants.PACKAGE_USER_ID_COLUMN;
21 import static com.android.providers.media.MediaGrants.PER_PACKAGE_GRANTS_LIMIT_CONST;
22 import static com.android.providers.media.photopicker.data.ItemsProvider.getItemsUri;
23 import static com.android.providers.media.util.FileCreationUtils.buildValidPickerUri;
24 import static com.android.providers.media.util.FileCreationUtils.insertFileInResolver;
25 
26 import static org.junit.Assert.assertEquals;
27 import static org.junit.Assert.assertNotNull;
28 import static org.junit.Assert.assertThrows;
29 import static org.junit.Assert.assertTrue;
30 
31 import android.Manifest;
32 import android.content.ContentResolver;
33 import android.content.ContentUris;
34 import android.content.ContentValues;
35 import android.content.Context;
36 import android.database.Cursor;
37 import android.net.Uri;
38 import android.os.Process;
39 import android.os.UserHandle;
40 import android.provider.MediaStore;
41 
42 import androidx.test.InstrumentationRegistry;
43 import androidx.test.runner.AndroidJUnit4;
44 
45 import com.android.providers.media.photopicker.PickerSyncController;
46 import com.android.providers.media.photopicker.data.model.UserId;
47 
48 import junit.framework.AssertionFailedError;
49 
50 import org.junit.Before;
51 import org.junit.BeforeClass;
52 import org.junit.Test;
53 import org.junit.runner.RunWith;
54 
55 import java.util.ArrayList;
56 import java.util.List;
57 
58 @RunWith(AndroidJUnit4.class)
59 public class MediaGrantsTest {
60     private Context mIsolatedContext;
61     private Context mContext;
62     private ContentResolver mIsolatedResolver;
63     private DatabaseHelper mExternalDatabase;
64     private MediaGrants mGrants;
65 
66     private static final String TEST_OWNER_PACKAGE_NAME = "com.android.test.package";
67     private static final String TEST_OWNER_PACKAGE_NAME2 = "com.android.test.package2";
68     private static final int TEST_USER_ID = UserHandle.myUserId();
69 
70     private static final String PNG_MIME_TYPE = "image/png";
71 
72     @BeforeClass
setUpClass()73     public static void setUpClass() {
74         androidx.test.platform.app.InstrumentationRegistry.getInstrumentation()
75                 .getUiAutomation()
76                 .adoptShellPermissionIdentity(
77                         Manifest.permission.LOG_COMPAT_CHANGE,
78                         Manifest.permission.READ_COMPAT_CHANGE_CONFIG,
79                         Manifest.permission.READ_DEVICE_CONFIG,
80                         Manifest.permission.INTERACT_ACROSS_USERS,
81                         Manifest.permission.WRITE_MEDIA_STORAGE,
82                         Manifest.permission.MANAGE_EXTERNAL_STORAGE);
83     }
84 
85     @Before
86     /** Clean up and old files / force a clean slate before each test case. */
setUp()87     public void setUp() {
88         if (mIsolatedResolver != null) {
89             // This is necessary, we wait for all unfinished tasks to finish before we create a
90             // new IsolatedContext.
91             MediaStore.waitForIdle(mIsolatedResolver);
92         }
93 
94         mContext = InstrumentationRegistry.getTargetContext();
95         mIsolatedContext = new IsolatedContext(mContext, "modern", /*asFuseThread*/ false);
96         mIsolatedResolver = mIsolatedContext.getContentResolver();
97         mExternalDatabase = ((IsolatedContext) mIsolatedContext).getExternalDatabase();
98         mGrants = new MediaGrants(mExternalDatabase);
99     }
100 
101     @Test
testAddMediaGrants()102     public void testAddMediaGrants() throws Exception {
103 
104         Long fileId1 = insertFileInResolver(mIsolatedResolver, "test_file1");
105         Long fileId2 = insertFileInResolver(mIsolatedResolver, "test_file2");
106         List<Uri> uris = List.of(buildValidPickerUri(fileId1), buildValidPickerUri(fileId2));
107 
108         mGrants.addMediaGrantsForPackage(TEST_OWNER_PACKAGE_NAME, uris, TEST_USER_ID);
109 
110         assertGrantExistsForPackage(fileId1, TEST_OWNER_PACKAGE_NAME, TEST_USER_ID);
111         assertGrantExistsForPackage(fileId2, TEST_OWNER_PACKAGE_NAME, TEST_USER_ID);
112     }
113 
114     @Test
testAddMediaGrantsCountExceedingLimit()115     public void testAddMediaGrantsCountExceedingLimit() throws Exception {
116         mGrants.setGrantsLimit(5);
117         String fileIdPlaceHolder = "test_file";
118         int numberOfInputFiles = 5;
119         List<Long> ids = new ArrayList<>();
120         List<Uri> uris = new ArrayList<>();
121         for (int i = 0; i < numberOfInputFiles; i++) {
122             Long file_id = insertFileInResolver(mIsolatedResolver, fileIdPlaceHolder + i);
123             ids.add(file_id);
124             uris.add(buildValidPickerUri(file_id));
125         }
126 
127         mGrants.addMediaGrantsForPackage(TEST_OWNER_PACKAGE_NAME, uris, TEST_USER_ID);
128         // 5 items were added assert that all of them are present as grants.
129         for (int i = 0; i < ids.size(); i++) {
130             assertGrantExistsForPackage(ids.get(i), TEST_OWNER_PACKAGE_NAME,
131                     TEST_USER_ID);
132         }
133 
134         // now add one more item and verify that the first item that was added is no longer in the
135         // database.
136         Long file_id = insertFileInResolver(mIsolatedResolver, fileIdPlaceHolder + 6);
137         ids.add(file_id);
138         mGrants.addMediaGrantsForPackage(TEST_OWNER_PACKAGE_NAME, List.of(
139                 buildValidPickerUri(file_id)), TEST_USER_ID);
140 
141         // new item was added, assert that the first item is not in the list anymore.
142         try {
143             assertGrantExistsForPackage(ids.get(0), TEST_OWNER_PACKAGE_NAME,
144                     TEST_USER_ID);
145             throw new AssertionFailedError("The assertion should have failed");
146         } catch (AssertionError ignored) {
147             // ignore this is the expected result.
148         }
149 
150         // assert grant should exist for file id 1 and above.
151         for (int i = 1; i < ids.size(); i++) {
152             assertGrantExistsForPackage(ids.get(i), TEST_OWNER_PACKAGE_NAME,
153                     TEST_USER_ID);
154         }
155         mGrants.setGrantsLimit(PER_PACKAGE_GRANTS_LIMIT_CONST);
156     }
157 
158     @Test
testAddMediaGrantsCountExceedingLimitForDifferentPackages()159     public void testAddMediaGrantsCountExceedingLimitForDifferentPackages() throws Exception {
160         mGrants.setGrantsLimit(5);
161         String fileIdPlaceHolder = "test_file";
162         int numberOfInputFiles = 6;
163         List<Long> ids = new ArrayList<>();
164         List<Uri> uris = new ArrayList<>();
165         for (int i = 0; i < numberOfInputFiles; i++) {
166             Long file_id = insertFileInResolver(mIsolatedResolver, fileIdPlaceHolder + i);
167             ids.add(file_id);
168             uris.add(buildValidPickerUri(file_id));
169         }
170 
171         mGrants.addMediaGrantsForPackage(TEST_OWNER_PACKAGE_NAME, uris, TEST_USER_ID);
172 
173         mGrants.addMediaGrantsForPackage(TEST_OWNER_PACKAGE_NAME2, uris, TEST_USER_ID);
174 
175         // verify different grants are present for different packages.
176 
177         // 5 items were added assert that all of them are present as grants.
178         for (int i = 1; i < ids.size(); i++) {
179             assertGrantExistsForPackage(ids.get(i), TEST_OWNER_PACKAGE_NAME,
180                     TEST_USER_ID);
181         }
182 
183         // 5 items were added assert that all of them are present as grants.
184         for (int i = 1; i < ids.size(); i++) {
185             assertGrantExistsForPackage(ids.get(i), TEST_OWNER_PACKAGE_NAME2,
186                     TEST_USER_ID);
187         }
188     }
189 
190     @Test
testGetMediaGrantsForPackages()191     public void testGetMediaGrantsForPackages() throws Exception {
192         Long fileId1 = insertFileInResolver(mIsolatedResolver, "test_file1");
193         Long fileId2 = insertFileInResolver(mIsolatedResolver, "test_file2");
194         Long fileId3 = insertFileInResolver(mIsolatedResolver, "test_file3");
195         List<Uri> uris1 = List.of(buildValidPickerUri(fileId1), buildValidPickerUri(fileId2));
196         List<Uri> uris2 = List.of(buildValidPickerUri(fileId3));
197 
198         mGrants.addMediaGrantsForPackage(TEST_OWNER_PACKAGE_NAME, uris1, TEST_USER_ID);
199         mGrants.addMediaGrantsForPackage(TEST_OWNER_PACKAGE_NAME2, uris2, TEST_USER_ID);
200 
201         String[] mimeTypes = {PNG_MIME_TYPE};
202         String[] volumes = {MediaStore.VOLUME_EXTERNAL_PRIMARY};
203 
204         List<Uri> fileUris = convertToListOfUri(mGrants.getMediaGrantsForPackages(
205                 new String[]{TEST_OWNER_PACKAGE_NAME}, TEST_USER_ID,  mimeTypes, volumes));
206 
207         List<Long> expectedFileIdsList = List.of(fileId1, fileId2);
208 
209         assertEquals(fileUris.size(), expectedFileIdsList.size());
210         for (Uri uri : fileUris) {
211             assertTrue(expectedFileIdsList.contains(Long.valueOf(ContentUris.parseId(uri))));
212         }
213 
214         List<Uri> fileUrisForTestPackage2 = convertToListOfUri(mGrants.getMediaGrantsForPackages(
215                 new String[]{TEST_OWNER_PACKAGE_NAME2}, TEST_USER_ID,  mimeTypes, volumes));
216 
217         List<Long> expectedFileIdsList2 = List.of(fileId3);
218 
219         assertEquals(fileUrisForTestPackage2.size(), expectedFileIdsList2.size());
220         for (Uri uri : fileUrisForTestPackage2) {
221             assertTrue(expectedFileIdsList2.contains(Long.valueOf(ContentUris.parseId(uri))));
222         }
223 
224         List<Uri> fileUrisForTestPackage3 = convertToListOfUri(mGrants.getMediaGrantsForPackages(
225                 new String[]{"non.existent.package"}, TEST_USER_ID,  mimeTypes, volumes));
226 
227         // assert no items are returned for an invalid package.
228         assertEquals(/* expected= */fileUrisForTestPackage3.size(), /* actual= */0);
229     }
230 
231     @Test
test_GetMediaGrantsForPackages_excludesIsTrashed()232     public void test_GetMediaGrantsForPackages_excludesIsTrashed() throws Exception {
233         Long fileId1 = insertFileInResolver(mIsolatedResolver, "test_file1");
234         Long fileId2 = insertFileInResolver(mIsolatedResolver, "test_file2");
235         List<Uri> uris1 = List.of(buildValidPickerUri(fileId1), buildValidPickerUri(fileId2));
236 
237         mGrants.addMediaGrantsForPackage(TEST_OWNER_PACKAGE_NAME, uris1, TEST_USER_ID);
238 
239         String[] mimeTypes = {PNG_MIME_TYPE};
240         String[] volumes = {MediaStore.VOLUME_EXTERNAL_PRIMARY};
241         // Mark one of the files as trashed.
242         updateFileValues(fileId1, MediaStore.Files.FileColumns.IS_TRASHED, "1");
243 
244         List<Uri> fileUris = convertToListOfUri(mGrants.getMediaGrantsForPackages(
245                 new String[]{TEST_OWNER_PACKAGE_NAME}, TEST_USER_ID,  mimeTypes, volumes));
246 
247         // Now the 1st file with fileId1 should not be part of the returned grants.
248         List<Long> expectedFileIdsList = List.of(fileId2);
249 
250         assertEquals(fileUris.size(), expectedFileIdsList.size());
251         for (Uri uri : fileUris) {
252             assertTrue(expectedFileIdsList.contains(Long.valueOf(ContentUris.parseId(uri))));
253         }
254     }
255 
256     @Test
test_GetMediaGrantsForPackages_excludesIsPending()257     public void test_GetMediaGrantsForPackages_excludesIsPending() throws Exception {
258         Long fileId1 = insertFileInResolver(mIsolatedResolver, "test_file1");
259         Long fileId2 = insertFileInResolver(mIsolatedResolver, "test_file2");
260         List<Uri> uris1 = List.of(buildValidPickerUri(fileId1), buildValidPickerUri(fileId2));
261 
262         mGrants.addMediaGrantsForPackage(TEST_OWNER_PACKAGE_NAME, uris1, TEST_USER_ID);
263 
264         String[] mimeTypes = {PNG_MIME_TYPE};
265         String[] volumes = {MediaStore.VOLUME_EXTERNAL_PRIMARY};
266         // Mark one of the files as pending.
267         updateFileValues(fileId1, MediaStore.Files.FileColumns.IS_PENDING, "1");
268 
269         List<Uri> fileUris = convertToListOfUri(mGrants.getMediaGrantsForPackages(
270                 new String[]{TEST_OWNER_PACKAGE_NAME}, TEST_USER_ID,  mimeTypes, volumes));
271 
272         // Now the 1st file with fileId1 should not be part of the returned grants.
273         List<Long> expectedFileIdsList = List.of(fileId2);
274 
275         assertEquals(fileUris.size(), expectedFileIdsList.size());
276         for (Uri uri : fileUris) {
277             assertTrue(expectedFileIdsList.contains(Long.valueOf(ContentUris.parseId(uri))));
278         }
279     }
280 
281     @Test
test_GetMediaGrantsForPackages_testMimeTypeFilter()282     public void test_GetMediaGrantsForPackages_testMimeTypeFilter() throws Exception {
283         Long fileId1 = insertFileInResolver(mIsolatedResolver, "test_file1");
284         Long fileId2 = insertFileInResolver(mIsolatedResolver, "test_file2");
285         List<Uri> uris1 = List.of(buildValidPickerUri(fileId1), buildValidPickerUri(fileId2));
286 
287         Long fileId3 = insertFileInResolver(mIsolatedResolver, "test_file3", "mp4");
288         List<Uri> uris2 = List.of(buildValidPickerUri(fileId3));
289 
290         mGrants.addMediaGrantsForPackage(TEST_OWNER_PACKAGE_NAME, uris1, TEST_USER_ID);
291         mGrants.addMediaGrantsForPackage(TEST_OWNER_PACKAGE_NAME, uris2, TEST_USER_ID);
292 
293         String[] volumes = {MediaStore.VOLUME_EXTERNAL_PRIMARY};
294 
295         // Test image only, should return 2 items.
296         String[] mimeTypes = {PNG_MIME_TYPE};
297 
298         List<Uri> fileUris = convertToListOfUri(mGrants.getMediaGrantsForPackages(
299                 new String[]{TEST_OWNER_PACKAGE_NAME}, TEST_USER_ID, mimeTypes, volumes));
300 
301         List<Long> expectedFileIdsList = List.of(fileId1, fileId2);
302         assertEquals(fileUris.size(), expectedFileIdsList.size());
303         for (Uri uri : fileUris) {
304             assertTrue(expectedFileIdsList.contains(Long.valueOf(ContentUris.parseId(uri))));
305         }
306 
307         // Test video only, should return 1 item.
308         String[] mimeTypes2 = {"video/mp4"};
309 
310         List<Uri> fileUris2 = convertToListOfUri(mGrants.getMediaGrantsForPackages(
311                 new String[]{TEST_OWNER_PACKAGE_NAME}, TEST_USER_ID, mimeTypes2, volumes));
312         List<Long> expectedFileIdsList2 = List.of(fileId3);
313         assertEquals(fileUris2.size(), expectedFileIdsList2.size());
314         for (Uri uri : fileUris2) {
315             assertTrue(expectedFileIdsList2.contains(Long.valueOf(ContentUris.parseId(uri))));
316         }
317 
318 
319         // Test jpeg mimeType, since no items with this mimeType is granted, empty list should be
320         // returned.
321         String[] mimeTypes3 = {"image/jpeg"};
322         List<Uri> fileUris3 = convertToListOfUri(mGrants.getMediaGrantsForPackages(
323                 new String[]{TEST_OWNER_PACKAGE_NAME}, TEST_USER_ID, mimeTypes3, volumes));
324         assertTrue(fileUris3.isEmpty());
325     }
326 
327     @Test
test_GetMediaGrantsForPackages_volume()328     public void test_GetMediaGrantsForPackages_volume() throws Exception {
329         Long fileId1 = insertFileInResolver(mIsolatedResolver, "test_file1");
330         Long fileId2 = insertFileInResolver(mIsolatedResolver, "test_file2");
331         List<Uri> uris1 = List.of(buildValidPickerUri(fileId1), buildValidPickerUri(fileId2));
332 
333         mGrants.addMediaGrantsForPackage(TEST_OWNER_PACKAGE_NAME, uris1, TEST_USER_ID);
334 
335         String[] volumes = {"test_volume"};
336         String[] mimeTypes = {PNG_MIME_TYPE};
337 
338         List<Uri> fileUris = convertToListOfUri(mGrants.getMediaGrantsForPackages(
339                 new String[]{TEST_OWNER_PACKAGE_NAME}, TEST_USER_ID,  mimeTypes, volumes));
340 
341         assertTrue(fileUris.isEmpty());
342     }
343 
344     @Test
testRemoveMediaGrantsForPackages()345     public void testRemoveMediaGrantsForPackages() throws Exception {
346         Long fileId1 = insertFileInResolver(mIsolatedResolver, "test_file1");
347         Long fileId2 = insertFileInResolver(mIsolatedResolver, "test_file2");
348         Long fileId3 = insertFileInResolver(mIsolatedResolver, "test_file3");
349         List<Uri> uris1 = List.of(buildValidPickerUri(fileId1), buildValidPickerUri(fileId2));
350         List<Uri> uris2 = List.of(buildValidPickerUri(fileId3));
351 
352         // Add grants for 2 different packages.
353         mGrants.addMediaGrantsForPackage(TEST_OWNER_PACKAGE_NAME, uris1, TEST_USER_ID);
354         mGrants.addMediaGrantsForPackage(TEST_OWNER_PACKAGE_NAME2, uris2, TEST_USER_ID);
355 
356         String[] mimeTypes = {PNG_MIME_TYPE};
357         String[] volumes = {MediaStore.VOLUME_EXTERNAL_PRIMARY};
358 
359         // Verify the grants for the first package were inserted.
360         List<Uri> fileUris = convertToListOfUri(mGrants.getMediaGrantsForPackages(
361                 new String[]{TEST_OWNER_PACKAGE_NAME}, TEST_USER_ID,
362                 mimeTypes, volumes));
363         List<Long> expectedFileIdsList = List.of(fileId1, fileId2);
364         assertEquals(fileUris.size(), expectedFileIdsList.size());
365         for (Uri uri : fileUris) {
366             assertTrue(expectedFileIdsList.contains(Long.valueOf(ContentUris.parseId(uri))));
367         }
368 
369         // Remove one of the 2 grants for TEST_OWNER_PACKAGE_NAME and verify the other grants is
370         // still present.
371         mGrants.removeMediaGrantsForPackage(new String[]{TEST_OWNER_PACKAGE_NAME},
372                 List.of(buildValidPickerUri(fileId1)), TEST_USER_ID);
373         List<Uri> fileUris3 = convertToListOfUri(mGrants.getMediaGrantsForPackages(
374                 new String[]{TEST_OWNER_PACKAGE_NAME}, TEST_USER_ID,  mimeTypes, volumes));
375         assertEquals(1, fileUris3.size());
376         assertEquals(fileId2, Long.valueOf(ContentUris.parseId(fileUris3.get(0))));
377 
378 
379         // Verify grants of other packages are unaffected.
380         List<Uri> fileUrisForTestPackage2 = convertToListOfUri(mGrants.getMediaGrantsForPackages(
381                 new String[]{TEST_OWNER_PACKAGE_NAME2}, TEST_USER_ID,  mimeTypes, volumes));
382         List<Long> expectedFileIdsList2 = List.of(fileId3);
383         assertEquals(fileUrisForTestPackage2.size(), expectedFileIdsList2.size());
384         for (Uri uri : fileUrisForTestPackage2) {
385             assertTrue(expectedFileIdsList2.contains(Long.valueOf(ContentUris.parseId(uri))));
386         }
387     }
388 
389     @Test
testRemoveMediaGrantsForPackagesLargerDataSet()390     public void testRemoveMediaGrantsForPackagesLargerDataSet() throws Exception {
391         List<Uri> inputFiles = new ArrayList<>();
392         for (int itr = 1; itr < 110; itr++) {
393             inputFiles.add(buildValidPickerUri(
394                     insertFileInResolver(mIsolatedResolver, "test_file" + itr)));
395         }
396         mGrants.addMediaGrantsForPackage(TEST_OWNER_PACKAGE_NAME, inputFiles, TEST_USER_ID);
397 
398         String[] mimeTypes = {PNG_MIME_TYPE};
399         String[] volumes = {MediaStore.VOLUME_EXTERNAL_PRIMARY};
400 
401         // The query used inside remove grants is batched by 50 ids, hence having a test like this
402         // would help ensure the batching worked perfectly.
403         mGrants.removeMediaGrantsForPackage(new String[]{TEST_OWNER_PACKAGE_NAME},
404                 inputFiles.subList(0, 101), TEST_USER_ID);
405         List<Uri> fileUris3 = convertToListOfUri(mGrants.getMediaGrantsForPackages(
406                 new String[]{TEST_OWNER_PACKAGE_NAME}, TEST_USER_ID, mimeTypes, volumes));
407         assertEquals(8, fileUris3.size());
408     }
409     @Test
testAddDuplicateMediaGrants()410     public void testAddDuplicateMediaGrants() throws Exception {
411 
412         Long fileId1 = insertFileInResolver(mIsolatedResolver, "test_file1");
413         List<Uri> uris = List.of(buildValidPickerUri(fileId1));
414         mGrants.addMediaGrantsForPackage(TEST_OWNER_PACKAGE_NAME, uris, TEST_USER_ID);
415         assertGrantExistsForPackage(fileId1, TEST_OWNER_PACKAGE_NAME, TEST_USER_ID);
416 
417         // Add the same grant again to ensure no database insert failure.
418         mGrants.addMediaGrantsForPackage(TEST_OWNER_PACKAGE_NAME, uris, TEST_USER_ID);
419         assertGrantExistsForPackage(fileId1, TEST_OWNER_PACKAGE_NAME, TEST_USER_ID);
420     }
421 
422     @Test
testAddMediaGrantsRequiresPickerUri()423     public void testAddMediaGrantsRequiresPickerUri() throws Exception {
424 
425         Uri invalidUri =
426                 Uri.EMPTY
427                         .buildUpon()
428                         .scheme("content")
429                         .encodedAuthority("some_authority")
430                         .appendPath("path")
431                         .appendPath("20180713")
432                         .build();
433 
434         assertThrows(
435                 IllegalArgumentException.class,
436                 () -> {
437                     mGrants.addMediaGrantsForPackage(
438                             TEST_OWNER_PACKAGE_NAME, List.of(invalidUri), TEST_USER_ID);
439                 });
440     }
441 
442     @Test
removeAllMediaGrantsForPackage()443     public void removeAllMediaGrantsForPackage() throws Exception {
444 
445         Long fileId1 = insertFileInResolver(mIsolatedResolver, "test_file1");
446         Long fileId2 = insertFileInResolver(mIsolatedResolver, "test_file2");
447         List<Uri> uris = List.of(buildValidPickerUri(fileId1), buildValidPickerUri(fileId2));
448         mGrants.addMediaGrantsForPackage(TEST_OWNER_PACKAGE_NAME, uris, TEST_USER_ID);
449 
450         assertGrantExistsForPackage(fileId1, TEST_OWNER_PACKAGE_NAME, TEST_USER_ID);
451         assertGrantExistsForPackage(fileId2, TEST_OWNER_PACKAGE_NAME, TEST_USER_ID);
452 
453         int removed =
454                 mGrants.removeAllMediaGrantsForPackages(
455                         new String[] {TEST_OWNER_PACKAGE_NAME}, "test", TEST_USER_ID);
456         assertEquals(2, removed);
457 
458         try (Cursor c =
459                 mExternalDatabase.runWithTransaction(
460                         (db) ->
461                                 db.query(
462                                         MediaGrants.MEDIA_GRANTS_TABLE,
463                                         new String[] {
464                                             MediaGrants.FILE_ID_COLUMN,
465                                             MediaGrants.OWNER_PACKAGE_NAME_COLUMN
466                                         },
467                                         String.format(
468                                                 "%s = '%s'",
469                                                 MediaGrants.OWNER_PACKAGE_NAME_COLUMN,
470                                                 TEST_OWNER_PACKAGE_NAME),
471                                         null,
472                                         null,
473                                         null,
474                                         null))) {
475             assertEquals(0, c.getCount());
476         }
477     }
478 
479     @Test
removeAllMediaGrantsForMultiplePackages()480     public void removeAllMediaGrantsForMultiplePackages() throws Exception {
481 
482         Long fileId1 = insertFileInResolver(mIsolatedResolver, "test_file1");
483         Long fileId2 = insertFileInResolver(mIsolatedResolver, "test_file2");
484         List<Uri> uris = List.of(buildValidPickerUri(fileId1), buildValidPickerUri(fileId2));
485         mGrants.addMediaGrantsForPackage(TEST_OWNER_PACKAGE_NAME, uris, TEST_USER_ID);
486         mGrants.addMediaGrantsForPackage(TEST_OWNER_PACKAGE_NAME2, uris, TEST_USER_ID);
487 
488         assertGrantExistsForPackage(fileId1, TEST_OWNER_PACKAGE_NAME, TEST_USER_ID);
489         assertGrantExistsForPackage(fileId2, TEST_OWNER_PACKAGE_NAME, TEST_USER_ID);
490         assertGrantExistsForPackage(fileId1, TEST_OWNER_PACKAGE_NAME2, TEST_USER_ID);
491         assertGrantExistsForPackage(fileId2, TEST_OWNER_PACKAGE_NAME2, TEST_USER_ID);
492 
493         int removed =
494                 mGrants.removeAllMediaGrantsForPackages(
495                         new String[] {TEST_OWNER_PACKAGE_NAME, TEST_OWNER_PACKAGE_NAME2},
496                         "test",
497                         TEST_USER_ID);
498         assertEquals(4, removed);
499 
500         try (Cursor c =
501                 mExternalDatabase.runWithTransaction(
502                         (db) ->
503                                 db.query(
504                                         MediaGrants.MEDIA_GRANTS_TABLE,
505                                         new String[] {
506                                             MediaGrants.FILE_ID_COLUMN,
507                                             MediaGrants.OWNER_PACKAGE_NAME_COLUMN
508                                         },
509                                         String.format(
510                                                 "%s = '%s'",
511                                                 MediaGrants.OWNER_PACKAGE_NAME_COLUMN,
512                                                 TEST_OWNER_PACKAGE_NAME),
513                                         null,
514                                         null,
515                                         null,
516                                         null))) {
517             assertEquals(0, c.getCount());
518         }
519 
520         try (Cursor c =
521                 mExternalDatabase.runWithTransaction(
522                         (db) ->
523                                 db.query(
524                                         MediaGrants.MEDIA_GRANTS_TABLE,
525                                         new String[] {
526                                             MediaGrants.FILE_ID_COLUMN,
527                                             MediaGrants.OWNER_PACKAGE_NAME_COLUMN
528                                         },
529                                         String.format(
530                                                 "%s = '%s'",
531                                                 MediaGrants.OWNER_PACKAGE_NAME_COLUMN,
532                                                 TEST_OWNER_PACKAGE_NAME2),
533                                         null,
534                                         null,
535                                         null,
536                                         null))) {
537             assertEquals(0, c.getCount());
538         }
539     }
540 
541     @Test
removeAllMediaGrantsForPackageRequiresNonEmpty()542     public void removeAllMediaGrantsForPackageRequiresNonEmpty() throws Exception {
543         assertThrows(
544                 IllegalArgumentException.class,
545                 () -> {
546                     mGrants.removeAllMediaGrantsForPackages(new String[]{}, "test", TEST_USER_ID);
547                 });
548     }
549 
550     @Test
removeAllMediaGrants()551     public void removeAllMediaGrants() throws Exception {
552 
553         final String secondPackageName = "com.android.test.another.package";
554         Long fileId1 = insertFileInResolver(mIsolatedResolver, "test_file1");
555         Long fileId2 = insertFileInResolver(mIsolatedResolver, "test_file2");
556         List<Uri> uris = List.of(buildValidPickerUri(fileId1), buildValidPickerUri(fileId2));
557         mGrants.addMediaGrantsForPackage(TEST_OWNER_PACKAGE_NAME, uris, TEST_USER_ID);
558         mGrants.addMediaGrantsForPackage(secondPackageName, uris, TEST_USER_ID);
559 
560         assertGrantExistsForPackage(fileId1, TEST_OWNER_PACKAGE_NAME, TEST_USER_ID);
561         assertGrantExistsForPackage(fileId2, TEST_OWNER_PACKAGE_NAME, TEST_USER_ID);
562         assertGrantExistsForPackage(fileId1, secondPackageName, TEST_USER_ID);
563         assertGrantExistsForPackage(fileId2, secondPackageName, TEST_USER_ID);
564 
565         int removed = mGrants.removeAllMediaGrants();
566         assertEquals(4, removed);
567 
568         try (Cursor c =
569                 mExternalDatabase.runWithTransaction(
570                         (db) ->
571                                 db.query(
572                                         MediaGrants.MEDIA_GRANTS_TABLE,
573                                         new String[] {
574                                             MediaGrants.FILE_ID_COLUMN,
575                                             MediaGrants.OWNER_PACKAGE_NAME_COLUMN
576                                         },
577                                         null,
578                                         null,
579                                         null,
580                                         null,
581                                         null))) {
582             assertEquals(0, c.getCount());
583         }
584     }
585 
586     @Test
addMediaGrantsIsPrivileged()587     public void addMediaGrantsIsPrivileged() throws Exception {
588         assertThrows(
589                 SecurityException.class,
590                 () -> {
591                     MediaStore.grantMediaReadForPackage(mContext, 1234, List.of());
592                 });
593     }
594 
595     @Test
mediaProviderUidCanAddMediaGrants()596     public void mediaProviderUidCanAddMediaGrants() throws Exception {
597 
598         Long fileId1 = insertFileInResolver(mIsolatedResolver, "test_file1");
599         Long fileId2 = insertFileInResolver(mIsolatedResolver, "test_file2");
600         List<Uri> uris = List.of(buildValidPickerUri(fileId1), buildValidPickerUri(fileId2));
601         // Use mIsolatedContext here to ensure we pass the security check.
602         MediaStore.grantMediaReadForPackage(mIsolatedContext, Process.myUid(), uris);
603 
604         assertGrantExistsForPackage(fileId1, mContext.getPackageName(), TEST_USER_ID);
605         assertGrantExistsForPackage(fileId2, mContext.getPackageName(), TEST_USER_ID);
606     }
607 
608     @Test
test_generationGrantedExistsAndIsIncreasing_success()609     public void test_generationGrantedExistsAndIsIncreasing_success() throws Exception {
610         Long fileId1 = insertFileInResolver(mIsolatedResolver, "test_file1");
611         Long fileId2 = insertFileInResolver(mIsolatedResolver, "test_file2");
612         Long fileId3 = insertFileInResolver(mIsolatedResolver, "test_file3");
613 
614         List<Uri> uris = List.of(buildValidPickerUri(fileId1), buildValidPickerUri(fileId2));
615         MediaStore.grantMediaReadForPackage(mIsolatedContext, Process.myUid(), uris);
616         // adding grants separately for fileId3 so that it has a different generation from fileId1
617         // and fileId2.
618         List<Uri> uris2 = List.of(buildValidPickerUri(fileId3));
619         MediaStore.grantMediaReadForPackage(mIsolatedContext, Process.myUid(), uris2);
620 
621         assertGrantExistsForPackage(
622                 fileId1,
623                 mContext.getPackageName(),
624                 TEST_USER_ID);
625         assertGrantExistsForPackage(
626                 fileId2,
627                 mContext.getPackageName(),
628                 TEST_USER_ID);
629         assertGrantExistsForPackage(
630                 fileId3,
631                 mContext.getPackageName(),
632                 TEST_USER_ID);
633 
634         long gen1 = getGenerationForMediaGrant(fileId1,
635                 mContext.getPackageName(),
636                 TEST_USER_ID);
637         long gen2 = getGenerationForMediaGrant(fileId2,
638                 mContext.getPackageName(),
639                 TEST_USER_ID);
640         long gen3 = getGenerationForMediaGrant(fileId3,
641                 mContext.getPackageName(),
642                 TEST_USER_ID);
643         // verify generation for items granted in the same session are equal.
644         assertEquals(gen2, gen1);
645         // verify generation are increasing.
646         assertTrue(gen1 < gen3);
647     }
648 
649     /**
650      * Assert a media grant exists in the given database.
651      *
652      * @param fileId        the corresponding files._id column value.
653      * @param packageName   i.e. com.android.test.package
654      * @param userId        the user id of the package.
655      */
656     private void assertGrantExistsForPackage(Long fileId, String packageName,
657             int userId) {
658         try (Cursor c = getMediaGrantRow(fileId, packageName, userId)) {
659             assertNotNull(c);
660             assertEquals(1, c.getCount());
661             Long fileIdValue;
662             String ownerValue;
663             assertTrue(c.moveToFirst());
664             fileIdValue = c.getLong(c.getColumnIndex(MediaGrants.FILE_ID_COLUMN));
665             ownerValue = c.getString(c.getColumnIndex(MediaGrants.OWNER_PACKAGE_NAME_COLUMN));
666             long generationGranted = c.getLong(
667                     c.getColumnIndex(MediaGrants.GENERATION_GRANTED));
668             assertEquals(fileIdValue, fileId);
669             assertEquals(packageName, ownerValue);
670             assertTrue(generationGranted > 0);
671         }
672     }
673 
getGenerationForMediaGrant(Long fileId, String packageName, int userId)674     private long getGenerationForMediaGrant(Long fileId, String packageName,
675             int userId) {
676 
677         long generationGranted = -1;
678         try (Cursor c = getMediaGrantRow(fileId, packageName, userId)) {
679             assertNotNull(c);
680             assertEquals(1, c.getCount());
681             assertTrue(c.moveToFirst());
682             generationGranted = c.getLong(
683                     c.getColumnIndex(MediaGrants.GENERATION_GRANTED));
684             assertTrue(generationGranted >= 0);
685 
686         }
687         return generationGranted;
688     }
689 
getMediaGrantRow(Long fileId, String packageName, int userId)690     private Cursor getMediaGrantRow(Long fileId, String packageName,
691             int userId) {
692         return mExternalDatabase.runWithTransaction(
693                 (db) ->
694                         db.query(
695                                 MediaGrants.MEDIA_GRANTS_TABLE,
696                                 new String[]{
697                                         MediaGrants.FILE_ID_COLUMN,
698                                         MediaGrants.OWNER_PACKAGE_NAME_COLUMN,
699                                         MediaGrants.PACKAGE_USER_ID_COLUMN,
700                                         MediaGrants.GENERATION_GRANTED
701                                 },
702                                 String.format(
703                                         "%s = '%s' AND %s = %s AND %s = %s",
704                                         MediaGrants.OWNER_PACKAGE_NAME_COLUMN,
705                                         packageName,
706                                         MediaGrants.FILE_ID_COLUMN,
707                                         Long.toString(fileId),
708                                         MediaGrants.PACKAGE_USER_ID_COLUMN,
709                                         Integer.toString(userId)),
710                                 null,
711                                 null,
712                                 null,
713                                 null));
714     }
715 
convertToListOfUri(Cursor c)716     private List<Uri> convertToListOfUri(Cursor c) {
717         List<Uri> filesUriList = new ArrayList<>(0);
718         while (c.moveToNext()) {
719             final Integer file_id = c.getInt(c.getColumnIndexOrThrow(FILE_ID_COLUMN));
720             final Integer userId = c.getInt(
721                     c.getColumnIndexOrThrow(PACKAGE_USER_ID_COLUMN));
722             // transforming ids to Item uris to use as a key in selection based features.
723             filesUriList.add(getItemsUri(String.valueOf(file_id),
724                     PickerSyncController.LOCAL_PICKER_PROVIDER_AUTHORITY,
725                     UserId.of(UserHandle.of(userId))));
726         }
727         return filesUriList;
728     }
729 
730     /**
731      * Modify column value for the fileId passed in the parameters with the modifiedValue.
732      */
updateFileValues(Long fileId, String columnToBeModified, String modifiedValue)733     private void updateFileValues(Long fileId, String columnToBeModified, String modifiedValue) {
734         int numberOfUpdatedRows = mExternalDatabase.runWithTransaction(
735                 (db) -> {
736                     ContentValues updatedRowValue = new ContentValues();
737                     updatedRowValue.put(columnToBeModified, modifiedValue);
738                     return db.update(MediaStore.Files.TABLE,
739                             updatedRowValue,
740                             String.format(
741                                     "%s = '%s'",
742                                     MediaStore.Files.FileColumns._ID,
743                                     Long.toString(fileId)),
744                             null);
745                 });
746         assertEquals(/* expected */ 1, numberOfUpdatedRows);
747     }
748 }
749