1 /*
2  * Copyright (C) 2020 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 android.Manifest.permission.ACCESS_MEDIA_LOCATION;
20 import static android.Manifest.permission.MANAGE_APP_OPS_MODES;
21 import static android.Manifest.permission.MANAGE_EXTERNAL_STORAGE;
22 import static android.Manifest.permission.MANAGE_MEDIA;
23 import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
24 import static android.Manifest.permission.READ_MEDIA_AUDIO;
25 import static android.Manifest.permission.READ_MEDIA_IMAGES;
26 import static android.Manifest.permission.READ_MEDIA_VIDEO;
27 import static android.Manifest.permission.UPDATE_APP_OPS_STATS;
28 
29 import static androidx.test.InstrumentationRegistry.getContext;
30 
31 import static com.android.providers.media.PermissionActivity.VERB_FAVORITE;
32 import static com.android.providers.media.PermissionActivity.VERB_TRASH;
33 import static com.android.providers.media.PermissionActivity.VERB_UNFAVORITE;
34 import static com.android.providers.media.PermissionActivity.VERB_WRITE;
35 import static com.android.providers.media.PermissionActivity.shouldShowActionDialog;
36 import static com.android.providers.media.util.PermissionUtils.checkPermissionAccessMediaLocation;
37 import static com.android.providers.media.util.PermissionUtils.checkPermissionManageMedia;
38 import static com.android.providers.media.util.PermissionUtils.checkPermissionManager;
39 import static com.android.providers.media.util.PermissionUtils.checkPermissionReadAudio;
40 import static com.android.providers.media.util.PermissionUtils.checkPermissionReadImages;
41 import static com.android.providers.media.util.PermissionUtils.checkPermissionReadStorage;
42 import static com.android.providers.media.util.PermissionUtils.checkPermissionReadVideo;
43 import static com.android.providers.media.util.TestUtils.adoptShellPermission;
44 import static com.android.providers.media.util.TestUtils.dropShellPermission;
45 
46 import static com.google.common.truth.Truth.assertThat;
47 
48 import android.app.AppOpsManager;
49 import android.app.Instrumentation;
50 import android.content.ClipData;
51 import android.content.ContentValues;
52 import android.content.Context;
53 import android.content.Intent;
54 import android.net.Uri;
55 import android.os.Build;
56 import android.os.Environment;
57 import android.provider.MediaStore;
58 import android.text.TextUtils;
59 
60 import androidx.annotation.NonNull;
61 import androidx.test.InstrumentationRegistry;
62 import androidx.test.filters.SdkSuppress;
63 import androidx.test.rule.ActivityTestRule;
64 import androidx.test.runner.AndroidJUnit4;
65 
66 import com.android.providers.media.scan.MediaScannerTest;
67 
68 import org.junit.Before;
69 import org.junit.Test;
70 import org.junit.runner.RunWith;
71 
72 import java.io.File;
73 import java.util.HashSet;
74 import java.util.concurrent.TimeoutException;
75 
76 /**
77  * We already have solid coverage of this logic in {@code CtsMediaProviderTestCases},
78  * but the coverage system currently doesn't measure that, so we add the bare
79  * minimum local testing here to convince the tooling that it's covered.
80  */
81 @RunWith(AndroidJUnit4.class)
82 public class PermissionActivityTest {
83     private static final String TEST_APP_PACKAGE_NAME =
84             "com.android.providers.media.testapp.permission";
85     private static final String TEST_APP_33_PACKAGE_NAME =
86             "com.android.providers.media.testapp.permissionmedia";
87 
88     private static final String OP_ACCESS_MEDIA_LOCATION =
89             AppOpsManager.permissionToOp(ACCESS_MEDIA_LOCATION);
90     private static final String OP_MANAGE_MEDIA =
91             AppOpsManager.permissionToOp(MANAGE_MEDIA);
92     private static final String OP_MANAGE_EXTERNAL_STORAGE =
93             AppOpsManager.permissionToOp(MANAGE_EXTERNAL_STORAGE);
94     private static final String OP_READ_EXTERNAL_STORAGE =
95             AppOpsManager.permissionToOp(READ_EXTERNAL_STORAGE);
96     private static final String OP_READ_MEDIA_IMAGES =
97             AppOpsManager.permissionToOp(READ_MEDIA_IMAGES);
98     private static final String OP_READ_MEDIA_AUDIO =
99             AppOpsManager.permissionToOp(READ_MEDIA_AUDIO);
100     private static final String OP_READ_MEDIA_VIDEO =
101             AppOpsManager.permissionToOp(READ_MEDIA_VIDEO);
102 
103     // The list is used to restore the permissions after the test is finished.
104     // The default value for these app ops is {@link AppOpsManager#MODE_DEFAULT}
105     private static final String[] DEFAULT_OP_PERMISSION_LIST = new String[] {
106             OP_MANAGE_EXTERNAL_STORAGE,
107             OP_MANAGE_MEDIA
108     };
109 
110     // The list is used to restore the permissions after the test is finished.
111     // The default value for these app ops is {@link AppOpsManager#MODE_ALLOWED}
112     private static final String[] ALLOWED_OP_PERMISSION_LIST = new String[] {
113             OP_ACCESS_MEDIA_LOCATION,
114             OP_READ_EXTERNAL_STORAGE
115     };
116 
117     private static final long TIMEOUT_MILLIS = 3000;
118     private static final long SLEEP_MILLIS = 30;
119 
120     private static final int TEST_APP_PID = -1;
121     private int mTestAppUid = -1;
122     private int mTestAppUid33 = -1;
123 
124     @Before
setUp()125     public void setUp() throws Exception {
126         mTestAppUid = getContext().getPackageManager().getPackageUid(TEST_APP_PACKAGE_NAME, 0);
127         mTestAppUid33 = getContext().getPackageManager().getPackageUid(TEST_APP_33_PACKAGE_NAME, 0);
128     }
129 
130     @Test
testSimple()131     public void testSimple() throws Exception {
132         final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
133         final Intent intent = new Intent(inst.getContext(), GetResultActivity.class);
134         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
135 
136         final GetResultActivity activity = (GetResultActivity) inst.startActivitySync(intent);
137         activity.startActivityForResult(createIntent(), 42);
138     }
139 
140     @Test
testLaunchWithNoCallerInfoNoCrash()141     public void testLaunchWithNoCallerInfoNoCrash() throws Exception {
142         ActivityTestRule<PermissionActivity> activityTestRule = new ActivityTestRule<>(
143                 PermissionActivity.class, /* initialTouchMode */ true, /* launchActivity */ false);
144         activityTestRule.launchActivity(new Intent());
145     }
146 
147     @Test
testShouldShowActionDialog_favorite_false()148     public void testShouldShowActionDialog_favorite_false() throws Exception {
149         assertThat(shouldShowActionDialog(getContext(), TEST_APP_PID, mTestAppUid,
150                 TEST_APP_PACKAGE_NAME, null, VERB_FAVORITE)).isFalse();
151     }
152 
153     @Test
testShouldShowActionDialog_unfavorite_false()154     public void testShouldShowActionDialog_unfavorite_false() throws Exception {
155         assertThat(shouldShowActionDialog(getContext(), TEST_APP_PID, mTestAppUid,
156                 TEST_APP_PACKAGE_NAME, null, VERB_UNFAVORITE)).isFalse();
157     }
158 
159     @Test
160     @SdkSuppress(minSdkVersion = 31, codeName = "S")
testShouldShowActionDialog_noRESAndMES_true()161     public void testShouldShowActionDialog_noRESAndMES_true() throws Exception {
162         final String[] enableAppOpsList = {OP_MANAGE_MEDIA};
163         final String[] disableAppOpsList = {OP_MANAGE_EXTERNAL_STORAGE, OP_READ_EXTERNAL_STORAGE};
164         adoptShellPermission(UPDATE_APP_OPS_STATS, MANAGE_APP_OPS_MODES);
165 
166         try {
167             setupPermissions(
168                     mTestAppUid, enableAppOpsList, disableAppOpsList, TEST_APP_PACKAGE_NAME);
169 
170             assertThat(shouldShowActionDialog(getContext(), TEST_APP_PID, mTestAppUid,
171                     TEST_APP_PACKAGE_NAME, null, VERB_TRASH)).isTrue();
172         } finally {
173             restoreDefaultAppOpPermissions(mTestAppUid);
174             dropShellPermission();
175         }
176     }
177 
178     @Test
179     @SdkSuppress(minSdkVersion = 33, codeName = "T")
testShouldShowActionDialog_noRMAAndMES_true_33()180     public void testShouldShowActionDialog_noRMAAndMES_true_33() throws Exception {
181         final String[] enableAppOpsList =
182                 {OP_MANAGE_MEDIA, OP_READ_MEDIA_IMAGES, OP_READ_MEDIA_VIDEO};
183         final String[] disableAppOpsList = {OP_MANAGE_EXTERNAL_STORAGE, OP_READ_MEDIA_AUDIO};
184         adoptShellPermission(UPDATE_APP_OPS_STATS, MANAGE_APP_OPS_MODES);
185 
186         try {
187             setupPermissions(
188                     mTestAppUid33, enableAppOpsList, disableAppOpsList, TEST_APP_33_PACKAGE_NAME);
189 
190             assertThat(shouldShowActionDialog(getContext(), TEST_APP_PID, mTestAppUid33,
191                     TEST_APP_33_PACKAGE_NAME, null, VERB_TRASH,
192                     /* shouldCheckMediaPermissions */ true, /* shouldCheckReadAudio */ true,
193                     /* shouldCheckReadImages */ false, /* shouldCheckReadVideo */ false,
194                     /* mShouldCheckReadAudioOrReadVideo */ false,
195                     /* isTargetSdkAtLeastT */ true)).isTrue();
196         } finally {
197             restoreDefaultAppOpPermissions(mTestAppUid33);
198             dropShellPermission();
199         }
200     }
201 
202     @Test
203     @SdkSuppress(minSdkVersion = 33, codeName = "T")
testShouldShowActionDialog_noRMIAndMES_true_33()204     public void testShouldShowActionDialog_noRMIAndMES_true_33() throws Exception {
205         final String[] enableAppOpsList =
206                 {OP_MANAGE_MEDIA, OP_READ_MEDIA_AUDIO, OP_READ_MEDIA_VIDEO};
207         final String[] disableAppOpsList = {OP_MANAGE_EXTERNAL_STORAGE, OP_READ_MEDIA_IMAGES};
208         adoptShellPermission(UPDATE_APP_OPS_STATS, MANAGE_APP_OPS_MODES);
209 
210         try {
211             setupPermissions(
212                     mTestAppUid33, enableAppOpsList, disableAppOpsList, TEST_APP_33_PACKAGE_NAME);
213 
214             assertThat(shouldShowActionDialog(getContext(), TEST_APP_PID, mTestAppUid33,
215                     TEST_APP_33_PACKAGE_NAME, null, VERB_TRASH,
216                     /* shouldCheckMediaPermissions */ true, /* shouldCheckReadAudio */ false,
217                     /* shouldCheckReadImages */ true, /* shouldCheckReadVideo */ false,
218                     /* mShouldCheckReadAudioOrReadVideo */ false,
219                     /* isTargetSdkAtLeastT */ true)).isTrue();
220         } finally {
221             restoreDefaultAppOpPermissions(mTestAppUid33);
222             dropShellPermission();
223         }
224     }
225 
226     @Test
227     @SdkSuppress(minSdkVersion = 33, codeName = "T")
testShouldShowActionDialog_noRMVAndMES_true_33()228     public void testShouldShowActionDialog_noRMVAndMES_true_33() throws Exception {
229         final String[] enableAppOpsList =
230                 {OP_MANAGE_MEDIA, OP_READ_MEDIA_AUDIO, OP_READ_MEDIA_IMAGES};
231         final String[] disableAppOpsList = {OP_MANAGE_EXTERNAL_STORAGE, OP_READ_MEDIA_VIDEO};
232         adoptShellPermission(UPDATE_APP_OPS_STATS, MANAGE_APP_OPS_MODES);
233 
234         try {
235             setupPermissions(
236                     mTestAppUid33, enableAppOpsList, disableAppOpsList, TEST_APP_33_PACKAGE_NAME);
237 
238             assertThat(shouldShowActionDialog(getContext(), TEST_APP_PID, mTestAppUid33,
239                     TEST_APP_33_PACKAGE_NAME, null, VERB_TRASH,
240                     /* shouldCheckMediaPermissions */ true, /* shouldCheckReadAudio */ false,
241                     /* shouldCheckReadImages */ false, /* shouldCheckReadVideo */ true,
242                     /* mShouldCheckReadAudioOrReadVideo */ false,
243                     /* isTargetSdkAtLeastT */ true)).isTrue();
244         } finally {
245             restoreDefaultAppOpPermissions(mTestAppUid33);
246             dropShellPermission();
247         }
248     }
249 
250     @Test
251     @SdkSuppress(minSdkVersion = 33, codeName = "T")
testShouldShowActionDialogForSubtitle_noRMARMVAndMES_true_33()252     public void testShouldShowActionDialogForSubtitle_noRMARMVAndMES_true_33() throws Exception {
253         final String[] enableAppOpsList =
254                 {OP_MANAGE_MEDIA, OP_READ_MEDIA_IMAGES};
255         final String[] disableAppOpsList =
256                 {OP_MANAGE_EXTERNAL_STORAGE, OP_READ_MEDIA_AUDIO, OP_READ_MEDIA_VIDEO};
257         adoptShellPermission(UPDATE_APP_OPS_STATS, MANAGE_APP_OPS_MODES);
258 
259         try {
260             setupPermissions(
261                     mTestAppUid33, enableAppOpsList, disableAppOpsList, TEST_APP_33_PACKAGE_NAME);
262 
263             assertThat(shouldShowActionDialog(getContext(), TEST_APP_PID, mTestAppUid33,
264                     TEST_APP_33_PACKAGE_NAME, null, VERB_TRASH,
265                     /* shouldCheckMediaPermissions */ true, /* shouldCheckReadAudio */ false,
266                     /* shouldCheckReadImages */ false, /* shouldCheckReadVideo */ false,
267                     /* mShouldCheckReadAudioOrReadVideo */ true,
268                     /* isTargetSdkAtLeastT */ true)).isTrue();
269         } finally {
270             restoreDefaultAppOpPermissions(mTestAppUid33);
271             dropShellPermission();
272         }
273     }
274 
275     @Test
276     @SdkSuppress(minSdkVersion = 31, codeName = "S")
testShouldShowActionDialog_noMANAGE_MEDIA_true()277     public void testShouldShowActionDialog_noMANAGE_MEDIA_true() throws Exception {
278         final String[] enableAppOpsList = {OP_MANAGE_EXTERNAL_STORAGE, OP_READ_EXTERNAL_STORAGE};
279         final String[] disableAppOpsList = {OP_MANAGE_MEDIA};
280         adoptShellPermission(UPDATE_APP_OPS_STATS, MANAGE_APP_OPS_MODES);
281 
282         try {
283             setupPermissions(
284                     mTestAppUid, enableAppOpsList, disableAppOpsList, TEST_APP_PACKAGE_NAME);
285 
286             assertThat(shouldShowActionDialog(getContext(), TEST_APP_PID, mTestAppUid,
287                     TEST_APP_PACKAGE_NAME, null, VERB_TRASH)).isTrue();
288         } finally {
289             restoreDefaultAppOpPermissions(mTestAppUid);
290             dropShellPermission();
291         }
292     }
293 
294     @Test
295     @SdkSuppress(minSdkVersion = 33, codeName = "T")
testShouldShowActionDialog_noMANAGE_MEDIA_true_33()296     public void testShouldShowActionDialog_noMANAGE_MEDIA_true_33() throws Exception {
297         final String[] enableAppOpsList = {
298             OP_MANAGE_EXTERNAL_STORAGE,
299             OP_READ_MEDIA_AUDIO,
300             OP_READ_MEDIA_VIDEO,
301             OP_READ_MEDIA_IMAGES
302         };
303         final String[] disableAppOpsList = {OP_MANAGE_MEDIA};
304         adoptShellPermission(UPDATE_APP_OPS_STATS, MANAGE_APP_OPS_MODES);
305 
306         try {
307             setupPermissions(
308                     mTestAppUid33, enableAppOpsList, disableAppOpsList, TEST_APP_33_PACKAGE_NAME);
309 
310             assertThat(shouldShowActionDialog(getContext(), TEST_APP_PID, mTestAppUid33,
311                     TEST_APP_33_PACKAGE_NAME, null, VERB_TRASH,
312                     /* shouldCheckMediaPermissions */ true, /* shouldCheckReadAudio */ true,
313                     /* shouldCheckReadImages */ true, /* shouldCheckReadVideo */ true,
314                     /* mShouldCheckReadAudioOrReadVideo */ true,
315                     /* isTargetSdkAtLeastT */ true)).isTrue();
316         } finally {
317             restoreDefaultAppOpPermissions(mTestAppUid33);
318             dropShellPermission();
319         }
320     }
321 
322     @Test
323     @SdkSuppress(minSdkVersion = 31, codeName = "S")
testShouldShowActionDialog_hasMMWithRES_false()324     public void testShouldShowActionDialog_hasMMWithRES_false() throws Exception {
325         final String[] enableAppOpsList = {OP_MANAGE_MEDIA, OP_READ_EXTERNAL_STORAGE};
326         final String[] disableAppOpsList = {OP_MANAGE_EXTERNAL_STORAGE};
327         adoptShellPermission(UPDATE_APP_OPS_STATS, MANAGE_APP_OPS_MODES);
328 
329         try {
330             setupPermissions(
331                     mTestAppUid, enableAppOpsList, disableAppOpsList, TEST_APP_PACKAGE_NAME);
332 
333             assertThat(shouldShowActionDialog(getContext(), TEST_APP_PID, mTestAppUid,
334                     TEST_APP_PACKAGE_NAME, null, VERB_TRASH)).isFalse();
335         } finally {
336             restoreDefaultAppOpPermissions(mTestAppUid);
337             dropShellPermission();
338         }
339     }
340 
341     @Test
342     @SdkSuppress(minSdkVersion = 33, codeName = "T")
testShouldShowActionDialog_hasMMWithRM_false_33()343     public void testShouldShowActionDialog_hasMMWithRM_false_33() throws Exception {
344         final String[] enableAppOpsList = {
345             OP_MANAGE_MEDIA, OP_READ_MEDIA_AUDIO, OP_READ_MEDIA_VIDEO, OP_READ_MEDIA_IMAGES
346         };
347         final String[] disableAppOpsList = {OP_MANAGE_EXTERNAL_STORAGE};
348         adoptShellPermission(UPDATE_APP_OPS_STATS, MANAGE_APP_OPS_MODES);
349 
350         try {
351             setupPermissions(
352                     mTestAppUid33, enableAppOpsList, disableAppOpsList, TEST_APP_33_PACKAGE_NAME);
353 
354             assertThat(shouldShowActionDialog(getContext(), TEST_APP_PID, mTestAppUid33,
355                     TEST_APP_33_PACKAGE_NAME, null, VERB_TRASH,
356                     /* shouldCheckMediaPermissions */ true, /* shouldCheckReadAudio */ true,
357                     /* shouldCheckReadImages */ true, /* shouldCheckReadVideo */ true,
358                     /* mShouldCheckReadAudioOrReadVideo */ true,
359                     /* isTargetSdkAtLeastT */ true)).isFalse();
360         } finally {
361             restoreDefaultAppOpPermissions(mTestAppUid33);
362             dropShellPermission();
363         }
364     }
365 
366     @Test
367     @SdkSuppress(minSdkVersion = 31, codeName = "S")
testShouldShowActionDialog_hasMMWithMES_false()368     public void testShouldShowActionDialog_hasMMWithMES_false() throws Exception {
369         final String[] enableAppOpsList = {OP_MANAGE_EXTERNAL_STORAGE, OP_MANAGE_MEDIA};
370         final String[] disableAppOpsList = {OP_READ_EXTERNAL_STORAGE};
371         adoptShellPermission(UPDATE_APP_OPS_STATS, MANAGE_APP_OPS_MODES);
372 
373         try {
374             setupPermissions(
375                     mTestAppUid, enableAppOpsList, disableAppOpsList, TEST_APP_PACKAGE_NAME);
376 
377             assertThat(shouldShowActionDialog(getContext(), TEST_APP_PID, mTestAppUid,
378                     TEST_APP_PACKAGE_NAME, null, VERB_TRASH)).isFalse();
379         } finally {
380             restoreDefaultAppOpPermissions(mTestAppUid);
381             dropShellPermission();
382         }
383     }
384 
385     @Test
386     @SdkSuppress(minSdkVersion = 33, codeName = "T")
testShouldShowActionDialog_hasMMWithMES_false_33()387     public void testShouldShowActionDialog_hasMMWithMES_false_33() throws Exception {
388         final String[] enableAppOpsList = {OP_MANAGE_EXTERNAL_STORAGE, OP_MANAGE_MEDIA};
389         final String[] disableAppOpsList = {
390             OP_READ_MEDIA_AUDIO, OP_READ_MEDIA_VIDEO, OP_READ_MEDIA_IMAGES
391         };
392 
393         adoptShellPermission(UPDATE_APP_OPS_STATS, MANAGE_APP_OPS_MODES);
394 
395         try {
396             setupPermissions(
397                     mTestAppUid33, enableAppOpsList, disableAppOpsList, TEST_APP_33_PACKAGE_NAME);
398 
399             assertThat(shouldShowActionDialog(getContext(), TEST_APP_PID, mTestAppUid33,
400                     TEST_APP_33_PACKAGE_NAME, null, VERB_TRASH,
401                     /* shouldCheckMediaPermissions */ true, /* shouldCheckReadAudio */ true,
402                     /* shouldCheckReadImages */ true, /* shouldCheckReadVideo */ true,
403                     /* mShouldCheckReadAudioOrReadVideo */ true,
404                     /* isTargetSdkAtLeastT */ true)).isFalse();
405         } finally {
406             restoreDefaultAppOpPermissions(mTestAppUid33);
407             dropShellPermission();
408         }
409     }
410 
411     @Test
412     @SdkSuppress(minSdkVersion = 31, codeName = "S")
testShouldShowActionDialog_writeNoACCESS_MEDIA_LOCATION_true()413     public void testShouldShowActionDialog_writeNoACCESS_MEDIA_LOCATION_true() throws Exception {
414         final String[] enableAppOpsList =
415                 {OP_MANAGE_EXTERNAL_STORAGE, OP_MANAGE_MEDIA, OP_READ_EXTERNAL_STORAGE};
416         final String[] disableAppOpsList = {OP_ACCESS_MEDIA_LOCATION};
417         adoptShellPermission(UPDATE_APP_OPS_STATS, MANAGE_APP_OPS_MODES);
418 
419         try {
420             setupPermissions(
421                     mTestAppUid, enableAppOpsList, disableAppOpsList, TEST_APP_PACKAGE_NAME);
422 
423             assertThat(shouldShowActionDialog(getContext(), TEST_APP_PID, mTestAppUid,
424                     TEST_APP_PACKAGE_NAME, null, VERB_WRITE)).isTrue();
425         } finally {
426             restoreDefaultAppOpPermissions(mTestAppUid);
427             dropShellPermission();
428         }
429     }
430 
431     @Test
432     @SdkSuppress(minSdkVersion = 31, codeName = "S")
testShouldShowActionDialog_writeHasACCESS_MEDIA_LOCATION_false()433     public void testShouldShowActionDialog_writeHasACCESS_MEDIA_LOCATION_false() throws Exception {
434         final String[] enableAppOpsList = {
435                 OP_ACCESS_MEDIA_LOCATION,
436                 OP_MANAGE_EXTERNAL_STORAGE,
437                 OP_MANAGE_MEDIA,
438                 OP_READ_EXTERNAL_STORAGE};
439         final String[] disableAppOpsList = new String[]{};
440         adoptShellPermission(UPDATE_APP_OPS_STATS, MANAGE_APP_OPS_MODES);
441 
442         try {
443             setupPermissions(
444                     mTestAppUid, enableAppOpsList, disableAppOpsList, TEST_APP_PACKAGE_NAME);
445 
446             assertThat(shouldShowActionDialog(getContext(), TEST_APP_PID, mTestAppUid,
447                     TEST_APP_PACKAGE_NAME, null, VERB_WRITE)).isFalse();
448         } finally {
449             restoreDefaultAppOpPermissions(mTestAppUid);
450             dropShellPermission();
451         }
452     }
453 
setupPermissions(int uid, @NonNull String[] enableAppOpsList, @NonNull String[] disableAppOpsList, @NonNull String packageName)454     private static void setupPermissions(int uid, @NonNull String[] enableAppOpsList,
455             @NonNull String[] disableAppOpsList, @NonNull String packageName) throws Exception {
456         for (String op : enableAppOpsList) {
457             modifyAppOp(uid, op, AppOpsManager.MODE_ALLOWED);
458         }
459 
460         for (String op : disableAppOpsList) {
461             modifyAppOp(uid, op, AppOpsManager.MODE_ERRORED);
462         }
463 
464         pollForAppOpPermissions(
465                 TEST_APP_PID, packageName, uid, enableAppOpsList, /* hasPermission= */ true);
466         pollForAppOpPermissions(
467                 TEST_APP_PID, packageName, uid, disableAppOpsList, /* hasPermission= */ false);
468     }
469 
restoreDefaultAppOpPermissions(int uid)470     private static void restoreDefaultAppOpPermissions(int uid) {
471         for (String op : DEFAULT_OP_PERMISSION_LIST) {
472             modifyAppOp(uid, op, AppOpsManager.MODE_DEFAULT);
473         }
474 
475         for (String op : ALLOWED_OP_PERMISSION_LIST) {
476             modifyAppOp(uid, op, AppOpsManager.MODE_ALLOWED);
477         }
478     }
479 
createIntent()480     private static Intent createIntent() throws Exception {
481         final Context context = InstrumentationRegistry.getContext();
482 
483         final File dir = Environment
484                 .getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
485         final File file = MediaScannerTest.stage(R.raw.test_image,
486                 new File(dir, "test" + System.nanoTime() + ".jpg"));
487         final Uri uri = MediaStore.scanFile(context.getContentResolver(), file);
488 
489         final Intent intent = new Intent(MediaStore.CREATE_WRITE_REQUEST_CALL, null,
490                 context, PermissionActivity.class);
491         intent.putExtra(MediaStore.EXTRA_CLIP_DATA, ClipData.newRawUri("", uri));
492         intent.putExtra(MediaStore.EXTRA_CONTENT_VALUES, new ContentValues());
493         return intent;
494     }
495 
modifyAppOp(int uid, @NonNull String op, int mode)496     private static void modifyAppOp(int uid, @NonNull String op, int mode) {
497         getContext().getSystemService(AppOpsManager.class).setUidMode(op, uid, mode);
498     }
499 
pollForAppOpPermissions(int pid, @NonNull String packageName, int uid, String[] opList, boolean hasPermission)500     private static void pollForAppOpPermissions(int pid, @NonNull String packageName, int uid,
501             String[] opList, boolean hasPermission) throws Exception {
502         long current = System.currentTimeMillis();
503         final long timeout = current + TIMEOUT_MILLIS;
504         final HashSet<String> checkedOpSet = new HashSet<>();
505 
506         while (current < timeout && checkedOpSet.size() < opList.length) {
507             for (String op : opList) {
508                 if (!checkedOpSet.contains(op)
509                         && checkPermission(op, pid, uid, packageName, hasPermission)) {
510                     checkedOpSet.add(op);
511                     continue;
512                 }
513             }
514             Thread.sleep(SLEEP_MILLIS);
515             current = System.currentTimeMillis();
516         }
517 
518         if (checkedOpSet.size() != opList.length) {
519             throw new TimeoutException("Check AppOp permissions with " + uid + " timeout");
520         }
521     }
522 
checkPermission(@onNull String op, int pid, int uid, @NonNull String packageName, boolean expected)523     private static boolean checkPermission(@NonNull String op, int pid, int uid,
524             @NonNull String packageName, boolean expected) throws Exception {
525         final Context context = getContext();
526 
527         if (TextUtils.equals(op, OP_READ_EXTERNAL_STORAGE)) {
528             return expected == checkPermissionReadStorage(context, pid, uid, packageName,
529                     /* attributionTag= */ null);
530         } else if (TextUtils.equals(op, OP_READ_MEDIA_IMAGES)) {
531             return expected == checkPermissionReadImages(
532                 context, pid, uid, packageName, /* attributionTag= */ null, /* isAtleastT */ true);
533         } else if (TextUtils.equals(op, OP_READ_MEDIA_AUDIO)) {
534             return expected == checkPermissionReadAudio(
535                 context, pid, uid, packageName, /* attributionTag= */ null, /* isAtleastT */ true);
536         } else if (TextUtils.equals(op, OP_READ_MEDIA_VIDEO)) {
537             return expected == checkPermissionReadVideo(
538                 context, pid, uid, packageName, /* attributionTag= */ null, /* isAtleastT */ true);
539         } else if (TextUtils.equals(op, OP_MANAGE_EXTERNAL_STORAGE)) {
540             return expected == checkPermissionManager(context, pid, uid, packageName,
541                     /* attributionTag= */ null);
542         } else if (TextUtils.equals(op, OP_MANAGE_MEDIA)) {
543             return expected == checkPermissionManageMedia(context, pid, uid, packageName,
544                     /* attributionTag= */ null);
545         } else if (TextUtils.equals(op, OP_ACCESS_MEDIA_LOCATION)) {
546             final int targetSdk = context.getPackageManager()
547                     .getApplicationInfo(packageName, 0).targetSdkVersion;
548 
549             return expected == checkPermissionAccessMediaLocation(context, pid, uid,
550                     packageName, /* attributionTag= */ null,
551                     targetSdk >= Build.VERSION_CODES.TIRAMISU);
552         } else {
553             throw new IllegalArgumentException("checkPermission is not supported for op: " + op);
554         }
555     }
556 }
557