Back up / restore runtime permission grants
Only user-originated grant actions are backed up/restored. This
includes outright grants, one-time denials, and "never ask again"
type denials.
Bug 19870549
Change-Id: I78b4a8abb713dc5d74b93cb53217b212d57b26e4
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index f611991..90a1198 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -294,6 +294,8 @@
void restoreDefaultApps(in byte[] backup, int userId);
byte[] getIntentFilterVerificationBackup(int userId);
void restoreIntentFilterVerification(in byte[] backup, int userId);
+ byte[] getPermissionGrantBackup(int userId);
+ void restorePermissionGrants(in byte[] backup, int userId);
/**
* Report the set of 'Home' activity candidates, plus (if any) which of them
diff --git a/core/java/com/android/server/backup/PermissionBackupHelper.java b/core/java/com/android/server/backup/PermissionBackupHelper.java
new file mode 100644
index 0000000..ff0e63d
--- /dev/null
+++ b/core/java/com/android/server/backup/PermissionBackupHelper.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2015 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.server.backup;
+
+import android.app.AppGlobals;
+import android.app.backup.BlobBackupHelper;
+import android.content.pm.IPackageManager;
+import android.os.UserHandle;
+import android.util.Slog;
+
+public class PermissionBackupHelper extends BlobBackupHelper {
+ private static final String TAG = "PermissionBackup";
+ private static final boolean DEBUG = false;
+
+ // current schema of the backup state blob
+ private static final int STATE_VERSION = 1;
+
+ // key under which the permission-grant state blob is committed to backup
+ private static final String KEY_PERMISSIONS = "permissions";
+
+ public PermissionBackupHelper() {
+ super(STATE_VERSION, KEY_PERMISSIONS);
+ }
+
+ @Override
+ protected byte[] getBackupPayload(String key) {
+ IPackageManager pm = AppGlobals.getPackageManager();
+ if (DEBUG) {
+ Slog.d(TAG, "Handling backup of " + key);
+ }
+ try {
+ switch (key) {
+ case KEY_PERMISSIONS:
+ return pm.getPermissionGrantBackup(UserHandle.USER_SYSTEM);
+
+ default:
+ Slog.w(TAG, "Unexpected backup key " + key);
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Unable to store payload " + key);
+ }
+ return null;
+ }
+
+ @Override
+ protected void applyRestoredPayload(String key, byte[] payload) {
+ IPackageManager pm = AppGlobals.getPackageManager();
+ if (DEBUG) {
+ Slog.d(TAG, "Handling restore of " + key);
+ }
+ try {
+ switch (key) {
+ case KEY_PERMISSIONS:
+ pm.restorePermissionGrants(payload, UserHandle.USER_SYSTEM);
+ break;
+
+ default:
+ Slog.w(TAG, "Unexpected restore key " + key);
+ }
+ } catch (Exception e) {
+ Slog.w(TAG, "Unable to restore key " + key);
+ }
+ }
+}
diff --git a/core/java/com/android/server/backup/SystemBackupAgent.java b/core/java/com/android/server/backup/SystemBackupAgent.java
index 234815f..3f76e13 100644
--- a/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -46,6 +46,7 @@
private static final String SYNC_SETTINGS_HELPER = "account_sync_settings";
private static final String PREFERRED_HELPER = "preferred_activities";
private static final String NOTIFICATION_HELPER = "notifications";
+ private static final String PERMISSION_HELPER = "permissions";
// These paths must match what the WallpaperManagerService uses. The leaf *_FILENAME
// are also used in the full-backup file format, so must not change unless steps are
@@ -94,6 +95,7 @@
addHelper(SYNC_SETTINGS_HELPER, new AccountSyncSettingsBackupHelper(this));
addHelper(PREFERRED_HELPER, new PreferredActivityBackupHelper());
addHelper(NOTIFICATION_HELPER, new NotificationBackupHelper(this));
+ addHelper(PERMISSION_HELPER, new PermissionBackupHelper());
super.onBackup(oldState, data, newState);
}
@@ -128,6 +130,7 @@
addHelper(SYNC_SETTINGS_HELPER, new AccountSyncSettingsBackupHelper(this));
addHelper(PREFERRED_HELPER, new PreferredActivityBackupHelper());
addHelper(NOTIFICATION_HELPER, new NotificationBackupHelper(this));
+ addHelper(PERMISSION_HELPER, new PermissionBackupHelper());
try {
super.onRestore(data, appVersionCode, newState);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c1cba97..9f571e3 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -76,7 +76,6 @@
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
import static android.system.OsConstants.O_CREAT;
import static android.system.OsConstants.O_RDWR;
-
import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE;
import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_PARENT;
import static com.android.internal.content.NativeLibraryHelper.LIB64_DIR_NAME;
@@ -227,6 +226,7 @@
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
+import com.android.internal.util.XmlUtils;
import com.android.server.EventLogTags;
import com.android.server.FgThread;
import com.android.server.IntentResolver;
@@ -978,6 +978,30 @@
private static final String TAG_DEFAULT_APPS = "da";
private static final String TAG_INTENT_FILTER_VERIFICATION = "iv";
+ private static final String TAG_PERMISSION_BACKUP = "perm-grant-backup";
+ 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_REVOKE_ON_UPGRADE = "rou";
+
+ // System/policy permission grants are not backed up
+ private static final int SYSTEM_RUNTIME_GRANT_MASK =
+ FLAG_PERMISSION_POLICY_FIXED
+ | FLAG_PERMISSION_SYSTEM_FIXED
+ | FLAG_PERMISSION_GRANTED_BY_DEFAULT;
+
+ // And we back up these user-adjusted states
+ private static final int USER_RUNTIME_GRANT_MASK =
+ FLAG_PERMISSION_USER_SET
+ | FLAG_PERMISSION_USER_FIXED
+ | FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+
final @Nullable String mRequiredVerifierPackage;
final @Nullable String mRequiredInstallerPackage;
@@ -1490,16 +1514,25 @@
deleteOld = true;
}
- // If this app is a browser and it's newly-installed for some
- // users, clear any default-browser state in those users
+
+ // Work that needs to happen on first install within each user
if (firstUsers.length > 0) {
- // the app's nature doesn't depend on the user, so we can just
- // check its browser nature in any user and generalize.
- if (packageIsBrowser(packageName, firstUsers[0])) {
+ for (int userId : firstUsers) {
synchronized (mPackages) {
- for (int userId : firstUsers) {
- mSettings.setDefaultBrowserPackageNameLPw(null, userId);
+ // If this app is a browser and it's newly-installed for
+ // some users, clear any default-browser state in those
+ // users. The app's nature doesn't depend on the user,
+ // so we can just check its browser nature in any user
+ // and generalize.
+ if (packageIsBrowser(packageName, firstUsers[0])) {
+ mSettings.setDefaultBrowserPackageNameLPw(
+ null, userId);
}
+
+ // We may also need to apply pending (restored) runtime
+ // permission grants within these users.
+ mSettings.applyPendingPermissionGrantsLPw(
+ packageName, userId);
}
}
}
@@ -14823,7 +14856,7 @@
}
return;
}
-
+Slog.v(TAG, ":: restoreFromXml() : got to tag " + parser.getName());
// this is supposed to be TAG_PREFERRED_BACKUP
if (!expectedStartTag.equals(parser.getName())) {
if (DEBUG_BACKUP) {
@@ -14834,6 +14867,7 @@
// skip interfering stuff, then we're aligned with the backing implementation
while ((type = parser.next()) == XmlPullParser.TEXT) { }
+Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
functor.apply(parser, userId);
}
@@ -15021,6 +15055,192 @@
}
@Override
+ public byte[] getPermissionGrantBackup(int userId) {
+ if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ throw new SecurityException("Only the system may call getPermissionGrantBackup()");
+ }
+
+ ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
+ try {
+ final XmlSerializer serializer = new FastXmlSerializer();
+ serializer.setOutput(dataStream, StandardCharsets.UTF_8.name());
+ serializer.startDocument(null, true);
+ serializer.startTag(null, TAG_PERMISSION_BACKUP);
+
+ synchronized (mPackages) {
+ serializeRuntimePermissionGrantsLPr(serializer, userId);
+ }
+
+ serializer.endTag(null, TAG_PERMISSION_BACKUP);
+ serializer.endDocument();
+ serializer.flush();
+ } catch (Exception e) {
+ if (DEBUG_BACKUP) {
+ Slog.e(TAG, "Unable to write default apps for backup", e);
+ }
+ return null;
+ }
+
+ return dataStream.toByteArray();
+ }
+
+ @Override
+ public void restorePermissionGrants(byte[] backup, int userId) {
+ if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ throw new SecurityException("Only the system may call restorePermissionGrants()");
+ }
+
+ try {
+ final XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new ByteArrayInputStream(backup), StandardCharsets.UTF_8.name());
+ restoreFromXml(parser, userId, TAG_PERMISSION_BACKUP,
+ new BlobXmlRestorer() {
+ @Override
+ public void apply(XmlPullParser parser, int userId)
+ throws XmlPullParserException, IOException {
+ synchronized (mPackages) {
+ processRestoredPermissionGrantsLPr(parser, userId);
+ }
+ }
+ } );
+ } catch (Exception e) {
+ if (DEBUG_BACKUP) {
+ Slog.e(TAG, "Exception restoring preferred activities: " + e.getMessage());
+ }
+ }
+ }
+
+ private void serializeRuntimePermissionGrantsLPr(XmlSerializer serializer, final int userId)
+ throws IOException {
+ serializer.startTag(null, TAG_ALL_GRANTS);
+
+ final int N = mSettings.mPackages.size();
+ for (int i = 0; i < N; i++) {
+ final PackageSetting ps = mSettings.mPackages.valueAt(i);
+ boolean pkgGrantsKnown = false;
+
+ PermissionsState packagePerms = ps.getPermissionsState();
+
+ for (PermissionState state : packagePerms.getRuntimePermissionStates(userId)) {
+ final int grantFlags = state.getFlags();
+ // only look at grants that are not system/policy fixed
+ if ((grantFlags & SYSTEM_RUNTIME_GRANT_MASK) == 0) {
+ final boolean isGranted = state.isGranted();
+ // And only back up the user-twiddled state bits
+ if (isGranted || (grantFlags & USER_RUNTIME_GRANT_MASK) != 0) {
+ final String packageName = mSettings.mPackages.keyAt(i);
+ if (!pkgGrantsKnown) {
+ serializer.startTag(null, TAG_GRANT);
+ serializer.attribute(null, ATTR_PACKAGE_NAME, packageName);
+ pkgGrantsKnown = true;
+ }
+
+ final boolean userSet =
+ (grantFlags & FLAG_PERMISSION_USER_SET) != 0;
+ final boolean userFixed =
+ (grantFlags & FLAG_PERMISSION_USER_FIXED) != 0;
+ final boolean revoke =
+ (grantFlags & FLAG_PERMISSION_REVOKE_ON_UPGRADE) != 0;
+
+ serializer.startTag(null, TAG_PERMISSION);
+ serializer.attribute(null, ATTR_PERMISSION_NAME, state.getName());
+ if (isGranted) {
+ serializer.attribute(null, ATTR_IS_GRANTED, "true");
+ }
+ if (userSet) {
+ serializer.attribute(null, ATTR_USER_SET, "true");
+ }
+ if (userFixed) {
+ serializer.attribute(null, ATTR_USER_FIXED, "true");
+ }
+ if (revoke) {
+ serializer.attribute(null, ATTR_REVOKE_ON_UPGRADE, "true");
+ }
+ serializer.endTag(null, TAG_PERMISSION);
+ }
+ }
+ }
+
+ if (pkgGrantsKnown) {
+ serializer.endTag(null, TAG_GRANT);
+ }
+ }
+
+ serializer.endTag(null, TAG_ALL_GRANTS);
+ }
+
+ private void processRestoredPermissionGrantsLPr(XmlPullParser parser, int userId)
+ throws XmlPullParserException, IOException {
+ String pkgName = null;
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ final String tagName = parser.getName();
+ if (tagName.equals(TAG_GRANT)) {
+ pkgName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
+ if (DEBUG_BACKUP) {
+ Slog.v(TAG, "+++ Restoring grants for package " + pkgName);
+ }
+ } else if (tagName.equals(TAG_PERMISSION)) {
+
+ final boolean isGranted = "true".equals(parser.getAttributeValue(null, ATTR_IS_GRANTED));
+ final String permName = parser.getAttributeValue(null, ATTR_PERMISSION_NAME);
+
+ int newFlagSet = 0;
+ if ("true".equals(parser.getAttributeValue(null, ATTR_USER_SET))) {
+ newFlagSet |= FLAG_PERMISSION_USER_SET;
+ }
+ if ("true".equals(parser.getAttributeValue(null, ATTR_USER_FIXED))) {
+ newFlagSet |= FLAG_PERMISSION_USER_FIXED;
+ }
+ if ("true".equals(parser.getAttributeValue(null, ATTR_REVOKE_ON_UPGRADE))) {
+ newFlagSet |= FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+ }
+ if (DEBUG_BACKUP) {
+ Slog.v(TAG, " + Restoring grant: pkg=" + pkgName + " perm=" + permName
+ + " granted=" + isGranted + " bits=0x" + Integer.toHexString(newFlagSet));
+ }
+ final PackageSetting ps = mSettings.mPackages.get(pkgName);
+ if (ps != null) {
+ // Already installed so we apply the grant immediately
+ if (DEBUG_BACKUP) {
+ Slog.v(TAG, " + already installed; applying");
+ }
+ PermissionsState perms = ps.getPermissionsState();
+ BasePermission bp = mSettings.mPermissions.get(permName);
+ if (bp != null) {
+ if (isGranted) {
+ perms.grantRuntimePermission(bp, userId);
+ }
+ if (newFlagSet != 0) {
+ perms.updatePermissionFlags(bp, userId, USER_RUNTIME_GRANT_MASK, newFlagSet);
+ }
+ }
+ } else {
+ // Need to wait for post-restore install to apply the grant
+ if (DEBUG_BACKUP) {
+ Slog.v(TAG, " - not yet installed; saving for later");
+ }
+ mSettings.processRestoredPermissionGrantLPr(pkgName, permName,
+ isGranted, newFlagSet, userId);
+ }
+ } else {
+ PackageManagerService.reportSettingsProblem(Log.WARN,
+ "Unknown element under <" + TAG_PERMISSION_BACKUP + ">: " + tagName);
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+
+ scheduleWriteSettingsLocked();
+ mSettings.writeRuntimePermissionsForUserLPr(userId, false);
+ }
+
+ @Override
public void addCrossProfileIntentFilter(IntentFilter intentFilter, String ownerPackage,
int sourceUserId, int targetUserId, int flags) {
mContext.enforceCallingOrSelfPermission(
@@ -16032,6 +16252,10 @@
mSettings.dumpSharedUsersLPr(pw, packageName, permissionNames, dumpState, checkin);
}
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_PERMISSIONS) && packageName == null) {
+ mSettings.dumpRestoredPermissionGrantsLPr(pw, dumpState);
+ }
+
if (!checkin && dumpState.isDumping(DumpState.DUMP_INSTALLS) && packageName == null) {
// XXX should handle packageName != null by dumping only install data that
// the given package is involved with.
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 9fef515..ad28685 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -22,6 +22,9 @@
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
@@ -32,7 +35,6 @@
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import static android.os.Process.PACKAGE_INFO_GID;
import static android.os.Process.SYSTEM_UID;
-
import static com.android.server.pm.PackageManagerService.DEBUG_DOMAIN_VERIFICATION;
import android.annotation.NonNull;
@@ -217,6 +219,22 @@
private static final String ATTR_SDK_VERSION = "sdkVersion";
private static final String ATTR_DATABASE_VERSION = "databaseVersion";
+ // Bookkeeping for restored permission grants
+ private static final String TAG_RESTORED_RUNTIME_PERMISSIONS = "restored-perms";
+ // package name: ATTR_PACKAGE_NAME
+ private static final String TAG_PERMISSION_ENTRY = "perm";
+ // permission name: ATTR_NAME
+ // permission granted (boolean): ATTR_GRANTED
+ private static final String ATTR_USER_SET = "set";
+ private static final String ATTR_USER_FIXED = "fixed";
+ private static final String ATTR_REVOKE_ON_UPGRADE = "rou";
+
+ // Flag mask of restored permission grants that are applied at install time
+ private static final int USER_RUNTIME_GRANT_MASK =
+ FLAG_PERMISSION_USER_SET
+ | FLAG_PERMISSION_USER_FIXED
+ | FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+
private final Object mLock;
private final RuntimePermissionPersistence mRuntimePermissionsPersistence;
@@ -238,6 +256,26 @@
private final ArrayMap<String, IntentFilterVerificationInfo> mRestoredIntentFilterVerifications =
new ArrayMap<String, IntentFilterVerificationInfo>();
+ // Bookkeeping for restored user permission grants
+ final class RestoredPermissionGrant {
+ String permissionName;
+ boolean granted;
+ int grantBits;
+
+ RestoredPermissionGrant(String name, boolean isGranted, int theGrantBits) {
+ permissionName = name;
+ granted = isGranted;
+ grantBits = theGrantBits;
+ }
+ }
+
+ // This would be more compact as a flat array of restored grants or something, but we
+ // may have quite a few, especially during early device lifetime, and avoiding all those
+ // linear lookups will be important.
+ private final SparseArray<ArrayMap<String, ArraySet<RestoredPermissionGrant>>>
+ mRestoredUserGrants =
+ new SparseArray<ArrayMap<String, ArraySet<RestoredPermissionGrant>>>();
+
private static int mFirstAvailableUid = 0;
/** Map from volume UUID to {@link VersionInfo} */
@@ -388,7 +426,7 @@
return mPackages.get(name);
}
- void setInstallStatus(String pkgName, int status) {
+ void setInstallStatus(String pkgName, final int status) {
PackageSetting p = mPackages.get(pkgName);
if(p != null) {
if(p.getInstallStatus() != status) {
@@ -397,6 +435,43 @@
}
}
+ void applyPendingPermissionGrantsLPw(String packageName, int userId) {
+ ArrayMap<String, ArraySet<RestoredPermissionGrant>> grantsByPackage =
+ mRestoredUserGrants.get(userId);
+ if (grantsByPackage == null || grantsByPackage.size() == 0) {
+ return;
+ }
+
+ ArraySet<RestoredPermissionGrant> grants = grantsByPackage.get(packageName);
+ if (grants == null || grants.size() == 0) {
+ return;
+ }
+
+ final PackageSetting ps = mPackages.get(packageName);
+ if (ps == null) {
+ Slog.e(TAG, "Can't find supposedly installed package " + packageName);
+ return;
+ }
+ final PermissionsState perms = ps.getPermissionsState();
+
+ for (RestoredPermissionGrant grant : grants) {
+ BasePermission bp = mPermissions.get(grant.permissionName);
+ if (bp != null) {
+ if (grant.granted) {
+ perms.grantRuntimePermission(bp, userId);
+ }
+ perms.updatePermissionFlags(bp, userId, USER_RUNTIME_GRANT_MASK, grant.grantBits);
+ }
+ }
+
+ // And remove it from the pending-grant bookkeeping
+ grantsByPackage.remove(packageName);
+ if (grantsByPackage.size() < 1) {
+ mRestoredUserGrants.remove(userId);
+ }
+ writeRuntimePermissionsForUserLPr(userId, false);
+ }
+
void setInstallerPackageName(String pkgName, String installerPkgName) {
PackageSetting p = mPackages.get(pkgName);
if (p != null) {
@@ -1725,6 +1800,14 @@
}
}
+ // Specifically for backup/restore
+ public void processRestoredPermissionGrantLPr(String pkgName, String permission,
+ boolean isGranted, int restoredFlagSet, int userId)
+ throws IOException, XmlPullParserException {
+ mRuntimePermissionsPersistence.rememberRestoredUserGrantLPr(
+ pkgName, permission, isGranted, restoredFlagSet, userId);
+ }
+
void writeDefaultAppsLPr(XmlSerializer serializer, int userId)
throws IllegalArgumentException, IllegalStateException, IOException {
serializer.startTag(null, TAG_DEFAULT_APPS);
@@ -4410,6 +4493,48 @@
pw.print(mReadMessages.toString());
}
+ void dumpRestoredPermissionGrantsLPr(PrintWriter pw, DumpState dumpState) {
+ if (mRestoredUserGrants.size() > 0) {
+ pw.println();
+ pw.println("Restored (pending) permission grants:");
+ for (int userIndex = 0; userIndex < mRestoredUserGrants.size(); userIndex++) {
+ ArrayMap<String, ArraySet<RestoredPermissionGrant>> grantsByPackage =
+ mRestoredUserGrants.valueAt(userIndex);
+ if (grantsByPackage != null && grantsByPackage.size() > 0) {
+ final int userId = mRestoredUserGrants.keyAt(userIndex);
+ pw.print(" User "); pw.println(userId);
+
+ for (int pkgIndex = 0; pkgIndex < grantsByPackage.size(); pkgIndex++) {
+ ArraySet<RestoredPermissionGrant> grants = grantsByPackage.valueAt(pkgIndex);
+ if (grants != null && grants.size() > 0) {
+ final String pkgName = grantsByPackage.keyAt(pkgIndex);
+ pw.print(" "); pw.print(pkgName); pw.println(" :");
+
+ for (RestoredPermissionGrant g : grants) {
+ pw.print(" ");
+ pw.print(g.permissionName);
+ if (g.granted) {
+ pw.print(" GRANTED");
+ }
+ if ((g.grantBits&FLAG_PERMISSION_USER_SET) != 0) {
+ pw.print(" user_set");
+ }
+ if ((g.grantBits&FLAG_PERMISSION_USER_FIXED) != 0) {
+ pw.print(" user_fixed");
+ }
+ if ((g.grantBits&FLAG_PERMISSION_REVOKE_ON_UPGRADE) != 0) {
+ pw.print(" revoke_on_upgrade");
+ }
+ pw.println();
+ }
+ }
+ }
+ }
+ }
+ pw.println();
+ }
+ }
+
private static void dumpSplitNames(PrintWriter pw, PackageParser.Package pkg) {
if (pkg == null) {
pw.print("unknown");
@@ -4507,7 +4632,6 @@
private final class RuntimePermissionPersistence {
private static final long WRITE_PERMISSIONS_DELAY_MILLIS = 200;
-
private static final long MAX_WRITE_PERMISSIONS_DELAY_MILLIS = 2000;
private final Handler mHandler = new MyHandler();
@@ -4624,6 +4748,7 @@
serializer.setFeature(
"http://xmlpull.org/v1/doc/features.html#indent-output", true);
serializer.startDocument(null, true);
+
serializer.startTag(null, TAG_RUNTIME_PERMISSIONS);
String fingerprint = mFingerprints.get(userId);
@@ -4652,6 +4777,51 @@
}
serializer.endTag(null, TAG_RUNTIME_PERMISSIONS);
+
+ // Now any restored permission grants that are waiting for the apps
+ // in question to be installed. These are stored as per-package
+ // TAG_RESTORED_RUNTIME_PERMISSIONS blocks, each containing some
+ // number of individual permission grant entities.
+ if (mRestoredUserGrants.get(userId) != null) {
+ ArrayMap<String, ArraySet<RestoredPermissionGrant>> restoredGrants =
+ mRestoredUserGrants.get(userId);
+ if (restoredGrants != null) {
+ final int pkgCount = restoredGrants.size();
+ for (int i = 0; i < pkgCount; i++) {
+ final ArraySet<RestoredPermissionGrant> pkgGrants =
+ restoredGrants.valueAt(i);
+ if (pkgGrants != null && pkgGrants.size() > 0) {
+ final String pkgName = restoredGrants.keyAt(i);
+ serializer.startTag(null, TAG_RESTORED_RUNTIME_PERMISSIONS);
+ serializer.attribute(null, ATTR_PACKAGE_NAME, pkgName);
+
+ final int N = pkgGrants.size();
+ for (int z = 0; z < N; z++) {
+ RestoredPermissionGrant g = pkgGrants.valueAt(z);
+ serializer.startTag(null, TAG_PERMISSION_ENTRY);
+ serializer.attribute(null, ATTR_NAME, g.permissionName);
+
+ if (g.granted) {
+ serializer.attribute(null, ATTR_GRANTED, "true");
+ }
+
+ if ((g.grantBits&FLAG_PERMISSION_USER_SET) != 0) {
+ serializer.attribute(null, ATTR_USER_SET, "true");
+ }
+ if ((g.grantBits&FLAG_PERMISSION_USER_FIXED) != 0) {
+ serializer.attribute(null, ATTR_USER_FIXED, "true");
+ }
+ if ((g.grantBits&FLAG_PERMISSION_REVOKE_ON_UPGRADE) != 0) {
+ serializer.attribute(null, ATTR_REVOKE_ON_UPGRADE, "true");
+ }
+ serializer.endTag(null, TAG_PERMISSION_ENTRY);
+ }
+ serializer.endTag(null, TAG_RESTORED_RUNTIME_PERMISSIONS);
+ }
+ }
+ }
+ }
+
serializer.endDocument();
destination.finishWrite(out);
@@ -4725,6 +4895,31 @@
}
}
+ // Backup/restore support
+
+ public void rememberRestoredUserGrantLPr(String pkgName, String permission,
+ boolean isGranted, int restoredFlagSet, int userId) {
+ // This change will be remembered at write-settings time
+ ArrayMap<String, ArraySet<RestoredPermissionGrant>> grantsByPackage =
+ mRestoredUserGrants.get(userId);
+ if (grantsByPackage == null) {
+ grantsByPackage = new ArrayMap<String, ArraySet<RestoredPermissionGrant>>();
+ mRestoredUserGrants.put(userId, grantsByPackage);
+ }
+
+ ArraySet<RestoredPermissionGrant> grants = grantsByPackage.get(pkgName);
+ if (grants == null) {
+ grants = new ArraySet<RestoredPermissionGrant>();
+ grantsByPackage.put(pkgName, grants);
+ }
+
+ RestoredPermissionGrant grant = new RestoredPermissionGrant(permission,
+ isGranted, restoredFlagSet);
+ grants.add(grant);
+ }
+
+ // Private internals
+
private void parseRuntimePermissionsLPr(XmlPullParser parser, int userId)
throws IOException, XmlPullParserException {
final int outerDepth = parser.getDepth();
@@ -4764,6 +4959,46 @@
}
parsePermissionsLPr(parser, sus.getPermissionsState(), userId);
} break;
+
+ case TAG_RESTORED_RUNTIME_PERMISSIONS: {
+ final String pkgName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
+ parseRestoredRuntimePermissionsLPr(parser, pkgName, userId);
+ } break;
+ }
+ }
+ }
+
+ private void parseRestoredRuntimePermissionsLPr(XmlPullParser parser,
+ final String pkgName, final int userId) throws IOException, XmlPullParserException {
+ final int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ switch (parser.getName()) {
+ case TAG_PERMISSION_ENTRY: {
+ final String permName = parser.getAttributeValue(null, ATTR_NAME);
+ final boolean isGranted = "true".equals(
+ parser.getAttributeValue(null, ATTR_GRANTED));
+
+ int permBits = 0;
+ if ("true".equals(parser.getAttributeValue(null, ATTR_USER_SET))) {
+ permBits |= FLAG_PERMISSION_USER_SET;
+ }
+ if ("true".equals(parser.getAttributeValue(null, ATTR_USER_FIXED))) {
+ permBits |= FLAG_PERMISSION_USER_FIXED;
+ }
+ if ("true".equals(parser.getAttributeValue(null, ATTR_REVOKE_ON_UPGRADE))) {
+ permBits |= FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+ }
+
+ if (isGranted || permBits != 0) {
+ rememberRestoredUserGrantLPr(pkgName, permName, isGranted, permBits, userId);
+ }
+ } break;
}
}
}