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