• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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