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