Loading core/java/android/app/supervision/flags.aconfig +9 −0 Original line number Diff line number Diff line Loading @@ -88,3 +88,12 @@ flag { description: "Enables usage of lock task feature to enable Quick Settings on LockTask mode" bug: "401576820" } flag { name: "persistent_supervision_settings" is_exported: false namespace: "supervision" description: "Saves supervision user data and recovery info into a binary xml file" bug: "406449915" } services/supervision/java/com/android/server/supervision/SupervisionLog.java 0 → 100644 +22 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.supervision; /** Constants used in supervision logs. */ public class SupervisionLog { public static final String TAG = "SupervisionService"; } services/supervision/java/com/android/server/supervision/SupervisionService.java +44 −16 Original line number Diff line number Diff line Loading @@ -74,8 +74,6 @@ import java.util.List; /** Service for handling system supervision. */ public class SupervisionService extends ISupervisionManager.Stub { private static final String LOG_TAG = "SupervisionService"; /** * Activity action: Requests user confirmation of supervision credentials. * Loading @@ -98,8 +96,11 @@ public class SupervisionService extends ISupervisionManager.Stub { private final Injector mInjector; final SupervisionManagerInternal mInternal = new SupervisionManagerInternalImpl(); @GuardedBy("getLockObject()") final SupervisionSettings mSupervisionSettings = SupervisionSettings.getInstance(); public SupervisionService(Context context) { mContext = context.createAttributionContext(LOG_TAG); mContext = context.createAttributionContext(SupervisionLog.TAG); mInjector = new Injector(context); mInjector.getUserManagerInternal().addUserLifecycleListener(new UserLifecycleListener()); } Loading Loading @@ -181,12 +182,19 @@ public class SupervisionService extends ISupervisionManager.Stub { /** Set the Supervision Recovery Info. */ @Override public void setSupervisionRecoveryInfo(SupervisionRecoveryInfo recoveryInfo) { if (Flags.persistentSupervisionSettings()) { mSupervisionSettings.saveRecoveryInfo(recoveryInfo); } else { SupervisionRecoveryInfoStorage.getInstance(mContext).saveRecoveryInfo(recoveryInfo); } } /** Returns the Supervision Recovery Info or null if recovery is not set. */ @Override public SupervisionRecoveryInfo getSupervisionRecoveryInfo() { if (Flags.persistentSupervisionSettings()) { return mSupervisionSettings.getRecoveryInfo(); } return SupervisionRecoveryInfoStorage.getInstance(mContext).loadRecoveryInfo(); } Loading @@ -199,12 +207,18 @@ public class SupervisionService extends ISupervisionManager.Stub { } synchronized (getLockObject()) { if (Flags.persistentSupervisionSettings()) { if (mSupervisionSettings.anySupervisedUser()) { return false; } } else { for (int i = 0; i < mUserData.size(); i++) { if (mUserData.valueAt(i).supervisionEnabled) { return false; } } } } return true; } Loading Loading @@ -262,7 +276,7 @@ public class SupervisionService extends ISupervisionManager.Stub { @Override protected void dump( @NonNull FileDescriptor fd, @NonNull PrintWriter printWriter, @Nullable String[] args) { if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, printWriter)) return; if (!DumpUtils.checkDumpPermission(mContext, SupervisionLog.TAG, printWriter)) return; try (var pw = new IndentingPrintWriter(printWriter, " ")) { pw.println("SupervisionService state:"); Loading @@ -285,6 +299,9 @@ public class SupervisionService extends ISupervisionManager.Stub { @NonNull @GuardedBy("getLockObject()") SupervisionUserData getUserDataLocked(@UserIdInt int userId) { if (Flags.persistentSupervisionSettings()) { return mSupervisionSettings.getUserData(userId); } else { SupervisionUserData data = mUserData.get(userId); if (data == null) { // TODO(b/362790738): Do not create user data for nonexistent users. Loading @@ -293,6 +310,7 @@ public class SupervisionService extends ISupervisionManager.Stub { } return data; } } /** * Sets supervision as enabled or disabled for the given user and, in case supervision is being Loading @@ -304,6 +322,9 @@ public class SupervisionService extends ISupervisionManager.Stub { SupervisionUserData data = getUserDataLocked(userId); data.supervisionEnabled = enabled; data.supervisionAppPackage = enabled ? supervisionAppPackage : null; if (Flags.persistentSupervisionSettings()) { mSupervisionSettings.saveUserData(); } } final long token = Binder.clearCallingIdentity(); try { Loading @@ -318,7 +339,7 @@ public class SupervisionService extends ISupervisionManager.Stub { (ISupervisionAppService) conn.getServiceBinder(); if (binder == null) { Slog.d( LOG_TAG, SupervisionLog.TAG, TextUtils.formatSimple( "Unable to toggle supervision for package %s. Binder is" + " null.", Loading @@ -333,7 +354,7 @@ public class SupervisionService extends ISupervisionManager.Stub { } } catch (RemoteException e) { Slog.d( LOG_TAG, SupervisionLog.TAG, TextUtils.formatSimple( "Unable to toggle supervision for package %s. e = %s", targetPackage, e)); Loading Loading @@ -574,6 +595,9 @@ public class SupervisionService extends ISupervisionManager.Stub { SupervisionUserData data = getUserDataLocked(userId); data.supervisionLockScreenEnabled = enabled; data.supervisionLockScreenOptions = options; if (Flags.persistentSupervisionSettings()) { mSupervisionSettings.saveUserData(); } } } } Loading @@ -583,8 +607,12 @@ public class SupervisionService extends ISupervisionManager.Stub { @Override public void onUserRemoved(UserInfo user) { synchronized (getLockObject()) { if (Flags.persistentSupervisionSettings()) { mSupervisionSettings.removeUserData(user.id); } else { mUserData.remove(user.id); } } } } } services/supervision/java/com/android/server/supervision/SupervisionSettings.java 0 → 100644 +266 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.supervision; import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.supervision.SupervisionRecoveryInfo; import android.os.Environment; import android.os.PersistableBundle; import android.util.AtomicFile; import android.util.Slog; import android.util.SparseArray; import android.util.Xml; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import org.xmlpull.v1.XmlPullParserException; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; /** * Provides storage and retrieval of device supervision recovery information and user data. * * <p>The storage is managed as a singleton, ensuring a single point of access for persistent user * data and recovery info. */ public class SupervisionSettings { private static SupervisionSettings sInstance; private static final Object sLock = new Object(); private final SparseArray<SupervisionUserData> mUserData = new SparseArray<>(); private static final String PREF_RECOVERY = "supervision_recovery_info"; private static final String KEY_ACCOUNT_TYPE = "account_type"; private static final String KEY_ACCOUNT_NAME = "account_name"; private static final String KEY_ACCOUNT_DATA = "account_data"; private static final String KEY_STATE = "state"; private static final String PREF_DATA = "supervision_data"; private static final String PREF_USER_DATA = "supervision_user_data"; private static final String KEY_USER_ID = "user_id"; private static final String KEY_ENABLED = "supervision_enabled"; private static final String KEY_APP_PACKAGE = "supervision_app_package"; private static final String KEY_LOCK_SCREEN_ENABLED = "supervision_lockscreen_enabled"; private static final String KEY_LOCK_SCREEN_OPTIONS = "supervision_lockscreen_options"; private AtomicFile recoveryInfoFile = new AtomicFile( new File(Environment.getDataSystemDirectory(), "supervision_recovery_info.xml"), "supervision"); private AtomicFile userDataFile = new AtomicFile( new File(Environment.getDataSystemDirectory(), "supervision_settings.xml"), "supervision"); private SupervisionSettings() { loadUserData(); } public static SupervisionSettings getInstance() { synchronized (sLock) { if (sInstance == null) { sInstance = new SupervisionSettings(); } return sInstance; } } @VisibleForTesting public void changeDirForTesting(File parent) { recoveryInfoFile = new AtomicFile(new File(parent, "supervision_recovery_info.xml"), "supervision"); userDataFile = new AtomicFile(new File(parent, "supervision_settings.xml"), "supervision"); } /** Gets data about a specific user. */ @NonNull public SupervisionUserData getUserData(@UserIdInt int userId) { SupervisionUserData data = mUserData.get(userId); if (data == null) { // TODO(b/362790738): Do not create user data for nonexistent users. data = new SupervisionUserData(userId); mUserData.append(userId, data); } return data; } /** Removes data of a specific user. */ public void removeUserData(int userId) { mUserData.remove(userId); saveUserData(); } /** Checks if there is at least one supervised user in the device. */ public boolean anySupervisedUser() { for (int i = 0; i < mUserData.size(); i++) { if (mUserData.valueAt(i).supervisionEnabled) { return true; } } return false; } /** Loads user data from persistent storage. */ public void loadUserData() { Slog.d(SupervisionLog.TAG, "Restoring supervision state"); mUserData.clear(); if (!userDataFile.getBaseFile().exists()) { return; } try (FileInputStream stream = userDataFile.openRead()) { final TypedXmlPullParser parser = Xml.resolvePullParser(stream); XmlUtils.beginDocument(parser, PREF_DATA); final int outerDepth = parser.getDepth(); while (XmlUtils.nextElementWithin(parser, outerDepth)) { if (parser.getName().equals(PREF_USER_DATA)) { int userId = parser.getAttributeInt(null, KEY_USER_ID); SupervisionUserData data = getUserData(userId); data.supervisionEnabled = parser.getAttributeBoolean(null, KEY_ENABLED); data.supervisionAppPackage = parser.getAttributeValue(null, KEY_APP_PACKAGE); if (data.supervisionAppPackage.isEmpty()) { data.supervisionAppPackage = null; } data.supervisionLockScreenEnabled = parser.getAttributeBoolean(null, KEY_LOCK_SCREEN_ENABLED); while (XmlUtils.nextElementWithin(parser, outerDepth + 1)) { if (parser.getName().equals(KEY_LOCK_SCREEN_OPTIONS)) { data.supervisionLockScreenOptions = PersistableBundle.restoreFromXml(parser); } } } } } catch (IOException | XmlPullParserException e) { Slog.e(SupervisionLog.TAG, "Failed to restore supervision state", e); } } /** Saves user data to persistent storage. */ public void saveUserData() { FileOutputStream stream = null; Slog.d(SupervisionLog.TAG, "Writing supervision state"); try { stream = userDataFile.startWrite(); final TypedXmlSerializer xml = Xml.resolveSerializer(stream); xml.startDocument(null, true); xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); xml.startTag(null, PREF_DATA); for (int i = 0; i < mUserData.size(); i++) { SupervisionUserData data = mUserData.valueAt(i); xml.startTag(null, PREF_USER_DATA); xml.attributeInt(null, KEY_USER_ID, data.userId); xml.attributeBoolean(null, KEY_ENABLED, data.supervisionEnabled); xml.attribute( null, KEY_APP_PACKAGE, data.supervisionAppPackage == null ? "" : data.supervisionAppPackage); xml.attributeBoolean( null, KEY_LOCK_SCREEN_ENABLED, data.supervisionLockScreenEnabled); if (data.supervisionLockScreenOptions != null) { xml.startTag(null, KEY_LOCK_SCREEN_OPTIONS); data.supervisionLockScreenOptions.saveToXml(xml); xml.endTag(null, KEY_LOCK_SCREEN_OPTIONS); } xml.endTag(null, PREF_USER_DATA); } xml.endTag(null, PREF_DATA); xml.endDocument(); userDataFile.finishWrite(stream); } catch (IOException | XmlPullParserException e) { userDataFile.failWrite(stream); Slog.e(SupervisionLog.TAG, "Failed to save supervision state", e); } } /** * Gets the device supervision recovery information from persistent storage. * * @return The {@link SupervisionRecoveryInfo} if found, otherwise {@code null}. */ public SupervisionRecoveryInfo getRecoveryInfo() { Slog.d(SupervisionLog.TAG, "Retrieving recovery info"); if (!recoveryInfoFile.getBaseFile().exists()) { Slog.d(SupervisionLog.TAG, "Recovery info file does not exist"); return null; } try (FileInputStream stream = recoveryInfoFile.openRead()) { final TypedXmlPullParser parser = Xml.resolvePullParser(stream); XmlUtils.beginDocument(parser, PREF_RECOVERY); int outerDepth = parser.getDepth(); String accountType = parser.getAttributeValue(null, KEY_ACCOUNT_TYPE); String accountName = parser.getAttributeValue(null, KEY_ACCOUNT_NAME); int state = parser.getAttributeInt(null, KEY_STATE, SupervisionRecoveryInfo.STATE_PENDING); PersistableBundle accountData = null; while (XmlUtils.nextElementWithin(parser, outerDepth)) { if (parser.getName().equals(KEY_ACCOUNT_DATA)) { accountData = PersistableBundle.restoreFromXml(parser); } } if (!accountType.isEmpty() && !accountName.isEmpty()) { return new SupervisionRecoveryInfo(accountName, accountType, state, accountData); } } catch (IOException | XmlPullParserException e) { Slog.e(SupervisionLog.TAG, "Failed to get recovery info from xml file", e); } return null; } /** * Saves the device supervision recovery information to persistent storage. * * @param recoveryInfo The {@link SupervisionRecoveryInfo} to save or {@code null} to clear the * stored information. */ public void saveRecoveryInfo(SupervisionRecoveryInfo recoveryInfo) { Slog.d(SupervisionLog.TAG, "Saving recovery info"); if (recoveryInfo == null) { recoveryInfoFile.delete(); return; } FileOutputStream stream = null; try { stream = recoveryInfoFile.startWrite(); final TypedXmlSerializer xml = Xml.resolveSerializer(stream); xml.startDocument(null, true); xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); xml.startTag(null, PREF_RECOVERY); xml.attribute(null, KEY_ACCOUNT_TYPE, recoveryInfo.getAccountType()); xml.attribute(null, KEY_ACCOUNT_NAME, recoveryInfo.getAccountName()); xml.attributeInt(null, KEY_STATE, recoveryInfo.getState()); xml.startTag(null, KEY_ACCOUNT_DATA); recoveryInfo.getAccountData().saveToXml(xml); xml.endTag(null, KEY_ACCOUNT_DATA); xml.endTag(null, PREF_RECOVERY); xml.endDocument(); recoveryInfoFile.finishWrite(stream); } catch (IOException | XmlPullParserException e) { userDataFile.failWrite(stream); Slog.e(SupervisionLog.TAG, "Failed to save recovery info", e); } } } services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt +1 −0 Original line number Diff line number Diff line Loading @@ -400,6 +400,7 @@ class SupervisionServiceTest { } @Test @RequiresFlagsEnabled(Flags.FLAG_PERSISTENT_SUPERVISION_SETTINGS) fun setSupervisionRecoveryInfo() { assertThat(service.supervisionRecoveryInfo).isNull() Loading Loading
core/java/android/app/supervision/flags.aconfig +9 −0 Original line number Diff line number Diff line Loading @@ -88,3 +88,12 @@ flag { description: "Enables usage of lock task feature to enable Quick Settings on LockTask mode" bug: "401576820" } flag { name: "persistent_supervision_settings" is_exported: false namespace: "supervision" description: "Saves supervision user data and recovery info into a binary xml file" bug: "406449915" }
services/supervision/java/com/android/server/supervision/SupervisionLog.java 0 → 100644 +22 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.supervision; /** Constants used in supervision logs. */ public class SupervisionLog { public static final String TAG = "SupervisionService"; }
services/supervision/java/com/android/server/supervision/SupervisionService.java +44 −16 Original line number Diff line number Diff line Loading @@ -74,8 +74,6 @@ import java.util.List; /** Service for handling system supervision. */ public class SupervisionService extends ISupervisionManager.Stub { private static final String LOG_TAG = "SupervisionService"; /** * Activity action: Requests user confirmation of supervision credentials. * Loading @@ -98,8 +96,11 @@ public class SupervisionService extends ISupervisionManager.Stub { private final Injector mInjector; final SupervisionManagerInternal mInternal = new SupervisionManagerInternalImpl(); @GuardedBy("getLockObject()") final SupervisionSettings mSupervisionSettings = SupervisionSettings.getInstance(); public SupervisionService(Context context) { mContext = context.createAttributionContext(LOG_TAG); mContext = context.createAttributionContext(SupervisionLog.TAG); mInjector = new Injector(context); mInjector.getUserManagerInternal().addUserLifecycleListener(new UserLifecycleListener()); } Loading Loading @@ -181,12 +182,19 @@ public class SupervisionService extends ISupervisionManager.Stub { /** Set the Supervision Recovery Info. */ @Override public void setSupervisionRecoveryInfo(SupervisionRecoveryInfo recoveryInfo) { if (Flags.persistentSupervisionSettings()) { mSupervisionSettings.saveRecoveryInfo(recoveryInfo); } else { SupervisionRecoveryInfoStorage.getInstance(mContext).saveRecoveryInfo(recoveryInfo); } } /** Returns the Supervision Recovery Info or null if recovery is not set. */ @Override public SupervisionRecoveryInfo getSupervisionRecoveryInfo() { if (Flags.persistentSupervisionSettings()) { return mSupervisionSettings.getRecoveryInfo(); } return SupervisionRecoveryInfoStorage.getInstance(mContext).loadRecoveryInfo(); } Loading @@ -199,12 +207,18 @@ public class SupervisionService extends ISupervisionManager.Stub { } synchronized (getLockObject()) { if (Flags.persistentSupervisionSettings()) { if (mSupervisionSettings.anySupervisedUser()) { return false; } } else { for (int i = 0; i < mUserData.size(); i++) { if (mUserData.valueAt(i).supervisionEnabled) { return false; } } } } return true; } Loading Loading @@ -262,7 +276,7 @@ public class SupervisionService extends ISupervisionManager.Stub { @Override protected void dump( @NonNull FileDescriptor fd, @NonNull PrintWriter printWriter, @Nullable String[] args) { if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, printWriter)) return; if (!DumpUtils.checkDumpPermission(mContext, SupervisionLog.TAG, printWriter)) return; try (var pw = new IndentingPrintWriter(printWriter, " ")) { pw.println("SupervisionService state:"); Loading @@ -285,6 +299,9 @@ public class SupervisionService extends ISupervisionManager.Stub { @NonNull @GuardedBy("getLockObject()") SupervisionUserData getUserDataLocked(@UserIdInt int userId) { if (Flags.persistentSupervisionSettings()) { return mSupervisionSettings.getUserData(userId); } else { SupervisionUserData data = mUserData.get(userId); if (data == null) { // TODO(b/362790738): Do not create user data for nonexistent users. Loading @@ -293,6 +310,7 @@ public class SupervisionService extends ISupervisionManager.Stub { } return data; } } /** * Sets supervision as enabled or disabled for the given user and, in case supervision is being Loading @@ -304,6 +322,9 @@ public class SupervisionService extends ISupervisionManager.Stub { SupervisionUserData data = getUserDataLocked(userId); data.supervisionEnabled = enabled; data.supervisionAppPackage = enabled ? supervisionAppPackage : null; if (Flags.persistentSupervisionSettings()) { mSupervisionSettings.saveUserData(); } } final long token = Binder.clearCallingIdentity(); try { Loading @@ -318,7 +339,7 @@ public class SupervisionService extends ISupervisionManager.Stub { (ISupervisionAppService) conn.getServiceBinder(); if (binder == null) { Slog.d( LOG_TAG, SupervisionLog.TAG, TextUtils.formatSimple( "Unable to toggle supervision for package %s. Binder is" + " null.", Loading @@ -333,7 +354,7 @@ public class SupervisionService extends ISupervisionManager.Stub { } } catch (RemoteException e) { Slog.d( LOG_TAG, SupervisionLog.TAG, TextUtils.formatSimple( "Unable to toggle supervision for package %s. e = %s", targetPackage, e)); Loading Loading @@ -574,6 +595,9 @@ public class SupervisionService extends ISupervisionManager.Stub { SupervisionUserData data = getUserDataLocked(userId); data.supervisionLockScreenEnabled = enabled; data.supervisionLockScreenOptions = options; if (Flags.persistentSupervisionSettings()) { mSupervisionSettings.saveUserData(); } } } } Loading @@ -583,8 +607,12 @@ public class SupervisionService extends ISupervisionManager.Stub { @Override public void onUserRemoved(UserInfo user) { synchronized (getLockObject()) { if (Flags.persistentSupervisionSettings()) { mSupervisionSettings.removeUserData(user.id); } else { mUserData.remove(user.id); } } } } }
services/supervision/java/com/android/server/supervision/SupervisionSettings.java 0 → 100644 +266 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.supervision; import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.supervision.SupervisionRecoveryInfo; import android.os.Environment; import android.os.PersistableBundle; import android.util.AtomicFile; import android.util.Slog; import android.util.SparseArray; import android.util.Xml; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import org.xmlpull.v1.XmlPullParserException; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; /** * Provides storage and retrieval of device supervision recovery information and user data. * * <p>The storage is managed as a singleton, ensuring a single point of access for persistent user * data and recovery info. */ public class SupervisionSettings { private static SupervisionSettings sInstance; private static final Object sLock = new Object(); private final SparseArray<SupervisionUserData> mUserData = new SparseArray<>(); private static final String PREF_RECOVERY = "supervision_recovery_info"; private static final String KEY_ACCOUNT_TYPE = "account_type"; private static final String KEY_ACCOUNT_NAME = "account_name"; private static final String KEY_ACCOUNT_DATA = "account_data"; private static final String KEY_STATE = "state"; private static final String PREF_DATA = "supervision_data"; private static final String PREF_USER_DATA = "supervision_user_data"; private static final String KEY_USER_ID = "user_id"; private static final String KEY_ENABLED = "supervision_enabled"; private static final String KEY_APP_PACKAGE = "supervision_app_package"; private static final String KEY_LOCK_SCREEN_ENABLED = "supervision_lockscreen_enabled"; private static final String KEY_LOCK_SCREEN_OPTIONS = "supervision_lockscreen_options"; private AtomicFile recoveryInfoFile = new AtomicFile( new File(Environment.getDataSystemDirectory(), "supervision_recovery_info.xml"), "supervision"); private AtomicFile userDataFile = new AtomicFile( new File(Environment.getDataSystemDirectory(), "supervision_settings.xml"), "supervision"); private SupervisionSettings() { loadUserData(); } public static SupervisionSettings getInstance() { synchronized (sLock) { if (sInstance == null) { sInstance = new SupervisionSettings(); } return sInstance; } } @VisibleForTesting public void changeDirForTesting(File parent) { recoveryInfoFile = new AtomicFile(new File(parent, "supervision_recovery_info.xml"), "supervision"); userDataFile = new AtomicFile(new File(parent, "supervision_settings.xml"), "supervision"); } /** Gets data about a specific user. */ @NonNull public SupervisionUserData getUserData(@UserIdInt int userId) { SupervisionUserData data = mUserData.get(userId); if (data == null) { // TODO(b/362790738): Do not create user data for nonexistent users. data = new SupervisionUserData(userId); mUserData.append(userId, data); } return data; } /** Removes data of a specific user. */ public void removeUserData(int userId) { mUserData.remove(userId); saveUserData(); } /** Checks if there is at least one supervised user in the device. */ public boolean anySupervisedUser() { for (int i = 0; i < mUserData.size(); i++) { if (mUserData.valueAt(i).supervisionEnabled) { return true; } } return false; } /** Loads user data from persistent storage. */ public void loadUserData() { Slog.d(SupervisionLog.TAG, "Restoring supervision state"); mUserData.clear(); if (!userDataFile.getBaseFile().exists()) { return; } try (FileInputStream stream = userDataFile.openRead()) { final TypedXmlPullParser parser = Xml.resolvePullParser(stream); XmlUtils.beginDocument(parser, PREF_DATA); final int outerDepth = parser.getDepth(); while (XmlUtils.nextElementWithin(parser, outerDepth)) { if (parser.getName().equals(PREF_USER_DATA)) { int userId = parser.getAttributeInt(null, KEY_USER_ID); SupervisionUserData data = getUserData(userId); data.supervisionEnabled = parser.getAttributeBoolean(null, KEY_ENABLED); data.supervisionAppPackage = parser.getAttributeValue(null, KEY_APP_PACKAGE); if (data.supervisionAppPackage.isEmpty()) { data.supervisionAppPackage = null; } data.supervisionLockScreenEnabled = parser.getAttributeBoolean(null, KEY_LOCK_SCREEN_ENABLED); while (XmlUtils.nextElementWithin(parser, outerDepth + 1)) { if (parser.getName().equals(KEY_LOCK_SCREEN_OPTIONS)) { data.supervisionLockScreenOptions = PersistableBundle.restoreFromXml(parser); } } } } } catch (IOException | XmlPullParserException e) { Slog.e(SupervisionLog.TAG, "Failed to restore supervision state", e); } } /** Saves user data to persistent storage. */ public void saveUserData() { FileOutputStream stream = null; Slog.d(SupervisionLog.TAG, "Writing supervision state"); try { stream = userDataFile.startWrite(); final TypedXmlSerializer xml = Xml.resolveSerializer(stream); xml.startDocument(null, true); xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); xml.startTag(null, PREF_DATA); for (int i = 0; i < mUserData.size(); i++) { SupervisionUserData data = mUserData.valueAt(i); xml.startTag(null, PREF_USER_DATA); xml.attributeInt(null, KEY_USER_ID, data.userId); xml.attributeBoolean(null, KEY_ENABLED, data.supervisionEnabled); xml.attribute( null, KEY_APP_PACKAGE, data.supervisionAppPackage == null ? "" : data.supervisionAppPackage); xml.attributeBoolean( null, KEY_LOCK_SCREEN_ENABLED, data.supervisionLockScreenEnabled); if (data.supervisionLockScreenOptions != null) { xml.startTag(null, KEY_LOCK_SCREEN_OPTIONS); data.supervisionLockScreenOptions.saveToXml(xml); xml.endTag(null, KEY_LOCK_SCREEN_OPTIONS); } xml.endTag(null, PREF_USER_DATA); } xml.endTag(null, PREF_DATA); xml.endDocument(); userDataFile.finishWrite(stream); } catch (IOException | XmlPullParserException e) { userDataFile.failWrite(stream); Slog.e(SupervisionLog.TAG, "Failed to save supervision state", e); } } /** * Gets the device supervision recovery information from persistent storage. * * @return The {@link SupervisionRecoveryInfo} if found, otherwise {@code null}. */ public SupervisionRecoveryInfo getRecoveryInfo() { Slog.d(SupervisionLog.TAG, "Retrieving recovery info"); if (!recoveryInfoFile.getBaseFile().exists()) { Slog.d(SupervisionLog.TAG, "Recovery info file does not exist"); return null; } try (FileInputStream stream = recoveryInfoFile.openRead()) { final TypedXmlPullParser parser = Xml.resolvePullParser(stream); XmlUtils.beginDocument(parser, PREF_RECOVERY); int outerDepth = parser.getDepth(); String accountType = parser.getAttributeValue(null, KEY_ACCOUNT_TYPE); String accountName = parser.getAttributeValue(null, KEY_ACCOUNT_NAME); int state = parser.getAttributeInt(null, KEY_STATE, SupervisionRecoveryInfo.STATE_PENDING); PersistableBundle accountData = null; while (XmlUtils.nextElementWithin(parser, outerDepth)) { if (parser.getName().equals(KEY_ACCOUNT_DATA)) { accountData = PersistableBundle.restoreFromXml(parser); } } if (!accountType.isEmpty() && !accountName.isEmpty()) { return new SupervisionRecoveryInfo(accountName, accountType, state, accountData); } } catch (IOException | XmlPullParserException e) { Slog.e(SupervisionLog.TAG, "Failed to get recovery info from xml file", e); } return null; } /** * Saves the device supervision recovery information to persistent storage. * * @param recoveryInfo The {@link SupervisionRecoveryInfo} to save or {@code null} to clear the * stored information. */ public void saveRecoveryInfo(SupervisionRecoveryInfo recoveryInfo) { Slog.d(SupervisionLog.TAG, "Saving recovery info"); if (recoveryInfo == null) { recoveryInfoFile.delete(); return; } FileOutputStream stream = null; try { stream = recoveryInfoFile.startWrite(); final TypedXmlSerializer xml = Xml.resolveSerializer(stream); xml.startDocument(null, true); xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); xml.startTag(null, PREF_RECOVERY); xml.attribute(null, KEY_ACCOUNT_TYPE, recoveryInfo.getAccountType()); xml.attribute(null, KEY_ACCOUNT_NAME, recoveryInfo.getAccountName()); xml.attributeInt(null, KEY_STATE, recoveryInfo.getState()); xml.startTag(null, KEY_ACCOUNT_DATA); recoveryInfo.getAccountData().saveToXml(xml); xml.endTag(null, KEY_ACCOUNT_DATA); xml.endTag(null, PREF_RECOVERY); xml.endDocument(); recoveryInfoFile.finishWrite(stream); } catch (IOException | XmlPullParserException e) { userDataFile.failWrite(stream); Slog.e(SupervisionLog.TAG, "Failed to save recovery info", e); } } }
services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt +1 −0 Original line number Diff line number Diff line Loading @@ -400,6 +400,7 @@ class SupervisionServiceTest { } @Test @RequiresFlagsEnabled(Flags.FLAG_PERSISTENT_SUPERVISION_SETTINGS) fun setSupervisionRecoveryInfo() { assertThat(service.supervisionRecoveryInfo).isNull() Loading