Tests for UserDataPreparer
Moved reconcileUsers, enforceSerialNumber to UserDataPreparer and added
unit tests
Test: manual + UserDataPreparerTest
Bug: 34736064
Change-Id: Icde93974ac68849e38357d8cfea0cc1b7a2aab49
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 072959f..4207998 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -833,7 +833,6 @@
private List<String> mKeepUninstalledPackages;
private UserManagerInternal mUserManagerInternal;
- private final UserDataPreparer mUserDataPreparer;
private File mCacheDir;
@@ -1937,7 +1936,7 @@
// Clean up any users or apps that were removed or recreated
// while this volume was missing
- reconcileUsers(volumeUuid);
+ sUserManager.reconcileUsers(volumeUuid);
reconcileApps(volumeUuid);
// Clean up any install sessions that expired or were
@@ -2270,8 +2269,8 @@
mEphemeralInstallDir = new File(dataDir, "app-ephemeral");
mAsecInternalPath = new File(dataDir, "app-asec").getPath();
mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
- mUserDataPreparer = new UserDataPreparer(mInstaller, mInstallLock, mContext, mOnlyCore);
- sUserManager = new UserManagerService(context, this, mUserDataPreparer, mPackages);
+ sUserManager = new UserManagerService(context, this,
+ new UserDataPreparer(mInstaller, mInstallLock, mContext, mOnlyCore), mPackages);
// Propagate permission configuration in to package manager.
ArrayMap<String, SystemConfig.PermissionEntry> permConfig
@@ -19971,7 +19970,7 @@
});
// Now that we're mostly running, clean up stale users and apps
- reconcileUsers(StorageManager.UUID_PRIVATE_INTERNAL);
+ sUserManager.reconcileUsers(StorageManager.UUID_PRIVATE_INTERNAL);
reconcileApps(StorageManager.UUID_PRIVATE_INTERNAL);
}
@@ -21309,60 +21308,6 @@
}
}
- /**
- * 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.
- */
- private void reconcileUsers(String volumeUuid) {
- final List<File> files = new ArrayList<>();
- Collections.addAll(files, FileUtils
- .listFilesOrEmpty(Environment.getDataUserDeDirectory(volumeUuid)));
- Collections.addAll(files, FileUtils
- .listFilesOrEmpty(Environment.getDataUserCeDirectory(volumeUuid)));
- Collections.addAll(files, FileUtils
- .listFilesOrEmpty(Environment.getDataSystemDeDirectory()));
- Collections.addAll(files, FileUtils
- .listFilesOrEmpty(Environment.getDataSystemCeDirectory()));
- Collections.addAll(files, FileUtils
- .listFilesOrEmpty(Environment.getDataMiscCeDirectory()));
- for (File file : files) {
- if (!file.isDirectory()) continue;
-
- final int userId;
- final UserInfo info;
- try {
- userId = Integer.parseInt(file.getName());
- info = sUserManager.getUserInfo(userId);
- } catch (NumberFormatException e) {
- Slog.w(TAG, "Invalid user directory " + file);
- continue;
- }
-
- boolean destroyUser = false;
- if (info == null) {
- logCriticalInfo(Log.WARN, "Destroying user directory " + file
- + " because no matching user was found");
- destroyUser = true;
- } else if (!mOnlyCore) {
- try {
- UserManagerService.enforceSerialNumber(file, info.serialNumber);
- } catch (IOException e) {
- logCriticalInfo(Log.WARN, "Destroying user directory " + file
- + " because we failed to enforce serial number: " + e);
- destroyUser = true;
- }
- }
-
- if (destroyUser) {
- synchronized (mInstallLock) {
- mUserDataPreparer.destroyUserDataLI(volumeUuid, userId,
- StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
- }
- }
- }
- }
-
private void assertPackageKnown(String volumeUuid, String packageName)
throws PackageManagerException {
synchronized (mPackages) {
diff --git a/services/core/java/com/android/server/pm/UserDataPreparer.java b/services/core/java/com/android/server/pm/UserDataPreparer.java
index 52599fd..fc00acc 100644
--- a/services/core/java/com/android/server/pm/UserDataPreparer.java
+++ b/services/core/java/com/android/server/pm/UserDataPreparer.java
@@ -17,13 +17,28 @@
package com.android.server.pm;
import android.content.Context;
+import android.content.pm.UserInfo;
import android.os.Environment;
import android.os.FileUtils;
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
import java.util.Objects;
+import java.util.Set;
import static com.android.server.pm.PackageManagerService.logCriticalInfo;
@@ -31,6 +46,9 @@
* Helper class for preparing and destroying user storage
*/
class UserDataPreparer {
+ private static final String TAG = "UserDataPreparer";
+ private static final String XATTR_SERIAL = "user.serial";
+
private final Object mInstallLock;
private final Context mContext;
private final boolean mOnlyCore;
@@ -65,19 +83,15 @@
storage.prepareUserStorage(volumeUuid, userId, userSerial, flags);
if ((flags & StorageManager.FLAG_STORAGE_DE) != 0 && !mOnlyCore) {
- UserManagerService.enforceSerialNumber(
- Environment.getDataUserDeDirectory(volumeUuid, userId), userSerial);
+ enforceSerialNumber(getDataUserDeDirectory(volumeUuid, userId), userSerial);
if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
- UserManagerService.enforceSerialNumber(
- Environment.getDataSystemDeDirectory(userId), userSerial);
+ enforceSerialNumber(getDataSystemDeDirectory(userId), userSerial);
}
}
if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && !mOnlyCore) {
- UserManagerService.enforceSerialNumber(
- Environment.getDataUserCeDirectory(volumeUuid, userId), userSerial);
+ enforceSerialNumber(getDataUserCeDirectory(volumeUuid, userId), userSerial);
if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
- UserManagerService.enforceSerialNumber(
- Environment.getDataSystemCeDirectory(userId), userSerial);
+ enforceSerialNumber(getDataSystemCeDirectory(userId), userSerial);
}
}
@@ -117,13 +131,13 @@
// 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));
+ FileUtils.deleteContentsAndDir(getUserSystemDirectory(userId));
+ FileUtils.deleteContentsAndDir(getDataSystemDeDirectory(userId));
+ FileUtils.deleteContentsAndDir(getDataMiscDeDirectory(userId));
}
if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) {
- FileUtils.deleteContentsAndDir(Environment.getDataSystemCeDirectory(userId));
- FileUtils.deleteContentsAndDir(Environment.getDataMiscCeDirectory(userId));
+ FileUtils.deleteContentsAndDir(getDataSystemCeDirectory(userId));
+ FileUtils.deleteContentsAndDir(getDataMiscCeDirectory(userId));
}
}
@@ -136,4 +150,183 @@
}
}
+ /**
+ * 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.
+ */
+ void reconcileUsers(String volumeUuid, List<UserInfo> validUsersList) {
+ final List<File> files = new ArrayList<>();
+ Collections.addAll(files, FileUtils
+ .listFilesOrEmpty(Environment.getDataUserDeDirectory(volumeUuid)));
+ Collections.addAll(files, FileUtils
+ .listFilesOrEmpty(Environment.getDataUserCeDirectory(volumeUuid)));
+ Collections.addAll(files, FileUtils
+ .listFilesOrEmpty(Environment.getDataSystemDeDirectory()));
+ Collections.addAll(files, FileUtils
+ .listFilesOrEmpty(Environment.getDataSystemCeDirectory()));
+ Collections.addAll(files, FileUtils
+ .listFilesOrEmpty(Environment.getDataMiscCeDirectory()));
+ reconcileUsers(volumeUuid, validUsersList, files);
+ }
+
+ @VisibleForTesting
+ void reconcileUsers(String volumeUuid, List<UserInfo> validUsersList, List<File> files) {
+ final int userCount = validUsersList.size();
+ SparseArray<UserInfo> users = new SparseArray<>(userCount);
+ for (int i = 0; i < userCount; i++) {
+ UserInfo user = validUsersList.get(i);
+ users.put(user.id, user);
+ }
+ for (File file : files) {
+ if (!file.isDirectory()) {
+ continue;
+ }
+
+ final int userId;
+ final UserInfo info;
+ try {
+ userId = Integer.parseInt(file.getName());
+ info = users.get(userId);
+ } catch (NumberFormatException e) {
+ Slog.w(TAG, "Invalid user directory " + file);
+ continue;
+ }
+
+ boolean destroyUser = false;
+ if (info == null) {
+ logCriticalInfo(Log.WARN, "Destroying user directory " + file
+ + " because no matching user was found");
+ destroyUser = true;
+ } else if (!mOnlyCore) {
+ try {
+ enforceSerialNumber(file, info.serialNumber);
+ } catch (IOException e) {
+ logCriticalInfo(Log.WARN, "Destroying user directory " + file
+ + " because we failed to enforce serial number: " + e);
+ destroyUser = true;
+ }
+ }
+
+ if (destroyUser) {
+ synchronized (mInstallLock) {
+ destroyUserDataLI(volumeUuid, userId,
+ StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
+ }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ protected File getDataMiscCeDirectory(int userId) {
+ return Environment.getDataMiscCeDirectory(userId);
+ }
+
+ @VisibleForTesting
+ protected File getDataSystemCeDirectory(int userId) {
+ return Environment.getDataSystemCeDirectory(userId);
+ }
+
+ @VisibleForTesting
+ protected File getDataMiscDeDirectory(int userId) {
+ return Environment.getDataMiscDeDirectory(userId);
+ }
+
+ @VisibleForTesting
+ protected File getUserSystemDirectory(int userId) {
+ return Environment.getUserSystemDirectory(userId);
+ }
+
+ @VisibleForTesting
+ protected File getDataUserCeDirectory(String volumeUuid, int userId) {
+ return Environment.getDataUserCeDirectory(volumeUuid, userId);
+ }
+
+ @VisibleForTesting
+ protected File getDataSystemDeDirectory(int userId) {
+ return Environment.getDataSystemDeDirectory(userId);
+ }
+
+ @VisibleForTesting
+ protected File getDataUserDeDirectory(String volumeUuid, int userId) {
+ return Environment.getDataUserDeDirectory(volumeUuid, userId);
+ }
+
+ @VisibleForTesting
+ protected boolean isFileEncryptedEmulatedOnly() {
+ return StorageManager.isFileEncryptedEmulatedOnly();
+ }
+
+ /**
+ * Enforce that serial number stored in user directory inode matches the
+ * given expected value. Gracefully sets the serial number if currently
+ * undefined.
+ *
+ * @throws IOException when problem extracting serial number, or serial
+ * number is mismatched.
+ */
+ void enforceSerialNumber(File file, int serialNumber) throws IOException {
+ if (isFileEncryptedEmulatedOnly()) {
+ // When we're emulating FBE, the directory may have been chmod
+ // 000'ed, meaning we can't read the serial number to enforce it;
+ // instead of destroying the user, just log a warning.
+ Slog.w(TAG, "Device is emulating FBE; assuming current serial number is valid");
+ return;
+ }
+
+ final int foundSerial = getSerialNumber(file);
+ Slog.v(TAG, "Found " + file + " with serial number " + foundSerial);
+
+ if (foundSerial == -1) {
+ Slog.d(TAG, "Serial number missing on " + file + "; assuming current is valid");
+ try {
+ setSerialNumber(file, serialNumber);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to set serial number on " + file, e);
+ }
+
+ } else if (foundSerial != serialNumber) {
+ throw new IOException("Found serial number " + foundSerial
+ + " doesn't match expected " + serialNumber);
+ }
+ }
+
+ /**
+ * Set serial number stored in user directory inode.
+ *
+ * @throws IOException if serial number was already set
+ */
+ private static void setSerialNumber(File file, int serialNumber) throws IOException {
+ try {
+ final byte[] buf = Integer.toString(serialNumber).getBytes(StandardCharsets.UTF_8);
+ Os.setxattr(file.getAbsolutePath(), XATTR_SERIAL, buf, OsConstants.XATTR_CREATE);
+ } catch (ErrnoException e) {
+ throw e.rethrowAsIOException();
+ }
+ }
+
+ /**
+ * Return serial number stored in user directory inode.
+ *
+ * @return parsed serial number, or -1 if not set
+ */
+ @VisibleForTesting
+ static int getSerialNumber(File file) throws IOException {
+ try {
+ final byte[] buf = Os.getxattr(file.getAbsolutePath(), XATTR_SERIAL);
+ final String serial = new String(buf);
+ try {
+ return Integer.parseInt(serial);
+ } catch (NumberFormatException e) {
+ throw new IOException("Bad serial number: " + serial);
+ }
+ } catch (ErrnoException e) {
+ if (e.errno == OsConstants.ENODATA) {
+ return -1;
+ } else {
+ throw e.rethrowAsIOException();
+ }
+ }
+ }
+
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 455d3e4..627fa54 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -218,8 +218,6 @@
static final int WRITE_USER_MSG = 1;
static final int WRITE_USER_DELAY = 2*1000; // 2 seconds
- private static final String XATTR_SERIAL = "user.serial";
-
// Tron counters
private static final String TRON_GUEST_CREATED = "users_guest_created";
private static final String TRON_USER_CREATED = "users_user_created";
@@ -3159,6 +3157,15 @@
}
/**
+ * 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.
+ */
+ void reconcileUsers(String volumeUuid) {
+ mUserDataPreparer.reconcileUsers(volumeUuid, getUsers(true /* excludeDying */));
+ }
+
+ /**
* Make a note of the last started time of a user and do some cleanup.
* This is called with ActivityManagerService lock held.
* @param userId the user that was just foregrounded
@@ -3219,78 +3226,6 @@
return RESTRICTIONS_FILE_PREFIX + packageName + XML_SUFFIX;
}
- /**
- * Enforce that serial number stored in user directory inode matches the
- * given expected value. Gracefully sets the serial number if currently
- * undefined.
- *
- * @throws IOException when problem extracting serial number, or serial
- * number is mismatched.
- */
- public static void enforceSerialNumber(File file, int serialNumber) throws IOException {
- if (StorageManager.isFileEncryptedEmulatedOnly()) {
- // When we're emulating FBE, the directory may have been chmod
- // 000'ed, meaning we can't read the serial number to enforce it;
- // instead of destroying the user, just log a warning.
- Slog.w(LOG_TAG, "Device is emulating FBE; assuming current serial number is valid");
- return;
- }
-
- final int foundSerial = getSerialNumber(file);
- Slog.v(LOG_TAG, "Found " + file + " with serial number " + foundSerial);
-
- if (foundSerial == -1) {
- Slog.d(LOG_TAG, "Serial number missing on " + file + "; assuming current is valid");
- try {
- setSerialNumber(file, serialNumber);
- } catch (IOException e) {
- Slog.w(LOG_TAG, "Failed to set serial number on " + file, e);
- }
-
- } else if (foundSerial != serialNumber) {
- throw new IOException("Found serial number " + foundSerial
- + " doesn't match expected " + serialNumber);
- }
- }
-
- /**
- * Set serial number stored in user directory inode.
- *
- * @throws IOException if serial number was already set
- */
- private static void setSerialNumber(File file, int serialNumber)
- throws IOException {
- try {
- final byte[] buf = Integer.toString(serialNumber).getBytes(StandardCharsets.UTF_8);
- Os.setxattr(file.getAbsolutePath(), XATTR_SERIAL, buf, OsConstants.XATTR_CREATE);
- } catch (ErrnoException e) {
- throw e.rethrowAsIOException();
- }
- }
-
- /**
- * Return serial number stored in user directory inode.
- *
- * @return parsed serial number, or -1 if not set
- */
- private static int getSerialNumber(File file) throws IOException {
- try {
- final byte[] buf = Os.getxattr(file.getAbsolutePath(), XATTR_SERIAL);
- final String serial = new String(buf);
- try {
- return Integer.parseInt(serial);
- } catch (NumberFormatException e) {
- throw new IOException("Bad serial number: " + serial);
- }
- } catch (ErrnoException e) {
- if (e.errno == OsConstants.ENODATA) {
- return -1;
- } else {
- throw e.rethrowAsIOException();
- }
- }
- }
-
@Override
public void setSeedAccountData(int userId, String accountName, String accountType,
PersistableBundle accountOptions, boolean persist) {
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserDataPreparerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserDataPreparerTest.java
new file mode 100644
index 0000000..7a676e25
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/UserDataPreparerTest.java
@@ -0,0 +1,273 @@
+/*
+ * 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.content.pm.UserInfo;
+import android.os.FileUtils;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.Collections;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * <p>Run with:<pre>
+ * m FrameworksServicesTests &&
+ * adb install \
+ * -r out/target/product/hammerhead/data/app/FrameworksServicesTests/FrameworksServicesTests.apk &&
+ * adb shell am instrument -e class com.android.server.pm.UserDataPreparerTest \
+ * -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
+ * </pre>
+ */
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+@SmallTest
+public class UserDataPreparerTest {
+
+ private static final int TEST_USER_SERIAL = 1000;
+ private static final int TEST_USER_ID = 10;
+
+ private TestUserDataPreparer mUserDataPreparer;
+
+ @Mock
+ private StorageManager mStorageManagerMock;
+
+ @Mock
+ private Context mContextMock;
+
+ @Mock
+ private Installer mInstaller;
+
+ private Object mInstallLock;
+
+ @Before
+ public void setup() {
+ Context ctx = InstrumentationRegistry.getContext();
+ FileUtils.deleteContents(ctx.getCacheDir());
+ mInstallLock = new Object();
+ MockitoAnnotations.initMocks(this);
+ mUserDataPreparer = new TestUserDataPreparer(mInstaller, mInstallLock, mContextMock, false,
+ ctx.getCacheDir());
+ when(mContextMock.getSystemServiceName(StorageManager.class))
+ .thenReturn(Context.STORAGE_SERVICE);
+ when(mContextMock.getSystemService(eq(Context.STORAGE_SERVICE)))
+ .thenReturn(mStorageManagerMock);
+ VolumeInfo testVolume = new VolumeInfo("testuuid", VolumeInfo.TYPE_PRIVATE, null, null);
+ when(mStorageManagerMock.getWritablePrivateVolumes()).thenReturn(Arrays.asList(testVolume));
+ }
+
+ @Test
+ public void testPrepareUserData_De() throws Exception {
+ File userDeDir = mUserDataPreparer.getDataUserDeDirectory(null, TEST_USER_ID);
+ userDeDir.mkdirs();
+ File systemDeDir = mUserDataPreparer.getDataSystemDeDirectory(TEST_USER_ID);
+ systemDeDir.mkdirs();
+ mUserDataPreparer
+ .prepareUserData(TEST_USER_ID, TEST_USER_SERIAL, StorageManager.FLAG_STORAGE_DE);
+ verify(mStorageManagerMock).prepareUserStorage(isNull(String.class), eq(TEST_USER_ID),
+ eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_DE));
+ verify(mInstaller).createUserData(isNull(String.class), eq(TEST_USER_ID),
+ eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_DE));
+ int serialNumber = UserDataPreparer.getSerialNumber(userDeDir);
+ assertEquals(TEST_USER_SERIAL, serialNumber);
+ serialNumber = UserDataPreparer.getSerialNumber(systemDeDir);
+ assertEquals(TEST_USER_SERIAL, serialNumber);
+ }
+
+ @Test
+ public void testPrepareUserData_Ce() throws Exception {
+ File userCeDir = mUserDataPreparer.getDataUserCeDirectory(null, TEST_USER_ID);
+ userCeDir.mkdirs();
+ File systemCeDir = mUserDataPreparer.getDataSystemCeDirectory(TEST_USER_ID);
+ systemCeDir.mkdirs();
+ mUserDataPreparer
+ .prepareUserData(TEST_USER_ID, TEST_USER_SERIAL, StorageManager.FLAG_STORAGE_CE);
+ verify(mStorageManagerMock).prepareUserStorage(isNull(String.class), eq(TEST_USER_ID),
+ eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_CE));
+ verify(mInstaller).createUserData(isNull(String.class), eq(TEST_USER_ID),
+ eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_CE));
+ int serialNumber = UserDataPreparer.getSerialNumber(userCeDir);
+ assertEquals(TEST_USER_SERIAL, serialNumber);
+ serialNumber = UserDataPreparer.getSerialNumber(systemCeDir);
+ assertEquals(TEST_USER_SERIAL, serialNumber);
+ }
+
+ @Test
+ public void testDestroyUserData() throws Exception {
+ // Add file in CE
+ File systemCeDir = mUserDataPreparer.getDataSystemCeDirectory(TEST_USER_ID);
+ systemCeDir.mkdirs();
+ File ceFile = new File(systemCeDir, "file");
+ writeFile(ceFile, "-----" );
+ testDestroyUserData_De();
+ // CE directory should be preserved
+ assertEquals(Collections.singletonList(ceFile), Arrays.asList(FileUtils.listFilesOrEmpty(
+ systemCeDir)));
+
+ testDestroyUserData_Ce();
+
+ // Verify that testDir is empty
+ assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty(
+ mUserDataPreparer.testDir)));
+ }
+
+ @Test
+ public void testDestroyUserData_De() throws Exception {
+ File systemDir = mUserDataPreparer.getUserSystemDirectory(TEST_USER_ID);
+ systemDir.mkdirs();
+ writeFile(new File(systemDir, "file"), "-----" );
+ File systemDeDir = mUserDataPreparer.getDataSystemDeDirectory(TEST_USER_ID);
+ systemDeDir.mkdirs();
+ writeFile(new File(systemDeDir, "file"), "-----" );
+ File miscDeDir = mUserDataPreparer.getDataMiscDeDirectory(TEST_USER_ID);
+ miscDeDir.mkdirs();
+ writeFile(new File(miscDeDir, "file"), "-----" );
+
+ mUserDataPreparer.destroyUserData(TEST_USER_ID, StorageManager.FLAG_STORAGE_DE);
+
+ verify(mInstaller).destroyUserData(isNull(String.class), eq(TEST_USER_ID),
+ eq(StorageManager.FLAG_STORAGE_DE));
+ verify(mStorageManagerMock).destroyUserStorage(isNull(String.class), eq(TEST_USER_ID),
+ eq(StorageManager.FLAG_STORAGE_DE));
+
+ assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty(systemDir)));
+ assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty(
+ systemDeDir)));
+ assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty(
+ miscDeDir)));
+ }
+
+ @Test
+ public void testDestroyUserData_Ce() throws Exception {
+ File systemCeDir = mUserDataPreparer.getDataSystemCeDirectory(TEST_USER_ID);
+ systemCeDir.mkdirs();
+ writeFile(new File(systemCeDir, "file"), "-----" );
+ File miscCeDir = mUserDataPreparer.getDataMiscCeDirectory(TEST_USER_ID);
+ miscCeDir.mkdirs();
+ writeFile(new File(miscCeDir, "file"), "-----" );
+
+ mUserDataPreparer.destroyUserData(TEST_USER_ID, StorageManager.FLAG_STORAGE_CE);
+
+ verify(mInstaller).destroyUserData(isNull(String.class), eq(TEST_USER_ID),
+ eq(StorageManager.FLAG_STORAGE_CE));
+ verify(mStorageManagerMock).destroyUserStorage(isNull(String.class), eq(TEST_USER_ID),
+ eq(StorageManager.FLAG_STORAGE_CE));
+
+ assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty(
+ systemCeDir)));
+ assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty(
+ miscCeDir)));
+ }
+
+ @Test
+ public void testReconcileUsers() throws Exception {
+ UserInfo u1 = new UserInfo(1, "u1", 0);
+ UserInfo u2 = new UserInfo(2, "u2", 0);
+ File testDir = mUserDataPreparer.testDir;
+ File dir1 = new File(testDir, "1");
+ dir1.mkdirs();
+ File dir2 = new File(testDir, "2");
+ dir2.mkdirs();
+ File dir3 = new File(testDir, "3");
+ dir3.mkdirs();
+
+ mUserDataPreparer
+ .reconcileUsers(StorageManager.UUID_PRIVATE_INTERNAL, Arrays.asList(u1, u2),
+ Arrays.asList(dir1, dir2, dir3));
+ // Verify that user 3 data is removed
+ verify(mInstaller).destroyUserData(isNull(String.class), eq(3),
+ eq(StorageManager.FLAG_STORAGE_DE|StorageManager.FLAG_STORAGE_CE));
+ }
+
+ private static void writeFile(File file, String content) throws IOException {
+ try (FileOutputStream os = new FileOutputStream(file)) {
+ os.write(content.getBytes(Charset.defaultCharset()));
+ }
+ }
+
+ private static class TestUserDataPreparer extends UserDataPreparer {
+ File testDir;
+
+ TestUserDataPreparer(Installer installer, Object installLock, Context context,
+ boolean onlyCore, File testDir) {
+ super(installer, installLock, context, onlyCore);
+ this.testDir = testDir;
+ }
+
+ @Override
+ protected File getDataMiscCeDirectory(int userId) {
+ return new File(testDir, "misc_ce_" + userId);
+ }
+
+ @Override
+ protected File getDataSystemCeDirectory(int userId) {
+ return new File(testDir, "system_ce_" + userId);
+ }
+
+ @Override
+ protected File getDataMiscDeDirectory(int userId) {
+ return new File(testDir, "misc_de_" + userId);
+ }
+
+ @Override
+ protected File getUserSystemDirectory(int userId) {
+ return new File(testDir, "user_system_" + userId);
+ }
+
+ @Override
+ protected File getDataUserCeDirectory(String volumeUuid, int userId) {
+ return new File(testDir, "user_ce_" + userId);
+ }
+
+ @Override
+ protected File getDataSystemDeDirectory(int userId) {
+ return new File(testDir, "system_de_" + userId);
+ }
+
+ @Override
+ protected File getDataUserDeDirectory(String volumeUuid, int userId) {
+ return new File(testDir, "user_de_" + userId);
+ }
+
+ @Override
+ protected boolean isFileEncryptedEmulatedOnly() {
+ return false;
+ }
+ }
+
+}