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