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