Progress towards FBE and adoptable storage.
Adoptable storage is a feature present on many Android devices with
physical SD card slots or stable USB OTG ports. We want to give
developers a way to validate and debug their apps on all device
styles, regardless of what that device physically supports, so we
added a new "virtual disk" feature that presents itself as a
physical SD card for testing purposes.
This is also valuable for devices that only have a single USB OTG
port, since it required an awkward CTS testing setup with adb-over-
network. Using this virtual disk feature we can validate the
behavior over a normal adb connection.
Also extend the test to run over mulitple users, when supported.
Since we can race ahead of PackageManager scanning and loading of
newly mounted volumes, use a new dump feature to explicitly watch
for the volume to finish being loaded/unloaded.
Test: cts-tradefed run commandAndExit cts-dev --abi armeabi-v7a -m CtsAppSecurityHostTestCases -t android.appsecurity.cts.AdoptableHostTest
Bug: 37436961, 29923055, 25861755, 30230655
Change-Id: Ie5be88429b7e182abeffed80b0d60612dd72c3a9
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/AdoptableHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/AdoptableHostTest.java
index afd7245..ec10304 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/AdoptableHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/AdoptableHostTest.java
@@ -41,6 +41,8 @@
private IAbi mAbi;
private IBuildInfo mCtsBuild;
+ private int[] mUsers;
+
@Override
public void setAbi(IAbi abi) {
mAbi = abi;
@@ -55,11 +57,12 @@
protected void setUp() throws Exception {
super.setUp();
- Utils.prepareSingleUser(getDevice());
+ mUsers = Utils.prepareMultipleUsers(getDevice(), Integer.MAX_VALUE);
assertNotNull(mAbi);
assertNotNull(mCtsBuild);
getDevice().uninstallPackage(PKG);
+ getDevice().executeShellCommand("sm set-virtual-disk true");
}
@Override
@@ -67,10 +70,10 @@
super.tearDown();
getDevice().uninstallPackage(PKG);
+ getDevice().executeShellCommand("sm set-virtual-disk false");
}
public void testApps() throws Exception {
- if (!hasAdoptable()) return;
final String diskId = getAdoptionDisk();
try {
final String abi = mAbi.getName();
@@ -79,10 +82,12 @@
// Install simple app on internal
new InstallMultiple().useNaturalAbi().addApk(APK).addApk(apk).run();
- runDeviceTests(PKG, CLASS, "testDataInternal");
- runDeviceTests(PKG, CLASS, "testDataWrite");
- runDeviceTests(PKG, CLASS, "testDataRead");
- runDeviceTests(PKG, CLASS, "testNative");
+ for (int user : mUsers) {
+ runDeviceTests(PKG, CLASS, "testDataInternal", user);
+ runDeviceTests(PKG, CLASS, "testDataWrite", user);
+ runDeviceTests(PKG, CLASS, "testDataRead", user);
+ runDeviceTests(PKG, CLASS, "testNative", user);
+ }
// Adopt that disk!
assertEmpty(getDevice().executeShellCommand("sm partition " + diskId + " private"));
@@ -91,28 +96,36 @@
// Move app and verify
assertSuccess(getDevice().executeShellCommand(
"pm move-package " + PKG + " " + vol.uuid));
- runDeviceTests(PKG, CLASS, "testDataNotInternal");
- runDeviceTests(PKG, CLASS, "testDataRead");
- runDeviceTests(PKG, CLASS, "testNative");
+ for (int user : mUsers) {
+ runDeviceTests(PKG, CLASS, "testDataNotInternal", user);
+ runDeviceTests(PKG, CLASS, "testDataRead", user);
+ runDeviceTests(PKG, CLASS, "testNative", user);
+ }
// Unmount, remount and verify
- getDevice().executeShellCommand("sm unmount " + vol.volId);
- getDevice().executeShellCommand("sm mount " + vol.volId);
- runDeviceTests(PKG, CLASS, "testDataNotInternal");
- runDeviceTests(PKG, CLASS, "testDataRead");
- runDeviceTests(PKG, CLASS, "testNative");
+ unmount(vol);
+ mount(vol);
+ for (int user : mUsers) {
+ runDeviceTests(PKG, CLASS, "testDataNotInternal", user);
+ runDeviceTests(PKG, CLASS, "testDataRead", user);
+ runDeviceTests(PKG, CLASS, "testNative", user);
+ }
// Move app back and verify
assertSuccess(getDevice().executeShellCommand("pm move-package " + PKG + " internal"));
- runDeviceTests(PKG, CLASS, "testDataInternal");
- runDeviceTests(PKG, CLASS, "testDataRead");
- runDeviceTests(PKG, CLASS, "testNative");
+ for (int user : mUsers) {
+ runDeviceTests(PKG, CLASS, "testDataInternal", user);
+ runDeviceTests(PKG, CLASS, "testDataRead", user);
+ runDeviceTests(PKG, CLASS, "testNative", user);
+ }
// Un-adopt volume and app should still be fine
getDevice().executeShellCommand("sm partition " + diskId + " public");
- runDeviceTests(PKG, CLASS, "testDataInternal");
- runDeviceTests(PKG, CLASS, "testDataRead");
- runDeviceTests(PKG, CLASS, "testNative");
+ for (int user : mUsers) {
+ runDeviceTests(PKG, CLASS, "testDataInternal", user);
+ runDeviceTests(PKG, CLASS, "testDataRead", user);
+ runDeviceTests(PKG, CLASS, "testNative", user);
+ }
} finally {
cleanUp(diskId);
@@ -120,7 +133,6 @@
}
public void testPrimaryStorage() throws Exception {
- if (!hasAdoptable()) return;
final String diskId = getAdoptionDisk();
try {
final String originalVol = getDevice()
@@ -139,10 +151,12 @@
private void verifyPrimaryInternal(String diskId) throws Exception {
// Write some data to shared storage
new InstallMultiple().addApk(APK).run();
- runDeviceTests(PKG, CLASS, "testPrimaryOnSameVolume");
- runDeviceTests(PKG, CLASS, "testPrimaryInternal");
- runDeviceTests(PKG, CLASS, "testPrimaryDataWrite");
- runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
+ for (int user : mUsers) {
+ runDeviceTests(PKG, CLASS, "testPrimaryOnSameVolume", user);
+ runDeviceTests(PKG, CLASS, "testPrimaryInternal", user);
+ runDeviceTests(PKG, CLASS, "testPrimaryDataWrite", user);
+ runDeviceTests(PKG, CLASS, "testPrimaryDataRead", user);
+ }
// Adopt that disk!
assertEmpty(getDevice().executeShellCommand("sm partition " + diskId + " private"));
@@ -153,20 +167,28 @@
getDevice().executeShellCommand("pm move-primary-storage " + vol.uuid, out, 2,
TimeUnit.HOURS, 1);
assertSuccess(out.getOutput());
- runDeviceTests(PKG, CLASS, "testPrimaryAdopted");
- runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
+ for (int user : mUsers) {
+ runDeviceTests(PKG, CLASS, "testPrimaryAdopted", user);
+ runDeviceTests(PKG, CLASS, "testPrimaryDataRead", user);
+ }
// Unmount and verify
- getDevice().executeShellCommand("sm unmount " + vol.volId);
- runDeviceTests(PKG, CLASS, "testPrimaryUnmounted");
- getDevice().executeShellCommand("sm mount " + vol.volId);
- runDeviceTests(PKG, CLASS, "testPrimaryAdopted");
- runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
+ unmount(vol);
+ for (int user : mUsers) {
+ runDeviceTests(PKG, CLASS, "testPrimaryUnmounted", user);
+ }
+ mount(vol);
+ for (int user : mUsers) {
+ runDeviceTests(PKG, CLASS, "testPrimaryAdopted", user);
+ runDeviceTests(PKG, CLASS, "testPrimaryDataRead", user);
+ }
// Move app and verify backing storage volume is same
assertSuccess(getDevice().executeShellCommand("pm move-package " + PKG + " " + vol.uuid));
- runDeviceTests(PKG, CLASS, "testPrimaryOnSameVolume");
- runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
+ for (int user : mUsers) {
+ runDeviceTests(PKG, CLASS, "testPrimaryOnSameVolume", user);
+ runDeviceTests(PKG, CLASS, "testPrimaryDataRead", user);
+ }
// And move back to internal
out = new CollectingOutputReceiver();
@@ -174,20 +196,26 @@
TimeUnit.HOURS, 1);
assertSuccess(out.getOutput());
- runDeviceTests(PKG, CLASS, "testPrimaryInternal");
- runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
+ for (int user : mUsers) {
+ runDeviceTests(PKG, CLASS, "testPrimaryInternal", user);
+ runDeviceTests(PKG, CLASS, "testPrimaryDataRead", user);
+ }
assertSuccess(getDevice().executeShellCommand("pm move-package " + PKG + " internal"));
- runDeviceTests(PKG, CLASS, "testPrimaryOnSameVolume");
- runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
+ for (int user : mUsers) {
+ runDeviceTests(PKG, CLASS, "testPrimaryOnSameVolume", user);
+ runDeviceTests(PKG, CLASS, "testPrimaryDataRead", user);
+ }
}
private void verifyPrimaryPhysical(String diskId) throws Exception {
// Write some data to shared storage
new InstallMultiple().addApk(APK).run();
- runDeviceTests(PKG, CLASS, "testPrimaryPhysical");
- runDeviceTests(PKG, CLASS, "testPrimaryDataWrite");
- runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
+ for (int user : mUsers) {
+ runDeviceTests(PKG, CLASS, "testPrimaryPhysical", user);
+ runDeviceTests(PKG, CLASS, "testPrimaryDataWrite", user);
+ runDeviceTests(PKG, CLASS, "testPrimaryDataRead", user);
+ }
// Adopt that disk!
assertEmpty(getDevice().executeShellCommand("sm partition " + diskId + " private"));
@@ -196,22 +224,30 @@
// Move primary storage there, but since we just nuked primary physical
// the storage device will be empty
assertSuccess(getDevice().executeShellCommand("pm move-primary-storage " + vol.uuid));
- runDeviceTests(PKG, CLASS, "testPrimaryAdopted");
- runDeviceTests(PKG, CLASS, "testPrimaryDataWrite");
- runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
+ for (int user : mUsers) {
+ runDeviceTests(PKG, CLASS, "testPrimaryAdopted", user);
+ runDeviceTests(PKG, CLASS, "testPrimaryDataWrite", user);
+ runDeviceTests(PKG, CLASS, "testPrimaryDataRead", user);
+ }
// Unmount and verify
- getDevice().executeShellCommand("sm unmount " + vol.volId);
- runDeviceTests(PKG, CLASS, "testPrimaryUnmounted");
- getDevice().executeShellCommand("sm mount " + vol.volId);
- runDeviceTests(PKG, CLASS, "testPrimaryAdopted");
- runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
+ unmount(vol);
+ for (int user : mUsers) {
+ runDeviceTests(PKG, CLASS, "testPrimaryUnmounted", user);
+ }
+ mount(vol);
+ for (int user : mUsers) {
+ runDeviceTests(PKG, CLASS, "testPrimaryAdopted", user);
+ runDeviceTests(PKG, CLASS, "testPrimaryDataRead", user);
+ }
// And move to internal
assertSuccess(getDevice().executeShellCommand("pm move-primary-storage internal"));
- runDeviceTests(PKG, CLASS, "testPrimaryOnSameVolume");
- runDeviceTests(PKG, CLASS, "testPrimaryInternal");
- runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
+ for (int user : mUsers) {
+ runDeviceTests(PKG, CLASS, "testPrimaryOnSameVolume", user);
+ runDeviceTests(PKG, CLASS, "testPrimaryInternal", user);
+ runDeviceTests(PKG, CLASS, "testPrimaryDataRead", user);
+ }
}
/**
@@ -219,7 +255,6 @@
* adopted volumes.
*/
public void testPackageInstaller() throws Exception {
- if (!hasAdoptable()) return;
final String diskId = getAdoptionDisk();
try {
assertEmpty(getDevice().executeShellCommand("sm partition " + diskId + " private"));
@@ -228,14 +263,18 @@
// Install directly onto adopted volume
new InstallMultiple().locationAuto().forceUuid(vol.uuid)
.addApk(APK).addApk(APK_mdpi).run();
- runDeviceTests(PKG, CLASS, "testDataNotInternal");
- runDeviceTests(PKG, CLASS, "testDensityBest1");
+ for (int user : mUsers) {
+ runDeviceTests(PKG, CLASS, "testDataNotInternal", user);
+ runDeviceTests(PKG, CLASS, "testDensityBest1", user);
+ }
// Now splice in an additional split which offers better resources
new InstallMultiple().locationAuto().inheritFrom(PKG)
.addApk(APK_xxhdpi).run();
- runDeviceTests(PKG, CLASS, "testDataNotInternal");
- runDeviceTests(PKG, CLASS, "testDensityBest2");
+ for (int user : mUsers) {
+ runDeviceTests(PKG, CLASS, "testDataNotInternal", user);
+ runDeviceTests(PKG, CLASS, "testDensityBest2", user);
+ }
} finally {
cleanUp(diskId);
@@ -247,7 +286,6 @@
* returned at a later time.
*/
public void testEjected() throws Exception {
- if (!hasAdoptable()) return;
final String diskId = getAdoptionDisk();
try {
assertEmpty(getDevice().executeShellCommand("sm partition " + diskId + " private"));
@@ -255,35 +293,43 @@
// Install directly onto adopted volume, and write data there
new InstallMultiple().locationAuto().forceUuid(vol.uuid).addApk(APK).run();
- runDeviceTests(PKG, CLASS, "testDataNotInternal");
- runDeviceTests(PKG, CLASS, "testDataWrite");
- runDeviceTests(PKG, CLASS, "testDataRead");
+ for (int user : mUsers) {
+ runDeviceTests(PKG, CLASS, "testDataNotInternal", user);
+ runDeviceTests(PKG, CLASS, "testDataWrite", user);
+ runDeviceTests(PKG, CLASS, "testDataRead", user);
+ }
// Now unmount and uninstall; leaving stale package on adopted volume
- getDevice().executeShellCommand("sm unmount " + vol.volId);
+ unmount(vol);
getDevice().uninstallPackage(PKG);
// Install second copy on internal, but don't write anything
new InstallMultiple().locationInternalOnly().addApk(APK).run();
- runDeviceTests(PKG, CLASS, "testDataInternal");
+ for (int user : mUsers) {
+ runDeviceTests(PKG, CLASS, "testDataInternal", user);
+ }
// Kick through a remount cycle, which should purge the adopted app
- getDevice().executeShellCommand("sm mount " + vol.volId);
- runDeviceTests(PKG, CLASS, "testDataInternal");
- boolean didThrow = false;
- try {
- runDeviceTests(PKG, CLASS, "testDataRead");
- } catch (AssertionError expected) {
- didThrow = true;
+ mount(vol);
+ for (int user : mUsers) {
+ runDeviceTests(PKG, CLASS, "testDataInternal", user);
}
- if (!didThrow) {
- fail("Unexpected data from adopted volume picked up");
+ for (int user : mUsers) {
+ boolean threw = false;
+ try {
+ runDeviceTests(PKG, CLASS, "testDataRead", user);
+ } catch (AssertionError expected) {
+ threw = true;
+ }
+ if (!threw) {
+ fail("Unexpected data from adopted volume picked up from user " + user);
+ }
}
- getDevice().executeShellCommand("sm unmount " + vol.volId);
+ unmount(vol);
// Uninstall the internal copy and remount; we should have no record of app
getDevice().uninstallPackage(PKG);
- getDevice().executeShellCommand("sm mount " + vol.volId);
+ mount(vol);
assertEmpty(getDevice().executeShellCommand("pm list packages " + PKG));
} finally {
@@ -291,10 +337,6 @@
}
}
- private boolean hasAdoptable() throws Exception {
- return Boolean.parseBoolean(getDevice().executeShellCommand("sm has-adoptable").trim());
- }
-
private String getAdoptionDisk() throws Exception {
// In the case where we run multiple test we cleanup the state of the device. This
// results in the execution of sm forget all which causes the MountService to "reset"
@@ -330,14 +372,38 @@
throw new AssertionError("Expected private volume; found " + Arrays.toString(lines));
}
+ private void unmount(LocalVolumeInfo vol) throws Exception {
+ getDevice().executeShellCommand("sm unmount " + vol.volId);
+ for (int i = 0; i < 15; i++) {
+ final String raw = getDevice().executeShellCommand("dumpsys package volumes");
+ if (raw.contains("Loaded volumes:") && !raw.contains(vol.volId)) {
+ return;
+ }
+ Thread.sleep(1000);
+ }
+ throw new AssertionError("Private volume " + vol.volId + " failed to be unloaded");
+ }
+
+ private void mount(LocalVolumeInfo vol) throws Exception {
+ getDevice().executeShellCommand("sm mount " + vol.volId);
+ for (int i = 0; i < 15; i++) {
+ final String raw = getDevice().executeShellCommand("dumpsys package volumes");
+ if (raw.contains("Loaded volumes:") && raw.contains(vol.volId)) {
+ return;
+ }
+ Thread.sleep(1000);
+ }
+ throw new AssertionError("Private volume " + vol.volId + " failed to be loaded");
+ }
+
private void cleanUp(String diskId) throws Exception {
getDevice().executeShellCommand("sm partition " + diskId + " public");
getDevice().executeShellCommand("sm forget all");
}
- private void runDeviceTests(String packageName, String testClassName, String testMethodName)
- throws DeviceNotAvailableException {
- Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName);
+ private void runDeviceTests(String packageName, String testClassName, String testMethodName,
+ int userId) throws DeviceNotAvailableException {
+ Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName, userId);
}
private static void assertSuccess(String str) {