Introduced UserDataPreparer class
Extracted the logic for preparing and destroying user data into a new
class. Unit tests will be added in a follow up cl.
Test: manual + UserManagerTest
Bug: 34736064
Change-Id: I4df7189c4ad8703cb34f54da21873b9ec83589c5
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index abf3526..745b91f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -831,6 +831,7 @@
private List<String> mKeepUninstalledPackages;
private UserManagerInternal mUserManagerInternal;
+ private final UserDataPreparer mUserDataPreparer;
private File mCacheDir;
@@ -2262,8 +2263,8 @@
mEphemeralInstallDir = new File(dataDir, "app-ephemeral");
mAsecInternalPath = new File(dataDir, "app-asec").getPath();
mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
-
- sUserManager = new UserManagerService(context, this, mPackages);
+ mUserDataPreparer = new UserDataPreparer(mInstaller, mInstallLock, mContext, mOnlyCore);
+ sUserManager = new UserManagerService(context, this, mUserDataPreparer, mPackages);
// Propagate permission configuration in to package manager.
ArrayMap<String, SystemConfig.PermissionEntry> permConfig
@@ -21256,101 +21257,6 @@
}
/**
- * Prepare storage areas for given user on all mounted devices.
- */
- void prepareUserData(int userId, int userSerial, int flags) {
- synchronized (mInstallLock) {
- final StorageManager storage = mContext.getSystemService(StorageManager.class);
- for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
- final String volumeUuid = vol.getFsUuid();
- prepareUserDataLI(volumeUuid, userId, userSerial, flags, true);
- }
- }
- }
-
- private void prepareUserDataLI(String volumeUuid, int userId, int userSerial, int flags,
- boolean allowRecover) {
- // Prepare storage and verify that serial numbers are consistent; if
- // there's a mismatch we need to destroy to avoid leaking data
- final StorageManager storage = mContext.getSystemService(StorageManager.class);
- try {
- storage.prepareUserStorage(volumeUuid, userId, userSerial, flags);
-
- if ((flags & StorageManager.FLAG_STORAGE_DE) != 0 && !mOnlyCore) {
- UserManagerService.enforceSerialNumber(
- Environment.getDataUserDeDirectory(volumeUuid, userId), userSerial);
- if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
- UserManagerService.enforceSerialNumber(
- Environment.getDataSystemDeDirectory(userId), userSerial);
- }
- }
- if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && !mOnlyCore) {
- UserManagerService.enforceSerialNumber(
- Environment.getDataUserCeDirectory(volumeUuid, userId), userSerial);
- if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
- UserManagerService.enforceSerialNumber(
- Environment.getDataSystemCeDirectory(userId), userSerial);
- }
- }
-
- synchronized (mInstallLock) {
- mInstaller.createUserData(volumeUuid, userId, userSerial, flags);
- }
- } catch (Exception e) {
- logCriticalInfo(Log.WARN, "Destroying user " + userId + " on volume " + volumeUuid
- + " because we failed to prepare: " + e);
- destroyUserDataLI(volumeUuid, userId,
- StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
-
- if (allowRecover) {
- // Try one last time; if we fail again we're really in trouble
- prepareUserDataLI(volumeUuid, userId, userSerial, flags, false);
- }
- }
- }
-
- /**
- * Destroy storage areas for given user on all mounted devices.
- */
- void destroyUserData(int userId, int flags) {
- synchronized (mInstallLock) {
- final StorageManager storage = mContext.getSystemService(StorageManager.class);
- for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
- final String volumeUuid = vol.getFsUuid();
- destroyUserDataLI(volumeUuid, userId, flags);
- }
- }
- }
-
- private void destroyUserDataLI(String volumeUuid, int userId, int flags) {
- final StorageManager storage = mContext.getSystemService(StorageManager.class);
- try {
- // Clean up app data, profile data, and media data
- mInstaller.destroyUserData(volumeUuid, userId, flags);
-
- // Clean up system data
- if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
- if ((flags & StorageManager.FLAG_STORAGE_DE) != 0) {
- FileUtils.deleteContentsAndDir(Environment.getUserSystemDirectory(userId));
- FileUtils.deleteContentsAndDir(Environment.getDataSystemDeDirectory(userId));
- FileUtils.deleteContentsAndDir(Environment.getDataMiscDeDirectory(userId));
- }
- if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) {
- FileUtils.deleteContentsAndDir(Environment.getDataSystemCeDirectory(userId));
- FileUtils.deleteContentsAndDir(Environment.getDataMiscCeDirectory(userId));
- }
- }
-
- // Data with special labels is now gone, so finish the job
- storage.destroyUserStorage(volumeUuid, userId, flags);
-
- } catch (Exception e) {
- logCriticalInfo(Log.WARN,
- "Failed to destroy user " + userId + " on volume " + volumeUuid + ": " + e);
- }
- }
-
- /**
* Examine all users present on given mounted volume, and destroy data
* belonging to users that are no longer valid, or whose user ID has been
* recycled.
@@ -21397,7 +21303,7 @@
if (destroyUser) {
synchronized (mInstallLock) {
- destroyUserDataLI(volumeUuid, userId,
+ mUserDataPreparer.destroyUserDataLI(volumeUuid, userId,
StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
}
}
diff --git a/services/core/java/com/android/server/pm/UserDataPreparer.java b/services/core/java/com/android/server/pm/UserDataPreparer.java
new file mode 100644
index 0000000..52599fd
--- /dev/null
+++ b/services/core/java/com/android/server/pm/UserDataPreparer.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2017 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.content.Context;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+import android.util.Log;
+
+import java.util.Objects;
+
+import static com.android.server.pm.PackageManagerService.logCriticalInfo;
+
+/**
+ * Helper class for preparing and destroying user storage
+ */
+class UserDataPreparer {
+ private final Object mInstallLock;
+ private final Context mContext;
+ private final boolean mOnlyCore;
+ private final Installer mInstaller;
+
+ UserDataPreparer(Installer installer, Object installLock, Context context, boolean onlyCore) {
+ mInstallLock = installLock;
+ mContext = context;
+ mOnlyCore = onlyCore;
+ mInstaller = installer;
+ }
+
+ /**
+ * Prepare storage areas for given user on all mounted devices.
+ */
+ void prepareUserData(int userId, int userSerial, int flags) {
+ synchronized (mInstallLock) {
+ final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
+ final String volumeUuid = vol.getFsUuid();
+ prepareUserDataLI(volumeUuid, userId, userSerial, flags, true);
+ }
+ }
+ }
+
+ private void prepareUserDataLI(String volumeUuid, int userId, int userSerial, int flags,
+ boolean allowRecover) {
+ // Prepare storage and verify that serial numbers are consistent; if
+ // there's a mismatch we need to destroy to avoid leaking data
+ final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ try {
+ storage.prepareUserStorage(volumeUuid, userId, userSerial, flags);
+
+ if ((flags & StorageManager.FLAG_STORAGE_DE) != 0 && !mOnlyCore) {
+ UserManagerService.enforceSerialNumber(
+ Environment.getDataUserDeDirectory(volumeUuid, userId), userSerial);
+ if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
+ UserManagerService.enforceSerialNumber(
+ Environment.getDataSystemDeDirectory(userId), userSerial);
+ }
+ }
+ if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && !mOnlyCore) {
+ UserManagerService.enforceSerialNumber(
+ Environment.getDataUserCeDirectory(volumeUuid, userId), userSerial);
+ if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
+ UserManagerService.enforceSerialNumber(
+ Environment.getDataSystemCeDirectory(userId), userSerial);
+ }
+ }
+
+ mInstaller.createUserData(volumeUuid, userId, userSerial, flags);
+ } catch (Exception e) {
+ logCriticalInfo(Log.WARN, "Destroying user " + userId + " on volume " + volumeUuid
+ + " because we failed to prepare: " + e);
+ destroyUserDataLI(volumeUuid, userId,
+ StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
+
+ if (allowRecover) {
+ // Try one last time; if we fail again we're really in trouble
+ prepareUserDataLI(volumeUuid, userId, userSerial, flags, false);
+ }
+ }
+ }
+
+ /**
+ * Destroy storage areas for given user on all mounted devices.
+ */
+ void destroyUserData(int userId, int flags) {
+ synchronized (mInstallLock) {
+ final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
+ final String volumeUuid = vol.getFsUuid();
+ destroyUserDataLI(volumeUuid, userId, flags);
+ }
+ }
+ }
+
+ void destroyUserDataLI(String volumeUuid, int userId, int flags) {
+ final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ try {
+ // Clean up app data, profile data, and media data
+ mInstaller.destroyUserData(volumeUuid, userId, flags);
+
+ // Clean up system data
+ if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
+ if ((flags & StorageManager.FLAG_STORAGE_DE) != 0) {
+ FileUtils.deleteContentsAndDir(Environment.getUserSystemDirectory(userId));
+ FileUtils.deleteContentsAndDir(Environment.getDataSystemDeDirectory(userId));
+ FileUtils.deleteContentsAndDir(Environment.getDataMiscDeDirectory(userId));
+ }
+ if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) {
+ FileUtils.deleteContentsAndDir(Environment.getDataSystemCeDirectory(userId));
+ FileUtils.deleteContentsAndDir(Environment.getDataMiscCeDirectory(userId));
+ }
+ }
+
+ // Data with special labels is now gone, so finish the job
+ storage.destroyUserStorage(volumeUuid, userId, flags);
+
+ } catch (Exception e) {
+ logCriticalInfo(Log.WARN,
+ "Failed to destroy user " + userId + " on volume " + volumeUuid + ": " + e);
+ }
+ }
+
+}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 9d2d9e5..455d3e4 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -227,6 +227,7 @@
private final Context mContext;
private final PackageManagerService mPm;
private final Object mPackagesLock;
+ private final UserDataPreparer mUserDataPreparer;
// Short-term lock for internal state, when interaction/sync with PM is not required
private final Object mUsersLock = new Object();
private final Object mRestrictionsLock = new Object();
@@ -433,7 +434,7 @@
// TODO b/28848102 Add support for test dependencies injection
@VisibleForTesting
UserManagerService(Context context) {
- this(context, null, new Object(), context.getCacheDir());
+ this(context, null, null, new Object(), context.getCacheDir());
}
/**
@@ -441,16 +442,18 @@
* associated with the package manager, and the given lock is the
* package manager's own lock.
*/
- UserManagerService(Context context, PackageManagerService pm, Object packagesLock) {
- this(context, pm, packagesLock, Environment.getDataDirectory());
+ UserManagerService(Context context, PackageManagerService pm, UserDataPreparer userDataPreparer,
+ Object packagesLock) {
+ this(context, pm, userDataPreparer, packagesLock, Environment.getDataDirectory());
}
private UserManagerService(Context context, PackageManagerService pm,
- Object packagesLock, File dataDir) {
+ UserDataPreparer userDataPreparer, Object packagesLock, File dataDir) {
mContext = context;
mPm = pm;
mPackagesLock = packagesLock;
mHandler = new MainHandler();
+ mUserDataPreparer = userDataPreparer;
synchronized (mPackagesLock) {
mUsersDir = new File(dataDir, USER_INFO_DIR);
mUsersDir.mkdirs();
@@ -2494,7 +2497,7 @@
}
final StorageManager storage = mContext.getSystemService(StorageManager.class);
storage.createUserKey(userId, userInfo.serialNumber, userInfo.isEphemeral());
- mPm.prepareUserData(userId, userInfo.serialNumber,
+ mUserDataPreparer.prepareUserData(userId, userInfo.serialNumber,
StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
mPm.createNewUser(userId, disallowedPackages);
userInfo.partial = false;
@@ -2788,7 +2791,7 @@
mPm.cleanUpUser(this, userHandle);
// Clean up all data before removing metadata
- mPm.destroyUserData(userHandle,
+ mUserDataPreparer.destroyUserData(userHandle,
StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
// Remove this user from the list
@@ -3129,7 +3132,7 @@
final int userSerial = userInfo.serialNumber;
// Migrate only if build fingerprints mismatch
boolean migrateAppsData = !Build.FINGERPRINT.equals(userInfo.lastLoggedInFingerprint);
- mPm.prepareUserData(userId, userSerial, StorageManager.FLAG_STORAGE_DE);
+ mUserDataPreparer.prepareUserData(userId, userSerial, StorageManager.FLAG_STORAGE_DE);
mPm.reconcileAppsData(userId, StorageManager.FLAG_STORAGE_DE, migrateAppsData);
if (userId != UserHandle.USER_SYSTEM) {
@@ -3151,7 +3154,7 @@
final int userSerial = userInfo.serialNumber;
// Migrate only if build fingerprints mismatch
boolean migrateAppsData = !Build.FINGERPRINT.equals(userInfo.lastLoggedInFingerprint);
- mPm.prepareUserData(userId, userSerial, StorageManager.FLAG_STORAGE_CE);
+ mUserDataPreparer.prepareUserData(userId, userSerial, StorageManager.FLAG_STORAGE_CE);
mPm.reconcileAppsData(userId, StorageManager.FLAG_STORAGE_CE, migrateAppsData);
}