Loading apex/permission/service/java/com/android/role/persistence/RolesPersistence.java 0 → 100644 +72 −0 Original line number Diff line number Diff line /* * 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.role.persistence; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.os.UserHandle; /** * Persistence for roles. * * TODO(b/147914847): Remove @hide when it becomes the default. * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES, process = SystemApi.Process.SYSTEM_SERVER) public interface RolesPersistence { /** * Read the roles from persistence. * * This will perform I/O operations synchronously. * * @param user the user to read for * @return the roles read */ @Nullable RolesState read(@NonNull UserHandle user); /** * Write the roles to persistence. * * This will perform I/O operations synchronously. * * @param roles the roles to write * @param user the user to write for */ void write(@NonNull RolesState roles, @NonNull UserHandle user); /** * Delete the roles 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 RolesPersistence} implementation. * * @return the new instance. */ @NonNull static RolesPersistence createInstance() { return new RolesPersistenceImpl(); } } apex/permission/service/java/com/android/role/persistence/RolesPersistenceImpl.java 0 → 100644 +217 −0 Original line number Diff line number Diff line /* * 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.role.persistence; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; import android.util.Log; import android.util.Xml; import com.android.permission.persistence.IoUtils; 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.Map; import java.util.Set; /** * Persistence implementation for roles. * * TODO(b/147914847): Remove @hide when it becomes the default. * @hide */ public class RolesPersistenceImpl implements RolesPersistence { private static final String LOG_TAG = RolesPersistenceImpl.class.getSimpleName(); private static final String ROLES_FILE_NAME = "roles.xml"; private static final String TAG_ROLES = "roles"; private static final String TAG_ROLE = "role"; private static final String TAG_HOLDER = "holder"; private static final String ATTRIBUTE_VERSION = "version"; private static final String ATTRIBUTE_NAME = "name"; private static final String ATTRIBUTE_PACKAGES_HASH = "packagesHash"; @Nullable @Override public RolesState 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, "roles.xml not found"); return null; } catch (XmlPullParserException | IOException e) { throw new IllegalStateException("Failed to read roles.xml: " + file , e); } } @NonNull private static RolesState 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_ROLES)) { return parseRoles(parser); } } throw new IllegalStateException("Missing <" + TAG_ROLES + "> in roles.xml"); } @NonNull private static RolesState parseRoles(@NonNull XmlPullParser parser) throws IOException, XmlPullParserException { int version = Integer.parseInt(parser.getAttributeValue(null, ATTRIBUTE_VERSION)); String packagesHash = parser.getAttributeValue(null, ATTRIBUTE_PACKAGES_HASH); Map<String, Set<String>> roles = 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; } if (parser.getName().equals(TAG_ROLE)) { String roleName = parser.getAttributeValue(null, ATTRIBUTE_NAME); Set<String> roleHolders = parseRoleHolders(parser); roles.put(roleName, roleHolders); } } return new RolesState(version, packagesHash, roles); } @NonNull private static Set<String> parseRoleHolders(@NonNull XmlPullParser parser) throws IOException, XmlPullParserException { Set<String> roleHolders = new ArraySet<>(); 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_HOLDER)) { String roleHolder = parser.getAttributeValue(null, ATTRIBUTE_NAME); roleHolders.add(roleHolder); } } return roleHolders; } @Override public void write(@NonNull RolesState roles, @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); serializeRoles(serializer, roles); serializer.endDocument(); atomicFile.finishWrite(outputStream); } catch (Exception e) { Log.wtf(LOG_TAG, "Failed to write roles.xml, restoring backup: " + file, e); atomicFile.failWrite(outputStream); } finally { IoUtils.closeQuietly(outputStream); } } private static void serializeRoles(@NonNull XmlSerializer serializer, @NonNull RolesState roles) throws IOException { serializer.startTag(null, TAG_ROLES); int version = roles.getVersion(); serializer.attribute(null, ATTRIBUTE_VERSION, Integer.toString(version)); String packagesHash = roles.getPackagesHash(); if (packagesHash != null) { serializer.attribute(null, ATTRIBUTE_PACKAGES_HASH, packagesHash); } for (Map.Entry<String, Set<String>> entry : roles.getRoles().entrySet()) { String roleName = entry.getKey(); Set<String> roleHolders = entry.getValue(); serializer.startTag(null, TAG_ROLE); serializer.attribute(null, ATTRIBUTE_NAME, roleName); serializeRoleHolders(serializer, roleHolders); serializer.endTag(null, TAG_ROLE); } serializer.endTag(null, TAG_ROLES); } private static void serializeRoleHolders(@NonNull XmlSerializer serializer, @NonNull Set<String> roleHolders) throws IOException { for (String roleHolder : roleHolders) { serializer.startTag(null, TAG_HOLDER); serializer.attribute(null, ATTRIBUTE_NAME, roleHolder); serializer.endTag(null, TAG_HOLDER); } } @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, ROLES_FILE_NAME); } } apex/permission/service/java/com/android/role/persistence/RolesState.java 0 → 100644 +72 −0 Original line number Diff line number Diff line /* * 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.role.persistence; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import java.util.Map; import java.util.Set; /** * State of all roles. * * 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 RolesState { /** * The version of the roles. */ private final int mVersion; /** * The hash of all packages in the system. */ @Nullable private final String mPackagesHash; /** * The roles. */ @NonNull private final Map<String, Set<String>> mRoles; public RolesState(int version, @Nullable String packagesHash, @NonNull Map<String, Set<String>> roles) { mVersion = version; mPackagesHash = packagesHash; mRoles = roles; } public int getVersion() { return mVersion; } @Nullable public String getPackagesHash() { return mPackagesHash; } @NonNull public Map<String, Set<String>> getRoles() { return mRoles; } } services/api/current.txt +18 −0 Original line number Diff line number Diff line Loading @@ -26,6 +26,24 @@ package com.android.permission.persistence { } package com.android.role.persistence { public interface RolesPersistence { method @NonNull public static com.android.role.persistence.RolesPersistence createInstance(); method public void delete(@NonNull android.os.UserHandle); method @Nullable public com.android.role.persistence.RolesState read(@NonNull android.os.UserHandle); method public void write(@NonNull com.android.role.persistence.RolesState, @NonNull android.os.UserHandle); } public final class RolesState { ctor public RolesState(int, @Nullable String, @NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>>); method @Nullable public String getPackagesHash(); method @NonNull public java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getRoles(); method public int getVersion(); } } package com.android.server { public abstract class SystemService { Loading services/core/java/com/android/server/role/RoleUserState.java +39 −84 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import android.annotation.UserIdInt; import android.annotation.WorkerThread; import android.os.Environment; import android.os.Handler; import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; Loading @@ -34,22 +35,21 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.util.CollectionUtils; import com.android.internal.util.dump.DualDumpOutputStream; import com.android.internal.util.function.pooled.PooledLambda; import libcore.io.IoUtils; import com.android.role.persistence.RolesPersistence; import com.android.role.persistence.RolesState; 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; import java.util.Objects; import java.util.Set; /** * Stores the state of roles for a user. Loading @@ -71,6 +71,8 @@ public class RoleUserState { private static final String ATTRIBUTE_NAME = "name"; private static final String ATTRIBUTE_PACKAGES_HASH = "packagesHash"; private final RolesPersistence mPersistence = RolesPersistence.createInstance(); @UserIdInt private final int mUserId; Loading Loading @@ -350,9 +352,7 @@ public class RoleUserState { @WorkerThread private void writeFile() { int version; String packagesHash; ArrayMap<String, ArraySet<String>> roles; RolesState roles; synchronized (mLock) { if (mDestroyed) { return; Loading @@ -360,79 +360,35 @@ public class RoleUserState { mWriteScheduled = false; version = mVersion; packagesHash = mPackagesHash; roles = snapshotRolesLocked(); roles = new RolesState(mVersion, mPackagesHash, (Map<String, Set<String>>) (Map<String, ?>) snapshotRolesLocked()); } AtomicFile atomicFile = new AtomicFile(getFile(mUserId), "roles-" + mUserId); FileOutputStream out = null; try { out = atomicFile.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); serializeRoles(serializer, version, packagesHash, roles); serializer.endDocument(); atomicFile.finishWrite(out); Slog.i(LOG_TAG, "Wrote roles.xml successfully"); } catch (IllegalArgumentException | IllegalStateException | IOException e) { Slog.wtf(LOG_TAG, "Failed to write roles.xml, restoring backup", e); if (out != null) { atomicFile.failWrite(out); } } finally { IoUtils.closeQuietly(out); mPersistence.write(roles, UserHandle.of(mUserId)); } } @WorkerThread private void serializeRoles(@NonNull XmlSerializer serializer, int version, @Nullable String packagesHash, @NonNull ArrayMap<String, ArraySet<String>> roles) throws IOException { serializer.startTag(null, TAG_ROLES); serializer.attribute(null, ATTRIBUTE_VERSION, Integer.toString(version)); if (packagesHash != null) { serializer.attribute(null, ATTRIBUTE_PACKAGES_HASH, packagesHash); private void readFile() { synchronized (mLock) { RolesState roles = mPersistence.read(UserHandle.of(mUserId)); if (roles == null) { readLegacyFileLocked(); scheduleWriteFileLocked(); return; } for (int i = 0, size = roles.size(); i < size; ++i) { String roleName = roles.keyAt(i); ArraySet<String> roleHolders = roles.valueAt(i); mVersion = roles.getVersion(); mPackagesHash = roles.getPackagesHash(); serializer.startTag(null, TAG_ROLE); serializer.attribute(null, ATTRIBUTE_NAME, roleName); serializeRoleHolders(serializer, roleHolders); serializer.endTag(null, TAG_ROLE); } serializer.endTag(null, TAG_ROLES); mRoles.clear(); for (Map.Entry<String, Set<String>> entry : roles.getRoles().entrySet()) { String roleName = entry.getKey(); ArraySet<String> roleHolders = new ArraySet<>(entry.getValue()); mRoles.put(roleName, roleHolders); } @WorkerThread private void serializeRoleHolders(@NonNull XmlSerializer serializer, @NonNull ArraySet<String> roleHolders) throws IOException { for (int i = 0, size = roleHolders.size(); i < size; ++i) { String roleHolder = roleHolders.valueAt(i); serializer.startTag(null, TAG_HOLDER); serializer.attribute(null, ATTRIBUTE_NAME, roleHolder); serializer.endTag(null, TAG_HOLDER); } } /** * Read the state from file. */ private void readFile() { synchronized (mLock) { private void readLegacyFileLocked() { File file = getFile(mUserId); try (FileInputStream in = new AtomicFile(file).openRead()) { XmlPullParser parser = Xml.newPullParser(); Loading @@ -445,7 +401,6 @@ public class RoleUserState { throw new IllegalStateException("Failed to parse roles.xml: " + file, e); } } } private void parseXmlLocked(@NonNull XmlPullParser parser) throws IOException, XmlPullParserException { Loading Loading @@ -590,7 +545,7 @@ public class RoleUserState { throw new IllegalStateException("This RoleUserState has already been destroyed"); } mWriteHandler.removeCallbacksAndMessages(null); getFile(mUserId).delete(); mPersistence.delete(UserHandle.of(mUserId)); mDestroyed = true; } } Loading Loading
apex/permission/service/java/com/android/role/persistence/RolesPersistence.java 0 → 100644 +72 −0 Original line number Diff line number Diff line /* * 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.role.persistence; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.os.UserHandle; /** * Persistence for roles. * * TODO(b/147914847): Remove @hide when it becomes the default. * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES, process = SystemApi.Process.SYSTEM_SERVER) public interface RolesPersistence { /** * Read the roles from persistence. * * This will perform I/O operations synchronously. * * @param user the user to read for * @return the roles read */ @Nullable RolesState read(@NonNull UserHandle user); /** * Write the roles to persistence. * * This will perform I/O operations synchronously. * * @param roles the roles to write * @param user the user to write for */ void write(@NonNull RolesState roles, @NonNull UserHandle user); /** * Delete the roles 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 RolesPersistence} implementation. * * @return the new instance. */ @NonNull static RolesPersistence createInstance() { return new RolesPersistenceImpl(); } }
apex/permission/service/java/com/android/role/persistence/RolesPersistenceImpl.java 0 → 100644 +217 −0 Original line number Diff line number Diff line /* * 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.role.persistence; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; import android.util.Log; import android.util.Xml; import com.android.permission.persistence.IoUtils; 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.Map; import java.util.Set; /** * Persistence implementation for roles. * * TODO(b/147914847): Remove @hide when it becomes the default. * @hide */ public class RolesPersistenceImpl implements RolesPersistence { private static final String LOG_TAG = RolesPersistenceImpl.class.getSimpleName(); private static final String ROLES_FILE_NAME = "roles.xml"; private static final String TAG_ROLES = "roles"; private static final String TAG_ROLE = "role"; private static final String TAG_HOLDER = "holder"; private static final String ATTRIBUTE_VERSION = "version"; private static final String ATTRIBUTE_NAME = "name"; private static final String ATTRIBUTE_PACKAGES_HASH = "packagesHash"; @Nullable @Override public RolesState 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, "roles.xml not found"); return null; } catch (XmlPullParserException | IOException e) { throw new IllegalStateException("Failed to read roles.xml: " + file , e); } } @NonNull private static RolesState 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_ROLES)) { return parseRoles(parser); } } throw new IllegalStateException("Missing <" + TAG_ROLES + "> in roles.xml"); } @NonNull private static RolesState parseRoles(@NonNull XmlPullParser parser) throws IOException, XmlPullParserException { int version = Integer.parseInt(parser.getAttributeValue(null, ATTRIBUTE_VERSION)); String packagesHash = parser.getAttributeValue(null, ATTRIBUTE_PACKAGES_HASH); Map<String, Set<String>> roles = 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; } if (parser.getName().equals(TAG_ROLE)) { String roleName = parser.getAttributeValue(null, ATTRIBUTE_NAME); Set<String> roleHolders = parseRoleHolders(parser); roles.put(roleName, roleHolders); } } return new RolesState(version, packagesHash, roles); } @NonNull private static Set<String> parseRoleHolders(@NonNull XmlPullParser parser) throws IOException, XmlPullParserException { Set<String> roleHolders = new ArraySet<>(); 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_HOLDER)) { String roleHolder = parser.getAttributeValue(null, ATTRIBUTE_NAME); roleHolders.add(roleHolder); } } return roleHolders; } @Override public void write(@NonNull RolesState roles, @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); serializeRoles(serializer, roles); serializer.endDocument(); atomicFile.finishWrite(outputStream); } catch (Exception e) { Log.wtf(LOG_TAG, "Failed to write roles.xml, restoring backup: " + file, e); atomicFile.failWrite(outputStream); } finally { IoUtils.closeQuietly(outputStream); } } private static void serializeRoles(@NonNull XmlSerializer serializer, @NonNull RolesState roles) throws IOException { serializer.startTag(null, TAG_ROLES); int version = roles.getVersion(); serializer.attribute(null, ATTRIBUTE_VERSION, Integer.toString(version)); String packagesHash = roles.getPackagesHash(); if (packagesHash != null) { serializer.attribute(null, ATTRIBUTE_PACKAGES_HASH, packagesHash); } for (Map.Entry<String, Set<String>> entry : roles.getRoles().entrySet()) { String roleName = entry.getKey(); Set<String> roleHolders = entry.getValue(); serializer.startTag(null, TAG_ROLE); serializer.attribute(null, ATTRIBUTE_NAME, roleName); serializeRoleHolders(serializer, roleHolders); serializer.endTag(null, TAG_ROLE); } serializer.endTag(null, TAG_ROLES); } private static void serializeRoleHolders(@NonNull XmlSerializer serializer, @NonNull Set<String> roleHolders) throws IOException { for (String roleHolder : roleHolders) { serializer.startTag(null, TAG_HOLDER); serializer.attribute(null, ATTRIBUTE_NAME, roleHolder); serializer.endTag(null, TAG_HOLDER); } } @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, ROLES_FILE_NAME); } }
apex/permission/service/java/com/android/role/persistence/RolesState.java 0 → 100644 +72 −0 Original line number Diff line number Diff line /* * 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.role.persistence; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import java.util.Map; import java.util.Set; /** * State of all roles. * * 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 RolesState { /** * The version of the roles. */ private final int mVersion; /** * The hash of all packages in the system. */ @Nullable private final String mPackagesHash; /** * The roles. */ @NonNull private final Map<String, Set<String>> mRoles; public RolesState(int version, @Nullable String packagesHash, @NonNull Map<String, Set<String>> roles) { mVersion = version; mPackagesHash = packagesHash; mRoles = roles; } public int getVersion() { return mVersion; } @Nullable public String getPackagesHash() { return mPackagesHash; } @NonNull public Map<String, Set<String>> getRoles() { return mRoles; } }
services/api/current.txt +18 −0 Original line number Diff line number Diff line Loading @@ -26,6 +26,24 @@ package com.android.permission.persistence { } package com.android.role.persistence { public interface RolesPersistence { method @NonNull public static com.android.role.persistence.RolesPersistence createInstance(); method public void delete(@NonNull android.os.UserHandle); method @Nullable public com.android.role.persistence.RolesState read(@NonNull android.os.UserHandle); method public void write(@NonNull com.android.role.persistence.RolesState, @NonNull android.os.UserHandle); } public final class RolesState { ctor public RolesState(int, @Nullable String, @NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>>); method @Nullable public String getPackagesHash(); method @NonNull public java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getRoles(); method public int getVersion(); } } package com.android.server { public abstract class SystemService { Loading
services/core/java/com/android/server/role/RoleUserState.java +39 −84 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import android.annotation.UserIdInt; import android.annotation.WorkerThread; import android.os.Environment; import android.os.Handler; import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; Loading @@ -34,22 +35,21 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.util.CollectionUtils; import com.android.internal.util.dump.DualDumpOutputStream; import com.android.internal.util.function.pooled.PooledLambda; import libcore.io.IoUtils; import com.android.role.persistence.RolesPersistence; import com.android.role.persistence.RolesState; 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; import java.util.Objects; import java.util.Set; /** * Stores the state of roles for a user. Loading @@ -71,6 +71,8 @@ public class RoleUserState { private static final String ATTRIBUTE_NAME = "name"; private static final String ATTRIBUTE_PACKAGES_HASH = "packagesHash"; private final RolesPersistence mPersistence = RolesPersistence.createInstance(); @UserIdInt private final int mUserId; Loading Loading @@ -350,9 +352,7 @@ public class RoleUserState { @WorkerThread private void writeFile() { int version; String packagesHash; ArrayMap<String, ArraySet<String>> roles; RolesState roles; synchronized (mLock) { if (mDestroyed) { return; Loading @@ -360,79 +360,35 @@ public class RoleUserState { mWriteScheduled = false; version = mVersion; packagesHash = mPackagesHash; roles = snapshotRolesLocked(); roles = new RolesState(mVersion, mPackagesHash, (Map<String, Set<String>>) (Map<String, ?>) snapshotRolesLocked()); } AtomicFile atomicFile = new AtomicFile(getFile(mUserId), "roles-" + mUserId); FileOutputStream out = null; try { out = atomicFile.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); serializeRoles(serializer, version, packagesHash, roles); serializer.endDocument(); atomicFile.finishWrite(out); Slog.i(LOG_TAG, "Wrote roles.xml successfully"); } catch (IllegalArgumentException | IllegalStateException | IOException e) { Slog.wtf(LOG_TAG, "Failed to write roles.xml, restoring backup", e); if (out != null) { atomicFile.failWrite(out); } } finally { IoUtils.closeQuietly(out); mPersistence.write(roles, UserHandle.of(mUserId)); } } @WorkerThread private void serializeRoles(@NonNull XmlSerializer serializer, int version, @Nullable String packagesHash, @NonNull ArrayMap<String, ArraySet<String>> roles) throws IOException { serializer.startTag(null, TAG_ROLES); serializer.attribute(null, ATTRIBUTE_VERSION, Integer.toString(version)); if (packagesHash != null) { serializer.attribute(null, ATTRIBUTE_PACKAGES_HASH, packagesHash); private void readFile() { synchronized (mLock) { RolesState roles = mPersistence.read(UserHandle.of(mUserId)); if (roles == null) { readLegacyFileLocked(); scheduleWriteFileLocked(); return; } for (int i = 0, size = roles.size(); i < size; ++i) { String roleName = roles.keyAt(i); ArraySet<String> roleHolders = roles.valueAt(i); mVersion = roles.getVersion(); mPackagesHash = roles.getPackagesHash(); serializer.startTag(null, TAG_ROLE); serializer.attribute(null, ATTRIBUTE_NAME, roleName); serializeRoleHolders(serializer, roleHolders); serializer.endTag(null, TAG_ROLE); } serializer.endTag(null, TAG_ROLES); mRoles.clear(); for (Map.Entry<String, Set<String>> entry : roles.getRoles().entrySet()) { String roleName = entry.getKey(); ArraySet<String> roleHolders = new ArraySet<>(entry.getValue()); mRoles.put(roleName, roleHolders); } @WorkerThread private void serializeRoleHolders(@NonNull XmlSerializer serializer, @NonNull ArraySet<String> roleHolders) throws IOException { for (int i = 0, size = roleHolders.size(); i < size; ++i) { String roleHolder = roleHolders.valueAt(i); serializer.startTag(null, TAG_HOLDER); serializer.attribute(null, ATTRIBUTE_NAME, roleHolder); serializer.endTag(null, TAG_HOLDER); } } /** * Read the state from file. */ private void readFile() { synchronized (mLock) { private void readLegacyFileLocked() { File file = getFile(mUserId); try (FileInputStream in = new AtomicFile(file).openRead()) { XmlPullParser parser = Xml.newPullParser(); Loading @@ -445,7 +401,6 @@ public class RoleUserState { throw new IllegalStateException("Failed to parse roles.xml: " + file, e); } } } private void parseXmlLocked(@NonNull XmlPullParser parser) throws IOException, XmlPullParserException { Loading Loading @@ -590,7 +545,7 @@ public class RoleUserState { throw new IllegalStateException("This RoleUserState has already been destroyed"); } mWriteHandler.removeCallbacksAndMessages(null); getFile(mUserId).delete(); mPersistence.delete(UserHandle.of(mUserId)); mDestroyed = true; } } Loading