| /* |
| * Copyright (C) 2019 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.packageinstaller.permission.service; |
| |
| import static android.content.Context.MODE_PRIVATE; |
| import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT; |
| import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED; |
| import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED; |
| import static android.content.pm.PackageManager.GET_PERMISSIONS; |
| import static android.util.Xml.newSerializer; |
| |
| import static com.android.packageinstaller.Constants.DELAYED_RESTORE_PERMISSIONS_FILE; |
| |
| import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; |
| import static org.xmlpull.v1.XmlPullParser.END_TAG; |
| import static org.xmlpull.v1.XmlPullParser.START_TAG; |
| |
| import static java.nio.charset.StandardCharsets.UTF_8; |
| |
| import android.content.Context; |
| import android.content.pm.PackageInfo; |
| import android.content.pm.PackageManager; |
| import android.os.Build; |
| import android.os.UserHandle; |
| import android.permission.PermissionManager; |
| import android.permission.PermissionManager.SplitPermissionInfo; |
| import android.util.Log; |
| import android.util.Xml; |
| |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| |
| import com.android.packageinstaller.Constants; |
| import com.android.packageinstaller.permission.model.AppPermissionGroup; |
| import com.android.packageinstaller.permission.model.AppPermissions; |
| import com.android.packageinstaller.permission.model.Permission; |
| |
| import org.xmlpull.v1.XmlPullParser; |
| import org.xmlpull.v1.XmlPullParserException; |
| import org.xmlpull.v1.XmlSerializer; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * Helper for creating and restoring permission backups. |
| */ |
| public class BackupHelper { |
| private static final String LOG_TAG = BackupHelper.class.getSimpleName(); |
| |
| private static final String TAG_PERMISSION_BACKUP = "perm-grant-backup"; |
| private static final String ATTR_PLATFORM_VERSION = "version"; |
| |
| private static final String TAG_ALL_GRANTS = "rt-grants"; |
| |
| private static final String TAG_GRANT = "grant"; |
| private static final String ATTR_PACKAGE_NAME = "pkg"; |
| |
| private static final String TAG_PERMISSION = "perm"; |
| private static final String ATTR_PERMISSION_NAME = "name"; |
| private static final String ATTR_IS_GRANTED = "g"; |
| private static final String ATTR_USER_SET = "set"; |
| private static final String ATTR_USER_FIXED = "fixed"; |
| private static final String ATTR_WAS_REVIEWED = "was-reviewed"; |
| |
| /** Flags of permissions to <u>not</u> back up */ |
| private static final int SYSTEM_RUNTIME_GRANT_MASK = FLAG_PERMISSION_POLICY_FIXED |
| | FLAG_PERMISSION_SYSTEM_FIXED; |
| |
| /** Make sure only one user can change the delayed permissions at a time */ |
| private static final Object sLock = new Object(); |
| |
| private final Context mContext; |
| |
| /** |
| * Create a new backup utils for a user. |
| * |
| * @param context A context to use |
| * @param user The user that is backed up / restored |
| */ |
| public BackupHelper(@NonNull Context context, @NonNull UserHandle user) { |
| try { |
| mContext = context.createPackageContextAsUser(context.getPackageName(), 0, user); |
| } catch (PackageManager.NameNotFoundException doesNotHappen) { |
| throw new IllegalStateException(); |
| } |
| } |
| |
| /** |
| * Forward parser and skip everything up to the end of the current tag. |
| * |
| * @param parser The parser to forward |
| */ |
| private static void skipToEndOfTag(@NonNull XmlPullParser parser) |
| throws IOException, XmlPullParserException { |
| int numOpenTags = 1; |
| while (numOpenTags > 0) { |
| switch (parser.next()) { |
| case START_TAG: |
| numOpenTags++; |
| break; |
| case END_TAG: |
| numOpenTags--; |
| break; |
| default: |
| // ignore |
| } |
| } |
| } |
| |
| /** |
| * Forward parser to a given direct sub-tag. |
| * |
| * @param parser The parser to forward |
| * @param tag The tag to search for |
| */ |
| private void skipToTag(@NonNull XmlPullParser parser, @NonNull String tag) |
| throws IOException, XmlPullParserException { |
| int type; |
| do { |
| type = parser.next(); |
| |
| switch (type) { |
| case START_TAG: |
| if (!parser.getName().equals(tag)) { |
| skipToEndOfTag(parser); |
| } |
| |
| return; |
| } |
| } while (type != END_DOCUMENT); |
| } |
| |
| /** |
| * Read a XML file and return the packages stored in it. |
| * |
| * @param parser The file to read |
| * |
| * @return The packages in this file |
| */ |
| private @NonNull ArrayList<BackupPackageState> parseFromXml(@NonNull XmlPullParser parser) |
| throws IOException, XmlPullParserException { |
| ArrayList<BackupPackageState> pkgStates = new ArrayList<>(); |
| |
| skipToTag(parser, TAG_PERMISSION_BACKUP); |
| |
| int backupPlatformVersion; |
| try { |
| backupPlatformVersion = Integer.parseInt( |
| parser.getAttributeValue(null, ATTR_PLATFORM_VERSION)); |
| } catch (NumberFormatException ignored) { |
| // Platforms P and before did not store the platform version |
| backupPlatformVersion = Build.VERSION_CODES.P; |
| } |
| |
| skipToTag(parser, TAG_ALL_GRANTS); |
| |
| if (parser.getEventType() != START_TAG && !parser.getName().equals(TAG_ALL_GRANTS)) { |
| throw new XmlPullParserException("Could not find " + TAG_PERMISSION_BACKUP + " > " |
| + TAG_ALL_GRANTS); |
| } |
| |
| // Read packages to restore from xml |
| int type; |
| do { |
| type = parser.next(); |
| |
| switch (type) { |
| case START_TAG: |
| switch (parser.getName()) { |
| case TAG_GRANT: |
| try { |
| pkgStates.add(BackupPackageState.parseFromXml(parser, mContext, |
| backupPlatformVersion)); |
| } catch (XmlPullParserException e) { |
| Log.e(LOG_TAG, "Could not parse permissions ", e); |
| skipToEndOfTag(parser); |
| } |
| break; |
| default: |
| // ignore tag |
| Log.w(LOG_TAG, "Found unexpected tag " + parser.getName() |
| + " during restore"); |
| skipToEndOfTag(parser); |
| } |
| } |
| } while (type != END_DOCUMENT); |
| |
| return pkgStates; |
| } |
| |
| /** |
| * Try to restore the permission state from XML. |
| * |
| * <p>If some apps could not be restored, the leftover apps are written to |
| * {@link Constants#DELAYED_RESTORE_PERMISSIONS_FILE}. |
| * |
| * @param parser The xml to read |
| */ |
| void restoreState(@NonNull XmlPullParser parser) throws IOException, XmlPullParserException { |
| ArrayList<BackupPackageState> pkgStates = parseFromXml(parser); |
| |
| ArrayList<BackupPackageState> packagesToRestoreLater = new ArrayList<>(); |
| int numPkgStates = pkgStates.size(); |
| if (numPkgStates > 0) { |
| // Try to restore packages |
| for (int i = 0; i < numPkgStates; i++) { |
| BackupPackageState pkgState = pkgStates.get(i); |
| |
| PackageInfo pkgInfo; |
| try { |
| pkgInfo = mContext.getPackageManager().getPackageInfo(pkgState.mPackageName, |
| GET_PERMISSIONS); |
| } catch (PackageManager.NameNotFoundException ignored) { |
| packagesToRestoreLater.add(pkgState); |
| continue; |
| } |
| |
| pkgState.restore(mContext, pkgInfo); |
| } |
| } |
| |
| synchronized (sLock) { |
| writeDelayedStorePkgsLocked(packagesToRestoreLater); |
| } |
| } |
| |
| /** |
| * Write a xml file for the given packages. |
| * |
| * @param serializer The file to write to |
| * @param pkgs The packages to write |
| */ |
| private static void writePkgsAsXml(@NonNull XmlSerializer serializer, |
| @NonNull ArrayList<BackupPackageState> pkgs) throws IOException { |
| serializer.startDocument(null, true); |
| |
| serializer.startTag(null, TAG_PERMISSION_BACKUP); |
| serializer.attribute(null, ATTR_PLATFORM_VERSION, |
| Integer.valueOf(Build.VERSION.SDK_INT).toString()); |
| |
| serializer.startTag(null, TAG_ALL_GRANTS); |
| |
| int numPkgs = pkgs.size(); |
| for (int i = 0; i < numPkgs; i++) { |
| BackupPackageState packageState = pkgs.get(i); |
| |
| if (packageState != null) { |
| packageState.writeAsXml(serializer); |
| } |
| } |
| |
| serializer.endTag(null, TAG_ALL_GRANTS); |
| serializer.endTag(null, TAG_PERMISSION_BACKUP); |
| |
| serializer.endDocument(); |
| } |
| |
| /** |
| * Update the {@link Constants#DELAYED_RESTORE_PERMISSIONS_FILE} to contain the |
| * {@code packagesToRestoreLater}. |
| * |
| * @param packagesToRestoreLater The new pkgs in the delayed restore file |
| */ |
| private void writeDelayedStorePkgsLocked( |
| @NonNull ArrayList<BackupPackageState> packagesToRestoreLater) { |
| try (OutputStream delayedRestoreData = mContext.openFileOutput( |
| DELAYED_RESTORE_PERMISSIONS_FILE, MODE_PRIVATE)) { |
| XmlSerializer serializer = newSerializer(); |
| serializer.setOutput(delayedRestoreData, UTF_8.name()); |
| |
| writePkgsAsXml(serializer, packagesToRestoreLater); |
| serializer.flush(); |
| } catch (IOException e) { |
| Log.e(LOG_TAG, "Could not remember which packages still need to be restored", e); |
| } |
| } |
| |
| /** |
| * Write the state of all packages as XML. |
| * |
| * @param serializer The xml to write to |
| */ |
| void writeState(@NonNull XmlSerializer serializer) throws IOException { |
| List<PackageInfo> pkgs = mContext.getPackageManager().getInstalledPackages( |
| GET_PERMISSIONS); |
| ArrayList<BackupPackageState> backupPkgs = new ArrayList<>(); |
| |
| int numPkgs = pkgs.size(); |
| for (int i = 0; i < numPkgs; i++) { |
| BackupPackageState packageState = BackupPackageState.fromAppPermissions(mContext, |
| pkgs.get(i)); |
| |
| if (packageState != null) { |
| backupPkgs.add(packageState); |
| } |
| } |
| |
| writePkgsAsXml(serializer, backupPkgs); |
| } |
| |
| /** |
| * Restore delayed permission state for a package (if delayed during {@link #restoreState}). |
| * |
| * @param packageName The package to be restored |
| * |
| * @return {@code true} if there is still delayed backup left |
| */ |
| boolean restoreDelayedState(@NonNull String packageName) { |
| synchronized (sLock) { |
| ArrayList<BackupPackageState> packagesToRestoreLater; |
| |
| try (FileInputStream delayedRestoreData = |
| mContext.openFileInput(DELAYED_RESTORE_PERMISSIONS_FILE)) { |
| XmlPullParser parser = Xml.newPullParser(); |
| parser.setInput(delayedRestoreData, UTF_8.name()); |
| |
| packagesToRestoreLater = parseFromXml(parser); |
| } catch (IOException | XmlPullParserException e) { |
| Log.e(LOG_TAG, "Could not parse delayed permissions", e); |
| return false; |
| } |
| |
| PackageInfo pkgInfo = null; |
| try { |
| pkgInfo = mContext.getPackageManager().getPackageInfo(packageName, GET_PERMISSIONS); |
| } catch (PackageManager.NameNotFoundException e) { |
| Log.e(LOG_TAG, "Could not restore delayed permissions for " + packageName, e); |
| } |
| |
| if (pkgInfo != null) { |
| int numPkgs = packagesToRestoreLater.size(); |
| for (int i = 0; i < numPkgs; i++) { |
| BackupPackageState pkgState = packagesToRestoreLater.get(i); |
| |
| if (pkgState.mPackageName.equals(packageName)) { |
| pkgState.restore(mContext, pkgInfo); |
| packagesToRestoreLater.remove(i); |
| |
| writeDelayedStorePkgsLocked(packagesToRestoreLater); |
| |
| break; |
| } |
| } |
| } |
| |
| return packagesToRestoreLater.size() > 0; |
| } |
| } |
| |
| /** |
| * State that needs to be backed up for a permission. |
| */ |
| private static class BackupPermissionState { |
| private final @NonNull String mPermissionName; |
| private final boolean mIsGranted; |
| private final boolean mIsUserSet; |
| private final boolean mIsUserFixed; |
| private final boolean mWasReviewed; |
| |
| private BackupPermissionState(@NonNull String permissionName, boolean isGranted, |
| boolean isUserSet, boolean isUserFixed, boolean wasReviewed) { |
| mPermissionName = permissionName; |
| mIsGranted = isGranted; |
| mIsUserSet = isUserSet; |
| mIsUserFixed = isUserFixed; |
| mWasReviewed = wasReviewed; |
| } |
| |
| /** |
| * Parse a package state from XML. |
| * |
| * @param parser The data to read |
| * @param context a context to use |
| * @param backupPlatformVersion The platform version the backup was created on |
| * |
| * @return The state |
| */ |
| static @NonNull ArrayList<BackupPermissionState> parseFromXml(@NonNull XmlPullParser parser, |
| @NonNull Context context, int backupPlatformVersion) |
| throws XmlPullParserException { |
| String permName = parser.getAttributeValue(null, ATTR_PERMISSION_NAME); |
| if (permName == null) { |
| throw new XmlPullParserException("Found " + TAG_PERMISSION + " without " |
| + ATTR_PERMISSION_NAME); |
| } |
| |
| ArrayList<String> expandedPermissions = new ArrayList<>(); |
| expandedPermissions.add(permName); |
| |
| List<SplitPermissionInfo> splitPerms = context.getSystemService( |
| PermissionManager.class).getSplitPermissions(); |
| |
| // Expand the properties to permissions that were split between the platform version the |
| // backup was taken and the current version. |
| int numSplitPerms = splitPerms.size(); |
| for (int i = 0; i < numSplitPerms; i++) { |
| SplitPermissionInfo splitPerm = splitPerms.get(i); |
| if (backupPlatformVersion < splitPerm.getTargetSdk() |
| && permName.equals(splitPerm.getSplitPermission())) { |
| expandedPermissions.addAll(splitPerm.getNewPermissions()); |
| } |
| } |
| |
| ArrayList<BackupPermissionState> parsedPermissions = new ArrayList<>( |
| expandedPermissions.size()); |
| int numExpandedPerms = expandedPermissions.size(); |
| for (int i = 0; i < numExpandedPerms; i++) { |
| parsedPermissions.add(new BackupPermissionState(expandedPermissions.get(i), |
| "true".equals(parser.getAttributeValue(null, ATTR_IS_GRANTED)), |
| "true".equals(parser.getAttributeValue(null, ATTR_USER_SET)), |
| "true".equals(parser.getAttributeValue(null, ATTR_USER_FIXED)), |
| "true".equals(parser.getAttributeValue(null, ATTR_WAS_REVIEWED)))); |
| } |
| |
| // TODO: Implement special behavior for location |
| |
| return parsedPermissions; |
| } |
| |
| /** |
| * Is the permission granted, also considering the app-op. |
| * |
| * <p>This does not consider the review-required state of the permission. |
| * |
| * @param perm The permission that might be granted |
| * |
| * @return {@code true} iff the permission and app-op is granted |
| */ |
| private static boolean isPermGrantedIncludingAppOp(@NonNull Permission perm) { |
| return perm.isGranted() && (!perm.affectsAppOp() || perm.isAppOpAllowed()); |
| } |
| |
| /** |
| * Get the state of a permission to back up. |
| * |
| * @param perm The permission to back up |
| * @param appSupportsRuntimePermissions If the app supports runtimePermissions |
| * |
| * @return The state to back up or {@code null} if the permission does not need to be |
| * backed up. |
| */ |
| private static @Nullable BackupPermissionState fromPermission(@NonNull Permission perm, |
| boolean appSupportsRuntimePermissions) { |
| int grantFlags = perm.getFlags(); |
| |
| if ((grantFlags & SYSTEM_RUNTIME_GRANT_MASK) != 0) { |
| return null; |
| } |
| |
| if (!perm.isUserSet() && perm.isGrantedByDefault()) { |
| return null; |
| } |
| |
| boolean permissionWasReviewed; |
| boolean isNotInDefaultGrantState; |
| if (appSupportsRuntimePermissions) { |
| isNotInDefaultGrantState = isPermGrantedIncludingAppOp(perm); |
| permissionWasReviewed = false; |
| } else { |
| isNotInDefaultGrantState = !isPermGrantedIncludingAppOp(perm); |
| permissionWasReviewed = !perm.isReviewRequired(); |
| } |
| |
| if (isNotInDefaultGrantState || perm.isUserSet() || perm.isUserFixed() |
| || permissionWasReviewed) { |
| return new BackupPermissionState(perm.getName(), isPermGrantedIncludingAppOp(perm), |
| perm.isUserSet(), perm.isUserFixed(), permissionWasReviewed); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Get the states of all permissions of a group to back up. |
| * |
| * @param group The group of the permissions to back up |
| * |
| * @return The state to back up. Empty list if no permissions in the group need to be backed |
| * up |
| */ |
| static @NonNull ArrayList<BackupPermissionState> fromPermissionGroup( |
| @NonNull AppPermissionGroup group) { |
| ArrayList<BackupPermissionState> permissionsToRestore = new ArrayList<>(); |
| List<Permission> perms = group.getPermissions(); |
| |
| boolean appSupportsRuntimePermissions = |
| group.getApp().applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M; |
| |
| int numPerms = perms.size(); |
| for (int i = 0; i < numPerms; i++) { |
| BackupPermissionState permState = fromPermission(perms.get(i), |
| appSupportsRuntimePermissions); |
| if (permState != null) { |
| permissionsToRestore.add(permState); |
| } |
| } |
| |
| return permissionsToRestore; |
| } |
| |
| /** |
| * Write this state as XML. |
| * |
| * @param serializer The file to write to |
| */ |
| void writeAsXml(@NonNull XmlSerializer serializer) throws IOException { |
| serializer.startTag(null, TAG_PERMISSION); |
| |
| serializer.attribute(null, ATTR_PERMISSION_NAME, mPermissionName); |
| |
| if (mIsGranted) { |
| serializer.attribute(null, ATTR_IS_GRANTED, "true"); |
| } |
| |
| if (mIsUserSet) { |
| serializer.attribute(null, ATTR_USER_SET, "true"); |
| } |
| |
| if (mIsUserFixed) { |
| serializer.attribute(null, ATTR_USER_FIXED, "true"); |
| } |
| |
| if (mWasReviewed) { |
| serializer.attribute(null, ATTR_WAS_REVIEWED, "true"); |
| } |
| |
| serializer.endTag(null, TAG_PERMISSION); |
| } |
| |
| /** |
| * Restore this permission state. |
| * |
| * @param appPerms The {@link AppPermissions} to restore the state to |
| * @param restoreBackgroundPerms if {@code true} only restore background permissions, |
| * if {@code false} do not restore background permissions |
| */ |
| void restore(@NonNull AppPermissions appPerms, boolean restoreBackgroundPerms) { |
| AppPermissionGroup group = appPerms.getGroupForPermission(mPermissionName); |
| if (group == null) { |
| Log.w(LOG_TAG, "Could not find group for " + mPermissionName + " in " |
| + appPerms.getPackageInfo().packageName); |
| return; |
| } |
| |
| if (restoreBackgroundPerms != group.isBackgroundGroup()) { |
| return; |
| } |
| |
| Permission perm = group.getPermission(mPermissionName); |
| if (mWasReviewed) { |
| perm.unsetReviewRequired(); |
| } |
| |
| // Don't grant or revoke fixed permission groups |
| if (group.isSystemFixed() || group.isPolicyFixed()) { |
| return; |
| } |
| |
| if (!perm.isUserSet()) { |
| if (mIsGranted) { |
| group.grantRuntimePermissions(mIsUserFixed, |
| new String[]{mPermissionName}); |
| } else { |
| group.revokeRuntimePermissions(mIsUserFixed, |
| new String[]{mPermissionName}); |
| } |
| |
| perm.setUserSet(mIsUserSet); |
| } |
| } |
| } |
| |
| /** |
| * State that needs to be backed up for a package. |
| */ |
| private static class BackupPackageState { |
| final @NonNull String mPackageName; |
| private final @NonNull ArrayList<BackupPermissionState> mPermissionsToRestore; |
| |
| private BackupPackageState(@NonNull String packageName, |
| @NonNull ArrayList<BackupPermissionState> permissionsToRestore) { |
| mPackageName = packageName; |
| mPermissionsToRestore = permissionsToRestore; |
| } |
| |
| /** |
| * Parse a package state from XML. |
| * |
| * @param parser The data to read |
| * @param context a context to use |
| * @param backupPlatformVersion The platform version the backup was created on |
| * |
| * @return The state |
| */ |
| static @NonNull BackupPackageState parseFromXml(@NonNull XmlPullParser parser, |
| @NonNull Context context, int backupPlatformVersion) |
| throws IOException, XmlPullParserException { |
| String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME); |
| if (packageName == null) { |
| throw new XmlPullParserException("Found " + TAG_GRANT + " without " |
| + ATTR_PACKAGE_NAME); |
| } |
| |
| ArrayList<BackupPermissionState> permissionsToRestore = new ArrayList<>(); |
| |
| while (true) { |
| switch (parser.next()) { |
| case START_TAG: |
| switch (parser.getName()) { |
| case TAG_PERMISSION: |
| try { |
| permissionsToRestore.addAll( |
| BackupPermissionState.parseFromXml(parser, context, |
| backupPlatformVersion)); |
| } catch (XmlPullParserException e) { |
| Log.e(LOG_TAG, "Could not parse permission for " |
| + packageName, e); |
| } |
| |
| skipToEndOfTag(parser); |
| break; |
| default: |
| // ignore tag |
| Log.w(LOG_TAG, "Found unexpected tag " + parser.getName() |
| + " while restoring " + packageName); |
| skipToEndOfTag(parser); |
| } |
| |
| break; |
| case END_TAG: |
| return new BackupPackageState(packageName, permissionsToRestore); |
| case END_DOCUMENT: |
| throw new XmlPullParserException("Could not parse state for " |
| + packageName); |
| } |
| } |
| } |
| |
| /** |
| * Get the state of a package to back up. |
| * |
| * @param context A context to use |
| * @param pkgInfo The package to back up. |
| * |
| * @return The state to back up or {@code null} if no permission of the package need to be |
| * backed up. |
| */ |
| static @Nullable BackupPackageState fromAppPermissions(@NonNull Context context, |
| @NonNull PackageInfo pkgInfo) { |
| AppPermissions appPerms = new AppPermissions(context, pkgInfo, false, null); |
| |
| ArrayList<BackupPermissionState> permissionsToRestore = new ArrayList<>(); |
| List<AppPermissionGroup> groups = appPerms.getPermissionGroups(); |
| |
| int numGroups = groups.size(); |
| for (int groupNum = 0; groupNum < numGroups; groupNum++) { |
| AppPermissionGroup group = groups.get(groupNum); |
| |
| permissionsToRestore.addAll(BackupPermissionState.fromPermissionGroup(group)); |
| |
| // Background permissions are in a subgroup that is not part of |
| // {@link AppPermission#getPermissionGroups}. Hence add it explicitly here. |
| if (group.getBackgroundPermissions() != null) { |
| permissionsToRestore.addAll(BackupPermissionState.fromPermissionGroup( |
| group.getBackgroundPermissions())); |
| } |
| } |
| |
| if (permissionsToRestore.size() == 0) { |
| return null; |
| } |
| |
| return new BackupPackageState(pkgInfo.packageName, permissionsToRestore); |
| } |
| |
| /** |
| * Write this state as XML. |
| * |
| * @param serializer The file to write to |
| */ |
| void writeAsXml(@NonNull XmlSerializer serializer) throws IOException { |
| if (mPermissionsToRestore.size() == 0) { |
| return; |
| } |
| |
| serializer.startTag(null, TAG_GRANT); |
| serializer.attribute(null, ATTR_PACKAGE_NAME, mPackageName); |
| |
| int numPerms = mPermissionsToRestore.size(); |
| for (int i = 0; i < numPerms; i++) { |
| mPermissionsToRestore.get(i).writeAsXml(serializer); |
| } |
| |
| serializer.endTag(null, TAG_GRANT); |
| } |
| |
| /** |
| * Restore this package state. |
| * |
| * @param context A context to use |
| * @param pkgInfo The package to restore. |
| */ |
| void restore(@NonNull Context context, @NonNull PackageInfo pkgInfo) { |
| AppPermissions appPerms = new AppPermissions(context, pkgInfo, false, true, null); |
| |
| // Restore background permissions after foreground permissions as for pre-M apps bg |
| // granted and fg revoked cannot be expressed. |
| int numPerms = mPermissionsToRestore.size(); |
| for (int i = 0; i < numPerms; i++) { |
| mPermissionsToRestore.get(i).restore(appPerms, false); |
| } |
| for (int i = 0; i < numPerms; i++) { |
| mPermissionsToRestore.get(i).restore(appPerms, true); |
| } |
| |
| int numGroups = appPerms.getPermissionGroups().size(); |
| for (int i = 0; i < numGroups; i++) { |
| AppPermissionGroup group = appPerms.getPermissionGroups().get(i); |
| |
| // Only denied groups can be user fixed |
| if (group.areRuntimePermissionsGranted()) { |
| group.setUserFixed(false); |
| } |
| |
| AppPermissionGroup bgGroup = group.getBackgroundPermissions(); |
| if (bgGroup != null) { |
| // Only denied groups can be user fixed |
| if (bgGroup.areRuntimePermissionsGranted()) { |
| bgGroup.setUserFixed(false); |
| } |
| } |
| } |
| |
| appPerms.persistChanges(); |
| } |
| } |
| } |