diff options
author | Hai Zhang <zhanghai@google.com> | 2020-01-16 01:42:58 -0800 |
---|---|---|
committer | Hai Zhang <zhanghai@google.com> | 2020-01-17 19:05:48 -0800 |
commit | 76f0defebf3c1bc419dc083d2bc65113ecd1f264 (patch) | |
tree | a3cc19917d210bed93b9adbff0bdde83ce5742d7 | |
parent | e796b879e9fec5921ce24d4a0494dee8d2f5f2da (diff) |
Move runtime permissions persistence into APEX.
Bug: 136503238
Test: presubmit
Change-Id: Id016d8c111ceadd27dc318c256b2f32ff0380f60
-rw-r--r-- | apex/permission/framework/Android.bp | 8 | ||||
-rw-r--r-- | apex/permission/service/Android.bp | 15 | ||||
-rw-r--r-- | apex/permission/service/java/com/android/permission/persistence/IoUtils.java (renamed from apex/permission/service/java/com/android/server/permission/RuntimePermissionPersistence.java) | 22 | ||||
-rw-r--r-- | apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistence.java | 72 | ||||
-rw-r--r-- | apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistenceImpl.java | 261 | ||||
-rw-r--r-- | apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsState.java | 131 | ||||
-rw-r--r-- | services/Android.bp | 5 | ||||
-rw-r--r-- | services/api/current.txt | 27 | ||||
-rw-r--r-- | services/core/Android.bp | 1 | ||||
-rw-r--r-- | services/core/java/com/android/server/pm/Settings.java | 217 |
10 files changed, 664 insertions, 95 deletions
diff --git a/apex/permission/framework/Android.bp b/apex/permission/framework/Android.bp index 8b03da3a9530..09571a1cd111 100644 --- a/apex/permission/framework/Android.bp +++ b/apex/permission/framework/Android.bp @@ -26,7 +26,13 @@ java_library { srcs: [ ":framework-permission-sources", ], - sdk_version: "system_current", + // TODO(b/146758669): Use "system_current" after nullability annotations are system APIs. + sdk_version: "core_current", + libs: [ + "framework-annotations-lib", + // TODO(b/146758669): Remove this line after nullability annotations are system APIs. + "android_system_stubs_current", + ], apex_available: [ "com.android.permission", "test_com.android.permission", diff --git a/apex/permission/service/Android.bp b/apex/permission/service/Android.bp index 972b362509c0..4172e95bca5b 100644 --- a/apex/permission/service/Android.bp +++ b/apex/permission/service/Android.bp @@ -12,13 +12,24 @@ // See the License for the specific language governing permissions and // limitations under the License. +filegroup { + name: "service-permission-sources", + srcs: [ + "java/**/*.java", + ], +} + java_library { name: "service-permission", srcs: [ - "java/**/*.java", + ":service-permission-sources", ], - sdk_version: "system_current", + // TODO(b/146758669): Use "system_current" after nullability annotations are system APIs. + sdk_version: "core_current", libs: [ + "framework-annotations-lib", + // TODO(b/146758669): Remove this line after nullability annotations are system APIs. + "android_system_stubs_current", "framework-permission", ], apex_available: [ diff --git a/apex/permission/service/java/com/android/server/permission/RuntimePermissionPersistence.java b/apex/permission/service/java/com/android/permission/persistence/IoUtils.java index a534e22c04cb..0ae44603516e 100644 --- a/apex/permission/service/java/com/android/server/permission/RuntimePermissionPersistence.java +++ b/apex/permission/service/java/com/android/permission/persistence/IoUtils.java @@ -14,9 +14,25 @@ * limitations under the License. */ -package com.android.server.permission; +package com.android.permission.persistence; + +import android.annotation.NonNull; /** - * Persistence for runtime permissions. + * Utility class for IO. */ -public class RuntimePermissionPersistence {} +public class IoUtils { + + private IoUtils() {} + + /** + * Close 'closeable' ignoring any exceptions. + */ + public static void closeQuietly(@NonNull AutoCloseable closeable) { + try { + closeable.close(); + } catch (Exception ignored) { + // Ignored. + } + } +} diff --git a/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistence.java b/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistence.java new file mode 100644 index 000000000000..5f2d94441965 --- /dev/null +++ b/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistence.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2020 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.permission.persistence; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.UserHandle; + +/** + * Persistence for runtime permissions. + * + * TODO(b/147914847): Remove @hide when it becomes the default. + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES, process = SystemApi.Process.SYSTEM_SERVER) +public interface RuntimePermissionsPersistence { + + /** + * Read the runtime permissions from persistence. + * + * This will perform I/O operations synchronously. + * + * @param user the user to read for + * @return the runtime permissions read + */ + @Nullable + RuntimePermissionsState read(@NonNull UserHandle user); + + /** + * Write the runtime permissions to persistence. + * + * This will perform I/O operations synchronously. + * + * @param runtimePermissions the runtime permissions to write + * @param user the user to write for + */ + void write(@NonNull RuntimePermissionsState runtimePermissions, @NonNull UserHandle user); + + /** + * Delete the runtime permissions from persistence. + * + * This will perform I/O operations synchronously. + * + * @param user the user to delete for + */ + void delete(@NonNull UserHandle user); + + /** + * Create a new instance of {@link RuntimePermissionsPersistence} implementation. + * + * @return the new instance. + */ + @NonNull + static RuntimePermissionsPersistence createInstance() { + return new RuntimePermissionsPersistenceImpl(); + } +} diff --git a/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistenceImpl.java b/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistenceImpl.java new file mode 100644 index 000000000000..51b911a94edc --- /dev/null +++ b/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistenceImpl.java @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2020 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.permission.persistence; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.UserHandle; +import android.util.ArrayMap; +import android.util.AtomicFile; +import android.util.Log; +import android.util.Xml; + +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.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Persistence implementation for runtime permissions. + * + * TODO(b/147914847): Remove @hide when it becomes the default. + * @hide + */ +public class RuntimePermissionsPersistenceImpl implements RuntimePermissionsPersistence { + + private static final String LOG_TAG = RuntimePermissionsPersistenceImpl.class.getSimpleName(); + + private static final String RUNTIME_PERMISSIONS_FILE_NAME = "runtime-permissions.xml"; + + private static final String TAG_PACKAGE = "package"; + private static final String TAG_PERMISSION = "permission"; + private static final String TAG_RUNTIME_PERMISSIONS = "runtime-permissions"; + private static final String TAG_SHARED_USER = "shared-user"; + + private static final String ATTRIBUTE_FINGERPRINT = "fingerprint"; + private static final String ATTRIBUTE_FLAGS = "flags"; + private static final String ATTRIBUTE_GRANTED = "granted"; + private static final String ATTRIBUTE_NAME = "name"; + private static final String ATTRIBUTE_VERSION = "version"; + + @Nullable + @Override + public RuntimePermissionsState read(@NonNull UserHandle user) { + File file = getFile(user); + try (FileInputStream inputStream = new AtomicFile(file).openRead()) { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(inputStream, null); + return parseXml(parser); + } catch (FileNotFoundException e) { + Log.i(LOG_TAG, "runtime-permissions.xml not found"); + return null; + } catch (XmlPullParserException | IOException e) { + throw new IllegalStateException("Failed to read runtime-permissions.xml: " + file , e); + } + } + + @NonNull + private static RuntimePermissionsState parseXml(@NonNull XmlPullParser parser) + throws IOException, XmlPullParserException { + int type; + int depth; + int innerDepth = parser.getDepth() + 1; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) { + if (depth > innerDepth || type != XmlPullParser.START_TAG) { + continue; + } + + if (parser.getName().equals(TAG_RUNTIME_PERMISSIONS)) { + return parseRuntimePermissions(parser); + } + } + throw new IllegalStateException("Missing <" + TAG_RUNTIME_PERMISSIONS + + "> in runtime-permissions.xml"); + } + + @NonNull + private static RuntimePermissionsState parseRuntimePermissions(@NonNull XmlPullParser parser) + throws IOException, XmlPullParserException { + String versionValue = parser.getAttributeValue(null, ATTRIBUTE_VERSION); + int version = versionValue != null ? Integer.parseInt(versionValue) + : RuntimePermissionsState.NO_VERSION; + String fingerprint = parser.getAttributeValue(null, ATTRIBUTE_FINGERPRINT); + + Map<String, List<RuntimePermissionsState.PermissionState>> packagePermissions = + new ArrayMap<>(); + Map<String, List<RuntimePermissionsState.PermissionState>> sharedUserPermissions = + new ArrayMap<>(); + int type; + int depth; + int innerDepth = parser.getDepth() + 1; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) { + if (depth > innerDepth || type != XmlPullParser.START_TAG) { + continue; + } + + switch (parser.getName()) { + case TAG_PACKAGE: { + String packageName = parser.getAttributeValue(null, ATTRIBUTE_NAME); + List<RuntimePermissionsState.PermissionState> permissions = parsePermissions( + parser); + packagePermissions.put(packageName, permissions); + break; + } + case TAG_SHARED_USER: { + String sharedUserName = parser.getAttributeValue(null, ATTRIBUTE_NAME); + List<RuntimePermissionsState.PermissionState> permissions = parsePermissions( + parser); + sharedUserPermissions.put(sharedUserName, permissions); + break; + } + } + } + + return new RuntimePermissionsState(version, fingerprint, packagePermissions, + sharedUserPermissions); + } + + @NonNull + private static List<RuntimePermissionsState.PermissionState> parsePermissions( + @NonNull XmlPullParser parser) throws IOException, XmlPullParserException { + List<RuntimePermissionsState.PermissionState> permissions = new ArrayList<>(); + int type; + int depth; + int innerDepth = parser.getDepth() + 1; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) { + if (depth > innerDepth || type != XmlPullParser.START_TAG) { + continue; + } + + if (parser.getName().equals(TAG_PERMISSION)) { + String name = parser.getAttributeValue(null, ATTRIBUTE_NAME); + boolean granted = Boolean.parseBoolean(parser.getAttributeValue(null, + ATTRIBUTE_GRANTED)); + int flags = Integer.parseInt(parser.getAttributeValue(null, + ATTRIBUTE_FLAGS), 16); + RuntimePermissionsState.PermissionState permission = + new RuntimePermissionsState.PermissionState(name, granted, flags); + permissions.add(permission); + } + } + return permissions; + } + + @Override + public void write(@NonNull RuntimePermissionsState runtimePermissions, + @NonNull UserHandle user) { + File file = getFile(user); + AtomicFile atomicFile = new AtomicFile(file); + FileOutputStream outputStream = null; + try { + outputStream = atomicFile.startWrite(); + + XmlSerializer serializer = Xml.newSerializer(); + serializer.setOutput(outputStream, StandardCharsets.UTF_8.name()); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + serializer.startDocument(null, true); + + serializeRuntimePermissions(serializer, runtimePermissions); + + serializer.endDocument(); + atomicFile.finishWrite(outputStream); + } catch (Exception e) { + Log.wtf(LOG_TAG, "Failed to write runtime-permissions.xml, restoring backup: " + file, + e); + atomicFile.failWrite(outputStream); + } finally { + IoUtils.closeQuietly(outputStream); + } + } + + private static void serializeRuntimePermissions(@NonNull XmlSerializer serializer, + @NonNull RuntimePermissionsState runtimePermissions) throws IOException { + serializer.startTag(null, TAG_RUNTIME_PERMISSIONS); + + int version = runtimePermissions.getVersion(); + serializer.attribute(null, ATTRIBUTE_VERSION, Integer.toString(version)); + String fingerprint = runtimePermissions.getFingerprint(); + if (fingerprint != null) { + serializer.attribute(null, ATTRIBUTE_FINGERPRINT, fingerprint); + } + + for (Map.Entry<String, List<RuntimePermissionsState.PermissionState>> entry + : runtimePermissions.getPackagePermissions().entrySet()) { + String packageName = entry.getKey(); + List<RuntimePermissionsState.PermissionState> permissions = entry.getValue(); + + serializer.startTag(null, TAG_PACKAGE); + serializer.attribute(null, ATTRIBUTE_NAME, packageName); + serializePermissions(serializer, permissions); + serializer.endTag(null, TAG_PACKAGE); + } + + for (Map.Entry<String, List<RuntimePermissionsState.PermissionState>> entry + : runtimePermissions.getSharedUserPermissions().entrySet()) { + String sharedUserName = entry.getKey(); + List<RuntimePermissionsState.PermissionState> permissions = entry.getValue(); + + serializer.startTag(null, TAG_SHARED_USER); + serializer.attribute(null, ATTRIBUTE_NAME, sharedUserName); + serializePermissions(serializer, permissions); + serializer.endTag(null, TAG_SHARED_USER); + } + + serializer.endTag(null, TAG_RUNTIME_PERMISSIONS); + } + + private static void serializePermissions(@NonNull XmlSerializer serializer, + @NonNull List<RuntimePermissionsState.PermissionState> permissions) throws IOException { + int permissionsSize = permissions.size(); + for (int i = 0; i < permissionsSize; i++) { + RuntimePermissionsState.PermissionState permissionState = permissions.get(i); + + serializer.startTag(null, TAG_PERMISSION); + serializer.attribute(null, ATTRIBUTE_NAME, permissionState.getName()); + serializer.attribute(null, ATTRIBUTE_GRANTED, Boolean.toString( + permissionState.isGranted())); + serializer.attribute(null, ATTRIBUTE_FLAGS, Integer.toHexString( + permissionState.getFlags())); + serializer.endTag(null, TAG_PERMISSION); + } + } + + @Override + public void delete(@NonNull UserHandle user) { + getFile(user).delete(); + } + + @NonNull + private static File getFile(@NonNull UserHandle user) { + // TODO: Use an API for this. + File dataDirectory = new File("/data/misc_de/" + user.getIdentifier() + + "/apexdata/com.android.permission"); + return new File(dataDirectory, RUNTIME_PERMISSIONS_FILE_NAME); + } +} diff --git a/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsState.java b/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsState.java new file mode 100644 index 000000000000..2a939e51b98e --- /dev/null +++ b/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsState.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2020 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.permission.persistence; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; + +import java.util.List; +import java.util.Map; + +/** + * State of all runtime permissions. + * + * TODO(b/147914847): Remove @hide when it becomes the default. + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES, process = SystemApi.Process.SYSTEM_SERVER) +public final class RuntimePermissionsState { + + /** + * Special value for {@link #mVersion} to indicate that no version was read. + */ + public static final int NO_VERSION = -1; + + /** + * The version of the runtime permissions. + */ + private final int mVersion; + + /** + * The fingerprint of the runtime permissions. + */ + @Nullable + private final String mFingerprint; + + /** + * The runtime permissions by packages. + */ + @NonNull + private final Map<String, List<PermissionState>> mPackagePermissions; + + /** + * The runtime permissions by shared users. + */ + @NonNull + private final Map<String, List<PermissionState>> mSharedUserPermissions; + + public RuntimePermissionsState(int version, @Nullable String fingerprint, + @NonNull Map<String, List<PermissionState>> packagePermissions, + @NonNull Map<String, List<PermissionState>> sharedUserPermissions) { + mVersion = version; + mFingerprint = fingerprint; + mPackagePermissions = packagePermissions; + mSharedUserPermissions = sharedUserPermissions; + } + + public int getVersion() { + return mVersion; + } + + @Nullable + public String getFingerprint() { + return mFingerprint; + } + + @NonNull + public Map<String, List<PermissionState>> getPackagePermissions() { + return mPackagePermissions; + } + + @NonNull + public Map<String, List<PermissionState>> getSharedUserPermissions() { + return mSharedUserPermissions; + } + + /** + * State of a single permission. + */ + public static class PermissionState { + + /** + * Name of the permission. + */ + @NonNull + private final String mName; + + /** + * Whether the permission is granted. + */ + private final boolean mGranted; + + /** + * Flags of the permission. + */ + private final int mFlags; + + public PermissionState(@NonNull String name, boolean granted, int flags) { + mName = name; + mGranted = granted; + mFlags = flags; + } + + @NonNull + public String getName() { + return mName; + } + + public boolean isGranted() { + return mGranted; + } + + public int getFlags() { + return mFlags; + } + } +} diff --git a/services/Android.bp b/services/Android.bp index 5afed6c4fd19..914ea2859fb1 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -6,7 +6,7 @@ filegroup { } filegroup { - name: "services-sources", + name: "services-stub-sources", srcs: [ ":services.core-sources", ":services.accessibility-sources", @@ -29,6 +29,7 @@ filegroup { ":services.usage-sources", ":services.usb-sources", ":services.voiceinteraction-sources", + ":service-permission-sources", ], visibility: ["//visibility:private"], } @@ -110,7 +111,7 @@ filegroup { droidstubs { name: "services-stubs.sources", - srcs: [":services-sources"], + srcs: [":services-stub-sources"], installable: false, // TODO: remove the --hide options below args: " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES,process=android.annotation.SystemApi.Process.SYSTEM_SERVER\\)" + diff --git a/services/api/current.txt b/services/api/current.txt index 18e38b1c6547..5ca048613e31 100644 --- a/services/api/current.txt +++ b/services/api/current.txt @@ -1,4 +1,31 @@ // Signature format: 2.0 +package com.android.permission.persistence { + + public interface RuntimePermissionsPersistence { + method @NonNull public static com.android.permission.persistence.RuntimePermissionsPersistence createInstance(); + method public void delete(@NonNull android.os.UserHandle); + method @Nullable public com.android.permission.persistence.RuntimePermissionsState read(@NonNull android.os.UserHandle); + method public void write(@NonNull com.android.permission.persistence.RuntimePermissionsState, @NonNull android.os.UserHandle); + } + + public final class RuntimePermissionsState { + ctor public RuntimePermissionsState(int, @Nullable String, @NonNull java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>>, @NonNull java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>>); + method @Nullable public String getFingerprint(); + method @NonNull public java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>> getPackagePermissions(); + method @NonNull public java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>> getSharedUserPermissions(); + method public int getVersion(); + field public static final int NO_VERSION = -1; // 0xffffffff + } + + public static class RuntimePermissionsState.PermissionState { + ctor public RuntimePermissionsState.PermissionState(@NonNull String, boolean, int); + method public int getFlags(); + method @NonNull public String getName(); + method public boolean isGranted(); + } + +} + package com.android.server { public abstract class SystemService { diff --git a/services/core/Android.bp b/services/core/Android.bp index b2fba730fac1..02d4f94662b5 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -90,6 +90,7 @@ java_library_static { ], libs: [ + "services-stubs", "services.net", "android.hardware.light-V2.0-java", "android.hardware.power-V1.0-java", diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 4f18cb43cc94..5d948b24256a 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -92,6 +92,8 @@ import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.JournaledFile; import com.android.internal.util.XmlUtils; +import com.android.permission.persistence.RuntimePermissionsPersistence; +import com.android.permission.persistence.RuntimePermissionsState; import com.android.server.LocalServices; import com.android.server.pm.Installer.InstallerException; import com.android.server.pm.permission.BasePermission; @@ -5096,6 +5098,9 @@ public final class Settings { private static final int UPGRADE_VERSION = -1; private static final int INITIAL_VERSION = 0; + private final RuntimePermissionsPersistence mPersistence = + RuntimePermissionsPersistence.createInstance(); + private final Handler mHandler = new MyHandler(); private final Object mPersistenceLock; @@ -5185,98 +5190,72 @@ public final class Settings { } private void writePermissionsSync(int userId) { - AtomicFile destination = new AtomicFile(getUserRuntimePermissionsFile(userId), - "package-perms-" + userId); - - ArrayMap<String, List<PermissionState>> permissionsForPackage = new ArrayMap<>(); - ArrayMap<String, List<PermissionState>> permissionsForSharedUser = new ArrayMap<>(); - + RuntimePermissionsState runtimePermissions; synchronized (mPersistenceLock) { mWriteScheduled.delete(userId); - final int packageCount = mPackages.size(); - for (int i = 0; i < packageCount; i++) { + int version = mVersions.get(userId, INITIAL_VERSION); + + String fingerprint = mFingerprints.get(userId); + + Map<String, List<RuntimePermissionsState.PermissionState>> packagePermissions = + new ArrayMap<>(); + int packagesSize = mPackages.size(); + for (int i = 0; i < packagesSize; i++) { String packageName = mPackages.keyAt(i); PackageSetting packageSetting = mPackages.valueAt(i); if (packageSetting.sharedUser == null) { - PermissionsState permissionsState = packageSetting.getPermissionsState(); - List<PermissionState> permissionsStates = permissionsState - .getRuntimePermissionStates(userId); - if (!permissionsStates.isEmpty()) { - permissionsForPackage.put(packageName, permissionsStates); + List<RuntimePermissionsState.PermissionState> permissions = + getPermissionsFromPermissionsState( + packageSetting.getPermissionsState(), userId); + if (permissions != null) { + packagePermissions.put(packageName, permissions); } } } - final int sharedUserCount = mSharedUsers.size(); - for (int i = 0; i < sharedUserCount; i++) { + Map<String, List<RuntimePermissionsState.PermissionState>> sharedUserPermissions = + new ArrayMap<>(); + final int sharedUsersSize = mSharedUsers.size(); + for (int i = 0; i < sharedUsersSize; i++) { String sharedUserName = mSharedUsers.keyAt(i); - SharedUserSetting sharedUser = mSharedUsers.valueAt(i); - PermissionsState permissionsState = sharedUser.getPermissionsState(); - List<PermissionState> permissionsStates = permissionsState - .getRuntimePermissionStates(userId); - if (!permissionsStates.isEmpty()) { - permissionsForSharedUser.put(sharedUserName, permissionsStates); + SharedUserSetting sharedUserSetting = mSharedUsers.valueAt(i); + List<RuntimePermissionsState.PermissionState> permissions = + getPermissionsFromPermissionsState( + sharedUserSetting.getPermissionsState(), userId); + if (permissions != null) { + sharedUserPermissions.put(sharedUserName, permissions); } } - } - - FileOutputStream out = null; - try { - out = destination.startWrite(); - - XmlSerializer serializer = Xml.newSerializer(); - serializer.setOutput(out, StandardCharsets.UTF_8.name()); - serializer.setFeature( - "http://xmlpull.org/v1/doc/features.html#indent-output", true); - serializer.startDocument(null, true); - - serializer.startTag(null, TAG_RUNTIME_PERMISSIONS); - - final int version = mVersions.get(userId, INITIAL_VERSION); - serializer.attribute(null, ATTR_VERSION, Integer.toString(version)); - - String fingerprint = mFingerprints.get(userId); - if (fingerprint != null) { - serializer.attribute(null, ATTR_FINGERPRINT, fingerprint); - } - final int packageCount = permissionsForPackage.size(); - for (int i = 0; i < packageCount; i++) { - String packageName = permissionsForPackage.keyAt(i); - List<PermissionState> permissionStates = permissionsForPackage.valueAt(i); - serializer.startTag(null, TAG_PACKAGE); - serializer.attribute(null, ATTR_NAME, packageName); - writePermissions(serializer, permissionStates); - serializer.endTag(null, TAG_PACKAGE); - } + runtimePermissions = new RuntimePermissionsState(version, fingerprint, + packagePermissions, sharedUserPermissions); + } - final int sharedUserCount = permissionsForSharedUser.size(); - for (int i = 0; i < sharedUserCount; i++) { - String packageName = permissionsForSharedUser.keyAt(i); - List<PermissionState> permissionStates = permissionsForSharedUser.valueAt(i); - serializer.startTag(null, TAG_SHARED_USER); - serializer.attribute(null, ATTR_NAME, packageName); - writePermissions(serializer, permissionStates); - serializer.endTag(null, TAG_SHARED_USER); - } + mPersistence.write(runtimePermissions, UserHandle.of(userId)); + } - serializer.endTag(null, TAG_RUNTIME_PERMISSIONS); + @Nullable + private List<RuntimePermissionsState.PermissionState> getPermissionsFromPermissionsState( + @NonNull PermissionsState permissionsState, @UserIdInt int userId) { + List<PermissionState> permissionStates = permissionsState.getRuntimePermissionStates( + userId); + if (permissionStates.isEmpty()) { + return null; + } - serializer.endDocument(); - destination.finishWrite(out); + List<RuntimePermissionsState.PermissionState> permissions = + new ArrayList<>(); + int permissionStatesSize = permissionStates.size(); + for (int i = 0; i < permissionStatesSize; i++) { + PermissionState permissionState = permissionStates.get(i); - if (Build.FINGERPRINT.equals(fingerprint)) { - mDefaultPermissionsGranted.put(userId, true); - } - // Any error while writing is fatal. - } catch (Throwable t) { - Slog.wtf(PackageManagerService.TAG, - "Failed to write settings, restoring backup", t); - destination.failWrite(out); - } finally { - IoUtils.closeQuietly(out); + RuntimePermissionsState.PermissionState permission = + new RuntimePermissionsState.PermissionState(permissionState.getName(), + permissionState.isGranted(), permissionState.getFlags()); + permissions.add(permission); } + return permissions; } @GuardedBy("Settings.this.mLock") @@ -5311,11 +5290,88 @@ public final class Settings { } public void deleteUserRuntimePermissionsFile(int userId) { - getUserRuntimePermissionsFile(userId).delete(); + mPersistence.delete(UserHandle.of(userId)); } @GuardedBy("Settings.this.mLock") public void readStateForUserSyncLPr(int userId) { + RuntimePermissionsState runtimePermissions = mPersistence.read(UserHandle.of(userId)); + if (runtimePermissions == null) { + readLegacyStateForUserSyncLPr(userId); + writePermissionsForUserAsyncLPr(userId); + return; + } + + // If the runtime permissions file exists but the version is not set this is + // an upgrade from P->Q. Hence mark it with the special UPGRADE_VERSION. + int version = runtimePermissions.getVersion(); + if (version == RuntimePermissionsState.NO_VERSION) { + version = UPGRADE_VERSION; + } + mVersions.put(userId, version); + + String fingerprint = runtimePermissions.getFingerprint(); + mFingerprints.put(userId, fingerprint); + boolean defaultPermissionsGranted = Build.FINGERPRINT.equals(fingerprint); + mDefaultPermissionsGranted.put(userId, defaultPermissionsGranted); + + for (Map.Entry<String, List<RuntimePermissionsState.PermissionState>> entry + : runtimePermissions.getPackagePermissions().entrySet()) { + String packageName = entry.getKey(); + List<RuntimePermissionsState.PermissionState> permissions = entry.getValue(); + + PackageSetting packageSetting = mPackages.get(packageName); + if (packageSetting == null) { + Slog.w(PackageManagerService.TAG, "Unknown package:" + packageName); + continue; + } + readPermissionsStateLpr(permissions, packageSetting.getPermissionsState(), userId); + } + + for (Map.Entry<String, List<RuntimePermissionsState.PermissionState>> entry + : runtimePermissions.getSharedUserPermissions().entrySet()) { + String sharedUserName = entry.getKey(); + List<RuntimePermissionsState.PermissionState> permissions = entry.getValue(); + + SharedUserSetting sharedUserSetting = mSharedUsers.get(sharedUserName); + if (sharedUserSetting == null) { + Slog.w(PackageManagerService.TAG, "Unknown shared user:" + sharedUserName); + continue; + } + readPermissionsStateLpr(permissions, sharedUserSetting.getPermissionsState(), + userId); + } + } + + private void readPermissionsStateLpr( + @NonNull List<RuntimePermissionsState.PermissionState> permissions, + @NonNull PermissionsState permissionsState, @UserIdInt int userId) { + int permissionsSize = permissions.size(); + for (int i = 0; i < permissionsSize; i++) { + RuntimePermissionsState.PermissionState permission = permissions.get(i); + + String name = permission.getName(); + BasePermission basePermission = mPermissions.getPermission(name); + if (basePermission == null) { + Slog.w(PackageManagerService.TAG, "Unknown permission:" + name); + continue; + } + boolean granted = permission.isGranted(); + int flags = permission.getFlags(); + + if (granted) { + permissionsState.grantRuntimePermission(basePermission, userId); + permissionsState.updatePermissionFlags(basePermission, userId, + PackageManager.MASK_PERMISSION_FLAGS_ALL, flags); + } else { + permissionsState.updatePermissionFlags(basePermission, userId, + PackageManager.MASK_PERMISSION_FLAGS_ALL, flags); + } + } + } + + @GuardedBy("Settings.this.mLock") + private void readLegacyStateForUserSyncLPr(int userId) { File permissionsFile = getUserRuntimePermissionsFile(userId); if (!permissionsFile.exists()) { return; @@ -5435,19 +5491,6 @@ public final class Settings { } } - private void writePermissions(XmlSerializer serializer, - List<PermissionState> permissionStates) throws IOException { - for (PermissionState permissionState : permissionStates) { - serializer.startTag(null, TAG_ITEM); - serializer.attribute(null, ATTR_NAME,permissionState.getName()); - serializer.attribute(null, ATTR_GRANTED, - String.valueOf(permissionState.isGranted())); - serializer.attribute(null, ATTR_FLAGS, - Integer.toHexString(permissionState.getFlags())); - serializer.endTag(null, TAG_ITEM); - } - } - private final class MyHandler extends Handler { public MyHandler() { super(BackgroundThread.getHandler().getLooper()); |