1 /* 2 * Copyright (C) 2019 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.packageinstaller.permission.service; 18 19 import static android.content.Context.MODE_PRIVATE; 20 import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED; 21 import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED; 22 import static android.content.pm.PackageManager.GET_PERMISSIONS; 23 import static android.util.Xml.newSerializer; 24 25 import static com.android.packageinstaller.Constants.DELAYED_RESTORE_PERMISSIONS_FILE; 26 27 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; 28 import static org.xmlpull.v1.XmlPullParser.END_TAG; 29 import static org.xmlpull.v1.XmlPullParser.START_TAG; 30 31 import static java.nio.charset.StandardCharsets.UTF_8; 32 33 import android.content.Context; 34 import android.content.pm.PackageInfo; 35 import android.content.pm.PackageManager; 36 import android.os.Build; 37 import android.os.UserHandle; 38 import android.permission.PermissionManager; 39 import android.permission.PermissionManager.SplitPermissionInfo; 40 import android.util.Log; 41 import android.util.Xml; 42 43 import androidx.annotation.NonNull; 44 import androidx.annotation.Nullable; 45 import androidx.core.os.BuildCompat; 46 47 import com.android.packageinstaller.Constants; 48 import com.android.packageinstaller.permission.model.AppPermissionGroup; 49 import com.android.packageinstaller.permission.model.AppPermissions; 50 import com.android.packageinstaller.permission.model.Permission; 51 52 import org.xmlpull.v1.XmlPullParser; 53 import org.xmlpull.v1.XmlPullParserException; 54 import org.xmlpull.v1.XmlSerializer; 55 56 import java.io.FileInputStream; 57 import java.io.IOException; 58 import java.io.OutputStream; 59 import java.util.ArrayList; 60 import java.util.List; 61 62 /** 63 * Helper for creating and restoring permission backups. 64 */ 65 public class BackupHelper { 66 private static final String LOG_TAG = BackupHelper.class.getSimpleName(); 67 68 private static final String TAG_PERMISSION_BACKUP = "perm-grant-backup"; 69 private static final String ATTR_PLATFORM_VERSION = "version"; 70 71 private static final String TAG_ALL_GRANTS = "rt-grants"; 72 73 private static final String TAG_GRANT = "grant"; 74 private static final String ATTR_PACKAGE_NAME = "pkg"; 75 76 private static final String TAG_PERMISSION = "perm"; 77 private static final String ATTR_PERMISSION_NAME = "name"; 78 private static final String ATTR_IS_GRANTED = "g"; 79 private static final String ATTR_USER_SET = "set"; 80 private static final String ATTR_USER_FIXED = "fixed"; 81 private static final String ATTR_WAS_REVIEWED = "was-reviewed"; 82 83 /** Flags of permissions to <u>not</u> back up */ 84 private static final int SYSTEM_RUNTIME_GRANT_MASK = FLAG_PERMISSION_POLICY_FIXED 85 | FLAG_PERMISSION_SYSTEM_FIXED; 86 87 /** Make sure only one user can change the delayed permissions at a time */ 88 private static final Object sLock = new Object(); 89 90 private final Context mContext; 91 92 /** 93 * Create a new backup utils for a user. 94 * 95 * @param context A context to use 96 * @param user The user that is backed up / restored 97 */ BackupHelper(@onNull Context context, @NonNull UserHandle user)98 public BackupHelper(@NonNull Context context, @NonNull UserHandle user) { 99 try { 100 mContext = context.createPackageContextAsUser(context.getPackageName(), 0, user); 101 } catch (PackageManager.NameNotFoundException doesNotHappen) { 102 throw new IllegalStateException(); 103 } 104 } 105 106 /** 107 * Forward parser and skip everything up to the end of the current tag. 108 * 109 * @param parser The parser to forward 110 */ skipToEndOfTag(@onNull XmlPullParser parser)111 private static void skipToEndOfTag(@NonNull XmlPullParser parser) 112 throws IOException, XmlPullParserException { 113 int numOpenTags = 1; 114 while (numOpenTags > 0) { 115 switch (parser.next()) { 116 case START_TAG: 117 numOpenTags++; 118 break; 119 case END_TAG: 120 numOpenTags--; 121 break; 122 default: 123 // ignore 124 } 125 } 126 } 127 128 /** 129 * Forward parser to a given direct sub-tag. 130 * 131 * @param parser The parser to forward 132 * @param tag The tag to search for 133 */ skipToTag(@onNull XmlPullParser parser, @NonNull String tag)134 private void skipToTag(@NonNull XmlPullParser parser, @NonNull String tag) 135 throws IOException, XmlPullParserException { 136 int type; 137 do { 138 type = parser.next(); 139 140 switch (type) { 141 case START_TAG: 142 if (!parser.getName().equals(tag)) { 143 skipToEndOfTag(parser); 144 } 145 146 return; 147 } 148 } while (type != END_DOCUMENT); 149 } 150 151 /** 152 * Read a XML file and return the packages stored in it. 153 * 154 * @param parser The file to read 155 * 156 * @return The packages in this file 157 */ parseFromXml(@onNull XmlPullParser parser)158 private @NonNull ArrayList<BackupPackageState> parseFromXml(@NonNull XmlPullParser parser) 159 throws IOException, XmlPullParserException { 160 ArrayList<BackupPackageState> pkgStates = new ArrayList<>(); 161 162 skipToTag(parser, TAG_PERMISSION_BACKUP); 163 164 int backupPlatformVersion; 165 try { 166 backupPlatformVersion = Integer.parseInt( 167 parser.getAttributeValue(null, ATTR_PLATFORM_VERSION)); 168 } catch (NumberFormatException ignored) { 169 // Platforms P and before did not store the platform version 170 backupPlatformVersion = Build.VERSION_CODES.P; 171 } 172 173 skipToTag(parser, TAG_ALL_GRANTS); 174 175 if (parser.getEventType() != START_TAG && !parser.getName().equals(TAG_ALL_GRANTS)) { 176 throw new XmlPullParserException("Could not find " + TAG_PERMISSION_BACKUP + " > " 177 + TAG_ALL_GRANTS); 178 } 179 180 // Read packages to restore from xml 181 int type; 182 do { 183 type = parser.next(); 184 185 switch (type) { 186 case START_TAG: 187 switch (parser.getName()) { 188 case TAG_GRANT: 189 try { 190 pkgStates.add(BackupPackageState.parseFromXml(parser, mContext, 191 backupPlatformVersion)); 192 } catch (XmlPullParserException e) { 193 Log.e(LOG_TAG, "Could not parse permissions ", e); 194 skipToEndOfTag(parser); 195 } 196 break; 197 default: 198 // ignore tag 199 Log.w(LOG_TAG, "Found unexpected tag " + parser.getName() 200 + " during restore"); 201 skipToEndOfTag(parser); 202 } 203 } 204 } while (type != END_DOCUMENT); 205 206 return pkgStates; 207 } 208 209 /** 210 * Try to restore the permission state from XML. 211 * 212 * <p>If some apps could not be restored, the leftover apps are written to 213 * {@link Constants#DELAYED_RESTORE_PERMISSIONS_FILE}. 214 * 215 * @param parser The xml to read 216 */ restoreState(@onNull XmlPullParser parser)217 void restoreState(@NonNull XmlPullParser parser) throws IOException, XmlPullParserException { 218 ArrayList<BackupPackageState> pkgStates = parseFromXml(parser); 219 220 ArrayList<BackupPackageState> packagesToRestoreLater = new ArrayList<>(); 221 int numPkgStates = pkgStates.size(); 222 if (numPkgStates > 0) { 223 // Try to restore packages 224 for (int i = 0; i < numPkgStates; i++) { 225 BackupPackageState pkgState = pkgStates.get(i); 226 227 PackageInfo pkgInfo; 228 try { 229 pkgInfo = mContext.getPackageManager().getPackageInfo(pkgState.mPackageName, 230 GET_PERMISSIONS); 231 } catch (PackageManager.NameNotFoundException ignored) { 232 packagesToRestoreLater.add(pkgState); 233 continue; 234 } 235 236 pkgState.restore(mContext, pkgInfo); 237 } 238 } 239 240 synchronized (sLock) { 241 writeDelayedStorePkgsLocked(packagesToRestoreLater); 242 } 243 } 244 245 /** 246 * Write a xml file for the given packages. 247 * 248 * @param serializer The file to write to 249 * @param pkgs The packages to write 250 */ writePkgsAsXml(@onNull XmlSerializer serializer, @NonNull ArrayList<BackupPackageState> pkgs)251 private static void writePkgsAsXml(@NonNull XmlSerializer serializer, 252 @NonNull ArrayList<BackupPackageState> pkgs) throws IOException { 253 serializer.startDocument(null, true); 254 255 serializer.startTag(null, TAG_PERMISSION_BACKUP); 256 257 if (BuildCompat.isAtLeastQ()) { 258 // STOPSHIP: Remove compatibility code once Q SDK level is declared 259 serializer.attribute(null, ATTR_PLATFORM_VERSION, 260 Integer.valueOf(Build.VERSION_CODES.Q).toString()); 261 } else { 262 serializer.attribute(null, ATTR_PLATFORM_VERSION, 263 Integer.valueOf(Build.VERSION.SDK_INT).toString()); 264 } 265 266 serializer.startTag(null, TAG_ALL_GRANTS); 267 268 int numPkgs = pkgs.size(); 269 for (int i = 0; i < numPkgs; i++) { 270 BackupPackageState packageState = pkgs.get(i); 271 272 if (packageState != null) { 273 packageState.writeAsXml(serializer); 274 } 275 } 276 277 serializer.endTag(null, TAG_ALL_GRANTS); 278 serializer.endTag(null, TAG_PERMISSION_BACKUP); 279 280 serializer.endDocument(); 281 } 282 283 /** 284 * Update the {@link Constants#DELAYED_RESTORE_PERMISSIONS_FILE} to contain the 285 * {@code packagesToRestoreLater}. 286 * 287 * @param packagesToRestoreLater The new pkgs in the delayed restore file 288 */ writeDelayedStorePkgsLocked( @onNull ArrayList<BackupPackageState> packagesToRestoreLater)289 private void writeDelayedStorePkgsLocked( 290 @NonNull ArrayList<BackupPackageState> packagesToRestoreLater) { 291 try (OutputStream delayedRestoreData = mContext.openFileOutput( 292 DELAYED_RESTORE_PERMISSIONS_FILE, MODE_PRIVATE)) { 293 XmlSerializer serializer = newSerializer(); 294 serializer.setOutput(delayedRestoreData, UTF_8.name()); 295 296 writePkgsAsXml(serializer, packagesToRestoreLater); 297 serializer.flush(); 298 } catch (IOException e) { 299 Log.e(LOG_TAG, "Could not remember which packages still need to be restored", e); 300 } 301 } 302 303 /** 304 * Write the state of all packages as XML. 305 * 306 * @param serializer The xml to write to 307 */ writeState(@onNull XmlSerializer serializer)308 void writeState(@NonNull XmlSerializer serializer) throws IOException { 309 List<PackageInfo> pkgs = mContext.getPackageManager().getInstalledPackages( 310 GET_PERMISSIONS); 311 ArrayList<BackupPackageState> backupPkgs = new ArrayList<>(); 312 313 int numPkgs = pkgs.size(); 314 for (int i = 0; i < numPkgs; i++) { 315 BackupPackageState packageState = BackupPackageState.fromAppPermissions(mContext, 316 pkgs.get(i)); 317 318 if (packageState != null) { 319 backupPkgs.add(packageState); 320 } 321 } 322 323 writePkgsAsXml(serializer, backupPkgs); 324 } 325 326 /** 327 * Restore delayed permission state for a package (if delayed during {@link #restoreState}). 328 * 329 * @param packageName The package to be restored 330 * 331 * @return {@code true} if there is still delayed backup left 332 */ restoreDelayedState(@onNull String packageName)333 boolean restoreDelayedState(@NonNull String packageName) { 334 synchronized (sLock) { 335 ArrayList<BackupPackageState> packagesToRestoreLater; 336 337 try (FileInputStream delayedRestoreData = 338 mContext.openFileInput(DELAYED_RESTORE_PERMISSIONS_FILE)) { 339 XmlPullParser parser = Xml.newPullParser(); 340 parser.setInput(delayedRestoreData, UTF_8.name()); 341 342 packagesToRestoreLater = parseFromXml(parser); 343 } catch (IOException | XmlPullParserException e) { 344 Log.e(LOG_TAG, "Could not parse delayed permissions", e); 345 return false; 346 } 347 348 PackageInfo pkgInfo = null; 349 try { 350 pkgInfo = mContext.getPackageManager().getPackageInfo(packageName, GET_PERMISSIONS); 351 } catch (PackageManager.NameNotFoundException e) { 352 Log.e(LOG_TAG, "Could not restore delayed permissions for " + packageName, e); 353 } 354 355 if (pkgInfo != null) { 356 int numPkgs = packagesToRestoreLater.size(); 357 for (int i = 0; i < numPkgs; i++) { 358 BackupPackageState pkgState = packagesToRestoreLater.get(i); 359 360 if (pkgState.mPackageName.equals(packageName)) { 361 pkgState.restore(mContext, pkgInfo); 362 packagesToRestoreLater.remove(i); 363 364 writeDelayedStorePkgsLocked(packagesToRestoreLater); 365 366 break; 367 } 368 } 369 } 370 371 return packagesToRestoreLater.size() > 0; 372 } 373 } 374 375 /** 376 * State that needs to be backed up for a permission. 377 */ 378 private static class BackupPermissionState { 379 private final @NonNull String mPermissionName; 380 private final boolean mIsGranted; 381 private final boolean mIsUserSet; 382 private final boolean mIsUserFixed; 383 private final boolean mWasReviewed; 384 BackupPermissionState(@onNull String permissionName, boolean isGranted, boolean isUserSet, boolean isUserFixed, boolean wasReviewed)385 private BackupPermissionState(@NonNull String permissionName, boolean isGranted, 386 boolean isUserSet, boolean isUserFixed, boolean wasReviewed) { 387 mPermissionName = permissionName; 388 mIsGranted = isGranted; 389 mIsUserSet = isUserSet; 390 mIsUserFixed = isUserFixed; 391 mWasReviewed = wasReviewed; 392 } 393 394 /** 395 * Parse a package state from XML. 396 * 397 * @param parser The data to read 398 * @param context a context to use 399 * @param backupPlatformVersion The platform version the backup was created on 400 * 401 * @return The state 402 */ parseFromXml(@onNull XmlPullParser parser, @NonNull Context context, int backupPlatformVersion)403 static @NonNull List<BackupPermissionState> parseFromXml(@NonNull XmlPullParser parser, 404 @NonNull Context context, int backupPlatformVersion) 405 throws XmlPullParserException { 406 String permName = parser.getAttributeValue(null, ATTR_PERMISSION_NAME); 407 if (permName == null) { 408 throw new XmlPullParserException("Found " + TAG_PERMISSION + " without " 409 + ATTR_PERMISSION_NAME); 410 } 411 412 ArrayList<String> expandedPermissions = new ArrayList<>(); 413 expandedPermissions.add(permName); 414 415 List<SplitPermissionInfo> splitPerms = context.getSystemService( 416 PermissionManager.class).getSplitPermissions(); 417 418 // Expand the properties to permissions that were split between the platform version the 419 // backup was taken and the current version. 420 int numSplitPerms = splitPerms.size(); 421 for (int i = 0; i < numSplitPerms; i++) { 422 SplitPermissionInfo splitPerm = splitPerms.get(i); 423 if (backupPlatformVersion < splitPerm.getTargetSdk() 424 && permName.equals(splitPerm.getSplitPermission())) { 425 expandedPermissions.addAll(splitPerm.getNewPermissions()); 426 } 427 } 428 429 ArrayList<BackupPermissionState> parsedPermissions = new ArrayList<>( 430 expandedPermissions.size()); 431 int numExpandedPerms = expandedPermissions.size(); 432 for (int i = 0; i < numExpandedPerms; i++) { 433 parsedPermissions.add(new BackupPermissionState(expandedPermissions.get(i), 434 "true".equals(parser.getAttributeValue(null, ATTR_IS_GRANTED)), 435 "true".equals(parser.getAttributeValue(null, ATTR_USER_SET)), 436 "true".equals(parser.getAttributeValue(null, ATTR_USER_FIXED)), 437 "true".equals(parser.getAttributeValue(null, ATTR_WAS_REVIEWED)))); 438 } 439 440 return parsedPermissions; 441 } 442 443 /** 444 * Is the permission granted, also considering the app-op. 445 * 446 * <p>This does not consider the review-required state of the permission. 447 * 448 * @param perm The permission that might be granted 449 * 450 * @return {@code true} iff the permission and app-op is granted 451 */ isPermGrantedIncludingAppOp(@onNull Permission perm)452 private static boolean isPermGrantedIncludingAppOp(@NonNull Permission perm) { 453 return perm.isGranted() && (!perm.affectsAppOp() || perm.isAppOpAllowed()); 454 } 455 456 /** 457 * Get the state of a permission to back up. 458 * 459 * @param perm The permission to back up 460 * @param appSupportsRuntimePermissions If the app supports runtimePermissions 461 * 462 * @return The state to back up or {@code null} if the permission does not need to be 463 * backed up. 464 */ fromPermission(@onNull Permission perm, boolean appSupportsRuntimePermissions)465 private static @Nullable BackupPermissionState fromPermission(@NonNull Permission perm, 466 boolean appSupportsRuntimePermissions) { 467 int grantFlags = perm.getFlags(); 468 469 if ((grantFlags & SYSTEM_RUNTIME_GRANT_MASK) != 0) { 470 return null; 471 } 472 473 if (!perm.isUserSet() && perm.isGrantedByDefault()) { 474 return null; 475 } 476 477 boolean permissionWasReviewed; 478 boolean isNotInDefaultGrantState; 479 if (appSupportsRuntimePermissions) { 480 isNotInDefaultGrantState = isPermGrantedIncludingAppOp(perm); 481 permissionWasReviewed = false; 482 } else { 483 isNotInDefaultGrantState = !isPermGrantedIncludingAppOp(perm); 484 permissionWasReviewed = !perm.isReviewRequired(); 485 } 486 487 if (isNotInDefaultGrantState || perm.isUserSet() || perm.isUserFixed() 488 || permissionWasReviewed) { 489 return new BackupPermissionState(perm.getName(), isPermGrantedIncludingAppOp(perm), 490 perm.isUserSet(), perm.isUserFixed(), permissionWasReviewed); 491 } else { 492 return null; 493 } 494 } 495 496 /** 497 * Get the states of all permissions of a group to back up. 498 * 499 * @param group The group of the permissions to back up 500 * 501 * @return The state to back up. Empty list if no permissions in the group need to be backed 502 * up 503 */ fromPermissionGroup( @onNull AppPermissionGroup group)504 static @NonNull ArrayList<BackupPermissionState> fromPermissionGroup( 505 @NonNull AppPermissionGroup group) { 506 ArrayList<BackupPermissionState> permissionsToRestore = new ArrayList<>(); 507 List<Permission> perms = group.getPermissions(); 508 509 boolean appSupportsRuntimePermissions = 510 group.getApp().applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M; 511 512 int numPerms = perms.size(); 513 for (int i = 0; i < numPerms; i++) { 514 BackupPermissionState permState = fromPermission(perms.get(i), 515 appSupportsRuntimePermissions); 516 if (permState != null) { 517 permissionsToRestore.add(permState); 518 } 519 } 520 521 return permissionsToRestore; 522 } 523 524 /** 525 * Write this state as XML. 526 * 527 * @param serializer The file to write to 528 */ writeAsXml(@onNull XmlSerializer serializer)529 void writeAsXml(@NonNull XmlSerializer serializer) throws IOException { 530 serializer.startTag(null, TAG_PERMISSION); 531 532 serializer.attribute(null, ATTR_PERMISSION_NAME, mPermissionName); 533 534 if (mIsGranted) { 535 serializer.attribute(null, ATTR_IS_GRANTED, "true"); 536 } 537 538 if (mIsUserSet) { 539 serializer.attribute(null, ATTR_USER_SET, "true"); 540 } 541 542 if (mIsUserFixed) { 543 serializer.attribute(null, ATTR_USER_FIXED, "true"); 544 } 545 546 if (mWasReviewed) { 547 serializer.attribute(null, ATTR_WAS_REVIEWED, "true"); 548 } 549 550 serializer.endTag(null, TAG_PERMISSION); 551 } 552 553 /** 554 * Restore this permission state. 555 * 556 * @param appPerms The {@link AppPermissions} to restore the state to 557 * @param restoreBackgroundPerms if {@code true} only restore background permissions, 558 * if {@code false} do not restore background permissions 559 */ restore(@onNull AppPermissions appPerms, boolean restoreBackgroundPerms)560 void restore(@NonNull AppPermissions appPerms, boolean restoreBackgroundPerms) { 561 AppPermissionGroup group = appPerms.getGroupForPermission(mPermissionName); 562 if (group == null) { 563 Log.w(LOG_TAG, "Could not find group for " + mPermissionName + " in " 564 + appPerms.getPackageInfo().packageName); 565 return; 566 } 567 568 if (restoreBackgroundPerms != group.isBackgroundGroup()) { 569 return; 570 } 571 572 Permission perm = group.getPermission(mPermissionName); 573 if (mWasReviewed) { 574 perm.unsetReviewRequired(); 575 } 576 577 // Don't grant or revoke fixed permission groups 578 if (group.isSystemFixed() || group.isPolicyFixed()) { 579 return; 580 } 581 582 if (!perm.isUserSet()) { 583 if (mIsGranted) { 584 group.grantRuntimePermissions(mIsUserFixed, 585 new String[]{mPermissionName}); 586 } else { 587 group.revokeRuntimePermissions(mIsUserFixed, 588 new String[]{mPermissionName}); 589 } 590 591 perm.setUserSet(mIsUserSet); 592 } 593 } 594 } 595 596 /** 597 * State that needs to be backed up for a package. 598 */ 599 private static class BackupPackageState { 600 final @NonNull String mPackageName; 601 private final @NonNull ArrayList<BackupPermissionState> mPermissionsToRestore; 602 BackupPackageState(@onNull String packageName, @NonNull ArrayList<BackupPermissionState> permissionsToRestore)603 private BackupPackageState(@NonNull String packageName, 604 @NonNull ArrayList<BackupPermissionState> permissionsToRestore) { 605 mPackageName = packageName; 606 mPermissionsToRestore = permissionsToRestore; 607 } 608 609 /** 610 * Parse a package state from XML. 611 * 612 * @param parser The data to read 613 * @param context a context to use 614 * @param backupPlatformVersion The platform version the backup was created on 615 * 616 * @return The state 617 */ parseFromXml(@onNull XmlPullParser parser, @NonNull Context context, int backupPlatformVersion)618 static @NonNull BackupPackageState parseFromXml(@NonNull XmlPullParser parser, 619 @NonNull Context context, int backupPlatformVersion) 620 throws IOException, XmlPullParserException { 621 String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME); 622 if (packageName == null) { 623 throw new XmlPullParserException("Found " + TAG_GRANT + " without " 624 + ATTR_PACKAGE_NAME); 625 } 626 627 ArrayList<BackupPermissionState> permissionsToRestore = new ArrayList<>(); 628 629 while (true) { 630 switch (parser.next()) { 631 case START_TAG: 632 switch (parser.getName()) { 633 case TAG_PERMISSION: 634 try { 635 permissionsToRestore.addAll( 636 BackupPermissionState.parseFromXml(parser, context, 637 backupPlatformVersion)); 638 } catch (XmlPullParserException e) { 639 Log.e(LOG_TAG, "Could not parse permission for " 640 + packageName, e); 641 } 642 643 skipToEndOfTag(parser); 644 break; 645 default: 646 // ignore tag 647 Log.w(LOG_TAG, "Found unexpected tag " + parser.getName() 648 + " while restoring " + packageName); 649 skipToEndOfTag(parser); 650 } 651 652 break; 653 case END_TAG: 654 return new BackupPackageState(packageName, permissionsToRestore); 655 case END_DOCUMENT: 656 throw new XmlPullParserException("Could not parse state for " 657 + packageName); 658 } 659 } 660 } 661 662 /** 663 * Get the state of a package to back up. 664 * 665 * @param context A context to use 666 * @param pkgInfo The package to back up. 667 * 668 * @return The state to back up or {@code null} if no permission of the package need to be 669 * backed up. 670 */ fromAppPermissions(@onNull Context context, @NonNull PackageInfo pkgInfo)671 static @Nullable BackupPackageState fromAppPermissions(@NonNull Context context, 672 @NonNull PackageInfo pkgInfo) { 673 AppPermissions appPerms = new AppPermissions(context, pkgInfo, false, null); 674 675 ArrayList<BackupPermissionState> permissionsToRestore = new ArrayList<>(); 676 List<AppPermissionGroup> groups = appPerms.getPermissionGroups(); 677 678 int numGroups = groups.size(); 679 for (int groupNum = 0; groupNum < numGroups; groupNum++) { 680 AppPermissionGroup group = groups.get(groupNum); 681 682 permissionsToRestore.addAll(BackupPermissionState.fromPermissionGroup(group)); 683 684 // Background permissions are in a subgroup that is not part of 685 // {@link AppPermission#getPermissionGroups}. Hence add it explicitly here. 686 if (group.getBackgroundPermissions() != null) { 687 permissionsToRestore.addAll(BackupPermissionState.fromPermissionGroup( 688 group.getBackgroundPermissions())); 689 } 690 } 691 692 if (permissionsToRestore.size() == 0) { 693 return null; 694 } 695 696 return new BackupPackageState(pkgInfo.packageName, permissionsToRestore); 697 } 698 699 /** 700 * Write this state as XML. 701 * 702 * @param serializer The file to write to 703 */ writeAsXml(@onNull XmlSerializer serializer)704 void writeAsXml(@NonNull XmlSerializer serializer) throws IOException { 705 if (mPermissionsToRestore.size() == 0) { 706 return; 707 } 708 709 serializer.startTag(null, TAG_GRANT); 710 serializer.attribute(null, ATTR_PACKAGE_NAME, mPackageName); 711 712 int numPerms = mPermissionsToRestore.size(); 713 for (int i = 0; i < numPerms; i++) { 714 mPermissionsToRestore.get(i).writeAsXml(serializer); 715 } 716 717 serializer.endTag(null, TAG_GRANT); 718 } 719 720 /** 721 * Restore this package state. 722 * 723 * @param context A context to use 724 * @param pkgInfo The package to restore. 725 */ restore(@onNull Context context, @NonNull PackageInfo pkgInfo)726 void restore(@NonNull Context context, @NonNull PackageInfo pkgInfo) { 727 AppPermissions appPerms = new AppPermissions(context, pkgInfo, false, true, null); 728 729 // Restore background permissions after foreground permissions as for pre-M apps bg 730 // granted and fg revoked cannot be expressed. 731 int numPerms = mPermissionsToRestore.size(); 732 for (int i = 0; i < numPerms; i++) { 733 mPermissionsToRestore.get(i).restore(appPerms, false); 734 } 735 for (int i = 0; i < numPerms; i++) { 736 mPermissionsToRestore.get(i).restore(appPerms, true); 737 } 738 739 int numGroups = appPerms.getPermissionGroups().size(); 740 for (int i = 0; i < numGroups; i++) { 741 AppPermissionGroup group = appPerms.getPermissionGroups().get(i); 742 743 // Only denied groups can be user fixed 744 if (group.areRuntimePermissionsGranted()) { 745 group.setUserFixed(false); 746 } 747 748 AppPermissionGroup bgGroup = group.getBackgroundPermissions(); 749 if (bgGroup != null) { 750 // Only denied groups can be user fixed 751 if (bgGroup.areRuntimePermissionsGranted()) { 752 bgGroup.setUserFixed(false); 753 } 754 } 755 } 756 757 appPerms.persistChanges(true); 758 } 759 } 760 } 761