1 /*
2  * Copyright (C) 2021 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 android.scopedstorage.cts.host;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.junit.Assume.assumeFalse;
22 import static org.junit.Assume.assumeTrue;
23 
24 import android.platform.test.annotations.AppModeFull;
25 
26 import com.android.tradefed.device.DeviceNotAvailableException;
27 import com.android.tradefed.device.contentprovider.ContentProviderHandler;
28 import com.android.tradefed.targetprep.TargetSetupError;
29 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
30 import com.android.tradefed.util.CommandResult;
31 
32 import org.junit.After;
33 import org.junit.Before;
34 import org.junit.Test;
35 import org.junit.runner.RunWith;
36 
37 /**
38  * Runs the AppCloning tests.
39  */
40 @RunWith(DeviceJUnit4ClassRunner.class)
41 @AppModeFull
42 public class AppCloningHostTest extends BaseHostTestCase {
43     private static final String APP_A = "CtsScopedStorageTestAppA.apk";
44     private static final String APP_A_PACKAGE = "android.scopedstorage.cts.testapp.A.withres";
45     private static final String CONTENT_PROVIDER_URL = "content://android.tradefed.contentprovider";
46     private static final int CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS = 20000;
47     private String mCloneUserId;
48     private ContentProviderHandler mContentProviderHandler;
49 
50 
51     @Before
setup()52     public void setup() throws Exception {
53         assumeFalse("Device is in headless system user mode", isHeadlessSystemUserMode());
54         assumeTrue(isAtLeastS());
55 
56         String output = executeShellCommand(
57                 "pm create-user --profileOf 0 --user-type android.os.usertype.profile.CLONE "
58                         + "testUser");
59         mCloneUserId = output.substring(output.lastIndexOf(' ') + 1).replaceAll("[^0-9]", "");
60         assertThat(mCloneUserId).isNotEmpty();
61         CommandResult out = executeShellV2Command("am start-user -w %s", mCloneUserId);
62         assertThat(out.getStderr()).isEmpty();
63         mContentProviderHandler = new ContentProviderHandler(getDevice());
64         mContentProviderHandler.setUp();
65     }
66 
67     @After
tearDown()68     public void tearDown() throws Exception {
69         if (isHeadlessSystemUserMode() || !isAtLeastS()) return;
70         mContentProviderHandler.tearDown();
71         executeShellCommand("pm remove-user %s", mCloneUserId);
72     }
73 
74     @Test
testInstallAppTwice()75     public void testInstallAppTwice() throws Exception {
76         installAppAsUser(APP_A, getCurrentUserId());
77         installAppAsUser(APP_A, Integer.valueOf(mCloneUserId));
78         uninstallPackage(APP_A_PACKAGE);
79     }
80 
81     @Test
testCreateCloneUserFile()82     public void testCreateCloneUserFile() throws Exception {
83         CommandResult out;
84 
85         // Check that the clone user directories exist
86         eventually(() -> {
87             // Wait for finish.
88             assertThat(isSuccessful(
89                     runContentProviderCommand("query", mCloneUserId, "/sdcard", ""))).isTrue();
90         }, CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS);
91 
92         // Create a file on the clone user storage
93         out = executeShellV2Command("touch /sdcard/testFile.txt");
94         assertThat(isSuccessful(out)).isTrue();
95         eventually(() -> {
96             // Wait for finish.
97             assertThat(isSuccessful(
98                     runContentProviderCommand("write", mCloneUserId, "/sdcard/testFile.txt",
99                             "< /sdcard/testFile.txt"))).isTrue();
100         }, CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS);
101 
102         // Check that the above created file exists on the clone user storage
103         out = runContentProviderCommand("query", mCloneUserId, "/sdcard/testFile.txt", "");
104         assertThat(isSuccessful(out)).isTrue();
105 
106         // Cleanup the created file
107         out = runContentProviderCommand("delete", mCloneUserId, "/sdcard/testFile.txt", "");
108         assertThat(isSuccessful(out)).isTrue();
109     }
110 
111     @Test
testPrivateAppDataDirectoryForCloneUser()112     public void testPrivateAppDataDirectoryForCloneUser() throws Exception {
113         installAppAsUser(APP_A, Integer.valueOf(mCloneUserId));
114         eventually(() -> {
115             // Wait for finish.
116             assertThat(isPackageInstalled(APP_A_PACKAGE, mCloneUserId)).isTrue();
117         }, CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS);
118     }
119 
installAppAsUser(String packageFile, int userId)120     private void installAppAsUser(String packageFile, int userId)
121             throws TargetSetupError, DeviceNotAvailableException {
122         installPackageAsUser(packageFile, false, userId, "-t");
123     }
124 
runContentProviderCommand(String commandType, String userId, String relativePath, String args)125     private CommandResult runContentProviderCommand(String commandType, String userId,
126             String relativePath, String args) throws Exception {
127         String fullUri = CONTENT_PROVIDER_URL + relativePath;
128         return executeShellV2Command("content %s --user %s --uri %s %s",
129                 commandType, userId, fullUri, args);
130     }
131 
132 }
133