• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.backup.cts;
18 
19 import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
20 import static android.Manifest.permission.ACCESS_FINE_LOCATION;
21 import static android.Manifest.permission.READ_CONTACTS;
22 import static android.Manifest.permission.WRITE_CONTACTS;
23 import static android.app.AppOpsManager.MODE_ALLOWED;
24 import static android.app.AppOpsManager.MODE_FOREGROUND;
25 import static android.app.AppOpsManager.MODE_IGNORED;
26 import static android.app.AppOpsManager.permissionToOp;
27 import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
28 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
29 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
30 import static android.content.pm.PackageManager.PERMISSION_DENIED;
31 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
32 import static android.permission.cts.PermissionUtils.grantPermission;
33 
34 import static com.android.compatibility.common.util.BackupUtils.LOCAL_TRANSPORT_TOKEN;
35 import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
36 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
37 
38 import static junit.framework.Assert.assertEquals;
39 import static junit.framework.Assert.assertFalse;
40 import static junit.framework.Assert.assertTrue;
41 
42 import android.app.AppOpsManager;
43 import android.content.Context;
44 import android.os.ParcelFileDescriptor;
45 
46 import androidx.annotation.NonNull;
47 import androidx.test.InstrumentationRegistry;
48 import androidx.test.runner.AndroidJUnit4;
49 
50 import com.android.compatibility.common.util.BackupUtils;
51 import com.android.compatibility.common.util.ShellUtils;
52 import com.android.modules.utils.build.SdkLevel;
53 
54 import org.junit.Before;
55 import org.junit.Test;
56 import org.junit.runner.RunWith;
57 
58 import java.io.IOException;
59 import java.io.InputStream;
60 
61 /**
62  * Verifies that restored permissions are the same with backup value.
63  *
64  * @see com.android.packageinstaller.permission.service.BackupHelper
65  */
66 @RunWith(AndroidJUnit4.class)
67 public class PermissionTest extends BaseBackupCtsTest {
68 
69     /** The name of the package of the apps under test */
70     private static final String APP = "android.backup.permission";
71     private static final String APP22 = "android.backup.permission22";
72 
73     /** The apk of the packages */
74     private static final String APK_PATH = "/data/local/tmp/cts/backup/";
75     private static final String APP_APK = APK_PATH + "CtsPermissionBackupApp.apk";
76     private static final String APP22_APK = APK_PATH + "CtsPermissionBackupApp22.apk";
77 
78     /** The name of the package for backup */
79     private static final String ANDROID_PACKAGE = "android";
80 
81     private static final Context sContext = InstrumentationRegistry.getTargetContext();
82     private static final long TIMEOUT_MILLIS = 10000;
83 
84     private BackupUtils mBackupUtils =
85             new BackupUtils() {
86                 @Override
87                 protected InputStream executeShellCommand(String command) throws IOException {
88                     ParcelFileDescriptor pfd =
89                             mInstrumentation.getUiAutomation().executeShellCommand(command);
90                     return new ParcelFileDescriptor.AutoCloseInputStream(pfd);
91                 }
92             };
93 
94     @Before
setUp()95     public void setUp() throws Exception {
96         super.setUp();
97 
98         resetApp(APP);
99         resetApp(APP22);
100     }
101 
102     /**
103      * Test backup and restore of regular runtime permission.
104      */
105     @Test
testGrantDeniedRuntimePermission()106     public void testGrantDeniedRuntimePermission() throws Exception {
107         if (!isBackupSupported()) {
108             return;
109         }
110         grantPermission(APP, ACCESS_FINE_LOCATION);
111 
112         mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
113         resetApp(APP);
114         mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE);
115 
116         eventually(() -> {
117             assertEquals(PERMISSION_GRANTED, checkPermission(APP, ACCESS_FINE_LOCATION));
118             assertEquals(PERMISSION_DENIED, checkPermission(APP, READ_CONTACTS));
119         });
120     }
121 
122     /**
123      * Test backup and restore of pre-M regular runtime permission.
124      */
125     @Test
testGrantDeniedRuntimePermission22()126     public void testGrantDeniedRuntimePermission22() throws Exception {
127         if (!isBackupSupported()) {
128             return;
129         }
130         setAppOp(APP22, READ_CONTACTS, MODE_IGNORED);
131 
132         mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
133         resetApp(APP22);
134         mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE);
135 
136         eventually(() -> {
137             assertEquals(MODE_IGNORED, getAppOp(APP22, READ_CONTACTS));
138             assertEquals(MODE_ALLOWED, getAppOp(APP22, ACCESS_FINE_LOCATION));
139         });
140     }
141 
142     /**
143      * Test backup and restore of foreground runtime permission.
144      */
145     @Test
testNoTriStateRuntimePermission()146     public void testNoTriStateRuntimePermission() throws Exception {
147         if (!isBackupSupported()) {
148             return;
149         }
150         // Set a marker
151         grantPermission(APP, WRITE_CONTACTS);
152 
153         // revoked is the default state. Hence mark the permissions as user set, so the permissions
154         // are even backed up
155         setFlag(APP, ACCESS_FINE_LOCATION, FLAG_PERMISSION_USER_SET);
156         setFlag(APP, ACCESS_BACKGROUND_LOCATION, FLAG_PERMISSION_USER_SET);
157 
158         mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
159         resetApp(APP);
160         mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE);
161 
162         eventually(() -> {
163             // Wait until marker is set
164             assertEquals(PERMISSION_GRANTED, checkPermission(APP, WRITE_CONTACTS));
165 
166             assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_FINE_LOCATION));
167             assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_BACKGROUND_LOCATION));
168             assertEquals(MODE_IGNORED, getAppOp(APP, ACCESS_FINE_LOCATION));
169         });
170     }
171 
172     /**
173      * Test backup and restore of foreground runtime permission.
174      */
175     @Test
testNoTriStateRuntimePermission22()176     public void testNoTriStateRuntimePermission22() throws Exception {
177         if (!isBackupSupported()) {
178             return;
179         }
180         setAppOp(APP22, ACCESS_FINE_LOCATION, MODE_IGNORED);
181 
182         mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
183         resetApp(APP22);
184         mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE);
185 
186         eventually(() -> assertEquals(MODE_IGNORED, getAppOp(APP22, ACCESS_FINE_LOCATION)));
187     }
188 
189     /**
190      * Test backup and restore of foreground runtime permission.
191      */
192     @Test
testGrantForegroundRuntimePermission()193     public void testGrantForegroundRuntimePermission() throws Exception {
194         if (!isBackupSupported()) {
195             return;
196         }
197         grantPermission(APP, ACCESS_FINE_LOCATION);
198 
199         // revoked is the default state. Hence mark the permission as user set, so the permissions
200         // are even backed up
201         setFlag(APP, ACCESS_BACKGROUND_LOCATION, FLAG_PERMISSION_USER_SET);
202 
203         mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
204         resetApp(APP);
205         mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE);
206 
207         eventually(() -> {
208             assertEquals(PERMISSION_GRANTED, checkPermission(APP, ACCESS_FINE_LOCATION));
209             assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_BACKGROUND_LOCATION));
210             assertEquals(MODE_FOREGROUND, getAppOp(APP, ACCESS_FINE_LOCATION));
211         });
212     }
213 
214     /**
215      * Test backup and restore of foreground runtime permission.
216      *
217      * Comment out the test since it's a JUnit 3 test which doesn't support @Ignore
218      * TODO: b/178522459 to fix the test once the foundamental issue has been fixed.
219      */
220 //    public void testGrantForegroundRuntimePermission22() throws Exception {
221 //        if (!isBackupSupported()) {
222 //            return;
223 //        }
224 //        setAppOp(APP22, ACCESS_FINE_LOCATION, MODE_FOREGROUND);
225 //
226 //        mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
227 //        resetApp(APP22);
228 //        mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE);
229 //
230 //        eventually(() -> assertEquals(MODE_FOREGROUND, getAppOp(APP22, ACCESS_FINE_LOCATION)));
231 //    }
232 
233     /**
234      * Test backup and restore of foreground runtime permission.
235      */
236     @Test
testGrantForegroundAndBackgroundRuntimePermission()237     public void testGrantForegroundAndBackgroundRuntimePermission() throws Exception {
238         if (!isBackupSupported()) {
239             return;
240         }
241         grantPermission(APP, ACCESS_FINE_LOCATION);
242         grantPermission(APP, ACCESS_BACKGROUND_LOCATION);
243 
244         mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
245         resetApp(APP);
246         mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE);
247 
248         eventually(() -> {
249             assertEquals(PERMISSION_GRANTED, checkPermission(APP, ACCESS_FINE_LOCATION));
250             assertEquals(PERMISSION_GRANTED, checkPermission(APP, ACCESS_BACKGROUND_LOCATION));
251             assertEquals(MODE_ALLOWED, getAppOp(APP, ACCESS_FINE_LOCATION));
252         });
253     }
254 
255     /**
256      * Test backup and restore of foreground runtime permission.
257      */
258     @Test
testGrantForegroundAndBackgroundRuntimePermission22()259     public void testGrantForegroundAndBackgroundRuntimePermission22() throws Exception {
260         if (!isBackupSupported()) {
261             return;
262         }
263         // Set a marker
264         setAppOp(APP22, WRITE_CONTACTS, MODE_IGNORED);
265 
266         mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
267         resetApp(APP22);
268         mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE);
269 
270         eventually(() -> {
271             // Wait for marker
272             assertEquals(MODE_IGNORED, getAppOp(APP22, WRITE_CONTACTS));
273 
274             assertEquals(MODE_ALLOWED, getAppOp(APP22, ACCESS_FINE_LOCATION));
275         });
276     }
277 
278     /**
279      * Restore if the permission was reviewed
280      */
281     @Test
testRestorePermReviewed()282     public void testRestorePermReviewed() throws Exception {
283         if (!isBackupSupported()) {
284             return;
285         }
286         clearFlag(APP22, WRITE_CONTACTS, FLAG_PERMISSION_REVIEW_REQUIRED);
287 
288         mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
289         resetApp(APP22);
290         mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE);
291 
292         eventually(() -> assertFalse(
293                 isFlagSet(APP22, WRITE_CONTACTS, FLAG_PERMISSION_REVIEW_REQUIRED)));
294     }
295 
296     /**
297      * Restore if the permission was user set
298      */
299     @Test
testRestoreUserSet()300     public void testRestoreUserSet() throws Exception {
301         if (!isBackupSupported()) {
302             return;
303         }
304         setFlag(APP, WRITE_CONTACTS, FLAG_PERMISSION_USER_SET);
305 
306         mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
307         resetApp(APP);
308         mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE);
309 
310         eventually(() -> assertTrue(isFlagSet(APP, WRITE_CONTACTS, FLAG_PERMISSION_USER_SET)));
311     }
312 
313     /**
314      * Restore if the permission was user fixed
315      */
316     @Test
testRestoreUserFixed()317     public void testRestoreUserFixed() throws Exception {
318         if (!isBackupSupported()) {
319             return;
320         }
321         setFlag(APP, WRITE_CONTACTS, FLAG_PERMISSION_USER_FIXED);
322 
323         mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
324         resetApp(APP);
325         mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE);
326 
327         eventually(() -> assertTrue(isFlagSet(APP, WRITE_CONTACTS, FLAG_PERMISSION_USER_FIXED)));
328     }
329 
330     /**
331      * Restoring of a flag should not grant the permission
332      */
333     @Test
testRestoreOfFlagDoesNotGrantPermission()334     public void testRestoreOfFlagDoesNotGrantPermission() throws Exception {
335         if (!isBackupSupported()) {
336             return;
337         }
338         setFlag(APP, WRITE_CONTACTS, FLAG_PERMISSION_USER_FIXED);
339 
340         mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
341         resetApp(APP);
342         mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE);
343 
344         eventually(() -> assertEquals(PERMISSION_DENIED, checkPermission(APP, WRITE_CONTACTS)));
345     }
346 
347     /**
348      * Test backup and delayed restore of regular runtime permission.
349      */
350     @Test
testDelayedRestore()351     public void testDelayedRestore() throws Exception {
352         if (!isBackupSupported()) {
353             return;
354         }
355         grantPermission(APP, ACCESS_FINE_LOCATION);
356 
357         setAppOp(APP22, READ_CONTACTS, MODE_IGNORED);
358 
359         mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
360 
361         uninstall(APP);
362         uninstall(APP22);
363 
364         try {
365             mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE);
366 
367             install(APP_APK);
368 
369             eventually(() -> assertEquals(PERMISSION_GRANTED,
370                     checkPermission(APP, ACCESS_FINE_LOCATION)));
371 
372             install(APP22_APK);
373 
374             eventually(() -> assertEquals(MODE_IGNORED, getAppOp(APP22, READ_CONTACTS)));
375         } finally {
376             install(APP_APK);
377             install(APP22_APK);
378         }
379     }
380 
install(String apk)381     private void install(String apk) {
382         ShellUtils.runShellCommand("pm install -r "
383                 + (SdkLevel.isAtLeastU() ? "--bypass-low-target-sdk-block " : "")
384                 + apk);
385     }
386 
uninstall(String packageName)387     private void uninstall(String packageName) {
388         ShellUtils.runShellCommand("pm uninstall " + packageName);
389     }
390 
resetApp(String packageName)391     private void resetApp(String packageName) {
392         ShellUtils.runShellCommand("pm clear " + packageName);
393         ShellUtils.runShellCommand("appops reset " + packageName);
394     }
395 
396     /**
397      * Make sure that a {@link Runnable} eventually finishes without throwing a {@link
398      * Exception}.
399      *
400      * @param r The {@link Runnable} to run.
401      */
eventually(@onNull Runnable r)402     public static void eventually(@NonNull Runnable r) {
403         long start = System.currentTimeMillis();
404 
405         while (true) {
406             try {
407                 r.run();
408                 return;
409             } catch (Throwable e) {
410                 if (System.currentTimeMillis() - start < TIMEOUT_MILLIS) {
411                     try {
412                         Thread.sleep(100);
413                     } catch (InterruptedException ignored) {
414                         throw new RuntimeException(e);
415                     }
416                 } else {
417                     throw e;
418                 }
419             }
420         }
421     }
422 
setFlag(String app, String permission, int flag)423     private void setFlag(String app, String permission, int flag) {
424         runWithShellPermissionIdentity(
425                 () -> sContext.getPackageManager().updatePermissionFlags(permission, app,
426                         flag, flag, sContext.getUser()));
427     }
428 
clearFlag(String app, String permission, int flag)429     private void clearFlag(String app, String permission, int flag) {
430         runWithShellPermissionIdentity(
431                 () -> sContext.getPackageManager().updatePermissionFlags(permission, app,
432                         flag, 0, sContext.getUser()));
433     }
434 
isFlagSet(String app, String permission, int flag)435     private boolean isFlagSet(String app, String permission, int flag) {
436         try {
437             return (callWithShellPermissionIdentity(
438                     () -> sContext.getPackageManager().getPermissionFlags(permission, app,
439                             sContext.getUser())) & flag) == flag;
440         } catch (Exception e) {
441             throw new RuntimeException(e);
442         }
443     }
444 
checkPermission(String app, String permission)445     private int checkPermission(String app, String permission) {
446         return sContext.getPackageManager().checkPermission(permission, app);
447     }
448 
setAppOp(String app, String permission, int mode)449     private void setAppOp(String app, String permission, int mode) {
450         runWithShellPermissionIdentity(
451                 () -> sContext.getSystemService(AppOpsManager.class).setUidMode(
452                         permissionToOp(permission),
453                         sContext.getPackageManager().getPackageUid(app, 0), mode));
454     }
455 
getAppOp(String app, String permission)456     private int getAppOp(String app, String permission) {
457         try {
458             return callWithShellPermissionIdentity(
459                     () -> sContext.getSystemService(AppOpsManager.class).unsafeCheckOpRaw(
460                             permissionToOp(permission),
461                             sContext.getPackageManager().getPackageUid(app, 0), app));
462         } catch (Exception e) {
463             throw new RuntimeException(e);
464         }
465     }
466 }
467