1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.tests.sdksandbox.host; 18 19 import static android.appsecurity.cts.Utils.waitForBootCompleted; 20 21 import static com.google.common.truth.Truth.assertThat; 22 import static com.google.common.truth.Truth.assertWithMessage; 23 24 import static org.hamcrest.CoreMatchers.equalTo; 25 import static org.junit.Assume.assumeThat; 26 import static org.junit.Assume.assumeTrue; 27 28 import android.app.sdksandbox.hosttestutils.AdoptableStorageUtils; 29 import android.app.sdksandbox.hosttestutils.AwaitUtils; 30 import android.app.sdksandbox.hosttestutils.DeviceSupportHostUtils; 31 import android.app.sdksandbox.hosttestutils.SecondaryUserUtils; 32 import android.platform.test.annotations.LargeTest; 33 34 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; 35 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; 36 37 import org.junit.After; 38 import org.junit.Before; 39 import org.junit.Test; 40 import org.junit.runner.RunWith; 41 42 import java.nio.file.Paths; 43 import java.util.ArrayList; 44 import java.util.Arrays; 45 import java.util.Collections; 46 import java.util.List; 47 48 import javax.annotation.Nullable; 49 50 @RunWith(DeviceJUnit4ClassRunner.class) 51 public final class SdkSandboxStorageHostTest extends BaseHostJUnit4Test { 52 53 private static final String TEST_APP_STORAGE_PACKAGE = "com.android.tests.sdksandbox"; 54 private static final String TEST_APP_STORAGE_APK = "SdkSandboxStorageTestApp.apk"; 55 private static final String TEST_APP_STORAGE_V2_NO_SDK = 56 "SdkSandboxStorageTestAppV2_DoesNotConsumeSdk.apk"; 57 58 private static final String TEST_UNLOCK_APP_PACKAGE = "com.android.tests.sdksandbox.unlock"; 59 private static final String TEST_UNLOCK_APP_APK = "SdkSandboxStorageTestUnlockApp.apk"; 60 61 private static final String SDK_NAME = "com.android.tests.codeprovider.storagetest"; 62 63 private static final String SHARED_DIR = "shared"; 64 private static final String SANDBOX_DIR = "sandbox"; 65 66 // Needs to be at least 20s since that's how long we delay reconcile on SdkSandboxManagerService 67 private static final long WAIT_FOR_RECONCILE_MS = 30000; 68 69 private final SecondaryUserUtils mUserUtils = new SecondaryUserUtils(this); 70 private final AdoptableStorageUtils mAdoptableUtils = new AdoptableStorageUtils(this); 71 private final DeviceLockUtils mDeviceLockUtils = new DeviceLockUtils(this); 72 private final DeviceSupportHostUtils mDeviceSupportUtils = new DeviceSupportHostUtils(this); 73 74 /** 75 * Runs the given phase of a test by calling into the device. 76 * Throws an exception if the test phase fails. 77 * <p> 78 * For example, <code>runPhase("testExample");</code> 79 */ runPhase(String phase)80 private void runPhase(String phase) throws Exception { 81 assertThat( 82 runDeviceTests( 83 TEST_APP_STORAGE_PACKAGE, 84 TEST_APP_STORAGE_PACKAGE + ".SdkSandboxStorageTestApp", 85 phase)) 86 .isTrue(); 87 } 88 89 @Before setUp()90 public void setUp() throws Exception { 91 assumeTrue(mDeviceSupportUtils.isSdkSandboxSupported()); 92 try { 93 uninstallPackage(TEST_APP_STORAGE_PACKAGE); 94 } finally { 95 mUserUtils.removeSecondaryUserIfNecessary(); 96 } 97 } 98 99 @After tearDown()100 public void tearDown() throws Exception { 101 try { 102 mUserUtils.removeSecondaryUserIfNecessary(); 103 } finally { 104 uninstallPackage(TEST_APP_STORAGE_PACKAGE); 105 } 106 } 107 108 @Test testSelinuxLabel()109 public void testSelinuxLabel() throws Exception { 110 installPackage(TEST_APP_STORAGE_APK); 111 waitForSdkDirectoryCreatedForUser(0); 112 113 assertSelinuxLabel("/data/misc_ce/0/sdksandbox", "sdk_sandbox_system_data_file"); 114 assertSelinuxLabel("/data/misc_de/0/sdksandbox", "sdk_sandbox_system_data_file"); 115 116 // Check label of /data/misc_{ce,de}/0/sdksandbox/<package-name> 117 assertSelinuxLabel(getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, true), 118 "sdk_sandbox_system_data_file"); 119 assertSelinuxLabel(getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, false), 120 "sdk_sandbox_system_data_file"); 121 // Check label of /data/misc_{ce,de}/0/sdksandbox/<app-name>/shared 122 assertSelinuxLabel( 123 getSdkDataInternalPath(0, TEST_APP_STORAGE_PACKAGE, SHARED_DIR, true), 124 "sdk_sandbox_data_file"); 125 assertSelinuxLabel( 126 getSdkDataInternalPath(0, TEST_APP_STORAGE_PACKAGE, SHARED_DIR, false), 127 "sdk_sandbox_data_file"); 128 // Check label of /data/misc_{ce,de}/0/sdksandbox/<app-name>/<sdk-package> 129 assertSelinuxLabel(getSdkDataPerSdkPath(0, TEST_APP_STORAGE_PACKAGE, SDK_NAME, true), 130 "sdk_sandbox_data_file"); 131 assertSelinuxLabel(getSdkDataPerSdkPath(0, TEST_APP_STORAGE_PACKAGE, SDK_NAME, false), 132 "sdk_sandbox_data_file"); 133 } 134 135 /** 136 * Verify that {@code /data/misc_{ce,de}/<user-id>/sdksandbox} is created when {@code <user-id>} 137 * is created. 138 */ 139 @Test 140 @LargeTest // New User Created testSdkDataRootDirectory_IsCreatedOnUserCreate()141 public void testSdkDataRootDirectory_IsCreatedOnUserCreate() throws Exception { 142 assumeTrue("Multiple user not supported", mUserUtils.isMultiUserSupported()); 143 144 { 145 // Verify root directory exists for primary user 146 final String cePath = getSdkDataRootPath(0, true); 147 final String dePath = getSdkDataRootPath(0, false); 148 assertThat(getDevice().isDirectory(dePath)).isTrue(); 149 assertThat(getDevice().isDirectory(cePath)).isTrue(); 150 } 151 152 { 153 // Verify root directory is created for new user 154 int secondaryUserId = mUserUtils.createAndStartSecondaryUser(); 155 final String cePath = getSdkDataRootPath(secondaryUserId, true); 156 final String dePath = getSdkDataRootPath(secondaryUserId, false); 157 assertThat(getDevice().isDirectory(dePath)).isTrue(); 158 assertThat(getDevice().isDirectory(cePath)).isTrue(); 159 } 160 } 161 162 @Test 163 @LargeTest // New User Created testSdkDataRootDirectory_IsDestroyedOnUserDeletion()164 public void testSdkDataRootDirectory_IsDestroyedOnUserDeletion() throws Exception { 165 assumeTrue("Multiple user not supported", mUserUtils.isMultiUserSupported()); 166 167 // delete the new user 168 final int newUser = mUserUtils.createAndStartSecondaryUser(); 169 mUserUtils.removeSecondaryUserIfNecessary(); 170 171 // Sdk Sandbox root directories should not exist as the user was removed 172 final String ceSdkSandboxDataRootPath = getSdkDataRootPath(newUser, true); 173 final String deSdkSandboxDataRootPath = getSdkDataRootPath(newUser, false); 174 assertThat(getDevice().isDirectory(ceSdkSandboxDataRootPath)).isFalse(); 175 assertThat(getDevice().isDirectory(deSdkSandboxDataRootPath)).isFalse(); 176 } 177 178 @Test testSdkSandboxDataMirrorAppDirectory_IsCreatedOnInstall()179 public void testSdkSandboxDataMirrorAppDirectory_IsCreatedOnInstall() throws Exception { 180 final String cePath = getSdkDataMirrorPackagePath(0, TEST_APP_STORAGE_PACKAGE, true); 181 final String dePath = getSdkDataMirrorPackagePath(0, TEST_APP_STORAGE_PACKAGE, false); 182 183 assertThat(getDevice().isDirectory(cePath)).isFalse(); 184 assertThat(getDevice().isDirectory(dePath)).isFalse(); 185 installPackage(TEST_APP_STORAGE_APK); 186 waitForSdkDirectoryCreatedForUser(0); 187 assertThat(getDevice().isDirectory(cePath)).isTrue(); 188 assertThat(getDevice().isDirectory(dePath)).isTrue(); 189 } 190 191 @Test 192 @LargeTest // New volume created testSdkSandboxDataMirrorDirectory_IsVolumeSpecific()193 public void testSdkSandboxDataMirrorDirectory_IsVolumeSpecific() throws Exception { 194 assumeTrue(mAdoptableUtils.isAdoptableStorageSupported()); 195 196 installPackage(TEST_APP_STORAGE_APK); 197 waitForSdkDirectoryCreatedForUser(0); 198 199 String mirrorCeVolPath; 200 String mirrorDeVolPath; 201 try { 202 final String uuid = mAdoptableUtils.createNewVolume(); 203 204 mirrorCeVolPath = "/data_mirror/misc_ce/" + uuid; 205 mirrorDeVolPath = "/data_mirror/misc_de/" + uuid; 206 final String mirrorCeVolPackagePath = 207 mirrorCeVolPath + "/0/sdksandbox/" + TEST_APP_STORAGE_PACKAGE; 208 final String mirrorDeVolPackagePath = 209 mirrorDeVolPath + "/0/sdksandbox/" + TEST_APP_STORAGE_PACKAGE; 210 211 assertThat(getDevice().isDirectory(mirrorCeVolPath)).isTrue(); 212 assertThat(getDevice().isDirectory(mirrorDeVolPath)).isTrue(); 213 assertThat(getDevice().isDirectory(mirrorCeVolPackagePath)).isFalse(); 214 assertThat(getDevice().isDirectory(mirrorDeVolPackagePath)).isFalse(); 215 216 // Move package to the newly created volume 217 assertSuccess( 218 getDevice() 219 .executeShellCommand( 220 "pm move-package " + TEST_APP_STORAGE_PACKAGE + " " + uuid)); 221 222 assertThat(getDevice().isDirectory(mirrorCeVolPackagePath)).isTrue(); 223 assertThat(getDevice().isDirectory(mirrorDeVolPackagePath)).isTrue(); 224 } finally { 225 mAdoptableUtils.cleanUpVolume(); 226 } 227 228 assertThat(getDevice().isDirectory(mirrorCeVolPath)).isFalse(); 229 assertThat(getDevice().isDirectory(mirrorDeVolPath)).isFalse(); 230 } 231 232 /** 233 * Verify that {@code /data/misc_{ce,de}/<user-id>/sdksandbox} is not accessible by apps 234 */ 235 @Test testSdkSandboxDataRootDirectory_IsNotAccessibleByApps()236 public void testSdkSandboxDataRootDirectory_IsNotAccessibleByApps() throws Exception { 237 // Install the app 238 installPackage(TEST_APP_STORAGE_APK); 239 waitForSdkDirectoryCreatedForUser(0); 240 241 // Verify root directory exists for primary user 242 final String cePath = getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, true); 243 final String dePath = getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, false); 244 assertThat(getDevice().isDirectory(dePath)).isTrue(); 245 assertThat(getDevice().isDirectory(cePath)).isTrue(); 246 247 runPhase("testSdkSandboxDataRootDirectory_IsNotAccessibleByApps"); 248 } 249 250 @Test testSdkDataPackageDirectory_IsCreatedOnInstall()251 public void testSdkDataPackageDirectory_IsCreatedOnInstall() throws Exception { 252 // Directory should not exist before install 253 final String cePath = getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, true); 254 final String dePath = getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, false); 255 assertThat(getDevice().isDirectory(cePath)).isFalse(); 256 assertThat(getDevice().isDirectory(dePath)).isFalse(); 257 258 // Install the app 259 installPackage(TEST_APP_STORAGE_APK); 260 waitForSdkDirectoryCreatedForUser(0); 261 262 // Verify directory is created 263 assertThat(getDevice().isDirectory(cePath)).isTrue(); 264 assertThat(getDevice().isDirectory(dePath)).isTrue(); 265 } 266 267 @Test testSdkDataPackageDirectory_IsNotCreatedWithoutSdkConsumption()268 public void testSdkDataPackageDirectory_IsNotCreatedWithoutSdkConsumption() 269 throws Exception { 270 // Install the an app that does not consume sdk 271 installPackage(TEST_APP_STORAGE_V2_NO_SDK); 272 273 // Verify directories are not created 274 final String cePath = getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, true); 275 final String dePath = getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, false); 276 assertThat(getDevice().isDirectory(cePath)).isFalse(); 277 assertThat(getDevice().isDirectory(dePath)).isFalse(); 278 } 279 280 @Test testSdkDataPackageDirectory_IsDestroyedOnUninstall()281 public void testSdkDataPackageDirectory_IsDestroyedOnUninstall() throws Exception { 282 // Install the app 283 installPackage(TEST_APP_STORAGE_APK); 284 waitForSdkDirectoryCreatedForUser(0); 285 286 //Uninstall the app 287 uninstallPackage(TEST_APP_STORAGE_PACKAGE); 288 289 // Directory should not exist after uninstall 290 final String cePath = getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, true); 291 final String dePath = getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, false); 292 // Verify directory is destoyed 293 assertThat(getDevice().isDirectory(cePath)).isFalse(); 294 assertThat(getDevice().isDirectory(dePath)).isFalse(); 295 } 296 297 @Test 298 @LargeTest testSdkDataPackageDirectory_IsDestroyedOnUninstall_DeviceLocked()299 public void testSdkDataPackageDirectory_IsDestroyedOnUninstall_DeviceLocked() throws Exception { 300 assumeThat("Device is NOT encrypted with file-based encryption.", 301 getDevice().getProperty("ro.crypto.type"), equalTo("file")); 302 assumeTrue("Screen lock is not supported so skip direct boot test", 303 hasDeviceFeature("android.software.secure_lock_screen")); 304 305 installPackage(TEST_UNLOCK_APP_APK); 306 installPackage(TEST_APP_STORAGE_APK); 307 waitForSdkDirectoryCreatedForUser(0); 308 309 // Verify sdk ce directory contains TEST_APP_STORAGE_PACKAGE 310 final String ceSandboxPath = getSdkDataRootPath(0, /*isCeData=*/ true); 311 String[] children = getDevice().getChildren(ceSandboxPath); 312 assertThat(children).isNotEmpty(); 313 final int numberOfChildren = children.length; 314 assertThat(children).asList().contains(TEST_APP_STORAGE_PACKAGE); 315 316 try { 317 mDeviceLockUtils.rebootToLockedDevice(); 318 319 // Verify sdk ce package directory is encrypted, so longer contains the test package 320 children = getDevice().getChildren(ceSandboxPath); 321 assertThat(children).hasLength(numberOfChildren); 322 assertThat(children).asList().doesNotContain(TEST_APP_STORAGE_PACKAGE); 323 324 // Uninstall while device is locked 325 uninstallPackage(TEST_APP_STORAGE_PACKAGE); 326 327 // Verify ce sdk data did not change while device is locked 328 children = getDevice().getChildren(ceSandboxPath); 329 assertThat(children).hasLength(numberOfChildren); 330 331 // Meanwhile, de storage area should already be deleted 332 final String dePath = getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, false); 333 assertThat(getDevice().isDirectory(dePath)).isFalse(); 334 } finally { 335 mDeviceLockUtils.clearScreenLock(); 336 uninstallPackage(TEST_UNLOCK_APP_PACKAGE); 337 } 338 339 // Once device is unlocked, the uninstallation during locked state should take effect. 340 // Allow some time for background task to run. 341 Thread.sleep(WAIT_FOR_RECONCILE_MS); 342 343 final String cePath = getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, true); 344 assertDirectoryDoesNotExist(cePath); 345 // Verify number of children under root directory is one less than before 346 children = getDevice().getChildren(ceSandboxPath); 347 assertThat(children).hasLength(numberOfChildren - 1); 348 assertThat(children).asList().doesNotContain(TEST_APP_STORAGE_PACKAGE); 349 } 350 351 @Test 352 @LargeTest // Device reboot testSdkDataPackageDirectory_IsReconciled_InvalidAndMissingPackage()353 public void testSdkDataPackageDirectory_IsReconciled_InvalidAndMissingPackage() 354 throws Exception { 355 356 installPackage(TEST_APP_STORAGE_APK); 357 waitForSdkDirectoryCreatedForUser(0); 358 359 // Rename the sdk data directory to some non-existing package name 360 final String cePackageDir = getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, true); 361 final String ceInvalidDir = getSdkDataPackagePath(0, "com.invalid.foo", true); 362 getDevice().executeShellCommand(String.format("mv %s %s", cePackageDir, ceInvalidDir)); 363 assertDirectoryExists(ceInvalidDir); 364 365 final String dePackageDir = getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, false); 366 final String deInvalidDir = getSdkDataPackagePath(0, "com.invalid.foo", false); 367 getDevice().executeShellCommand(String.format("mv %s %s", dePackageDir, deInvalidDir)); 368 assertDirectoryExists(deInvalidDir); 369 370 // Reboot since reconcilation happens on user unlock only 371 getDevice().reboot(); 372 Thread.sleep(WAIT_FOR_RECONCILE_MS); 373 374 // Verify invalid directory doesn't exist 375 assertDirectoryDoesNotExist(ceInvalidDir); 376 assertDirectoryDoesNotExist(deInvalidDir); 377 assertDirectoryExists(cePackageDir); 378 assertDirectoryExists(dePackageDir); 379 } 380 381 @Test 382 @LargeTest // Device reboot testSdkDataPackageDirectory_IsReconciled_IncludesDifferentVolumes()383 public void testSdkDataPackageDirectory_IsReconciled_IncludesDifferentVolumes() 384 throws Exception { 385 assumeTrue(mAdoptableUtils.isAdoptableStorageSupported()); 386 387 try { 388 installPackage(TEST_APP_STORAGE_APK); 389 waitForSdkDirectoryCreatedForUser(0); 390 391 final String newVolumeUuid = mAdoptableUtils.createNewVolume(); 392 393 assertSuccess( 394 getDevice() 395 .executeShellCommand( 396 "pm move-package " 397 + TEST_APP_STORAGE_PACKAGE 398 + " " 399 + newVolumeUuid)); 400 401 final String ceSdkDataPackagePath = 402 getSdkDataPackagePath(newVolumeUuid, 0, TEST_APP_STORAGE_PACKAGE, true); 403 final String deSdkDataPackagePath = 404 getSdkDataPackagePath(newVolumeUuid, 0, TEST_APP_STORAGE_PACKAGE, false); 405 406 // Rename the sdk data directory to some non-existing package name 407 final String ceInvalidDir = 408 getSdkDataPackagePath(newVolumeUuid, 0, "com.invalid.foo", true); 409 getDevice() 410 .executeShellCommand( 411 String.format("mv %s %s", ceSdkDataPackagePath, ceInvalidDir)); 412 assertDirectoryExists(ceInvalidDir); 413 414 final String deInvalidDir = 415 getSdkDataPackagePath(newVolumeUuid, 0, "com.invalid.foo", false); 416 getDevice() 417 .executeShellCommand( 418 String.format("mv %s %s", deSdkDataPackagePath, deInvalidDir)); 419 assertDirectoryExists(deInvalidDir); 420 421 // Reboot since reconcilation happens on user unlock only 422 getDevice().reboot(); 423 Thread.sleep(WAIT_FOR_RECONCILE_MS); 424 425 // Verify invalid directory doesn't exist 426 assertDirectoryDoesNotExist(ceInvalidDir); 427 assertDirectoryDoesNotExist(deInvalidDir); 428 assertDirectoryExists(ceSdkDataPackagePath); 429 assertDirectoryExists(deSdkDataPackagePath); 430 431 } finally { 432 mAdoptableUtils.cleanUpVolume(); 433 } 434 } 435 436 @Test 437 @LargeTest // Create new volume testSdkDataPackageDirectory_IsReconciled_ChecksForPackageOnWrongVolume()438 public void testSdkDataPackageDirectory_IsReconciled_ChecksForPackageOnWrongVolume() 439 throws Exception { 440 assumeTrue(mAdoptableUtils.isAdoptableStorageSupported()); 441 442 try { 443 installPackage(TEST_APP_STORAGE_APK); 444 waitForSdkDirectoryCreatedForUser(0); 445 446 final String newVolumeUuid = mAdoptableUtils.createNewVolume(); 447 448 assertSuccess( 449 getDevice() 450 .executeShellCommand( 451 "pm move-package " 452 + TEST_APP_STORAGE_PACKAGE 453 + " " 454 + newVolumeUuid)); 455 456 final String ceInvalidDir = getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, true); 457 final String deInvalidDir = getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, false); 458 459 // Create sdk package directory for testapp on null volume 460 getDevice().executeShellCommand(String.format("mkdir %s", ceInvalidDir)); 461 getDevice().executeShellCommand(String.format("mkdir %s", deInvalidDir)); 462 463 // Reboot since reconcilation happens on user unlock only 464 getDevice().reboot(); 465 Thread.sleep(WAIT_FOR_RECONCILE_MS); 466 467 // Verify invalid directory doesn't exist 468 assertDirectoryDoesNotExist(ceInvalidDir); 469 assertDirectoryDoesNotExist(deInvalidDir); 470 471 } finally { 472 mAdoptableUtils.cleanUpVolume(); 473 } 474 } 475 476 @Test 477 @LargeTest // Device reboot testSdkDataPackageDirectory_IsReconciled_MissingSubDirs()478 public void testSdkDataPackageDirectory_IsReconciled_MissingSubDirs() throws Exception { 479 480 installPackage(TEST_APP_STORAGE_APK); 481 waitForSdkDirectoryCreatedForUser(0); 482 483 final String cePackageDir = getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, true); 484 // Delete the shared directory 485 final String sharedDir = cePackageDir + "/" + SHARED_DIR; 486 getDevice().deleteFile(sharedDir); 487 assertDirectoryDoesNotExist(sharedDir); 488 489 // Reboot since reconcilation happens on user unlock only 490 getDevice().reboot(); 491 Thread.sleep(WAIT_FOR_RECONCILE_MS); 492 493 // Verify shared dir exists 494 assertDirectoryExists(sharedDir); 495 } 496 497 @Test 498 @LargeTest // Device reboot testSdkDataPackageDirectory_IsReconciled_DeleteKeepData()499 public void testSdkDataPackageDirectory_IsReconciled_DeleteKeepData() throws Exception { 500 501 installPackage(TEST_APP_STORAGE_APK); 502 waitForSdkDirectoryCreatedForUser(0); 503 504 // Uninstall while keeping the data 505 getDevice().executeShellCommand("pm uninstall -k --user 0 " + TEST_APP_STORAGE_PACKAGE); 506 507 final String cePackageDir = getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, true); 508 final String dePackageDir = getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, false); 509 assertDirectoryExists(cePackageDir); 510 assertDirectoryExists(dePackageDir); 511 512 // Reboot since reconcilation happens on user unlock only 513 getDevice().reboot(); 514 Thread.sleep(WAIT_FOR_RECONCILE_MS); 515 516 // Verify sdk data are not cleaned up during reconcilation 517 assertDirectoryExists(cePackageDir); 518 assertDirectoryExists(dePackageDir); 519 } 520 521 @Test 522 @LargeTest // Device reboot testSdkDataPackageDirectory_IsReconciled_DeleteKeepNewVolumeData()523 public void testSdkDataPackageDirectory_IsReconciled_DeleteKeepNewVolumeData() 524 throws Exception { 525 assumeTrue(mAdoptableUtils.isAdoptableStorageSupported()); 526 527 try { 528 installPackage(TEST_APP_STORAGE_APK); 529 waitForSdkDirectoryCreatedForUser(0); 530 531 final String newVolumeUuid = mAdoptableUtils.createNewVolume(); 532 533 assertSuccess( 534 getDevice() 535 .executeShellCommand( 536 "pm move-package " 537 + TEST_APP_STORAGE_PACKAGE 538 + " " 539 + newVolumeUuid)); 540 541 // Uninstall while keeping the data 542 getDevice().executeShellCommand("pm uninstall -k --user 0 " + TEST_APP_STORAGE_PACKAGE); 543 544 final String ceSdkDataPackagePath = 545 getSdkDataPackagePath(newVolumeUuid, 0, TEST_APP_STORAGE_PACKAGE, true); 546 final String deSdkDataPackagePath = 547 getSdkDataPackagePath(newVolumeUuid, 0, TEST_APP_STORAGE_PACKAGE, false); 548 549 assertDirectoryExists(ceSdkDataPackagePath); 550 assertDirectoryExists(deSdkDataPackagePath); 551 552 // Reboot since reconcilation happens on user unlock only 553 getDevice().reboot(); 554 Thread.sleep(WAIT_FOR_RECONCILE_MS); 555 556 // Verify sdk data are not cleaned up during reconcilation 557 assertDirectoryExists(ceSdkDataPackagePath); 558 assertDirectoryExists(deSdkDataPackagePath); 559 } finally { 560 mAdoptableUtils.cleanUpVolume(); 561 } 562 } 563 564 @Test testSdkDataPackageDirectory_IsClearedOnClearAppData()565 public void testSdkDataPackageDirectory_IsClearedOnClearAppData() throws Exception { 566 // Install the app 567 installPackage(TEST_APP_STORAGE_APK); 568 waitForSdkDirectoryCreatedForUser(0); 569 570 // Ensure per-sdk storage has been created 571 runPhase("loadSdk"); 572 573 // Create app data to be cleared 574 final List<String> dataPaths = 575 Arrays.asList( 576 getAppDataPath(0, TEST_APP_STORAGE_PACKAGE, true), // CE app data 577 getAppDataPath(0, TEST_APP_STORAGE_PACKAGE, false), // DE app data 578 getSdkDataInternalPath( 579 0, TEST_APP_STORAGE_PACKAGE, SHARED_DIR, true), // CE sdk data 580 getSdkDataInternalPath( 581 0, TEST_APP_STORAGE_PACKAGE, SHARED_DIR, false), // DE sdk data 582 getSdkDataPerSdkPath( 583 0, TEST_APP_STORAGE_PACKAGE, SDK_NAME, true), // CE per-sdk 584 getSdkDataPerSdkPath( 585 0, TEST_APP_STORAGE_PACKAGE, SDK_NAME, false) // DE per-sdk 586 ); 587 for (String dataPath : dataPaths) { 588 final String fileToDelete = dataPath + "/cache/deleteme.txt"; 589 getDevice().executeShellCommand("echo something to delete > " + fileToDelete); 590 assertThat(getDevice().doesFileExist(fileToDelete)).isTrue(); 591 } 592 593 // Clear the app data 594 getDevice().executeShellCommand("pm clear " + TEST_APP_STORAGE_PACKAGE); 595 596 // Verify cache directories are empty 597 for (String dataPath : dataPaths) { 598 final String[] cacheChildren = getDevice().getChildren(dataPath); 599 assertWithMessage(dataPath + " is not empty").that(cacheChildren).asList().isEmpty(); 600 } 601 } 602 603 @Test testSdkDataPackageDirectory_IsClearedOnFreeCache()604 public void testSdkDataPackageDirectory_IsClearedOnFreeCache() throws Exception { 605 // Install the app 606 installPackage(TEST_APP_STORAGE_APK); 607 waitForSdkDirectoryCreatedForUser(0); 608 609 // Ensure per-sdk storage has been created 610 runPhase("loadSdk"); 611 612 // Create cache data to be cleared 613 final List<String> dataPaths = 614 Arrays.asList( 615 getAppDataPath(0, TEST_APP_STORAGE_PACKAGE, true), // CE app data 616 getAppDataPath(0, TEST_APP_STORAGE_PACKAGE, false), // DE app data 617 getSdkDataInternalPath( 618 0, TEST_APP_STORAGE_PACKAGE, SHARED_DIR, true), // CE sdk data 619 getSdkDataInternalPath( 620 0, TEST_APP_STORAGE_PACKAGE, SHARED_DIR, false), // DE sdk data 621 getSdkDataPerSdkPath( 622 0, TEST_APP_STORAGE_PACKAGE, SDK_NAME, true), // CE per-sdk 623 getSdkDataPerSdkPath( 624 0, TEST_APP_STORAGE_PACKAGE, SDK_NAME, false) // DE per-sdk 625 ); 626 for (String dataPath : dataPaths) { 627 final String fileToDelete = dataPath + "/cache/deleteme.txt"; 628 getDevice().executeShellCommand("echo something to delete > " + fileToDelete); 629 assertThat(getDevice().doesFileExist(fileToDelete)).isTrue(); 630 } 631 632 // Clear all other cached data to give ourselves a clean slate 633 getDevice().executeShellCommand("pm trim-caches 4096G"); 634 635 // Verify cache directories are empty 636 for (String dataPath : dataPaths) { 637 final String[] cacheChildren = getDevice().getChildren(dataPath + "/cache"); 638 assertWithMessage(dataPath + " is not empty").that(cacheChildren).asList().isEmpty(); 639 } 640 } 641 642 @Test testSdkDataPackageDirectory_IsClearedOnClearCache()643 public void testSdkDataPackageDirectory_IsClearedOnClearCache() throws Exception { 644 // Install the app 645 installPackage(TEST_APP_STORAGE_APK); 646 waitForSdkDirectoryCreatedForUser(0); 647 648 // Ensure per-sdk storage has been created 649 runPhase("loadSdk"); 650 651 // Create cache data to be cleared 652 final List<String> dataPaths = 653 Arrays.asList( 654 getAppDataPath(0, TEST_APP_STORAGE_PACKAGE, true), // CE app data 655 getAppDataPath(0, TEST_APP_STORAGE_PACKAGE, false), // DE app data 656 getSdkDataInternalPath( 657 0, TEST_APP_STORAGE_PACKAGE, SHARED_DIR, true), // CE sdk data 658 getSdkDataInternalPath( 659 0, TEST_APP_STORAGE_PACKAGE, SHARED_DIR, false), // DE sdk data 660 getSdkDataPerSdkPath( 661 0, TEST_APP_STORAGE_PACKAGE, SDK_NAME, true), // CE per-sdk 662 getSdkDataPerSdkPath( 663 0, TEST_APP_STORAGE_PACKAGE, SDK_NAME, false) // DE per-sdk 664 ); 665 for (String dataPath : dataPaths) { 666 final String fileToDelete = dataPath + "/cache/deleteme.txt"; 667 getDevice().executeShellCommand("echo something to delete > " + fileToDelete); 668 assertThat(getDevice().doesFileExist(fileToDelete)).isTrue(); 669 } 670 671 // Clear the cached data for the test app 672 getDevice() 673 .executeShellCommand("pm clear --user 0 --cache-only com.android.tests.sdksandbox"); 674 675 // Verify cache directories are empty 676 for (String dataPath : dataPaths) { 677 final String[] cacheChildren = getDevice().getChildren(dataPath + "/cache"); 678 assertWithMessage(dataPath + " is not empty").that(cacheChildren).asList().isEmpty(); 679 } 680 } 681 682 @Test 683 @LargeTest // New user created testSdkDataPackageDirectory_IsUserSpecific()684 public void testSdkDataPackageDirectory_IsUserSpecific() throws Exception { 685 assumeTrue("Multiple user not supported", mUserUtils.isMultiUserSupported()); 686 687 // Install first before creating the user 688 installPackage(TEST_APP_STORAGE_APK, "--user all"); 689 waitForSdkDirectoryCreatedForUser(0); 690 691 int secondaryUserId = mUserUtils.createAndStartSecondaryUser(); 692 693 // Data directories should not exist as the package is not installed on new user 694 final String ceAppPath = getAppDataPath(secondaryUserId, TEST_APP_STORAGE_PACKAGE, true); 695 final String deAppPath = getAppDataPath(secondaryUserId, TEST_APP_STORAGE_PACKAGE, false); 696 final String cePath = 697 getSdkDataPackagePath(secondaryUserId, TEST_APP_STORAGE_PACKAGE, true); 698 final String dePath = 699 getSdkDataPackagePath(secondaryUserId, TEST_APP_STORAGE_PACKAGE, false); 700 701 assertThat(getDevice().isDirectory(ceAppPath)).isFalse(); 702 assertThat(getDevice().isDirectory(deAppPath)).isFalse(); 703 assertThat(getDevice().isDirectory(cePath)).isFalse(); 704 assertThat(getDevice().isDirectory(dePath)).isFalse(); 705 706 // Install the app on new user 707 installPackage(TEST_APP_STORAGE_APK); 708 709 assertThat(getDevice().isDirectory(ceAppPath)).isTrue(); 710 assertThat(getDevice().isDirectory(deAppPath)).isTrue(); 711 assertThat(getDevice().isDirectory(cePath)).isTrue(); 712 assertThat(getDevice().isDirectory(dePath)).isTrue(); 713 } 714 715 @Test testSdkDataPackageDirectory_SharedStorageIsUsable()716 public void testSdkDataPackageDirectory_SharedStorageIsUsable() throws Exception { 717 installPackage(TEST_APP_STORAGE_APK); 718 waitForSdkDirectoryCreatedForUser(0); 719 720 // Verify that shared storage exist 721 final String sharedCePath = 722 getSdkDataInternalPath(0, TEST_APP_STORAGE_PACKAGE, SHARED_DIR, true); 723 assertThat(getDevice().isDirectory(sharedCePath)).isTrue(); 724 725 // Write a file in the shared storage that code needs to read and write it back 726 // in another file 727 String fileToRead = sharedCePath + "/readme.txt"; 728 getDevice().executeShellCommand("echo something to read > " + fileToRead); 729 assertThat(getDevice().doesFileExist(fileToRead)).isTrue(); 730 731 runPhase("testSdkDataPackageDirectory_SharedStorageIsUsable"); 732 733 // Assert that code was able to create file and directories 734 assertThat(getDevice().isDirectory(sharedCePath + "/dir")).isTrue(); 735 assertThat(getDevice().doesFileExist(sharedCePath + "/dir/file")).isTrue(); 736 String content = getDevice().executeShellCommand("cat " + sharedCePath + "/dir/file"); 737 assertThat(content).isEqualTo("something to read"); 738 } 739 740 @Test testSdkDataPackageDirectory_CreateMissingSdkSubDirsWhenPackageDirEmpty()741 public void testSdkDataPackageDirectory_CreateMissingSdkSubDirsWhenPackageDirEmpty() 742 throws Exception { 743 installPackage(TEST_APP_STORAGE_APK); 744 waitForSdkDirectoryCreatedForUser(0); 745 746 // Now delete the sdk data sub-dirs so that package directory is empty 747 final String cePackagePath = getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, true); 748 final String dePackagePath = 749 getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, false); 750 final List<String> ceSdkDirsBeforeLoadingSdksList = getSubDirs(cePackagePath, 751 /*includeRandomSuffix=*/true); 752 final List<String> deSdkDirsBeforeLoadingSdksList = getSubDirs(dePackagePath, 753 /*includeRandomSuffix=*/true); 754 assertThat(getDevice().getChildren(cePackagePath)).asList().isNotEmpty(); 755 // Delete the sdk sub directories 756 for (String child : ceSdkDirsBeforeLoadingSdksList) { 757 getDevice().deleteFile(cePackagePath + "/" + child); 758 } 759 for (String child : deSdkDirsBeforeLoadingSdksList) { 760 getDevice().deleteFile(dePackagePath + "/" + child); 761 } 762 assertThat(getDevice().getChildren(cePackagePath)).asList().isEmpty(); 763 764 runPhase("loadSdk"); 765 766 final List<String> ceSdkDirsAfterLoadingSdksList = getSubDirs(cePackagePath, 767 /*includeRandomSuffix=*/false); 768 final List<String> deSdkDirsAfterLoadingSdksList = getSubDirs(dePackagePath, 769 /*includeRandomSuffix=*/false); 770 assertThat(ceSdkDirsAfterLoadingSdksList) 771 .containsExactly(SHARED_DIR, SDK_NAME, SANDBOX_DIR); 772 assertThat(deSdkDirsAfterLoadingSdksList) 773 .containsExactly(SHARED_DIR, SDK_NAME, SANDBOX_DIR); 774 } 775 776 @Test testSdkDataPackageDirectory_CreateMissingSdkSubDirsWhenPackageDirMissing()777 public void testSdkDataPackageDirectory_CreateMissingSdkSubDirsWhenPackageDirMissing() 778 throws Exception { 779 installPackage(TEST_APP_STORAGE_APK); 780 waitForSdkDirectoryCreatedForUser(0); 781 782 final String cePackagePath = 783 getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, true); 784 final String dePackagePath = 785 getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, false); 786 // Delete the package paths 787 getDevice().deleteFile(cePackagePath); 788 getDevice().deleteFile(dePackagePath); 789 runPhase("loadSdk"); 790 791 final List<String> ceSdkDirsAfterLoadingSdksList = getSubDirs(cePackagePath, 792 /*includeRandomSuffix=*/false); 793 final List<String> deSdkDirsAfterLoadingSdksList = getSubDirs(dePackagePath, 794 /*includeRandomSuffix=*/false); 795 assertThat(ceSdkDirsAfterLoadingSdksList) 796 .containsExactly(SHARED_DIR, SDK_NAME, SANDBOX_DIR); 797 assertThat(deSdkDirsAfterLoadingSdksList) 798 .containsExactly(SHARED_DIR, SDK_NAME, SANDBOX_DIR); 799 } 800 801 @Test testSdkDataPackageDirectory_CreateMissingSdkSubDirsWhenPackageDirIsNotEmpty()802 public void testSdkDataPackageDirectory_CreateMissingSdkSubDirsWhenPackageDirIsNotEmpty() 803 throws Exception { 804 installPackage(TEST_APP_STORAGE_APK); 805 waitForSdkDirectoryCreatedForUser(0); 806 final String cePackagePath = 807 getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, true); 808 final String dePackagePath = 809 getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, false); 810 final List<String> ceSdkDirsBeforeLoadingSdksList = getSubDirs(cePackagePath, 811 /*includeRandomSuffix=*/true); 812 final List<String> deSdkDirsBeforeLoadingSdksList = getSubDirs(dePackagePath, 813 /*includeRandomSuffix=*/true); 814 // Delete the sdk sub directories 815 getDevice().deleteFile(cePackagePath + "/" + ceSdkDirsBeforeLoadingSdksList.get(0)); 816 getDevice().deleteFile(dePackagePath + "/" + deSdkDirsBeforeLoadingSdksList.get(0)); 817 runPhase("loadSdk"); 818 819 final List<String> ceSdkDirsAfterLoadingSdksList = getSubDirs(cePackagePath, 820 /*includeRandomSuffix=*/false); 821 final List<String> deSdkDirsAfterLoadingSdksList = 822 getSubDirs(dePackagePath, /*includeRandomSuffix=*/ false); 823 assertThat(ceSdkDirsAfterLoadingSdksList) 824 .containsExactly(SHARED_DIR, SDK_NAME, SANDBOX_DIR); 825 assertThat(deSdkDirsAfterLoadingSdksList) 826 .containsExactly(SHARED_DIR, SDK_NAME, SANDBOX_DIR); 827 } 828 829 @Test testSdkDataPackageDirectory_ReuseExistingRandomSuffixInReconcile()830 public void testSdkDataPackageDirectory_ReuseExistingRandomSuffixInReconcile() 831 throws Exception { 832 installPackage(TEST_APP_STORAGE_APK); 833 waitForSdkDirectoryCreatedForUser(0); 834 835 final String cePackagePath = getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, true); 836 final String dePackagePath = getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, false); 837 final List<String> ceSdkDirsBeforeLoadingSdksList = 838 getSubDirs(cePackagePath, /*includeRandomSuffix=*/ true); 839 final List<String> deSdkDirsBeforeLoadingSdksList = 840 getSubDirs(dePackagePath, /*includeRandomSuffix=*/ true); 841 842 // Delete the sdk sub directories 843 getDevice().deleteFile(cePackagePath + "/" + SHARED_DIR); 844 getDevice().deleteFile(dePackagePath + "/" + SHARED_DIR); 845 846 runPhase("loadSdk"); 847 848 final List<String> ceSdkDirsAfterLoadingSdksList = 849 getSubDirs(cePackagePath, /*includeRandomSuffix=*/ true); 850 final List<String> deSdkDirsAfterLoadingSdksList = 851 getSubDirs(dePackagePath, /*includeRandomSuffix=*/ true); 852 assertThat(ceSdkDirsAfterLoadingSdksList) 853 .containsExactlyElementsIn(ceSdkDirsBeforeLoadingSdksList); 854 assertThat(deSdkDirsAfterLoadingSdksList) 855 .containsExactlyElementsIn(deSdkDirsBeforeLoadingSdksList); 856 } 857 858 @Test testSdkDataPackageDirectory_OnUpdateDoesNotConsumeSdk()859 public void testSdkDataPackageDirectory_OnUpdateDoesNotConsumeSdk() throws Exception { 860 installPackage(TEST_APP_STORAGE_APK); 861 waitForSdkDirectoryCreatedForUser(0); 862 863 final String cePath = getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, true); 864 final String dePath = getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, false); 865 assertThat(getDevice().isDirectory(cePath)).isTrue(); 866 assertThat(getDevice().isDirectory(dePath)).isTrue(); 867 868 // Update app so that it no longer consumes any sdk 869 installPackage(TEST_APP_STORAGE_V2_NO_SDK); 870 assertThat(getDevice().isDirectory(cePath)).isFalse(); 871 assertThat(getDevice().isDirectory(dePath)).isFalse(); 872 } 873 874 @Test testSdkDataSubDirectory_IsCreatedOnInstall()875 public void testSdkDataSubDirectory_IsCreatedOnInstall() throws Exception { 876 // Directory should not exist before install 877 assertThat(getSdkDataInternalPath(0, TEST_APP_STORAGE_PACKAGE, SANDBOX_DIR, true)).isNull(); 878 assertThat(getSdkDataInternalPath(0, TEST_APP_STORAGE_PACKAGE, SANDBOX_DIR, false)) 879 .isNull(); 880 assertThat(getSdkDataPerSdkPath(0, TEST_APP_STORAGE_PACKAGE, SDK_NAME, true)).isNull(); 881 assertThat(getSdkDataPerSdkPath(0, TEST_APP_STORAGE_PACKAGE, SDK_NAME, false)).isNull(); 882 883 // Install the app 884 installPackage(TEST_APP_STORAGE_APK); 885 waitForSdkDirectoryCreatedForUser(0); 886 887 // Verify directory is created 888 assertThat(getSdkDataInternalPath(0, TEST_APP_STORAGE_PACKAGE, SANDBOX_DIR, true)) 889 .isNotNull(); 890 assertThat(getSdkDataInternalPath(0, TEST_APP_STORAGE_PACKAGE, SANDBOX_DIR, false)) 891 .isNotNull(); 892 assertThat(getSdkDataPerSdkPath(0, TEST_APP_STORAGE_PACKAGE, SDK_NAME, true)).isNotNull(); 893 assertThat(getSdkDataPerSdkPath(0, TEST_APP_STORAGE_PACKAGE, SDK_NAME, false)).isNotNull(); 894 } 895 896 @Test 897 @LargeTest testSdkDataSubDirectory_IsCreatedOnInstall_DeviceLocked()898 public void testSdkDataSubDirectory_IsCreatedOnInstall_DeviceLocked() throws Exception { 899 assumeThat( 900 "Device is NOT encrypted with file-based encryption.", 901 getDevice().getProperty("ro.crypto.type"), 902 equalTo("file")); 903 assumeTrue( 904 "Screen lock is not supported so skip direct boot test", 905 hasDeviceFeature("android.software.secure_lock_screen")); 906 907 try { 908 installPackage(TEST_UNLOCK_APP_APK); 909 mDeviceLockUtils.rebootToLockedDevice(); 910 // Install app after installation 911 installPackage(TEST_APP_STORAGE_APK); 912 // De storage area should already have per-sdk directories 913 assertThat( 914 getSdkDataPerSdkPath( 915 0, TEST_APP_STORAGE_PACKAGE, SDK_NAME, /*isCeData=*/ false)) 916 .isNotNull(); 917 918 mDeviceLockUtils.unlockDevice(); 919 920 // Allow some time for reconciliation task to finish 921 Thread.sleep(WAIT_FOR_RECONCILE_MS); 922 923 assertThat( 924 getSdkDataPerSdkPath( 925 0, TEST_APP_STORAGE_PACKAGE, SDK_NAME, /*isCeData=*/ false)) 926 .isNotNull(); 927 // Once device is unlocked, the per-sdk ce directories should be created 928 assertThat( 929 getSdkDataPerSdkPath( 930 0, TEST_APP_STORAGE_PACKAGE, SDK_NAME, /*isCeData=*/ true)) 931 .isNotNull(); 932 } finally { 933 mDeviceLockUtils.clearScreenLock(); 934 uninstallPackage(TEST_UNLOCK_APP_PACKAGE); 935 } 936 } 937 938 @Test testSdkDataSubDirectory_PerSdkStorageIsUsable()939 public void testSdkDataSubDirectory_PerSdkStorageIsUsable() throws Exception { 940 installPackage(TEST_APP_STORAGE_APK); 941 waitForSdkDirectoryCreatedForUser(0); 942 943 // Verify that per-sdk storage exist 944 final String perSdkStorage = 945 getSdkDataPerSdkPath(0, TEST_APP_STORAGE_PACKAGE, SDK_NAME, true); 946 assertThat(getDevice().isDirectory(perSdkStorage)).isTrue(); 947 948 // Write a file in the storage that code needs to read and write it back 949 // in another file 950 String fileToRead = perSdkStorage + "/readme.txt"; 951 getDevice().executeShellCommand("echo something to read > " + fileToRead); 952 assertThat(getDevice().doesFileExist(fileToRead)).isTrue(); 953 954 runPhase("testSdkDataSubDirectory_PerSdkStorageIsUsable"); 955 956 // Assert that code was able to create file and directories 957 assertWithMessage("Failed to create directory in per-sdk storage") 958 .that(getDevice().isDirectory(perSdkStorage + "/dir")) 959 .isTrue(); 960 assertThat(getDevice().doesFileExist(perSdkStorage + "/dir/file")).isTrue(); 961 String content = getDevice().executeShellCommand("cat " + perSdkStorage + "/dir/file"); 962 assertThat(content).isEqualTo("something to read"); 963 } 964 965 @Test 966 @LargeTest // New volume created testSdkDataSubDirectory_PerSdkStorageIsUsable_DifferentVolume()967 public void testSdkDataSubDirectory_PerSdkStorageIsUsable_DifferentVolume() throws Exception { 968 assumeTrue(mAdoptableUtils.isAdoptableStorageSupported()); 969 970 installPackage(TEST_APP_STORAGE_APK); 971 waitForSdkDirectoryCreatedForUser(0); 972 973 try { 974 final String newVolumeUuid = mAdoptableUtils.createNewVolume(); 975 assertSuccess( 976 getDevice() 977 .executeShellCommand( 978 "pm move-package " 979 + TEST_APP_STORAGE_PACKAGE 980 + " " 981 + newVolumeUuid)); 982 983 // Verify that per-sdk storage exist 984 final String perSdkStorage = 985 getSdkDataPerSdkPath( 986 newVolumeUuid, 0, TEST_APP_STORAGE_PACKAGE, SDK_NAME, true); 987 988 assertThat(getDevice().isDirectory(perSdkStorage)).isTrue(); 989 990 // Write a file in the storage that code needs to read and write it back 991 // in another file 992 String fileToRead = perSdkStorage + "/readme.txt"; 993 getDevice().executeShellCommand("echo something to read > " + fileToRead); 994 assertThat(getDevice().doesFileExist(fileToRead)).isTrue(); 995 996 runPhase("testSdkDataSubDirectory_PerSdkStorageIsUsable"); 997 998 // Assert that code was able to create file and directories 999 assertWithMessage("Failed to create directory in per-sdk storage") 1000 .that(getDevice().isDirectory(perSdkStorage + "/dir")) 1001 .isTrue(); 1002 assertThat(getDevice().doesFileExist(perSdkStorage + "/dir/file")).isTrue(); 1003 String content = getDevice().executeShellCommand("cat " + perSdkStorage + "/dir/file"); 1004 assertThat(content).isEqualTo("something to read"); 1005 } finally { 1006 mAdoptableUtils.cleanUpVolume(); 1007 } 1008 } 1009 1010 @Test 1011 @LargeTest // New volume created testSdkData_CanBeMovedToDifferentVolume()1012 public void testSdkData_CanBeMovedToDifferentVolume() throws Exception { 1013 assumeTrue(mAdoptableUtils.isAdoptableStorageSupported()); 1014 1015 installPackage(TEST_APP_STORAGE_APK); 1016 waitForSdkDirectoryCreatedForUser(0); 1017 1018 // Create a new adoptable storage where we will be moving our installed package 1019 try { 1020 final String newVolumeUuid = mAdoptableUtils.createNewVolume(); 1021 1022 assertSuccess(getDevice().executeShellCommand( 1023 "pm move-package " + TEST_APP_STORAGE_PACKAGE + " " + newVolumeUuid)); 1024 1025 // Verify that sdk data is moved 1026 for (int i = 0; i < 2; i++) { 1027 boolean isCeData = (i == 0) ? true : false; 1028 final String sdkDataRootPath = getSdkDataRootPath(newVolumeUuid, 0, isCeData); 1029 final String sdkDataPackagePath = sdkDataRootPath + "/" + TEST_APP_STORAGE_PACKAGE; 1030 final String sdkDataSharedPath = sdkDataPackagePath + "/" + SHARED_DIR; 1031 1032 assertThat(getDevice().isDirectory(sdkDataRootPath)).isTrue(); 1033 assertThat(getDevice().isDirectory(sdkDataPackagePath)).isTrue(); 1034 assertThat(getDevice().isDirectory(sdkDataSharedPath)).isTrue(); 1035 1036 assertSelinuxLabel(sdkDataRootPath, "system_data_file"); 1037 assertSelinuxLabel(sdkDataPackagePath, "system_data_file"); 1038 assertSelinuxLabel(sdkDataSharedPath, "sdk_sandbox_data_file"); 1039 } 1040 } finally { 1041 mAdoptableUtils.cleanUpVolume(); 1042 } 1043 } 1044 1045 @Test 1046 @LargeTest // New volume created testSdkSharedStorage_DifferentVolumeIsUsable()1047 public void testSdkSharedStorage_DifferentVolumeIsUsable() throws Exception { 1048 assumeTrue(mAdoptableUtils.isAdoptableStorageSupported()); 1049 1050 installPackage(TEST_APP_STORAGE_APK); 1051 waitForSdkDirectoryCreatedForUser(0); 1052 1053 // Move the app to another volume and check if the sdk can read and write to it. 1054 try { 1055 final String newVolumeUuid = mAdoptableUtils.createNewVolume(); 1056 assertSuccess(getDevice().executeShellCommand( 1057 "pm move-package " + TEST_APP_STORAGE_PACKAGE + " " + newVolumeUuid)); 1058 1059 final String sharedCePath = 1060 getSdkDataInternalPath( 1061 newVolumeUuid, 0, TEST_APP_STORAGE_PACKAGE, SHARED_DIR, true); 1062 assertThat(getDevice().isDirectory(sharedCePath)).isTrue(); 1063 1064 String fileToRead = sharedCePath + "/readme.txt"; 1065 getDevice().executeShellCommand("echo something to read > " + fileToRead); 1066 assertThat(getDevice().doesFileExist(fileToRead)).isTrue(); 1067 1068 runPhase("testSdkDataPackageDirectory_SharedStorageIsUsable"); 1069 1070 // Assert that the sdk was able to create file and directories 1071 assertThat(getDevice().isDirectory(sharedCePath + "/dir")).isTrue(); 1072 assertThat(getDevice().doesFileExist(sharedCePath + "/dir/file")).isTrue(); 1073 String content = getDevice().executeShellCommand("cat " + sharedCePath + "/dir/file"); 1074 assertThat(content).isEqualTo("something to read"); 1075 1076 } finally { 1077 mAdoptableUtils.cleanUpVolume(); 1078 } 1079 } 1080 1081 @Test 1082 @LargeTest // New volume created testSdkData_ReconcileSdkDataSubDirsIncludesDifferentVolumes()1083 public void testSdkData_ReconcileSdkDataSubDirsIncludesDifferentVolumes() throws Exception { 1084 assumeTrue(mAdoptableUtils.isAdoptableStorageSupported()); 1085 1086 installPackage(TEST_APP_STORAGE_APK); 1087 waitForSdkDirectoryCreatedForUser(0); 1088 1089 // Create a new adoptable storage where we will be moving our installed package 1090 try { 1091 final String newVolumeUuid = mAdoptableUtils.createNewVolume(); 1092 1093 assertSuccess( 1094 getDevice() 1095 .executeShellCommand( 1096 "pm move-package " 1097 + TEST_APP_STORAGE_PACKAGE 1098 + " " 1099 + newVolumeUuid)); 1100 1101 // Verify that sdk data is moved 1102 for (int i = 0; i < 2; i++) { 1103 boolean isCeData = (i == 0) ? true : false; 1104 final String sdkDataPackagePath = 1105 getSdkDataPackagePath(newVolumeUuid, 0, TEST_APP_STORAGE_PACKAGE, isCeData); 1106 1107 final List<String> sdkDirsBeforeLoadingSdksList = 1108 getSubDirs(sdkDataPackagePath, /*includeRandomSuffix=*/ true); 1109 // Forcing the reconciling by deleting the sdk sub directory 1110 getDevice() 1111 .deleteFile(sdkDataPackagePath + "/" + sdkDirsBeforeLoadingSdksList.get(0)); 1112 1113 runPhase("loadSdk"); 1114 1115 final String OldPackagePath = 1116 getSdkDataPackagePath(0, TEST_APP_STORAGE_PACKAGE, isCeData); 1117 assertDirectoryDoesNotExist(OldPackagePath); 1118 final List<String> SdkDirsInNewVolume = 1119 getSubDirs(sdkDataPackagePath, /*includeRandomSuffix=*/ false); 1120 assertThat(SdkDirsInNewVolume).containsExactly(SHARED_DIR, SDK_NAME, SANDBOX_DIR); 1121 } 1122 } finally { 1123 mAdoptableUtils.cleanUpVolume(); 1124 } 1125 } 1126 1127 @Test testSdkData_IsAttributedToApp()1128 public void testSdkData_IsAttributedToApp() throws Exception { 1129 installPackage(TEST_APP_STORAGE_APK); 1130 runPhase("testSdkDataIsAttributedToApp"); 1131 } 1132 1133 @Test testSdkData_IsAttributedToApp_DisableQuota()1134 public void testSdkData_IsAttributedToApp_DisableQuota() throws Exception { 1135 installPackage(TEST_APP_STORAGE_APK); 1136 String initialValue = getDevice().getProperty("fw.disable_quota"); 1137 try { 1138 assertThat(getDevice().setProperty("fw.disable_quota", "true")).isTrue(); 1139 runPhase("testSdkDataIsAttributedToApp"); 1140 } finally { 1141 if (initialValue == null) initialValue = "false"; 1142 assertThat(getDevice().setProperty("fw.disable_quota", initialValue)).isTrue(); 1143 } 1144 } 1145 getAppDataPath(int userId, String packageName, boolean isCeData)1146 private String getAppDataPath(int userId, String packageName, boolean isCeData) { 1147 return getAppDataPath(/*volumeUuid=*/ null, userId, packageName, isCeData); 1148 } 1149 getAppDataPath( @ullable String volumeUuid, int userId, String packageName, boolean isCeData)1150 private String getAppDataPath( 1151 @Nullable String volumeUuid, int userId, String packageName, boolean isCeData) { 1152 if (isCeData) { 1153 return String.format( 1154 "/%s/user/%d/%s", getDataDirectory(volumeUuid), userId, packageName); 1155 } else { 1156 return String.format( 1157 "/%s/user_de/%d/%s", getDataDirectory(volumeUuid), userId, packageName); 1158 } 1159 } 1160 getSdkDataMirrorRootPath(int userId, boolean isCeData)1161 private String getSdkDataMirrorRootPath(int userId, boolean isCeData) { 1162 if (isCeData) { 1163 return String.format("/data_mirror/misc_ce/null/%d/sdksandbox", userId); 1164 } else { 1165 return String.format("/data_mirror/misc_de/null/%d/sdksandbox", userId); 1166 } 1167 } 1168 getSdkDataMirrorPackagePath(int userId, String packageName, boolean isCeData)1169 private String getSdkDataMirrorPackagePath(int userId, String packageName, boolean isCeData) { 1170 return String.format("%s/%s", getSdkDataMirrorRootPath(userId, isCeData), packageName); 1171 } 1172 getDataDirectory(@ullable String volumeUuid)1173 private String getDataDirectory(@Nullable String volumeUuid) { 1174 if (volumeUuid == null) { 1175 return "/data"; 1176 } else { 1177 return "/mnt/expand/" + volumeUuid; 1178 } 1179 } 1180 getSdkDataRootPath(int userId, boolean isCeData)1181 private String getSdkDataRootPath(int userId, boolean isCeData) { 1182 return getSdkDataRootPath(/*volumeUuid=*/ null, userId, isCeData); 1183 } 1184 getSdkDataRootPath(@ullable String volumeUuid, int userId, boolean isCeData)1185 private String getSdkDataRootPath(@Nullable String volumeUuid, int userId, boolean isCeData) { 1186 return String.format( 1187 "%s/%s/%d/%s", 1188 getDataDirectory(volumeUuid), 1189 (isCeData ? "misc_ce" : "misc_de"), 1190 userId, 1191 "sdksandbox"); 1192 } 1193 getSdkDataPackagePath(int userId, String packageName, boolean isCeData)1194 private String getSdkDataPackagePath(int userId, String packageName, boolean isCeData) { 1195 return getSdkDataPackagePath(/*volumeUuid=*/ null, userId, packageName, isCeData); 1196 } 1197 getSdkDataPackagePath( @ullable String volumeUuid, int userId, String packageName, boolean isCeData)1198 private String getSdkDataPackagePath( 1199 @Nullable String volumeUuid, int userId, String packageName, boolean isCeData) { 1200 return String.format( 1201 "%s/%s", getSdkDataRootPath(volumeUuid, userId, isCeData), packageName); 1202 } 1203 getSdkDataPerSdkPath( int userId, String packageName, String sdkName, boolean isCeData)1204 private String getSdkDataPerSdkPath( 1205 int userId, String packageName, String sdkName, boolean isCeData) throws Exception { 1206 return getSdkDataPerSdkPath(/*volumeUuid=*/ null, userId, packageName, sdkName, isCeData); 1207 } 1208 1209 @Nullable getSdkDataInternalPath( int userId, String packageName, String internalDirName, boolean isCeData)1210 private String getSdkDataInternalPath( 1211 int userId, String packageName, String internalDirName, boolean isCeData) 1212 throws Exception { 1213 return getSdkDataInternalPath( 1214 /*volumeUuid=*/ null, userId, packageName, internalDirName, isCeData); 1215 } 1216 1217 // Internal sub-directory can have random suffix. So we need to iterate over the app-level 1218 // directory to find it. 1219 @Nullable getSdkDataInternalPath( @ullable String volumeUuid, int userId, String packageName, String internalDirName, boolean isCeData)1220 private String getSdkDataInternalPath( 1221 @Nullable String volumeUuid, 1222 int userId, 1223 String packageName, 1224 String internalDirName, 1225 boolean isCeData) 1226 throws Exception { 1227 final String appLevelPath = 1228 getSdkDataPackagePath(volumeUuid, userId, packageName, isCeData); 1229 if (internalDirName.equals(SHARED_DIR)) { 1230 return Paths.get(appLevelPath, SHARED_DIR).toString(); 1231 } 1232 1233 final String[] children = getDevice().getChildren(appLevelPath); 1234 String result = null; 1235 for (String child : children) { 1236 if (!child.contains("#")) continue; 1237 String[] tokens = child.split("#"); 1238 if (tokens.length != 2) { 1239 continue; 1240 } 1241 String dirNameFound = tokens[0]; 1242 if (internalDirName.equals(dirNameFound)) { 1243 if (result == null) { 1244 result = Paths.get(appLevelPath, child).toString(); 1245 } else { 1246 throw new IllegalStateException( 1247 "Found two internal directory with same name: " + internalDirName); 1248 } 1249 } 1250 } 1251 return result; 1252 } 1253 1254 // Per-Sdk directory has random suffix. So we need to iterate over the app-level directory 1255 // to find it. 1256 @Nullable getSdkDataPerSdkPath( @ullable String volumeUuid, int userId, String packageName, String sdkName, boolean isCeData)1257 private String getSdkDataPerSdkPath( 1258 @Nullable String volumeUuid, 1259 int userId, 1260 String packageName, 1261 String sdkName, 1262 boolean isCeData) 1263 throws Exception { 1264 final String appLevelPath = 1265 getSdkDataPackagePath(volumeUuid, userId, packageName, isCeData); 1266 final String[] children = getDevice().getChildren(appLevelPath); 1267 String result = null; 1268 for (String child : children) { 1269 if (!child.contains("@")) continue; 1270 String[] tokens = child.split("@"); 1271 if (tokens.length != 2) { 1272 continue; 1273 } 1274 String sdkNameFound = tokens[0]; 1275 if (sdkName.equals(sdkNameFound)) { 1276 if (result == null) { 1277 result = appLevelPath + "/" + child; 1278 } else { 1279 throw new IllegalStateException("Found two per-sdk directory for " + sdkName); 1280 } 1281 } 1282 } 1283 return result; 1284 } 1285 getSubDirs(String path, boolean includeRandomSuffix)1286 private List<String> getSubDirs(String path, boolean includeRandomSuffix) 1287 throws Exception { 1288 final String[] children = getDevice().getChildren(path); 1289 if (children == null) { 1290 return Collections.emptyList(); 1291 } 1292 if (includeRandomSuffix) { 1293 return new ArrayList<>(Arrays.asList(children)); 1294 } 1295 final List<String> result = new ArrayList(); 1296 for (int i = 0; i < children.length; i++) { 1297 String[] tokens; 1298 if (children[i].contains("@")) { 1299 tokens = children[i].split("@"); 1300 } else { 1301 tokens = children[i].split("#"); 1302 } 1303 result.add(tokens[0]); 1304 } 1305 return result; 1306 } 1307 assertSelinuxLabel(@ullable String path, String label)1308 private void assertSelinuxLabel(@Nullable String path, String label) throws Exception { 1309 assertThat(path).isNotNull(); 1310 final String output = getDevice().executeShellCommand("ls -ldZ " + path); 1311 assertThat(output).contains("u:object_r:" + label); 1312 } 1313 assertSuccess(String str)1314 private static void assertSuccess(String str) { 1315 if (str == null || !str.startsWith("Success")) { 1316 throw new AssertionError("Expected success string but found " + str); 1317 } 1318 } 1319 assertDirectoryExists(String path)1320 private void assertDirectoryExists(String path) throws Exception { 1321 assertWithMessage(path + " is not a directory or does not exist") 1322 .that(getDevice().isDirectory(path)) 1323 .isTrue(); 1324 } 1325 assertDirectoryDoesNotExist(String path)1326 private void assertDirectoryDoesNotExist(String path) throws Exception { 1327 assertWithMessage(path + " exists when expected not to") 1328 .that(getDevice().doesFileExist(path)) 1329 .isFalse(); 1330 } 1331 waitForSdkDirectoryCreatedForUser(int userId)1332 private void waitForSdkDirectoryCreatedForUser(int userId) throws Exception { 1333 final String sharedDir = 1334 getSdkDataInternalPath(userId, TEST_APP_STORAGE_PACKAGE, SHARED_DIR, true); 1335 waitForDirectoryCreated(sharedDir); 1336 } 1337 waitForDirectoryCreated(String path)1338 private void waitForDirectoryCreated(String path) throws Exception { 1339 AwaitUtils.waitFor(() -> getDevice().isDirectory(path), path + " wasn't created"); 1340 } 1341 1342 private static class DeviceLockUtils { 1343 1344 private final BaseHostJUnit4Test mTest; 1345 DeviceLockUtils(BaseHostJUnit4Test test)1346 DeviceLockUtils(BaseHostJUnit4Test test) { 1347 mTest = test; 1348 } 1349 rebootToLockedDevice()1350 public void rebootToLockedDevice() throws Exception { 1351 // Setup screenlock 1352 mTest.getDevice().executeShellCommand("locksettings set-disabled false"); 1353 String response = mTest.getDevice().executeShellCommand("locksettings set-pin 1234"); 1354 if (!response.contains("1234")) { 1355 // This seems to fail occasionally. Try again once, then give up. 1356 Thread.sleep(500); 1357 response = mTest.getDevice().executeShellCommand("locksettings set-pin 1234"); 1358 assertWithMessage("Test requires setting a pin, which failed: " + response) 1359 .that(response) 1360 .contains("1234"); 1361 } 1362 1363 // Give enough time for vold to update keys 1364 Thread.sleep(15000); 1365 1366 // Follow DirectBootHostTest, reboot system into known state with keys ejected 1367 mTest.getDevice().rebootUntilOnline(); 1368 waitForBootCompleted(mTest.getDevice()); 1369 } 1370 clearScreenLock()1371 public void clearScreenLock() throws Exception { 1372 Thread.sleep(5000); 1373 try { 1374 unlockDevice(); 1375 mTest.getDevice().executeShellCommand("locksettings clear --old 1234"); 1376 mTest.getDevice().executeShellCommand("locksettings set-disabled true"); 1377 } finally { 1378 // Get ourselves back into a known-good state 1379 mTest.getDevice().rebootUntilOnline(); 1380 mTest.getDevice().waitForDeviceAvailable(); 1381 } 1382 } 1383 unlockDevice()1384 public void unlockDevice() throws Exception { 1385 mTest.runDeviceTests( 1386 TEST_UNLOCK_APP_PACKAGE, 1387 TEST_UNLOCK_APP_PACKAGE + ".TestDeviceUnlocker", 1388 "unlockDevice"); 1389 } 1390 } 1391 } 1392