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