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