Protect package-restrictions from corruption.

+ refactor to encapulate all logic into ResilientAtomicFile

Bug: 253568736
Test: atest PackageManagerSettingsTests com.android.server.pm.test.SettingsTest
Change-Id: I2bba7d623c56f40bc8ed93a21098c9184698609d
diff --git a/services/core/java/com/android/server/pm/ResilientAtomicFile.java b/services/core/java/com/android/server/pm/ResilientAtomicFile.java
new file mode 100644
index 0000000..19aa4f8
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ResilientAtomicFile.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2023 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.pm;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.FileUtils;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.server.security.FileIntegrity;
+
+import libcore.io.IoUtils;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+final class ResilientAtomicFile implements Closeable {
+    private static final String LOG_TAG = "ResilientAtomicFile";
+
+    private final File mFile;
+
+    private final File mTemporaryBackup;
+
+    private final File mReserveCopy;
+
+    private final int mFileMode;
+
+    private final String mDebugName;
+
+    private final ReadEventLogger mReadEventLogger;
+
+    // Write state.
+    private FileOutputStream mMainOutStream = null;
+    private FileInputStream mMainInStream = null;
+    private FileOutputStream mReserveOutStream = null;
+    private FileInputStream mReserveInStream = null;
+
+    // Read state.
+    private File mCurrentFile = null;
+    private FileInputStream mCurrentInStream = null;
+
+    private void finalizeOutStream(FileOutputStream str) throws IOException {
+        // Flash/sync + set permissions.
+        str.flush();
+        FileUtils.sync(str);
+        FileUtils.setPermissions(str.getFD(), mFileMode, -1, -1);
+    }
+
+    ResilientAtomicFile(@NonNull File file, @NonNull File temporaryBackup,
+            @NonNull File reserveCopy, int fileMode, String debugName,
+            @Nullable ReadEventLogger readEventLogger) {
+        mFile = file;
+        mTemporaryBackup = temporaryBackup;
+        mReserveCopy = reserveCopy;
+        mFileMode = fileMode;
+        mDebugName = debugName;
+        mReadEventLogger = readEventLogger;
+    }
+
+    public File getBaseFile() {
+        return mFile;
+    }
+
+    public FileOutputStream startWrite() throws IOException {
+        if (mMainOutStream != null) {
+            throw new IllegalStateException("Duplicate startWrite call?");
+        }
+
+        new File(mFile.getParent()).mkdirs();
+
+        if (mFile.exists()) {
+            // Presence of backup settings file indicates that we failed
+            // to persist packages earlier. So preserve the older
+            // backup for future reference since the current packages
+            // might have been corrupted.
+            if (!mTemporaryBackup.exists()) {
+                if (!mFile.renameTo(mTemporaryBackup)) {
+                    throw new IOException("Unable to backup " + mDebugName
+                            + " file, current changes will be lost at reboot");
+                }
+            } else {
+                mFile.delete();
+                Slog.w(LOG_TAG, "Preserving older " + mDebugName + " backup");
+            }
+        }
+        // Reserve copy is not valid anymore.
+        mReserveCopy.delete();
+
+        // In case of MT access, it's possible the files get overwritten during write.
+        // Let's open all FDs we need now.
+        mMainOutStream = new FileOutputStream(mFile);
+        mMainInStream = new FileInputStream(mFile);
+        mReserveOutStream = new FileOutputStream(mReserveCopy);
+        mReserveInStream = new FileInputStream(mReserveCopy);
+
+        return mMainOutStream;
+    }
+
+    public void finishWrite(FileOutputStream str) throws IOException {
+        if (mMainOutStream != str) {
+            throw new IllegalStateException("Invalid incoming stream.");
+        }
+
+        // Flush and set permissions.
+        try (FileOutputStream mainOutStream = mMainOutStream) {
+            mMainOutStream = null;
+            finalizeOutStream(mainOutStream);
+        }
+        // New file successfully written, old one are no longer needed.
+        mTemporaryBackup.delete();
+
+        try (FileInputStream mainInStream = mMainInStream;
+             FileInputStream reserveInStream = mReserveInStream) {
+            mMainInStream = null;
+            mReserveInStream = null;
+
+            // Copy main file to reserve.
+            try (FileOutputStream reserveOutStream = mReserveOutStream) {
+                mReserveOutStream = null;
+                FileUtils.copy(mainInStream, reserveOutStream);
+                finalizeOutStream(reserveOutStream);
+            }
+
+            // Protect both main and reserve using fs-verity.
+            try (ParcelFileDescriptor mainPfd = ParcelFileDescriptor.dup(mainInStream.getFD());
+                 ParcelFileDescriptor copyPfd = ParcelFileDescriptor.dup(reserveInStream.getFD())) {
+                FileIntegrity.setUpFsVerity(mainPfd);
+                FileIntegrity.setUpFsVerity(copyPfd);
+            } catch (IOException e) {
+                Slog.e(LOG_TAG, "Failed to verity-protect " + mDebugName, e);
+            }
+        } catch (IOException e) {
+            Slog.e(LOG_TAG, "Failed to write reserve copy " + mDebugName + ": " + mReserveCopy, e);
+        }
+    }
+
+    public void failWrite(FileOutputStream str) {
+        if (mMainOutStream != str) {
+            throw new IllegalStateException("Invalid incoming stream.");
+        }
+
+        // Close all FDs.
+        close();
+
+        // Clean up partially written files
+        if (mFile.exists()) {
+            if (!mFile.delete()) {
+                Slog.i(LOG_TAG, "Failed to clean up mangled file: " + mFile);
+            }
+        }
+    }
+
+    public FileInputStream openRead() throws IOException {
+        if (mTemporaryBackup.exists()) {
+            try {
+                mCurrentFile = mTemporaryBackup;
+                mCurrentInStream = new FileInputStream(mCurrentFile);
+                if (mReadEventLogger != null) {
+                    mReadEventLogger.logEvent(Log.INFO,
+                            "Need to read from backup " + mDebugName + " file");
+                }
+                if (mFile.exists()) {
+                    // If both the backup and normal file exist, we
+                    // ignore the normal one since it might have been
+                    // corrupted.
+                    Slog.w(LOG_TAG, "Cleaning up " + mDebugName + " file " + mFile);
+                    mFile.delete();
+                }
+                // Ignore reserve copy as well.
+                mReserveCopy.delete();
+            } catch (java.io.IOException e) {
+                // We'll try for the normal settings file.
+            }
+        }
+
+        if (mCurrentInStream != null) {
+            return mCurrentInStream;
+        }
+
+        if (mFile.exists()) {
+            mCurrentFile = mFile;
+            mCurrentInStream = new FileInputStream(mCurrentFile);
+        } else if (mReserveCopy.exists()) {
+            mCurrentFile = mReserveCopy;
+            mCurrentInStream = new FileInputStream(mCurrentFile);
+            if (mReadEventLogger != null) {
+                mReadEventLogger.logEvent(Log.INFO,
+                        "Need to read from reserve copy " + mDebugName + " file");
+            }
+        }
+
+        if (mCurrentInStream == null) {
+            if (mReadEventLogger != null) {
+                mReadEventLogger.logEvent(Log.INFO, "No " + mDebugName + " file");
+            }
+        }
+
+        return mCurrentInStream;
+    }
+
+    public void failRead(FileInputStream str, Exception e) {
+        if (mCurrentInStream != str) {
+            throw new IllegalStateException("Invalid incoming stream.");
+        }
+        mCurrentInStream = null;
+        IoUtils.closeQuietly(str);
+
+        if (mReadEventLogger != null) {
+            mReadEventLogger.logEvent(Log.ERROR,
+                    "Error reading " + mDebugName + ", removing " + mCurrentFile + '\n'
+                            + Log.getStackTraceString(e));
+        }
+
+        mCurrentFile.delete();
+        mCurrentFile = null;
+    }
+
+    public void delete() {
+        mFile.delete();
+        mTemporaryBackup.delete();
+        mReserveCopy.delete();
+    }
+
+    @Override
+    public void close() {
+        IoUtils.closeQuietly(mMainOutStream);
+        IoUtils.closeQuietly(mMainInStream);
+        IoUtils.closeQuietly(mReserveOutStream);
+        IoUtils.closeQuietly(mReserveInStream);
+        IoUtils.closeQuietly(mCurrentInStream);
+        mMainOutStream = null;
+        mMainInStream = null;
+        mReserveOutStream = null;
+        mReserveInStream = null;
+        mCurrentInStream = null;
+        mCurrentFile = null;
+    }
+
+    public String toString() {
+        return mFile.getPath();
+    }
+
+    interface ReadEventLogger {
+        void logEvent(int priority, String msg);
+    }
+}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index f1998f7..9501367 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -119,7 +119,6 @@
 import com.android.server.pm.verify.domain.DomainVerificationLegacySettings;
 import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
 import com.android.server.pm.verify.domain.DomainVerificationPersistence;
-import com.android.server.security.FileIntegrity;
 import com.android.server.utils.Slogf;
 import com.android.server.utils.Snappable;
 import com.android.server.utils.SnapshotCache;
@@ -172,7 +171,7 @@
 /**
  * Holds information about dynamic settings.
  */
-public final class Settings implements Watchable, Snappable {
+public final class Settings implements Watchable, Snappable, ResilientAtomicFile.ReadEventLogger {
     private static final String TAG = "PackageSettings";
 
     /**
@@ -344,7 +343,7 @@
     private static final String ATTR_BLOCK_UNINSTALL = "blockUninstall";
     private static final String ATTR_ENABLED = "enabled";
     private static final String ATTR_ENABLED_CALLER = "enabledCaller";
-    private static final String ATTR_DOMAIN_VERIFICATON_STATE = "domainVerificationStatus";
+    private static final String ATTR_DOMAIN_VERIFICATION_STATE = "domainVerificationStatus";
     private static final String ATTR_APP_LINK_GENERATION = "app-link-generation";
     private static final String ATTR_INSTALL_REASON = "install-reason";
     private static final String ATTR_UNINSTALL_REASON = "uninstall-reason";
@@ -1511,16 +1510,22 @@
         return new File(new File(mSystemDir, "users"), Integer.toString(userId));
     }
 
-    // The method itself does not have to be guarded, but the file does.
-    @GuardedBy("mPackageRestrictionsLock")
-    private File getUserPackagesStateFile(int userId) {
-        return new File(getUserSystemDirectory(userId), "package-restrictions.xml");
+    private ResilientAtomicFile getUserPackagesStateFile(int userId) {
+        File mainFile = new File(getUserSystemDirectory(userId), "package-restrictions.xml");
+        File temporaryBackup = new File(getUserSystemDirectory(userId),
+                "package-restrictions-backup.xml");
+        File reserveCopy = new File(getUserSystemDirectory(userId),
+                "package-restrictions.xml.reservecopy");
+        return new ResilientAtomicFile(mainFile, temporaryBackup, reserveCopy,
+                FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IWGRP,
+                "package restrictions", this);
     }
 
-    // The method itself does not have to be guarded, but the file does.
-    @GuardedBy("mPackageRestrictionsLock")
-    private File getUserPackagesStateBackupFile(int userId) {
-        return new File(getUserSystemDirectory(userId), "package-restrictions-backup.xml");
+    private ResilientAtomicFile getSettingsFile() {
+        return new ResilientAtomicFile(mSettingsFilename, mPreviousSettingsFilename,
+                mSettingsReserveCopyFilename,
+                FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IWGRP,
+                "package manager settings", this);
     }
 
     private File getUserRuntimePermissionsFile(int userId) {
@@ -1730,272 +1735,243 @@
         }
     }
 
+    @Override
+    public void logEvent(int priority, String msg) {
+        mReadMessages.append(msg + "\n");
+        PackageManagerService.reportSettingsProblem(priority, msg);
+    }
+
+
     void readPackageRestrictionsLPr(int userId,
             @NonNull ArrayMap<String, Long> origFirstInstallTimes) {
         if (DEBUG_MU) {
             Log.i(TAG, "Reading package restrictions for user=" + userId);
         }
-        FileInputStream str = null;
 
-        synchronized (mPackageRestrictionsLock) {
-            File userPackagesStateFile = getUserPackagesStateFile(userId);
-            File backupFile = getUserPackagesStateBackupFile(userId);
-            if (backupFile.exists()) {
-                try {
-                    str = new FileInputStream(backupFile);
-                    mReadMessages.append("Reading from backup stopped packages file\n");
-                    PackageManagerService.reportSettingsProblem(Log.INFO,
-                            "Need to read from backup stopped packages file");
-                    if (userPackagesStateFile.exists()) {
-                        // If both the backup and normal file exist, we
-                        // ignore the normal one since it might have been
-                        // corrupted.
-                        Slog.w(PackageManagerService.TAG, "Cleaning up stopped packages file "
-                                + userPackagesStateFile);
-                        userPackagesStateFile.delete();
+        try (ResilientAtomicFile atomicFile = getUserPackagesStateFile(userId)) {
+            FileInputStream str = null;
+            try {
+                synchronized (mPackageRestrictionsLock) {
+                    str = atomicFile.openRead();
+                    if (str == null) {
+                        // At first boot, make sure no packages are stopped.
+                        // We usually want to have third party apps initialize
+                        // in the stopped state, but not at first boot.  Also
+                        // consider all applications to be installed.
+                        for (PackageSetting pkg : mPackages.values()) {
+                            pkg.setUserState(userId, 0, COMPONENT_ENABLED_STATE_DEFAULT,
+                                    true  /*installed*/,
+                                    false /*stopped*/,
+                                    false /*notLaunched*/,
+                                    false /*hidden*/,
+                                    0 /*distractionFlags*/,
+                                    null /*suspendParams*/,
+                                    false /*instantApp*/,
+                                    false /*virtualPreload*/,
+                                    null /*lastDisableAppCaller*/,
+                                    null /*enabledComponents*/,
+                                    null /*disabledComponents*/,
+                                    PackageManager.INSTALL_REASON_UNKNOWN,
+                                    PackageManager.UNINSTALL_REASON_UNKNOWN,
+                                    null /*harmfulAppWarning*/,
+                                    null /* splashScreenTheme*/,
+                                    0 /*firstInstallTime*/
+                            );
+                        }
+                        return;
                     }
-                } catch (java.io.IOException e) {
-                    // We'll try for the normal settings file.
-                }
-            }
-
-            if (str == null && userPackagesStateFile.exists()) {
-                try {
-                    str = new FileInputStream(userPackagesStateFile);
-                    if (DEBUG_MU) Log.i(TAG, "Reading " + userPackagesStateFile);
-                } catch (java.io.IOException e) {
-                    mReadMessages.append("Error reading: " + e.toString());
-                    PackageManagerService.reportSettingsProblem(Log.ERROR,
-                            "Error reading settings: " + e);
-                    Slog.wtf(TAG, "Error reading package manager stopped packages", e);
-                }
-            }
-        }
-
-        if (str == null) {
-            mReadMessages.append("No stopped packages file found\n");
-            PackageManagerService.reportSettingsProblem(Log.INFO,
-                    "No stopped packages file; "
-                            + "assuming all started");
-            // At first boot, make sure no packages are stopped.
-            // We usually want to have third party apps initialize
-            // in the stopped state, but not at first boot.  Also
-            // consider all applications to be installed.
-            for (PackageSetting pkg : mPackages.values()) {
-                pkg.setUserState(userId, 0, COMPONENT_ENABLED_STATE_DEFAULT,
-                        true  /*installed*/,
-                        false /*stopped*/,
-                        false /*notLaunched*/,
-                        false /*hidden*/,
-                        0 /*distractionFlags*/,
-                        null /*suspendParams*/,
-                        false /*instantApp*/,
-                        false /*virtualPreload*/,
-                        null /*lastDisableAppCaller*/,
-                        null /*enabledComponents*/,
-                        null /*disabledComponents*/,
-                        PackageManager.INSTALL_REASON_UNKNOWN,
-                        PackageManager.UNINSTALL_REASON_UNKNOWN,
-                        null /*harmfulAppWarning*/,
-                        null /* splashScreenTheme*/,
-                        0 /*firstInstallTime*/
-                );
-            }
-            return;
-        }
-
-        try {
-            final TypedXmlPullParser parser = Xml.resolvePullParser(str);
-
-            int type;
-            while ((type=parser.next()) != XmlPullParser.START_TAG
-                       && type != XmlPullParser.END_DOCUMENT) {
-                ;
-            }
-
-            if (type != XmlPullParser.START_TAG) {
-                mReadMessages.append("No start tag found in package restrictions file\n");
-                PackageManagerService.reportSettingsProblem(Log.WARN,
-                        "No start tag found in package manager stopped packages");
-                return;
-            }
-
-            int outerDepth = parser.getDepth();
-            PackageSetting ps = null;
-            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
-                   && (type != XmlPullParser.END_TAG
-                           || parser.getDepth() > outerDepth)) {
-                if (type == XmlPullParser.END_TAG
-                        || type == XmlPullParser.TEXT) {
-                    continue;
                 }
 
-                String tagName = parser.getName();
-                if (tagName.equals(TAG_PACKAGE)) {
-                    String name = parser.getAttributeValue(null, ATTR_NAME);
-                    ps = mPackages.get(name);
-                    if (ps == null) {
-                        Slog.w(PackageManagerService.TAG, "No package known for stopped package "
-                                + name);
-                        XmlUtils.skipCurrentTag(parser);
+                final TypedXmlPullParser parser = Xml.resolvePullParser(str);
+
+                int type;
+                while ((type = parser.next()) != XmlPullParser.START_TAG
+                        && type != XmlPullParser.END_DOCUMENT) {
+                    // nothing
+                }
+
+                if (type != XmlPullParser.START_TAG) {
+                    mReadMessages.append("No start tag found in package restrictions file\n");
+                    PackageManagerService.reportSettingsProblem(Log.WARN,
+                            "No start tag found in package manager package restrictions file");
+                    return;
+                }
+
+                int outerDepth = parser.getDepth();
+                PackageSetting ps = null;
+                while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                        && (type != XmlPullParser.END_TAG
+                        || parser.getDepth() > outerDepth)) {
+                    if (type == XmlPullParser.END_TAG
+                            || type == XmlPullParser.TEXT) {
                         continue;
                     }
 
-                    final long ceDataInode =
-                            parser.getAttributeLong(null, ATTR_CE_DATA_INODE, 0);
-                    final boolean installed =
-                            parser.getAttributeBoolean(null, ATTR_INSTALLED, true);
-                    final boolean stopped =
-                            parser.getAttributeBoolean(null, ATTR_STOPPED, false);
-                    final boolean notLaunched =
-                            parser.getAttributeBoolean(null, ATTR_NOT_LAUNCHED, false);
-
-                    // For backwards compatibility with the previous name of "blocked", which
-                    // now means hidden, read the old attribute as well.
-                    boolean hidden = parser.getAttributeBoolean(null, ATTR_HIDDEN, false);
-                    if (!hidden) {
-                        hidden = parser.getAttributeBoolean(null, ATTR_BLOCKED, false);
-                    }
-
-                    final int distractionFlags = parser.getAttributeInt(null, ATTR_DISTRACTION_FLAGS, 0);
-                    final boolean suspended = parser.getAttributeBoolean(null, ATTR_SUSPENDED, false);
-                    String oldSuspendingPackage = parser.getAttributeValue(null,
-                            ATTR_SUSPENDING_PACKAGE);
-                    final String dialogMessage = parser.getAttributeValue(null,
-                            ATTR_SUSPEND_DIALOG_MESSAGE);
-                    if (suspended && oldSuspendingPackage == null) {
-                        oldSuspendingPackage = PLATFORM_PACKAGE_NAME;
-                    }
-
-                    final boolean blockUninstall =
-                            parser.getAttributeBoolean(null, ATTR_BLOCK_UNINSTALL, false);
-                    final boolean instantApp =
-                            parser.getAttributeBoolean(null, ATTR_INSTANT_APP, false);
-                    final boolean virtualPreload =
-                            parser.getAttributeBoolean(null, ATTR_VIRTUAL_PRELOAD, false);
-                    final int enabled = parser.getAttributeInt(null, ATTR_ENABLED,
-                            COMPONENT_ENABLED_STATE_DEFAULT);
-                    final String enabledCaller = parser.getAttributeValue(null,
-                            ATTR_ENABLED_CALLER);
-                    final String harmfulAppWarning =
-                            parser.getAttributeValue(null, ATTR_HARMFUL_APP_WARNING);
-                    final int verifState = parser.getAttributeInt(null,
-                            ATTR_DOMAIN_VERIFICATON_STATE,
-                            PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED);
-                    final int installReason = parser.getAttributeInt(null, ATTR_INSTALL_REASON,
-                            PackageManager.INSTALL_REASON_UNKNOWN);
-                    final int uninstallReason = parser.getAttributeInt(null, ATTR_UNINSTALL_REASON,
-                            PackageManager.UNINSTALL_REASON_UNKNOWN);
-                    final String splashScreenTheme = parser.getAttributeValue(null,
-                            ATTR_SPLASH_SCREEN_THEME);
-                    final long firstInstallTime = parser.getAttributeLongHex(null,
-                            ATTR_FIRST_INSTALL_TIME, 0);
-
-                    ArraySet<String> enabledComponents = null;
-                    ArraySet<String> disabledComponents = null;
-                    PersistableBundle suspendedAppExtras = null;
-                    PersistableBundle suspendedLauncherExtras = null;
-                    SuspendDialogInfo oldSuspendDialogInfo = null;
-
-                    int packageDepth = parser.getDepth();
-                    ArrayMap<String, SuspendParams> suspendParamsMap = null;
-                    while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
-                            && (type != XmlPullParser.END_TAG
-                            || parser.getDepth() > packageDepth)) {
-                        if (type == XmlPullParser.END_TAG
-                                || type == XmlPullParser.TEXT) {
+                    String tagName = parser.getName();
+                    if (tagName.equals(TAG_PACKAGE)) {
+                        String name = parser.getAttributeValue(null, ATTR_NAME);
+                        ps = mPackages.get(name);
+                        if (ps == null) {
+                            Slog.w(PackageManagerService.TAG,
+                                    "No package known for package restrictions " + name);
+                            XmlUtils.skipCurrentTag(parser);
                             continue;
                         }
-                        switch (parser.getName()) {
-                            case TAG_ENABLED_COMPONENTS:
-                                enabledComponents = readComponentsLPr(parser);
-                                break;
-                            case TAG_DISABLED_COMPONENTS:
-                                disabledComponents = readComponentsLPr(parser);
-                                break;
-                            case TAG_SUSPENDED_APP_EXTRAS:
-                                suspendedAppExtras = PersistableBundle.restoreFromXml(parser);
-                                break;
-                            case TAG_SUSPENDED_LAUNCHER_EXTRAS:
-                                suspendedLauncherExtras = PersistableBundle.restoreFromXml(parser);
-                                break;
-                            case TAG_SUSPENDED_DIALOG_INFO:
-                                oldSuspendDialogInfo = SuspendDialogInfo.restoreFromXml(parser);
-                                break;
-                            case TAG_SUSPEND_PARAMS:
-                                final String suspendingPackage = parser.getAttributeValue(null,
-                                        ATTR_SUSPENDING_PACKAGE);
-                                if (suspendingPackage == null) {
-                                    Slog.wtf(TAG, "No suspendingPackage found inside tag "
-                                            + TAG_SUSPEND_PARAMS);
-                                    continue;
-                                }
-                                if (suspendParamsMap == null) {
-                                    suspendParamsMap = new ArrayMap<>();
-                                }
-                                suspendParamsMap.put(suspendingPackage,
-                                        SuspendParams.restoreFromXml(parser));
-                                break;
-                            default:
-                                Slog.wtf(TAG, "Unknown tag " + parser.getName() + " under tag "
-                                        + TAG_PACKAGE);
+
+                        final long ceDataInode =
+                                parser.getAttributeLong(null, ATTR_CE_DATA_INODE, 0);
+                        final boolean installed =
+                                parser.getAttributeBoolean(null, ATTR_INSTALLED, true);
+                        final boolean stopped =
+                                parser.getAttributeBoolean(null, ATTR_STOPPED, false);
+                        final boolean notLaunched =
+                                parser.getAttributeBoolean(null, ATTR_NOT_LAUNCHED, false);
+
+                        // For backwards compatibility with the previous name of "blocked", which
+                        // now means hidden, read the old attribute as well.
+                        boolean hidden = parser.getAttributeBoolean(null, ATTR_HIDDEN, false);
+                        if (!hidden) {
+                            hidden = parser.getAttributeBoolean(null, ATTR_BLOCKED, false);
                         }
-                    }
-                    if (oldSuspendDialogInfo == null && !TextUtils.isEmpty(dialogMessage)) {
-                        oldSuspendDialogInfo = new SuspendDialogInfo.Builder()
-                                .setMessage(dialogMessage)
-                                .build();
-                    }
-                    if (suspended && suspendParamsMap == null) {
-                        final SuspendParams suspendParams = new SuspendParams(
-                                        oldSuspendDialogInfo,
-                                        suspendedAppExtras,
-                                        suspendedLauncherExtras);
-                        suspendParamsMap = new ArrayMap<>();
-                        suspendParamsMap.put(oldSuspendingPackage, suspendParams);
-                    }
 
-                    if (blockUninstall) {
-                        setBlockUninstallLPw(userId, name, true);
-                    }
-                    ps.setUserState(userId, ceDataInode, enabled, installed, stopped, notLaunched,
-                            hidden, distractionFlags, suspendParamsMap, instantApp, virtualPreload,
-                            enabledCaller, enabledComponents, disabledComponents, installReason,
-                            uninstallReason, harmfulAppWarning, splashScreenTheme,
-                            firstInstallTime != 0 ? firstInstallTime :
-                                    origFirstInstallTimes.getOrDefault(name, 0L));
+                        final int distractionFlags = parser.getAttributeInt(null,
+                                ATTR_DISTRACTION_FLAGS, 0);
+                        final boolean suspended = parser.getAttributeBoolean(null, ATTR_SUSPENDED,
+                                false);
+                        String oldSuspendingPackage = parser.getAttributeValue(null,
+                                ATTR_SUSPENDING_PACKAGE);
+                        final String dialogMessage = parser.getAttributeValue(null,
+                                ATTR_SUSPEND_DIALOG_MESSAGE);
+                        if (suspended && oldSuspendingPackage == null) {
+                            oldSuspendingPackage = PLATFORM_PACKAGE_NAME;
+                        }
 
-                    mDomainVerificationManager.setLegacyUserState(name, userId, verifState);
-                } else if (tagName.equals("preferred-activities")) {
-                    readPreferredActivitiesLPw(parser, userId);
-                } else if (tagName.equals(TAG_PERSISTENT_PREFERRED_ACTIVITIES)) {
-                    readPersistentPreferredActivitiesLPw(parser, userId);
-                } else if (tagName.equals(TAG_CROSS_PROFILE_INTENT_FILTERS)) {
-                    readCrossProfileIntentFiltersLPw(parser, userId);
-                } else if (tagName.equals(TAG_DEFAULT_APPS)) {
-                    readDefaultAppsLPw(parser, userId);
-                } else if (tagName.equals(TAG_BLOCK_UNINSTALL_PACKAGES)) {
-                    readBlockUninstallPackagesLPw(parser, userId);
-                } else {
-                    Slog.w(PackageManagerService.TAG, "Unknown element under <stopped-packages>: "
-                          + parser.getName());
-                    XmlUtils.skipCurrentTag(parser);
+                        final boolean blockUninstall =
+                                parser.getAttributeBoolean(null, ATTR_BLOCK_UNINSTALL, false);
+                        final boolean instantApp =
+                                parser.getAttributeBoolean(null, ATTR_INSTANT_APP, false);
+                        final boolean virtualPreload =
+                                parser.getAttributeBoolean(null, ATTR_VIRTUAL_PRELOAD, false);
+                        final int enabled = parser.getAttributeInt(null, ATTR_ENABLED,
+                                COMPONENT_ENABLED_STATE_DEFAULT);
+                        final String enabledCaller = parser.getAttributeValue(null,
+                                ATTR_ENABLED_CALLER);
+                        final String harmfulAppWarning =
+                                parser.getAttributeValue(null, ATTR_HARMFUL_APP_WARNING);
+                        final int verifState = parser.getAttributeInt(null,
+                                ATTR_DOMAIN_VERIFICATION_STATE,
+                                PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED);
+                        final int installReason = parser.getAttributeInt(null, ATTR_INSTALL_REASON,
+                                PackageManager.INSTALL_REASON_UNKNOWN);
+                        final int uninstallReason = parser.getAttributeInt(null,
+                                ATTR_UNINSTALL_REASON,
+                                PackageManager.UNINSTALL_REASON_UNKNOWN);
+                        final String splashScreenTheme = parser.getAttributeValue(null,
+                                ATTR_SPLASH_SCREEN_THEME);
+                        final long firstInstallTime = parser.getAttributeLongHex(null,
+                                ATTR_FIRST_INSTALL_TIME, 0);
+
+                        ArraySet<String> enabledComponents = null;
+                        ArraySet<String> disabledComponents = null;
+                        PersistableBundle suspendedAppExtras = null;
+                        PersistableBundle suspendedLauncherExtras = null;
+                        SuspendDialogInfo oldSuspendDialogInfo = null;
+
+                        int packageDepth = parser.getDepth();
+                        ArrayMap<String, SuspendParams> suspendParamsMap = null;
+                        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                                && (type != XmlPullParser.END_TAG
+                                || parser.getDepth() > packageDepth)) {
+                            if (type == XmlPullParser.END_TAG
+                                    || type == XmlPullParser.TEXT) {
+                                continue;
+                            }
+                            switch (parser.getName()) {
+                                case TAG_ENABLED_COMPONENTS:
+                                    enabledComponents = readComponentsLPr(parser);
+                                    break;
+                                case TAG_DISABLED_COMPONENTS:
+                                    disabledComponents = readComponentsLPr(parser);
+                                    break;
+                                case TAG_SUSPENDED_APP_EXTRAS:
+                                    suspendedAppExtras = PersistableBundle.restoreFromXml(parser);
+                                    break;
+                                case TAG_SUSPENDED_LAUNCHER_EXTRAS:
+                                    suspendedLauncherExtras = PersistableBundle.restoreFromXml(
+                                            parser);
+                                    break;
+                                case TAG_SUSPENDED_DIALOG_INFO:
+                                    oldSuspendDialogInfo = SuspendDialogInfo.restoreFromXml(parser);
+                                    break;
+                                case TAG_SUSPEND_PARAMS:
+                                    final String suspendingPackage = parser.getAttributeValue(null,
+                                            ATTR_SUSPENDING_PACKAGE);
+                                    if (suspendingPackage == null) {
+                                        Slog.wtf(TAG, "No suspendingPackage found inside tag "
+                                                + TAG_SUSPEND_PARAMS);
+                                        continue;
+                                    }
+                                    if (suspendParamsMap == null) {
+                                        suspendParamsMap = new ArrayMap<>();
+                                    }
+                                    suspendParamsMap.put(suspendingPackage,
+                                            SuspendParams.restoreFromXml(parser));
+                                    break;
+                                default:
+                                    Slog.wtf(TAG, "Unknown tag " + parser.getName() + " under tag "
+                                            + TAG_PACKAGE);
+                            }
+                        }
+                        if (oldSuspendDialogInfo == null && !TextUtils.isEmpty(dialogMessage)) {
+                            oldSuspendDialogInfo = new SuspendDialogInfo.Builder()
+                                    .setMessage(dialogMessage)
+                                    .build();
+                        }
+                        if (suspended && suspendParamsMap == null) {
+                            final SuspendParams suspendParams = new SuspendParams(
+                                    oldSuspendDialogInfo,
+                                    suspendedAppExtras,
+                                    suspendedLauncherExtras);
+                            suspendParamsMap = new ArrayMap<>();
+                            suspendParamsMap.put(oldSuspendingPackage, suspendParams);
+                        }
+
+                        if (blockUninstall) {
+                            setBlockUninstallLPw(userId, name, true);
+                        }
+                        ps.setUserState(userId, ceDataInode, enabled, installed, stopped,
+                                notLaunched,
+                                hidden, distractionFlags, suspendParamsMap, instantApp,
+                                virtualPreload,
+                                enabledCaller, enabledComponents, disabledComponents, installReason,
+                                uninstallReason, harmfulAppWarning, splashScreenTheme,
+                                firstInstallTime != 0 ? firstInstallTime :
+                                        origFirstInstallTimes.getOrDefault(name, 0L));
+
+                        mDomainVerificationManager.setLegacyUserState(name, userId, verifState);
+                    } else if (tagName.equals("preferred-activities")) {
+                        readPreferredActivitiesLPw(parser, userId);
+                    } else if (tagName.equals(TAG_PERSISTENT_PREFERRED_ACTIVITIES)) {
+                        readPersistentPreferredActivitiesLPw(parser, userId);
+                    } else if (tagName.equals(TAG_CROSS_PROFILE_INTENT_FILTERS)) {
+                        readCrossProfileIntentFiltersLPw(parser, userId);
+                    } else if (tagName.equals(TAG_DEFAULT_APPS)) {
+                        readDefaultAppsLPw(parser, userId);
+                    } else if (tagName.equals(TAG_BLOCK_UNINSTALL_PACKAGES)) {
+                        readBlockUninstallPackagesLPw(parser, userId);
+                    } else {
+                        Slog.w(PackageManagerService.TAG,
+                                "Unknown element under <stopped-packages>: "
+                                        + parser.getName());
+                        XmlUtils.skipCurrentTag(parser);
+                    }
                 }
+            } catch (IOException | XmlPullParserException e) {
+                // Remove corrupted file and retry.
+                atomicFile.failRead(str, e);
+
+                readPackageRestrictionsLPr(userId, origFirstInstallTimes);
             }
-
-            str.close();
-        } catch (XmlPullParserException e) {
-            mReadMessages.append("Error reading: " + e.toString());
-            PackageManagerService.reportSettingsProblem(Log.ERROR,
-                    "Error reading stopped packages: " + e);
-            Slog.wtf(PackageManagerService.TAG, "Error reading package manager stopped packages",
-                    e);
-
-        } catch (java.io.IOException e) {
-            mReadMessages.append("Error reading: " + e.toString());
-            PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading settings: " + e);
-            Slog.wtf(PackageManagerService.TAG, "Error reading package manager stopped packages",
-                    e);
         }
     }
 
@@ -2165,219 +2141,176 @@
             Log.i(TAG, "Writing package restrictions for user=" + userId);
         }
 
-        final File userPackagesStateFile;
-        final File backupFile;
-        final FileOutputStream fstr;
+        FileOutputStream str = null;
+        try (ResilientAtomicFile atomicFile = getUserPackagesStateFile(userId)) {
+            try {
+                synchronized (mPackageRestrictionsLock) {
+                    if (!sync) {
+                        int pending = mPendingAsyncPackageRestrictionsWrites.get(userId, 0) - 1;
+                        if (pending < 0) {
+                            Log.i(TAG, "Cancel writing package restrictions for user=" + userId);
+                            return;
+                        }
+                        mPendingAsyncPackageRestrictionsWrites.put(userId, pending);
+                    }
 
-        synchronized (mPackageRestrictionsLock) {
-            if (!sync) {
-                int pending = mPendingAsyncPackageRestrictionsWrites.get(userId, 0) - 1;
-                if (pending < 0) {
-                    Log.i(TAG, "Cancel writing package restrictions for user=" + userId);
-                    return;
-                }
-                mPendingAsyncPackageRestrictionsWrites.put(userId, pending);
-            }
-
-            // Keep the old stopped packages around until we know the new ones have
-            // been successfully written.
-            userPackagesStateFile = getUserPackagesStateFile(userId);
-            backupFile = getUserPackagesStateBackupFile(userId);
-            new File(userPackagesStateFile.getParent()).mkdirs();
-            if (userPackagesStateFile.exists()) {
-                // Presence of backup settings file indicates that we failed
-                // to persist packages earlier. So preserve the older
-                // backup for future reference since the current packages
-                // might have been corrupted.
-                if (!backupFile.exists()) {
-                    if (!userPackagesStateFile.renameTo(backupFile)) {
+                    try {
+                        str = atomicFile.startWrite();
+                    } catch (java.io.IOException e) {
                         Slog.wtf(PackageManagerService.TAG,
-                                "Unable to backup user packages state file, "
-                                        + "current changes will be lost at reboot");
+                                "Unable to write package manager package restrictions, "
+                                        + " current changes will be lost at reboot", e);
                         return;
                     }
-                } else {
-                    userPackagesStateFile.delete();
-                    Slog.w(PackageManagerService.TAG, "Preserving older stopped packages backup");
                 }
-            }
 
-            try {
-                fstr = new FileOutputStream(userPackagesStateFile);
-                // File is created, set permissions.
-                FileUtils.setPermissions(userPackagesStateFile.toString(),
-                        FileUtils.S_IRUSR | FileUtils.S_IWUSR
-                                | FileUtils.S_IRGRP | FileUtils.S_IWGRP,
-                        -1, -1);
-            } catch (java.io.IOException e) {
-                Slog.wtf(PackageManagerService.TAG,
-                        "Unable to write package manager user packages state, "
-                                + " current changes will be lost at reboot", e);
-                return;
-            }
-        }
+                synchronized (mLock) {
+                    final TypedXmlSerializer serializer = Xml.resolveSerializer(str);
+                    serializer.startDocument(null, true);
+                    serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output",
+                            true);
 
-        try {
-            synchronized (mLock) {
-                final TypedXmlSerializer serializer = Xml.resolveSerializer(fstr);
-                serializer.startDocument(null, true);
-                serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output",
-                        true);
+                    serializer.startTag(null, TAG_PACKAGE_RESTRICTIONS);
 
-                serializer.startTag(null, TAG_PACKAGE_RESTRICTIONS);
+                    if (DEBUG_MU) {
+                        Slogf.i(TAG, "Writing %s (%d packages)", atomicFile,
+                                mPackages.values().size());
+                    }
+                    for (final PackageSetting pkg : mPackages.values()) {
+                        final PackageUserStateInternal ustate = pkg.readUserState(userId);
+                        if (DEBUG_MU) {
+                            Log.v(TAG, "  pkg=" + pkg.getPackageName()
+                                    + ", installed=" + ustate.isInstalled()
+                                    + ", state=" + ustate.getEnabledState());
+                        }
+
+                        serializer.startTag(null, TAG_PACKAGE);
+                        serializer.attribute(null, ATTR_NAME, pkg.getPackageName());
+                        if (ustate.getCeDataInode() != 0) {
+                            serializer.attributeLong(null, ATTR_CE_DATA_INODE,
+                                    ustate.getCeDataInode());
+                        }
+                        if (!ustate.isInstalled()) {
+                            serializer.attributeBoolean(null, ATTR_INSTALLED, false);
+                        }
+                        if (ustate.isStopped()) {
+                            serializer.attributeBoolean(null, ATTR_STOPPED, true);
+                        }
+                        if (ustate.isNotLaunched()) {
+                            serializer.attributeBoolean(null, ATTR_NOT_LAUNCHED, true);
+                        }
+                        if (ustate.isHidden()) {
+                            serializer.attributeBoolean(null, ATTR_HIDDEN, true);
+                        }
+                        if (ustate.getDistractionFlags() != 0) {
+                            serializer.attributeInt(null, ATTR_DISTRACTION_FLAGS,
+                                    ustate.getDistractionFlags());
+                        }
+                        if (ustate.isSuspended()) {
+                            serializer.attributeBoolean(null, ATTR_SUSPENDED, true);
+                        }
+                        if (ustate.isInstantApp()) {
+                            serializer.attributeBoolean(null, ATTR_INSTANT_APP, true);
+                        }
+                        if (ustate.isVirtualPreload()) {
+                            serializer.attributeBoolean(null, ATTR_VIRTUAL_PRELOAD, true);
+                        }
+                        if (ustate.getEnabledState() != COMPONENT_ENABLED_STATE_DEFAULT) {
+                            serializer.attributeInt(null, ATTR_ENABLED, ustate.getEnabledState());
+                            if (ustate.getLastDisableAppCaller() != null) {
+                                serializer.attribute(null, ATTR_ENABLED_CALLER,
+                                        ustate.getLastDisableAppCaller());
+                            }
+                        }
+                        if (ustate.getInstallReason() != PackageManager.INSTALL_REASON_UNKNOWN) {
+                            serializer.attributeInt(null, ATTR_INSTALL_REASON,
+                                    ustate.getInstallReason());
+                        }
+                        serializer.attributeLongHex(null, ATTR_FIRST_INSTALL_TIME,
+                                ustate.getFirstInstallTimeMillis());
+                        if (ustate.getUninstallReason()
+                                != PackageManager.UNINSTALL_REASON_UNKNOWN) {
+                            serializer.attributeInt(null, ATTR_UNINSTALL_REASON,
+                                    ustate.getUninstallReason());
+                        }
+                        if (ustate.getHarmfulAppWarning() != null) {
+                            serializer.attribute(null, ATTR_HARMFUL_APP_WARNING,
+                                    ustate.getHarmfulAppWarning());
+                        }
+                        if (ustate.getSplashScreenTheme() != null) {
+                            serializer.attribute(null, ATTR_SPLASH_SCREEN_THEME,
+                                    ustate.getSplashScreenTheme());
+                        }
+                        if (ustate.isSuspended()) {
+                            for (int i = 0; i < ustate.getSuspendParams().size(); i++) {
+                                final String suspendingPackage = ustate.getSuspendParams().keyAt(i);
+                                serializer.startTag(null, TAG_SUSPEND_PARAMS);
+                                serializer.attribute(null, ATTR_SUSPENDING_PACKAGE,
+                                        suspendingPackage);
+                                final SuspendParams params =
+                                        ustate.getSuspendParams().valueAt(i);
+                                if (params != null) {
+                                    params.saveToXml(serializer);
+                                }
+                                serializer.endTag(null, TAG_SUSPEND_PARAMS);
+                            }
+                        }
+                        final ArraySet<String> enabledComponents = ustate.getEnabledComponents();
+                        if (enabledComponents != null && enabledComponents.size() > 0) {
+                            serializer.startTag(null, TAG_ENABLED_COMPONENTS);
+                            for (int i = 0; i < enabledComponents.size(); i++) {
+                                serializer.startTag(null, TAG_ITEM);
+                                serializer.attribute(null, ATTR_NAME,
+                                        enabledComponents.valueAt(i));
+                                serializer.endTag(null, TAG_ITEM);
+                            }
+                            serializer.endTag(null, TAG_ENABLED_COMPONENTS);
+                        }
+                        final ArraySet<String> disabledComponents = ustate.getDisabledComponents();
+                        if (disabledComponents != null && disabledComponents.size() > 0) {
+                            serializer.startTag(null, TAG_DISABLED_COMPONENTS);
+                            for (int i = 0; i < disabledComponents.size(); i++) {
+                                serializer.startTag(null, TAG_ITEM);
+                                serializer.attribute(null, ATTR_NAME,
+                                        disabledComponents.valueAt(i));
+                                serializer.endTag(null, TAG_ITEM);
+                            }
+                            serializer.endTag(null, TAG_DISABLED_COMPONENTS);
+                        }
+
+                        serializer.endTag(null, TAG_PACKAGE);
+                    }
+
+                    writePreferredActivitiesLPr(serializer, userId, true);
+                    writePersistentPreferredActivitiesLPr(serializer, userId);
+                    writeCrossProfileIntentFiltersLPr(serializer, userId);
+                    writeDefaultAppsLPr(serializer, userId);
+                    writeBlockUninstallPackagesLPr(serializer, userId);
+
+                    serializer.endTag(null, TAG_PACKAGE_RESTRICTIONS);
+
+                    serializer.endDocument();
+                }
+
+                atomicFile.finishWrite(str);
 
                 if (DEBUG_MU) {
-                    Slogf.i(TAG, "Writing %s (%d packages)", userPackagesStateFile,
-                            mPackages.values().size());
-                }
-                for (final PackageSetting pkg : mPackages.values()) {
-                    final PackageUserStateInternal ustate = pkg.readUserState(userId);
-                    if (DEBUG_MU) {
-                        Log.v(TAG, "  pkg=" + pkg.getPackageName()
-                                + ", installed=" + ustate.isInstalled()
-                                + ", state=" + ustate.getEnabledState());
-                    }
-
-                    serializer.startTag(null, TAG_PACKAGE);
-                    serializer.attribute(null, ATTR_NAME, pkg.getPackageName());
-                    if (ustate.getCeDataInode() != 0) {
-                        serializer.attributeLong(null, ATTR_CE_DATA_INODE, ustate.getCeDataInode());
-                    }
-                    if (!ustate.isInstalled()) {
-                        serializer.attributeBoolean(null, ATTR_INSTALLED, false);
-                    }
-                    if (ustate.isStopped()) {
-                        serializer.attributeBoolean(null, ATTR_STOPPED, true);
-                    }
-                    if (ustate.isNotLaunched()) {
-                        serializer.attributeBoolean(null, ATTR_NOT_LAUNCHED, true);
-                    }
-                    if (ustate.isHidden()) {
-                        serializer.attributeBoolean(null, ATTR_HIDDEN, true);
-                    }
-                    if (ustate.getDistractionFlags() != 0) {
-                        serializer.attributeInt(null, ATTR_DISTRACTION_FLAGS,
-                                ustate.getDistractionFlags());
-                    }
-                    if (ustate.isSuspended()) {
-                        serializer.attributeBoolean(null, ATTR_SUSPENDED, true);
-                    }
-                    if (ustate.isInstantApp()) {
-                        serializer.attributeBoolean(null, ATTR_INSTANT_APP, true);
-                    }
-                    if (ustate.isVirtualPreload()) {
-                        serializer.attributeBoolean(null, ATTR_VIRTUAL_PRELOAD, true);
-                    }
-                    if (ustate.getEnabledState() != COMPONENT_ENABLED_STATE_DEFAULT) {
-                        serializer.attributeInt(null, ATTR_ENABLED, ustate.getEnabledState());
-                        if (ustate.getLastDisableAppCaller() != null) {
-                            serializer.attribute(null, ATTR_ENABLED_CALLER,
-                                    ustate.getLastDisableAppCaller());
-                        }
-                    }
-                    if (ustate.getInstallReason() != PackageManager.INSTALL_REASON_UNKNOWN) {
-                        serializer.attributeInt(null, ATTR_INSTALL_REASON,
-                                ustate.getInstallReason());
-                    }
-                    serializer.attributeLongHex(null, ATTR_FIRST_INSTALL_TIME,
-                            ustate.getFirstInstallTimeMillis());
-                    if (ustate.getUninstallReason() != PackageManager.UNINSTALL_REASON_UNKNOWN) {
-                        serializer.attributeInt(null, ATTR_UNINSTALL_REASON,
-                                ustate.getUninstallReason());
-                    }
-                    if (ustate.getHarmfulAppWarning() != null) {
-                        serializer.attribute(null, ATTR_HARMFUL_APP_WARNING,
-                                ustate.getHarmfulAppWarning());
-                    }
-                    if (ustate.getSplashScreenTheme() != null) {
-                        serializer.attribute(null, ATTR_SPLASH_SCREEN_THEME,
-                                ustate.getSplashScreenTheme());
-                    }
-                    if (ustate.isSuspended()) {
-                        for (int i = 0; i < ustate.getSuspendParams().size(); i++) {
-                            final String suspendingPackage = ustate.getSuspendParams().keyAt(i);
-                            serializer.startTag(null, TAG_SUSPEND_PARAMS);
-                            serializer.attribute(null, ATTR_SUSPENDING_PACKAGE, suspendingPackage);
-                            final SuspendParams params =
-                                    ustate.getSuspendParams().valueAt(i);
-                            if (params != null) {
-                                params.saveToXml(serializer);
-                            }
-                            serializer.endTag(null, TAG_SUSPEND_PARAMS);
-                        }
-                    }
-                    final ArraySet<String> enabledComponents = ustate.getEnabledComponents();
-                    if (enabledComponents != null && enabledComponents.size() > 0) {
-                        serializer.startTag(null, TAG_ENABLED_COMPONENTS);
-                        for (int i = 0; i < enabledComponents.size(); i++) {
-                            serializer.startTag(null, TAG_ITEM);
-                            serializer.attribute(null, ATTR_NAME,
-                                    enabledComponents.valueAt(i));
-                            serializer.endTag(null, TAG_ITEM);
-                        }
-                        serializer.endTag(null, TAG_ENABLED_COMPONENTS);
-                    }
-                    final ArraySet<String> disabledComponents = ustate.getDisabledComponents();
-                    if (disabledComponents != null && disabledComponents.size() > 0) {
-                        serializer.startTag(null, TAG_DISABLED_COMPONENTS);
-                        for (int i = 0; i < disabledComponents.size(); i++) {
-                            serializer.startTag(null, TAG_ITEM);
-                            serializer.attribute(null, ATTR_NAME,
-                                    disabledComponents.valueAt(i));
-                            serializer.endTag(null, TAG_ITEM);
-                        }
-                        serializer.endTag(null, TAG_DISABLED_COMPONENTS);
-                    }
-
-                    serializer.endTag(null, TAG_PACKAGE);
+                    Log.i(TAG, "New package restrictions successfully written for user=" + userId
+                            + ": " + atomicFile);
                 }
 
-                writePreferredActivitiesLPr(serializer, userId, true);
-                writePersistentPreferredActivitiesLPr(serializer, userId);
-                writeCrossProfileIntentFiltersLPr(serializer, userId);
-                writeDefaultAppsLPr(serializer, userId);
-                writeBlockUninstallPackagesLPr(serializer, userId);
+                com.android.internal.logging.EventLogTags.writeCommitSysConfigFile(
+                        "package-user-" + userId, SystemClock.uptimeMillis() - startTime);
 
-                serializer.endTag(null, TAG_PACKAGE_RESTRICTIONS);
-
-                serializer.endDocument();
-            }
-
-            fstr.flush();
-            FileUtils.sync(fstr);
-            IoUtils.closeQuietly(fstr);
-
-            synchronized (mPackageRestrictionsLock) {
-                // File is created, set permissions.
-                FileUtils.setPermissions(userPackagesStateFile.toString(),
-                        FileUtils.S_IRUSR | FileUtils.S_IWUSR
-                                | FileUtils.S_IRGRP | FileUtils.S_IWGRP,
-                        -1, -1);
-                // New settings successfully written, old ones are no longer needed.
-                backupFile.delete();
-            }
-
-            if (DEBUG_MU) {
-                Log.i(TAG, "New settings successfully written for user=" + userId + ": "
-                        + userPackagesStateFile);
-            }
-
-            com.android.internal.logging.EventLogTags.writeCommitSysConfigFile(
-                    "package-user-" + userId, SystemClock.uptimeMillis() - startTime);
-
-            // Done, all is good!
-            return;
-        } catch (java.io.IOException e) {
-            Slog.wtf(PackageManagerService.TAG,
-                    "Unable to write package manager user packages state, "
-                    + " current changes will be lost at reboot", e);
-        }
-
-        // Clean up partially written files
-        if (userPackagesStateFile.exists()) {
-            if (!userPackagesStateFile.delete()) {
-                Log.i(PackageManagerService.TAG, "Failed to clean up mangled file: "
-                        + mStoppedPackagesFilename);
+                // Done, all is good!
+                return;
+            } catch (java.io.IOException e) {
+                Slog.wtf(PackageManagerService.TAG,
+                        "Unable to write package manager package restrictions, "
+                                + " current changes will be lost at reboot", e);
+                if (str != null) {
+                    atomicFile.failWrite(str);
+                }
             }
         }
     }
@@ -2589,153 +2522,108 @@
         // right time.
         invalidatePackageCache();
 
-        // Keep the old settings around until we know the new ones have
-        // been successfully written.
-        if (mSettingsFilename.exists()) {
-            // Presence of backup settings file indicates that we failed
-            // to persist settings earlier. So preserve the older
-            // backup for future reference since the current settings
-            // might have been corrupted.
-            if (!mPreviousSettingsFilename.exists()) {
-                if (!mSettingsFilename.renameTo(mPreviousSettingsFilename)) {
-                    Slog.wtf(PackageManagerService.TAG,
-                            "Unable to store older package manager settings, "
-                            + " current changes will be lost at reboot");
-                    return;
-                }
-            } else {
-                mSettingsFilename.delete();
-                Slog.w(PackageManagerService.TAG, "Preserving older settings backup");
-            }
-        }
-
         mPastSignatures.clear();
 
-        try {
-            final FileOutputStream fstr = new FileOutputStream(mSettingsFilename);
-            final TypedXmlSerializer serializer = Xml.resolveSerializer(fstr);
-            serializer.startDocument(null, true);
-            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
-
-            serializer.startTag(null, "packages");
-
-            for (int i = 0; i < mVersion.size(); i++) {
-                final String volumeUuid = mVersion.keyAt(i);
-                final VersionInfo ver = mVersion.valueAt(i);
-
-                serializer.startTag(null, TAG_VERSION);
-                XmlUtils.writeStringAttribute(serializer, ATTR_VOLUME_UUID, volumeUuid);
-                serializer.attributeInt(null, ATTR_SDK_VERSION, ver.sdkVersion);
-                serializer.attributeInt(null, ATTR_DATABASE_VERSION, ver.databaseVersion);
-                XmlUtils.writeStringAttribute(serializer, ATTR_BUILD_FINGERPRINT,
-                        ver.buildFingerprint);
-                XmlUtils.writeStringAttribute(serializer, ATTR_FINGERPRINT, ver.fingerprint);
-                serializer.endTag(null, TAG_VERSION);
-            }
-
-            if (mVerifierDeviceIdentity != null) {
-                serializer.startTag(null, "verifier");
-                serializer.attribute(null, "device", mVerifierDeviceIdentity.toString());
-                serializer.endTag(null, "verifier");
-            }
-
-            serializer.startTag(null, "permission-trees");
-            mPermissions.writePermissionTrees(serializer);
-            serializer.endTag(null, "permission-trees");
-
-            serializer.startTag(null, "permissions");
-            mPermissions.writePermissions(serializer);
-            serializer.endTag(null, "permissions");
-
-            for (final PackageSetting pkg : mPackages.values()) {
-                if (pkg.getPkg() != null && pkg.getPkg().isApex()) {
-                    // Don't persist APEX which doesn't have a valid app id and will fail to load
-                    continue;
-                }
-                writePackageLPr(serializer, pkg);
-            }
-
-            for (final PackageSetting pkg : mDisabledSysPackages.values()) {
-                if (pkg.getPkg() != null && pkg.getPkg().isApex()) {
-                    // Don't persist APEX which doesn't have a valid app id and will fail to load
-                    continue;
-                }
-                writeDisabledSysPackageLPr(serializer, pkg);
-            }
-
-            for (final SharedUserSetting usr : mSharedUsers.values()) {
-                serializer.startTag(null, "shared-user");
-                serializer.attribute(null, ATTR_NAME, usr.name);
-                serializer.attributeInt(null, "userId", usr.mAppId);
-                usr.signatures.writeXml(serializer, "sigs", mPastSignatures.untrackedStorage());
-                serializer.endTag(null, "shared-user");
-            }
-
-            if (mRenamedPackages.size() > 0) {
-                for (Map.Entry<String, String> e : mRenamedPackages.entrySet()) {
-                    serializer.startTag(null, "renamed-package");
-                    serializer.attribute(null, "new", e.getKey());
-                    serializer.attribute(null, "old", e.getValue());
-                    serializer.endTag(null, "renamed-package");
-                }
-            }
-
-            mDomainVerificationManager.writeSettings(computer, serializer,
-                    false /* includeSignatures */, UserHandle.USER_ALL);
-
-            mKeySetManagerService.writeKeySetManagerServiceLPr(serializer);
-
-            serializer.endTag(null, "packages");
-
-            serializer.endDocument();
-
-            fstr.flush();
-            FileUtils.sync(fstr);
-            fstr.close();
-
-            // New settings successfully written, old ones are no longer needed.
-            mPreviousSettingsFilename.delete();
-            mSettingsReserveCopyFilename.delete();
-
-            FileUtils.setPermissions(mSettingsFilename.toString(),
-                    FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IWGRP,
-                    -1, -1);
-
-            try (FileInputStream in = new FileInputStream(mSettingsFilename);
-                 FileOutputStream out = new FileOutputStream(mSettingsReserveCopyFilename)) {
-                FileUtils.copy(in, out);
-                out.flush();
-                FileUtils.sync(out);
-            } catch (IOException e) {
-                Slog.e(TAG,
-                        "Failed to write reserve copy of settings: " + mSettingsReserveCopyFilename,
-                        e);
-            }
-
+        try (ResilientAtomicFile atomicFile = getSettingsFile()) {
+            FileOutputStream str = null;
             try {
-                FileIntegrity.setUpFsVerity(mSettingsFilename);
-                FileIntegrity.setUpFsVerity(mSettingsReserveCopyFilename);
-            } catch (IOException e) {
-                Slog.e(TAG, "Failed to verity-protect settings", e);
-            }
+                str = atomicFile.startWrite();
 
-            writeKernelMappingLPr();
-            writePackageListLPr();
-            writeAllUsersPackageRestrictionsLPr(sync);
-            writeAllRuntimePermissionsLPr();
-            com.android.internal.logging.EventLogTags.writeCommitSysConfigFile(
-                    "package", SystemClock.uptimeMillis() - startTime);
-            return;
+                final TypedXmlSerializer serializer = Xml.resolveSerializer(str);
+                serializer.startDocument(null, true);
+                serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output",
+                        true);
 
-        } catch(java.io.IOException e) {
-            Slog.wtf(PackageManagerService.TAG, "Unable to write package manager settings, "
-                    + "current changes will be lost at reboot", e);
-        }
-        // Clean up partially written files
-        if (mSettingsFilename.exists()) {
-            if (!mSettingsFilename.delete()) {
-                Slog.wtf(PackageManagerService.TAG, "Failed to clean up mangled file: "
-                        + mSettingsFilename);
+                serializer.startTag(null, "packages");
+
+                for (int i = 0; i < mVersion.size(); i++) {
+                    final String volumeUuid = mVersion.keyAt(i);
+                    final VersionInfo ver = mVersion.valueAt(i);
+
+                    serializer.startTag(null, TAG_VERSION);
+                    XmlUtils.writeStringAttribute(serializer, ATTR_VOLUME_UUID, volumeUuid);
+                    serializer.attributeInt(null, ATTR_SDK_VERSION, ver.sdkVersion);
+                    serializer.attributeInt(null, ATTR_DATABASE_VERSION, ver.databaseVersion);
+                    XmlUtils.writeStringAttribute(serializer, ATTR_BUILD_FINGERPRINT,
+                            ver.buildFingerprint);
+                    XmlUtils.writeStringAttribute(serializer, ATTR_FINGERPRINT, ver.fingerprint);
+                    serializer.endTag(null, TAG_VERSION);
+                }
+
+                if (mVerifierDeviceIdentity != null) {
+                    serializer.startTag(null, "verifier");
+                    serializer.attribute(null, "device", mVerifierDeviceIdentity.toString());
+                    serializer.endTag(null, "verifier");
+                }
+
+                serializer.startTag(null, "permission-trees");
+                mPermissions.writePermissionTrees(serializer);
+                serializer.endTag(null, "permission-trees");
+
+                serializer.startTag(null, "permissions");
+                mPermissions.writePermissions(serializer);
+                serializer.endTag(null, "permissions");
+
+                for (final PackageSetting pkg : mPackages.values()) {
+                    if (pkg.getPkg() != null && pkg.getPkg().isApex()) {
+                        // Don't persist APEX which doesn't have a valid app id and will fail to
+                        // load
+                        continue;
+                    }
+                    writePackageLPr(serializer, pkg);
+                }
+
+                for (final PackageSetting pkg : mDisabledSysPackages.values()) {
+                    if (pkg.getPkg() != null && pkg.getPkg().isApex()) {
+                        // Don't persist APEX which doesn't have a valid app id and will fail to
+                        // load
+                        continue;
+                    }
+                    writeDisabledSysPackageLPr(serializer, pkg);
+                }
+
+                for (final SharedUserSetting usr : mSharedUsers.values()) {
+                    serializer.startTag(null, "shared-user");
+                    serializer.attribute(null, ATTR_NAME, usr.name);
+                    serializer.attributeInt(null, "userId", usr.mAppId);
+                    usr.signatures.writeXml(serializer, "sigs", mPastSignatures.untrackedStorage());
+                    serializer.endTag(null, "shared-user");
+                }
+
+                if (mRenamedPackages.size() > 0) {
+                    for (Map.Entry<String, String> e : mRenamedPackages.entrySet()) {
+                        serializer.startTag(null, "renamed-package");
+                        serializer.attribute(null, "new", e.getKey());
+                        serializer.attribute(null, "old", e.getValue());
+                        serializer.endTag(null, "renamed-package");
+                    }
+                }
+
+                mDomainVerificationManager.writeSettings(computer, serializer,
+                        false /* includeSignatures */, UserHandle.USER_ALL);
+
+                mKeySetManagerService.writeKeySetManagerServiceLPr(serializer);
+
+                serializer.endTag(null, "packages");
+
+                serializer.endDocument();
+
+                atomicFile.finishWrite(str);
+
+                writeKernelMappingLPr();
+                writePackageListLPr();
+                writeAllUsersPackageRestrictionsLPr(sync);
+                writeAllRuntimePermissionsLPr();
+                com.android.internal.logging.EventLogTags.writeCommitSysConfigFile(
+                        "package", SystemClock.uptimeMillis() - startTime);
+                return;
+
+            } catch (java.io.IOException e) {
+                Slog.wtf(PackageManagerService.TAG, "Unable to write package manager settings, "
+                        + "current changes will be lost at reboot", e);
+                if (str != null) {
+                    atomicFile.failWrite(str);
+                }
             }
         }
         //Debug.stopMethodTracing();
@@ -3160,183 +3048,140 @@
         mInstallerPackages.clear();
         originalFirstInstallTimes.clear();
 
-        File file = null;
-        FileInputStream str = null;
+        try (ResilientAtomicFile atomicFile = getSettingsFile()) {
+            FileInputStream str = null;
+            try {
+                str = atomicFile.openRead();
+                if (str == null) {
+                    // Not necessary, but will avoid wtf-s in the "finally" section.
+                    findOrCreateVersion(StorageManager.UUID_PRIVATE_INTERNAL).forceCurrent();
+                    findOrCreateVersion(StorageManager.UUID_PRIMARY_PHYSICAL).forceCurrent();
+                    return false;
+                }
+                final TypedXmlPullParser parser = Xml.resolvePullParser(str);
 
-        try {
-            // Check if the previous write was incomplete.
-            if (mPreviousSettingsFilename.exists()) {
-                try {
-                    file = mPreviousSettingsFilename;
-                    str = new FileInputStream(file);
-                    mReadMessages.append("Reading from backup settings file\n");
-                    PackageManagerService.reportSettingsProblem(Log.INFO,
-                            "Need to read from backup settings file");
-                    if (mSettingsFilename.exists()) {
-                        // If both the previous and current settings files exist,
-                        // we ignore the current since it might have been corrupted.
-                        Slog.w(PackageManagerService.TAG, "Cleaning up settings file "
-                                + mSettingsFilename);
-                        mSettingsFilename.delete();
+                int type;
+                while ((type = parser.next()) != XmlPullParser.START_TAG
+                        && type != XmlPullParser.END_DOCUMENT) {
+                    // nothing
+                }
+
+                if (type != XmlPullParser.START_TAG) {
+                    mReadMessages.append("No start tag found in settings file\n");
+                    PackageManagerService.reportSettingsProblem(Log.WARN,
+                            "No start tag found in package manager settings");
+                    Slog.wtf(PackageManagerService.TAG,
+                            "No start tag found in package manager settings");
+                    return false;
+                }
+
+                int outerDepth = parser.getDepth();
+                while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                        && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                    if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                        continue;
                     }
-                    // Ignore reserve copy as well.
-                    mSettingsReserveCopyFilename.delete();
-                } catch (java.io.IOException e) {
-                    // We'll try for the normal settings file.
-                }
-            }
-            if (str == null) {
-                if (mSettingsFilename.exists()) {
-                    // Using packages.xml.
-                    file = mSettingsFilename;
-                    str = new FileInputStream(file);
-                } else if (mSettingsReserveCopyFilename.exists()) {
-                    // Using reserve copy.
-                    file = mSettingsReserveCopyFilename;
-                    str = new FileInputStream(file);
-                    mReadMessages.append("Reading from reserve copy settings file\n");
-                    PackageManagerService.reportSettingsProblem(Log.INFO,
-                            "Need to read from reserve copy settings file");
-                }
-            }
-            if (str == null) {
-                // No available data sources.
-                mReadMessages.append("No settings file found\n");
-                PackageManagerService.reportSettingsProblem(Log.INFO,
-                        "No settings file; creating initial state");
-                // Not necessary, but will avoid wtf-s in the "finally" section.
-                findOrCreateVersion(StorageManager.UUID_PRIVATE_INTERNAL).forceCurrent();
-                findOrCreateVersion(StorageManager.UUID_PRIMARY_PHYSICAL).forceCurrent();
-                return false;
-            }
-            final TypedXmlPullParser parser = Xml.resolvePullParser(str);
 
-            int type;
-            while ((type = parser.next()) != XmlPullParser.START_TAG
-                    && type != XmlPullParser.END_DOCUMENT) {
-                ;
-            }
+                    String tagName = parser.getName();
+                    if (tagName.equals("package")) {
+                        readPackageLPw(parser, users, originalFirstInstallTimes);
+                    } else if (tagName.equals("permissions")) {
+                        mPermissions.readPermissions(parser);
+                    } else if (tagName.equals("permission-trees")) {
+                        mPermissions.readPermissionTrees(parser);
+                    } else if (tagName.equals("shared-user")) {
+                        readSharedUserLPw(parser, users);
+                    } else if (tagName.equals("preferred-packages")) {
+                        // no longer used.
+                    } else if (tagName.equals("preferred-activities")) {
+                        // Upgrading from old single-user implementation;
+                        // these are the preferred activities for user 0.
+                        readPreferredActivitiesLPw(parser, 0);
+                    } else if (tagName.equals(TAG_PERSISTENT_PREFERRED_ACTIVITIES)) {
+                        // TODO: check whether this is okay! as it is very
+                        // similar to how preferred-activities are treated
+                        readPersistentPreferredActivitiesLPw(parser, 0);
+                    } else if (tagName.equals(TAG_CROSS_PROFILE_INTENT_FILTERS)) {
+                        // TODO: check whether this is okay! as it is very
+                        // similar to how preferred-activities are treated
+                        readCrossProfileIntentFiltersLPw(parser, 0);
+                    } else if (tagName.equals(TAG_DEFAULT_BROWSER)) {
+                        readDefaultAppsLPw(parser, 0);
+                    } else if (tagName.equals("updated-package")) {
+                        readDisabledSysPackageLPw(parser, users);
+                    } else if (tagName.equals("renamed-package")) {
+                        String nname = parser.getAttributeValue(null, "new");
+                        String oname = parser.getAttributeValue(null, "old");
+                        if (nname != null && oname != null) {
+                            mRenamedPackages.put(nname, oname);
+                        }
+                    } else if (tagName.equals("last-platform-version")) {
+                        // Upgrade from older XML schema
+                        final VersionInfo internal = findOrCreateVersion(
+                                StorageManager.UUID_PRIVATE_INTERNAL);
+                        final VersionInfo external = findOrCreateVersion(
+                                StorageManager.UUID_PRIMARY_PHYSICAL);
 
-            if (type != XmlPullParser.START_TAG) {
-                mReadMessages.append("No start tag found in settings file\n");
-                PackageManagerService.reportSettingsProblem(Log.WARN,
-                        "No start tag found in package manager settings");
-                Slog.wtf(PackageManagerService.TAG,
-                        "No start tag found in package manager settings");
-                return false;
-            }
+                        internal.sdkVersion = parser.getAttributeInt(null, "internal", 0);
+                        external.sdkVersion = parser.getAttributeInt(null, "external", 0);
+                        internal.buildFingerprint = external.buildFingerprint =
+                                XmlUtils.readStringAttribute(parser, "buildFingerprint");
+                        internal.fingerprint = external.fingerprint =
+                                XmlUtils.readStringAttribute(parser, "fingerprint");
 
-            int outerDepth = parser.getDepth();
-            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                    continue;
-                }
+                    } else if (tagName.equals("database-version")) {
+                        // Upgrade from older XML schema
+                        final VersionInfo internal = findOrCreateVersion(
+                                StorageManager.UUID_PRIVATE_INTERNAL);
+                        final VersionInfo external = findOrCreateVersion(
+                                StorageManager.UUID_PRIMARY_PHYSICAL);
 
-                String tagName = parser.getName();
-                if (tagName.equals("package")) {
-                    readPackageLPw(parser, users, originalFirstInstallTimes);
-                } else if (tagName.equals("permissions")) {
-                    mPermissions.readPermissions(parser);
-                } else if (tagName.equals("permission-trees")) {
-                    mPermissions.readPermissionTrees(parser);
-                } else if (tagName.equals("shared-user")) {
-                    readSharedUserLPw(parser, users);
-                } else if (tagName.equals("preferred-packages")) {
-                    // no longer used.
-                } else if (tagName.equals("preferred-activities")) {
-                    // Upgrading from old single-user implementation;
-                    // these are the preferred activities for user 0.
-                    readPreferredActivitiesLPw(parser, 0);
-                } else if (tagName.equals(TAG_PERSISTENT_PREFERRED_ACTIVITIES)) {
-                    // TODO: check whether this is okay! as it is very
-                    // similar to how preferred-activities are treated
-                    readPersistentPreferredActivitiesLPw(parser, 0);
-                } else if (tagName.equals(TAG_CROSS_PROFILE_INTENT_FILTERS)) {
-                    // TODO: check whether this is okay! as it is very
-                    // similar to how preferred-activities are treated
-                    readCrossProfileIntentFiltersLPw(parser, 0);
-                } else if (tagName.equals(TAG_DEFAULT_BROWSER)) {
-                    readDefaultAppsLPw(parser, 0);
-                } else if (tagName.equals("updated-package")) {
-                    readDisabledSysPackageLPw(parser, users);
-                } else if (tagName.equals("renamed-package")) {
-                    String nname = parser.getAttributeValue(null, "new");
-                    String oname = parser.getAttributeValue(null, "old");
-                    if (nname != null && oname != null) {
-                        mRenamedPackages.put(nname, oname);
+                        internal.databaseVersion = parser.getAttributeInt(null, "internal", 0);
+                        external.databaseVersion = parser.getAttributeInt(null, "external", 0);
+
+                    } else if (tagName.equals("verifier")) {
+                        final String deviceIdentity = parser.getAttributeValue(null, "device");
+                        try {
+                            mVerifierDeviceIdentity = VerifierDeviceIdentity.parse(deviceIdentity);
+                        } catch (IllegalArgumentException e) {
+                            Slog.w(PackageManagerService.TAG, "Discard invalid verifier device id: "
+                                    + e.getMessage());
+                        }
+                    } else if (TAG_READ_EXTERNAL_STORAGE.equals(tagName)) {
+                        // No longer used.
+                    } else if (tagName.equals("keyset-settings")) {
+                        mKeySetManagerService.readKeySetsLPw(parser,
+                                mKeySetRefs.untrackedStorage());
+                    } else if (TAG_VERSION.equals(tagName)) {
+                        final String volumeUuid = XmlUtils.readStringAttribute(parser,
+                                ATTR_VOLUME_UUID);
+                        final VersionInfo ver = findOrCreateVersion(volumeUuid);
+                        ver.sdkVersion = parser.getAttributeInt(null, ATTR_SDK_VERSION);
+                        ver.databaseVersion = parser.getAttributeInt(null, ATTR_DATABASE_VERSION);
+                        ver.buildFingerprint = XmlUtils.readStringAttribute(parser,
+                                ATTR_BUILD_FINGERPRINT);
+                        ver.fingerprint = XmlUtils.readStringAttribute(parser, ATTR_FINGERPRINT);
+                    } else if (tagName.equals(
+                            DomainVerificationPersistence.TAG_DOMAIN_VERIFICATIONS)) {
+                        mDomainVerificationManager.readSettings(computer, parser);
+                    } else if (tagName.equals(
+                            DomainVerificationLegacySettings.TAG_DOMAIN_VERIFICATIONS_LEGACY)) {
+                        mDomainVerificationManager.readLegacySettings(parser);
+                    } else {
+                        Slog.w(PackageManagerService.TAG, "Unknown element under <packages>: "
+                                + parser.getName());
+                        XmlUtils.skipCurrentTag(parser);
                     }
-                } else if (tagName.equals("last-platform-version")) {
-                    // Upgrade from older XML schema
-                    final VersionInfo internal = findOrCreateVersion(
-                            StorageManager.UUID_PRIVATE_INTERNAL);
-                    final VersionInfo external = findOrCreateVersion(
-                            StorageManager.UUID_PRIMARY_PHYSICAL);
-
-                    internal.sdkVersion = parser.getAttributeInt(null, "internal", 0);
-                    external.sdkVersion = parser.getAttributeInt(null, "external", 0);
-                    internal.buildFingerprint = external.buildFingerprint =
-                            XmlUtils.readStringAttribute(parser, "buildFingerprint");
-                    internal.fingerprint = external.fingerprint =
-                            XmlUtils.readStringAttribute(parser, "fingerprint");
-
-                } else if (tagName.equals("database-version")) {
-                    // Upgrade from older XML schema
-                    final VersionInfo internal = findOrCreateVersion(
-                            StorageManager.UUID_PRIVATE_INTERNAL);
-                    final VersionInfo external = findOrCreateVersion(
-                            StorageManager.UUID_PRIMARY_PHYSICAL);
-
-                    internal.databaseVersion = parser.getAttributeInt(null, "internal", 0);
-                    external.databaseVersion = parser.getAttributeInt(null, "external", 0);
-
-                } else if (tagName.equals("verifier")) {
-                    final String deviceIdentity = parser.getAttributeValue(null, "device");
-                    try {
-                        mVerifierDeviceIdentity = VerifierDeviceIdentity.parse(deviceIdentity);
-                    } catch (IllegalArgumentException e) {
-                        Slog.w(PackageManagerService.TAG, "Discard invalid verifier device id: "
-                                + e.getMessage());
-                    }
-                } else if (TAG_READ_EXTERNAL_STORAGE.equals(tagName)) {
-                    // No longer used.
-                } else if (tagName.equals("keyset-settings")) {
-                    mKeySetManagerService.readKeySetsLPw(parser, mKeySetRefs.untrackedStorage());
-                } else if (TAG_VERSION.equals(tagName)) {
-                    final String volumeUuid = XmlUtils.readStringAttribute(parser,
-                            ATTR_VOLUME_UUID);
-                    final VersionInfo ver = findOrCreateVersion(volumeUuid);
-                    ver.sdkVersion = parser.getAttributeInt(null, ATTR_SDK_VERSION);
-                    ver.databaseVersion = parser.getAttributeInt(null, ATTR_DATABASE_VERSION);
-                    ver.buildFingerprint = XmlUtils.readStringAttribute(parser,
-                            ATTR_BUILD_FINGERPRINT);
-                    ver.fingerprint = XmlUtils.readStringAttribute(parser, ATTR_FINGERPRINT);
-                } else if (tagName.equals(DomainVerificationPersistence.TAG_DOMAIN_VERIFICATIONS)) {
-                    mDomainVerificationManager.readSettings(computer, parser);
-                } else if (tagName.equals(
-                        DomainVerificationLegacySettings.TAG_DOMAIN_VERIFICATIONS_LEGACY)) {
-                    mDomainVerificationManager.readLegacySettings(parser);
-                } else {
-                    Slog.w(PackageManagerService.TAG, "Unknown element under <packages>: "
-                            + parser.getName());
-                    XmlUtils.skipCurrentTag(parser);
                 }
+
+                str.close();
+            } catch (IOException | XmlPullParserException e) {
+                // Remove corrupted file and retry.
+                atomicFile.failRead(str, e);
+
+                // Ignore the result to not mark this as a "first boot".
+                readSettingsLPw(computer, users, originalFirstInstallTimes);
             }
-
-            str.close();
-        } catch (IOException | XmlPullParserException e) {
-            mReadMessages.append("Error reading: " + e.toString());
-            PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading settings: " + e);
-            Slog.wtf(PackageManagerService.TAG, "Error reading package manager settings", e);
-
-            // Remove corrupted file and retry.
-            Slog.e(TAG,
-                    "Error reading package manager settings, removing " + file + " and retrying.",
-                    e);
-            file.delete();
-
-            // Ignore the result to not mark this as a "first boot".
-            readSettingsLPw(computer, users, originalFirstInstallTimes);
         }
 
         return true;
@@ -4488,10 +4333,7 @@
         mPreferredActivities.remove(userId);
 
         synchronized (mPackageRestrictionsLock) {
-            File file = getUserPackagesStateFile(userId);
-            file.delete();
-            file = getUserPackagesStateBackupFile(userId);
-            file.delete();
+            getUserPackagesStateFile(userId).delete();
             mPendingAsyncPackageRestrictionsWrites.delete(userId);
         }
 
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
index e20330d..8b118da 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
@@ -27,6 +27,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
 import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.security.FileIntegrity;
 
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -180,6 +181,12 @@
 
             os.flush();
             file.finishWrite(os);
+
+            try {
+                FileIntegrity.setUpFsVerity(path);
+            } catch (IOException e) {
+                Slog.e(TAG, "Failed to verity-protect " + path, e);
+            }
         } catch (XmlPullParserException | IOException e) {
             Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
             file.failWrite(os);
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 372b3bb..20cb485 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -119,7 +119,6 @@
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
-import com.android.server.security.FileIntegrity;
 import com.android.server.uri.UriGrantsManagerInternal;
 
 import org.json.JSONArray;
@@ -1070,57 +1069,38 @@
     }
 
     @VisibleForTesting
-    final File getUserFile(@UserIdInt int userId) {
-        return new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES);
-    }
-
-    @VisibleForTesting
-    final File getReserveCopyUserFile(@UserIdInt int userId) {
-        return new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES_RESERVE_COPY);
+    final ResilientAtomicFile getUserFile(@UserIdInt int userId) {
+        File mainFile = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES);
+        File temporaryBackup = new File(injectUserDataPath(userId),
+                FILENAME_USER_PACKAGES + ".backup");
+        File reserveCopy = new File(injectUserDataPath(userId),
+                FILENAME_USER_PACKAGES_RESERVE_COPY);
+        int fileMode = FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH;
+        return new ResilientAtomicFile(mainFile, temporaryBackup, reserveCopy, fileMode,
+                "user shortcut", null);
     }
 
     @GuardedBy("mLock")
     private void saveUserLocked(@UserIdInt int userId) {
-        final File path = getUserFile(userId);
-        if (DEBUG || DEBUG_REBOOT) {
-            Slog.d(TAG, "Saving to " + path);
-        }
+        try (ResilientAtomicFile file = getUserFile(userId)) {
+            FileOutputStream os = null;
+            try {
+                if (DEBUG || DEBUG_REBOOT) {
+                    Slog.d(TAG, "Saving to " + file);
+                }
 
-        final File reservePath = getReserveCopyUserFile(userId);
-        reservePath.delete();
+                os = file.startWrite();
 
-        path.getParentFile().mkdirs();
-        final AtomicFile file = new AtomicFile(path);
-        FileOutputStream os = null;
-        try {
-            os = file.startWrite();
+                saveUserInternalLocked(userId, os, /* forBackup= */ false);
 
-            saveUserInternalLocked(userId, os, /* forBackup= */ false);
+                file.finishWrite(os);
 
-            file.finishWrite(os);
-
-            // Remove all dangling bitmap files.
-            cleanupDanglingBitmapDirectoriesLocked(userId);
-        } catch (XmlPullParserException | IOException e) {
-            Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
-            file.failWrite(os);
-        }
-
-        // Store the reserve copy of the file.
-        try (FileInputStream in = new FileInputStream(path);
-             FileOutputStream out = new FileOutputStream(reservePath)) {
-            FileUtils.copy(in, out);
-            FileUtils.sync(out);
-        } catch (IOException e) {
-            Slog.e(TAG, "Failed to write reserve copy: " + path, e);
-        }
-
-        // Protect both primary and reserve copy with fs-verity.
-        try {
-            FileIntegrity.setUpFsVerity(path);
-            FileIntegrity.setUpFsVerity(reservePath);
-        } catch (IOException e) {
-            Slog.e(TAG, "Failed to verity-protect", e);
+                // Remove all dangling bitmap files.
+                cleanupDanglingBitmapDirectoriesLocked(userId);
+            } catch (XmlPullParserException | IOException e) {
+                Slog.e(TAG, "Failed to write to file " + file, e);
+                file.failWrite(os);
+            }
         }
 
         getUserShortcutsLocked(userId).logSharingShortcutStats(mMetricsLogger);
@@ -1157,29 +1137,26 @@
 
     @Nullable
     private ShortcutUser loadUserLocked(@UserIdInt int userId) {
-        final File path = getUserFile(userId);
-        if (DEBUG || DEBUG_REBOOT) {
-            Slog.d(TAG, "Loading from " + path);
-        }
-
-        try (FileInputStream in = new AtomicFile(path).openRead()) {
-            return loadUserInternal(userId, in, /* forBackup= */ false);
-        } catch (FileNotFoundException e) {
-            if (DEBUG || DEBUG_REBOOT) {
-                Slog.d(TAG, "Not found " + path);
-            }
-        } catch (Exception e) {
-            final File reservePath = getReserveCopyUserFile(userId);
-            Slog.e(TAG, "Reading from reserve copy: " + reservePath, e);
-            try (FileInputStream in = new AtomicFile(reservePath).openRead()) {
+        try (ResilientAtomicFile file = getUserFile(userId)) {
+            FileInputStream in = null;
+            try {
+                if (DEBUG || DEBUG_REBOOT) {
+                    Slog.d(TAG, "Loading from " + file);
+                }
+                in = file.openRead();
+                if (in == null) {
+                    if (DEBUG || DEBUG_REBOOT) {
+                        Slog.d(TAG, "Not found " + file);
+                    }
+                    return null;
+                }
                 return loadUserInternal(userId, in, /* forBackup= */ false);
-            } catch (Exception exceptionReadingReserveFile) {
-                Slog.e(TAG, "Failed to read reserve copy: " + reservePath,
-                        exceptionReadingReserveFile);
+            } catch (Exception e) {
+                // Remove corrupted file and retry.
+                file.failRead(in, e);
+                return loadUserLocked(userId);
             }
-            Slog.e(TAG, "Failed to read file " + path, e);
         }
-        return null;
     }
 
     private ShortcutUser loadUserInternal(@UserIdInt int userId, InputStream is,
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index a39e021..836f858 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -566,6 +566,22 @@
     }
 
     @Test
+    public void testWriteCorruptReadPackageRestrictions() {
+        final Settings settingsUnderTest = makeSettings();
+
+        populateDistractionFlags(settingsUnderTest);
+        settingsUnderTest.writePackageRestrictionsLPr(0, /*sync=*/true);
+
+        // Corrupt primary file.
+        writeCorruptedPackageRestrictions(0);
+
+        // now read and verify
+        populateDefaultSettings(settingsUnderTest);
+        settingsUnderTest.readPackageRestrictionsLPr(0, mOrigFirstInstallTimes);
+        verifyDistractionFlags(settingsUnderTest);
+    }
+
+    @Test
     public void testReadWritePackageRestrictionsAsync() {
         final Settings settingsWrite = makeSettings();
         final Settings settingsRead = makeSettings();
@@ -1811,6 +1827,14 @@
                         .getBytes());
     }
 
+    private void writeCorruptedPackageRestrictions(final int userId) {
+        writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), "system/users/"
+                        + userId + "/package-restrictions.xml"),
+                ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                        + "<package-restrictions>\n"
+                        + "    <pkg name=\"" + PACKAGE_NAME_1 + "\" ").getBytes());
+    }
+
     private static void writeStoppedPackagesXml() {
         writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), "system/packages-stopped.xml"),
                 ( "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 0a718e3..861087a 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -4096,11 +4096,12 @@
 
         // Save and corrupt the primary files.
         mService.saveDirtyInfo();
-        try (Writer os = new FileWriter(mService.getUserFile(UserHandle.USER_SYSTEM))) {
+        try (Writer os = new FileWriter(
+                mService.getUserFile(UserHandle.USER_SYSTEM).getBaseFile())) {
             os.write("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
                     + "<user locales=\"en\" last-app-scan-time2=\"14400000");
         }
-        try (Writer os = new FileWriter(mService.getUserFile(USER_10))) {
+        try (Writer os = new FileWriter(mService.getUserFile(USER_10).getBaseFile())) {
             os.write("<?xml version='1.0' encoding='utf");
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
index 15fd73c..01e56a0 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -2349,7 +2349,7 @@
      * can still be read.
      */
     public void testLoadLegacySavedFile() throws Exception {
-        final File path = mService.getUserFile(USER_0);
+        final File path = mService.getUserFile(USER_0).getBaseFile();
         path.getParentFile().mkdirs();
         try (Writer w = new FileWriter(path)) {
             w.write(readTestAsset("shortcut/shortcut_legacy_file.xml"));