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 android.scopedstorage.cts.host;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 import static com.google.common.truth.Truth.assertWithMessage;
21 
22 import static org.junit.Assume.assumeFalse;
23 import static org.junit.Assume.assumeTrue;
24 
25 import android.platform.test.annotations.AppModeFull;
26 
27 import com.android.tradefed.device.ITestDevice;
28 import com.android.tradefed.invoker.TestInformation;
29 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
30 import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
31 import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
32 import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
33 import com.android.tradefed.util.CommandResult;
34 
35 import org.junit.Before;
36 import org.junit.Test;
37 import org.junit.runner.RunWith;
38 
39 import java.util.HashMap;
40 import java.util.Map;
41 
42 import javax.annotation.Nonnull;
43 
44 @RunWith(DeviceJUnit4ClassRunner.class)
45 @AppModeFull
46 public class AppCloningMediaProviderHostTest extends BaseHostTestCase{
47 
48     protected static ITestDevice sDevice = null;
49     protected static final String DEVICE_TEST_APP_PACKAGE = "android.scopedstorage.cts";
50     protected static final String DEVICE_TEST_APP = "AppCloningDeviceTest.apk";
51     private static final String DEVICE_TEST_CLASS = DEVICE_TEST_APP_PACKAGE
52             + ".AppCloningDeviceTest";
53     // This app performs the File Creation and Read operations from the Device.
54     protected static final String SCOPED_STORAGE_TEST_APP_B_APK = "CtsScopedStorageTestAppB.apk";
55 
56     private static final int CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS = 20000;
57     private static final long DEFAULT_INSTRUMENTATION_TIMEOUT_MS = 600_000;
58     private static final String EXTERNAL_STORAGE_PATH = "/storage/emulated/%d/";
59     private static final String CURRENT_USER_ID = "currentUserId";
60     private static final String FILE_TO_BE_CREATED = "fileToBeCreated";
61     private static final String FILE_EXPECTED_TO_BE_PRESENT = "fileExpectedToBePresent";
62     private static final String FILE_NOT_EXPECTED_TO_BE_PRESENT = "fileNotExpectedToBePresent";
63 
64     private static final String USER_ID_FOR_PATH = "userIdForPath";
65     /**
66      * Provide different name to Files being created, on each execution of the test, so that
67      * flakiness from previously existing files can be avoided.
68      */
69     private static String sNonce;
70     private static String sCloneUserId;
71     @BeforeClassWithInfo
beforeClassWithDevice(TestInformation testInfo)72     public static void beforeClassWithDevice(TestInformation testInfo) throws Exception {
73         sDevice = testInfo.getDevice();
74         assertThat(sDevice).isNotNull();
75 
76         assumeTrue("Device doesn't support multiple users", supportsMultipleUsers(sDevice));
77         assumeFalse("Device is in headless system user mode",
78                 isHeadlessSystemUserMode(sDevice));
79         assumeTrue(isAtLeastS(sDevice));
80         assumeFalse("Device uses sdcardfs", usesSdcardFs(sDevice));
81 
82         // create clone user
83         String output = sDevice.executeShellCommand(
84                 "pm create-user --profileOf 0 --user-type android.os.usertype.profile.CLONE "
85                         + "testUser");
86         sCloneUserId = output.substring(output.lastIndexOf(' ') + 1).replaceAll("[^0-9]",
87                 "");
88         assertThat(sCloneUserId).isNotEmpty();
89         // start clone user
90         CommandResult out = sDevice.executeShellV2Command("am start-user -w " + sCloneUserId);
91         assertThat(isSuccessful(out)).isTrue();
92 
93         Integer mCloneUserIdInt = Integer.parseInt(sCloneUserId);
94         String sCloneUserStoragePath = String.format(EXTERNAL_STORAGE_PATH,
95                 Integer.parseInt(sCloneUserId));
96         // Check that the clone user directories have been created
97         eventually(() -> sDevice.doesFileExist(sCloneUserStoragePath, mCloneUserIdInt),
98                 CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS);
99     }
100 
101     @Before
beforeTest()102     public void beforeTest() {
103         sNonce = String.valueOf(System.nanoTime());
104     }
105 
106     @AfterClassWithInfo
afterClass(TestInformation testInfo)107     public static void afterClass(TestInformation testInfo) throws Exception {
108         ITestDevice device = testInfo.getDevice();
109         if (!supportsMultipleUsers(device) || isHeadlessSystemUserMode(device)
110                 || !isAtLeastS(device) || usesSdcardFs(device)) return;
111         testInfo.getDevice().executeShellCommand("pm remove-user " + sCloneUserId);
112     }
113 
114     @Test
testInsertCrossUserFilesInDirectoryViaMediaProvider()115     public void testInsertCrossUserFilesInDirectoryViaMediaProvider() throws Exception {
116         // Only run on U+
117         assumeTrue(isAtLeastU(sDevice));
118 
119         // Install the Device Test App in both the user spaces.
120         installPackage(DEVICE_TEST_APP, "--user all");
121         // Install the Scoped Storage Test App in both the user spaces.
122         installPackage(SCOPED_STORAGE_TEST_APP_B_APK, "--user all");
123 
124         int currentUserId = getCurrentUserId();
125 
126         // We add the file in DCIM directory of clone from owner user's app.
127         final String fileNameClonedUser = "tmpFileToPushClonedUser" + sNonce + ".png";
128         Map<String, String> ownerArgs = new HashMap<>();
129         ownerArgs.put(USER_ID_FOR_PATH, sCloneUserId);
130         ownerArgs.put(FILE_TO_BE_CREATED, fileNameClonedUser);
131         runDeviceTestAsUserInPkgA("testInsertFilesInDirectoryViaMediaProviderWithPathSpecified",
132                 currentUserId, ownerArgs);
133 
134         // We add the file in DCIM directory of owner from cloned user's app.
135         final String fileNameOwnerUser = "tmpFileToPushOwnerUser" + sNonce + ".png";
136         Map<String, String> cloneArgs = new HashMap<>();
137         cloneArgs.put(USER_ID_FOR_PATH, String.valueOf(currentUserId));
138         cloneArgs.put(FILE_TO_BE_CREATED, fileNameOwnerUser);
139         runDeviceTestAsUserInPkgA("testInsertFilesInDirectoryViaMediaProviderWithPathSpecified",
140                 Integer.parseInt(sCloneUserId), cloneArgs);
141     }
142 
143     @Test
testGetFilesInDirectoryViaMediaProvider()144     public void testGetFilesInDirectoryViaMediaProvider() throws Exception {
145         // Install the Device Test App in both the user spaces.
146         installPackage(DEVICE_TEST_APP, "--user all");
147         // Install the Scoped Storage Test App in both the user spaces.
148         installPackage(SCOPED_STORAGE_TEST_APP_B_APK, "--user all");
149 
150         int currentUserId = getCurrentUserId();
151         final String fileName = "tmpFileToPush" + sNonce + ".png";
152 
153         // We add the file in DCIM directory of User 0.
154         Map<String, String> ownerArgs = new HashMap<>();
155         ownerArgs.put(CURRENT_USER_ID, String.valueOf(currentUserId));
156         ownerArgs.put(FILE_TO_BE_CREATED, fileName);
157         runDeviceTestAsUserInPkgA("testInsertFilesInDirectoryViaMediaProvider",
158                 currentUserId, ownerArgs);
159 
160         // We add the file in DCIM directory of Cloned User.
161         final String fileNameClonedUser = "tmpFileToPushClonedUser" + sNonce + ".png";
162         Map<String, String> cloneArgs = new HashMap<>();
163         cloneArgs.put(CURRENT_USER_ID, sCloneUserId);
164         cloneArgs.put(FILE_TO_BE_CREATED, fileNameClonedUser);
165         runDeviceTestAsUserInPkgA("testInsertFilesInDirectoryViaMediaProvider",
166                 Integer.parseInt(sCloneUserId), cloneArgs);
167 
168         // Querying as user 0 should enlist the file(s) created by user 0 only.
169         Map<String, String> listFilesArgs = new HashMap<>();
170         listFilesArgs.put(CURRENT_USER_ID, String.valueOf(currentUserId));
171         listFilesArgs.put(FILE_EXPECTED_TO_BE_PRESENT, fileName);
172         listFilesArgs.put(FILE_NOT_EXPECTED_TO_BE_PRESENT, fileNameClonedUser);
173         runDeviceTestAsUserInPkgA("testGetFilesInDirectoryViaMediaProviderRespectsUserId",
174                 currentUserId, listFilesArgs);
175 
176         // Querying as cloned user should enlist the file(s) created by cloned user only.
177         listFilesArgs.put(CURRENT_USER_ID, sCloneUserId);
178         listFilesArgs.put(FILE_EXPECTED_TO_BE_PRESENT, fileNameClonedUser);
179         listFilesArgs.put(FILE_NOT_EXPECTED_TO_BE_PRESENT, fileName);
180         runDeviceTestAsUserInPkgA("testGetFilesInDirectoryViaMediaProviderRespectsUserId",
181                 Integer.parseInt(sCloneUserId), listFilesArgs);
182     }
183 
runDeviceTestAsUserInPkgA(@onnull String testMethod, int userId, @Nonnull Map<String, String> args)184     protected void runDeviceTestAsUserInPkgA(@Nonnull String testMethod, int userId,
185             @Nonnull Map<String, String> args) throws Exception {
186         DeviceTestRunOptions deviceTestRunOptions =
187                 new DeviceTestRunOptions(DEVICE_TEST_APP_PACKAGE)
188                 .setDevice(getDevice())
189                 .setTestClassName(DEVICE_TEST_CLASS)
190                 .setTestMethodName(testMethod)
191                 .setMaxInstrumentationTimeoutMs(DEFAULT_INSTRUMENTATION_TIMEOUT_MS)
192                 .setUserId(userId);
193         for (Map.Entry<String, String> entry : args.entrySet()) {
194             deviceTestRunOptions.addInstrumentationArg(entry.getKey(), entry.getValue());
195         }
196         assertWithMessage(testMethod + " failed").that(
197                 runDeviceTests(deviceTestRunOptions)).isTrue();
198     }
199 }
200