diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 072959f0ea038..4207998c9dccc 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 @@ public class PackageManagerService extends IPackageManager.Stub { private List mKeepUninstalledPackages; private UserManagerInternal mUserManagerInternal; - private final UserDataPreparer mUserDataPreparer; private File mCacheDir; @@ -1937,7 +1936,7 @@ public class PackageManagerService extends IPackageManager.Stub { // 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 @@ public class PackageManagerService extends IPackageManager.Stub { 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 permConfig @@ -19971,7 +19970,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); }); // 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 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); } } - /** - * 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 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 52599fd031c98..fc00acc8a2812 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 @@ import static com.android.server.pm.PackageManagerService.logCriticalInfo; * 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 @@ class UserDataPreparer { 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 @@ class UserDataPreparer { // 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 @@ class UserDataPreparer { } } + /** + * 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 validUsersList) { + final List 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 validUsersList, List files) { + final int userCount = validUsersList.size(); + SparseArray 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 455d3e4f0820a..627fa545bf587 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 @@ public class UserManagerService extends IUserManager.Stub { 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"; @@ -3158,6 +3156,15 @@ public class UserManagerService extends IUserManager.Stub { mPm.reconcileAppsData(userId, StorageManager.FLAG_STORAGE_CE, migrateAppsData); } + /** + * 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. @@ -3219,78 +3226,6 @@ public class UserManagerService extends IUserManager.Stub { 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 0000000000000..7a676e258aba0 --- /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; + +/** + *

Run with:

+ * 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
+ * 
+ */ +@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; + } + } + +}