1 /* 2 * Copyright (C) 2014 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.appsecurity.cts; 18 19 import static org.junit.Assume.assumeTrue; 20 21 import android.app.usage.Flags; 22 import android.platform.test.annotations.RequiresFlagsEnabled; 23 import android.platform.test.flag.junit.CheckFlagsRule; 24 import android.platform.test.flag.junit.host.HostFlagsValueProvider; 25 26 import com.android.ddmlib.testrunner.TestResult.TestStatus; 27 import com.android.tradefed.device.DeviceNotAvailableException; 28 import com.android.tradefed.result.TestDescription; 29 import com.android.tradefed.result.TestResult; 30 import com.android.tradefed.result.TestRunResult; 31 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; 32 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; 33 import com.android.tradefed.testtype.junit4.DeviceTestRunOptions; 34 import com.android.tradefed.util.CommandResult; 35 import com.android.tradefed.util.CommandStatus; 36 import com.android.tradefed.util.RunInterruptedException; 37 import com.android.tradefed.util.RunUtil; 38 39 import com.google.common.truth.Truth; 40 41 import junit.framework.AssertionFailedError; 42 43 import org.junit.After; 44 import org.junit.Before; 45 import org.junit.Ignore; 46 import org.junit.Rule; 47 import org.junit.Test; 48 import org.junit.runner.RunWith; 49 50 import java.util.Map; 51 52 /** 53 * Tests that exercise various storage APIs. 54 */ 55 @RunWith(DeviceJUnit4ClassRunner.class) 56 public class StorageHostTest extends BaseHostJUnit4Test { 57 private static final String PKG_STATS = "com.android.cts.storagestatsapp"; 58 private static final String PKG_A = "com.android.cts.storageapp_a"; 59 private static final String PKG_B = "com.android.cts.storageapp_b"; 60 private static final String PKG_C = "com.android.cts.storageapp_c"; 61 private static final String APK_STATS = "CtsStorageStatsApp.apk"; 62 private static final String APK_A = "CtsStorageAppA.apk"; 63 private static final String APK_B = "CtsStorageAppB.apk"; 64 private static final String APK_C = "CtsStorageAppC.apk"; 65 private static final String DM_C = "CtsStorageAppC.dm"; 66 private static final String CLASS_STATS = "com.android.cts.storagestatsapp.StorageStatsTest"; 67 private static final String CLASS = "com.android.cts.storageapp.StorageTest"; 68 private static final String EXTERNAL_STORAGE_PATH = "/storage/emulated/%d/"; 69 private static final String ERROR_MESSAGE_TAG = "[ERROR]"; 70 71 private static final int CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS = 20000; 72 73 private int[] mUsers; 74 75 @Rule 76 public final CheckFlagsRule mCheckFlagsRule = 77 HostFlagsValueProvider.createCheckFlagsRule(this::getDevice); 78 79 @Before setUp()80 public void setUp() throws Exception { 81 mUsers = Utils.prepareMultipleUsers(getDevice()); 82 83 installPackage(APK_STATS); 84 installPackage(APK_A); 85 installPackage(APK_B); 86 installPackage(APK_C); 87 new InstallMultiple().addFile(APK_C).addFile(DM_C).disableFileNamePrefix().run(); 88 89 for (int user : mUsers) { 90 getDevice().executeShellCommand("appops set --user " + user + " " + PKG_STATS 91 + " android:get_usage_stats allow"); 92 } 93 94 waitForIdle(); 95 } 96 97 @After tearDown()98 public void tearDown() throws Exception { 99 getDevice().uninstallPackage(PKG_STATS); 100 getDevice().uninstallPackage(PKG_A); 101 getDevice().uninstallPackage(PKG_B); 102 getDevice().uninstallPackage(PKG_C); 103 } 104 105 @Test testVerify()106 public void testVerify() throws Exception { 107 Utils.runDeviceTests(getDevice(), PKG_STATS, CLASS_STATS, "testVerify"); 108 } 109 110 @Test testVerifyAppStats()111 public void testVerifyAppStats() throws Exception { 112 for (int user : mUsers) { 113 runDeviceTests(PKG_A, CLASS, "testAllocate", user); 114 } 115 116 // for fuse file system 117 RunUtil.getDefault().sleep(10000); 118 119 // TODO: remove this once 34723223 is fixed 120 getDevice().executeShellCommand("sync"); 121 122 for (int user : mUsers) { 123 runDeviceTests(PKG_A, CLASS, "testVerifySpaceManual", user); 124 runDeviceTests(PKG_A, CLASS, "testVerifySpaceApi", user); 125 } 126 } 127 128 @Test testVerifyAppQuota()129 public void testVerifyAppQuota() throws Exception { 130 for (int user : mUsers) { 131 runDeviceTests(PKG_A, CLASS, "testVerifyQuotaApi", user); 132 } 133 } 134 135 @Test testVerifyAppAllocate()136 public void testVerifyAppAllocate() throws Exception { 137 for (int user : mUsers) { 138 runDeviceTests(PKG_A, CLASS, "testVerifyAllocateApi", user); 139 } 140 } 141 142 @Test testVerifySummary()143 public void testVerifySummary() throws Exception { 144 for (int user : mUsers) { 145 runDeviceTests(PKG_STATS, CLASS_STATS, "testVerifySummary", user); 146 } 147 } 148 149 @Test testVerifyStats()150 public void testVerifyStats() throws Exception { 151 for (int user : mUsers) { 152 runDeviceTests(PKG_STATS, CLASS_STATS, "testVerifyStats", user); 153 } 154 } 155 156 @Test 157 @RequiresFlagsEnabled(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API) testVerifyStatsByDataType()158 public void testVerifyStatsByDataType() throws Exception { 159 for (int user : mUsers) { 160 runDeviceTests(PKG_STATS, CLASS_STATS, "testVerifyStatsByDataType", user); 161 } 162 } 163 164 @Test testVerifyStatsMultiple()165 public void testVerifyStatsMultiple() throws Exception { 166 for (int user : mUsers) { 167 runDeviceTests(PKG_A, CLASS, "testAllocate", user); 168 runDeviceTests(PKG_A, CLASS, "testAllocate", user); 169 170 runDeviceTests(PKG_B, CLASS, "testAllocate", user); 171 } 172 173 for (int user : mUsers) { 174 runDeviceTests(PKG_STATS, CLASS_STATS, "testVerifyStatsMultiple", user); 175 } 176 } 177 178 @Test testVerifyStatsExternal()179 public void testVerifyStatsExternal() throws Exception { 180 for (int user : mUsers) { 181 runDeviceTests(PKG_STATS, CLASS_STATS, "testVerifyStatsExternal", user, true); 182 } 183 } 184 185 @Ignore("b/279718458") 186 // Equivalent test for clone profile added in AppCloningStorageHostTest testVerifyStatsExternalForClonedUser()187 public void testVerifyStatsExternalForClonedUser() throws Exception { 188 int mCloneUserIdInt = createCloneUserAndInstallDeviceTestApk(); 189 runDeviceTests(PKG_STATS, CLASS_STATS, "testVerifyStatsExternal", mCloneUserIdInt, true); 190 } 191 192 @Test testVerifyStatsExternalConsistent()193 public void testVerifyStatsExternalConsistent() throws Exception { 194 for (int user : mUsers) { 195 runDeviceTests(PKG_STATS, CLASS_STATS, "testVerifyStatsExternalConsistent", user, true); 196 } 197 } 198 199 @Test testVerifyCategory()200 public void testVerifyCategory() throws Exception { 201 for (int user : mUsers) { 202 runDeviceTests(PKG_STATS, CLASS_STATS, "testVerifyCategory", user); 203 } 204 } 205 206 @Test testCache()207 public void testCache() throws Exception { 208 // To make the cache clearing logic easier to verify, ignore any cache 209 // and low space reserved space. 210 getDevice().executeShellCommand("settings put global sys_storage_threshold_max_bytes 0"); 211 getDevice().executeShellCommand("svc data disable"); 212 getDevice().executeShellCommand("svc wifi disable"); 213 try { 214 waitForIdle(); 215 for (int user : mUsers) { 216 // Clear all other cached data to give ourselves a clean slate 217 getDevice().executeShellCommand("pm trim-caches 4096G"); 218 runDeviceTests(PKG_STATS, CLASS_STATS, "testCacheClearing", user); 219 220 getDevice().executeShellCommand("pm trim-caches 4096G"); 221 runDeviceTests(PKG_STATS, CLASS_STATS, "testCacheBehavior", user); 222 } 223 } finally { 224 getDevice().executeShellCommand("settings delete global sys_storage_threshold_max_bytes"); 225 getDevice().executeShellCommand("svc data enable"); 226 getDevice().executeShellCommand("svc wifi enable"); 227 } 228 } 229 230 @Test testFullDisk()231 public void testFullDisk() throws Exception { 232 assumeTrue(!isWatch()); 233 // Clear all other cached and external storage data to give ourselves a 234 // clean slate to test against 235 getDevice().executeShellCommand("pm trim-caches 4096G"); 236 getDevice().executeShellCommand("rm -rf /sdcard/*"); 237 238 getDevice().executeShellCommand("settings put global hide_error_dialogs 1"); 239 try { 240 try { 241 // Try our hardest to fill up the entire disk 242 Utils.runDeviceTestsAsCurrentUser(getDevice(), PKG_B, CLASS, "testFullDisk"); 243 } catch (Throwable t) { 244 if (t.getMessage().contains("Skipping")) { 245 // If the device doesn't have resgid support, there's nothing 246 // for this test to verify 247 return; 248 } else { 249 throw new AssertionFailedError(t.getMessage()); 250 } 251 } 252 253 // Tweak something that causes PackageManager to persist data 254 Utils.runDeviceTestsAsCurrentUser(getDevice(), PKG_A, CLASS, "testTweakComponent"); 255 256 // Wake up/unlock device before running tests 257 getDevice().executeShellCommand("input keyevent KEYCODE_WAKEUP"); 258 getDevice().disableKeyguard(); 259 260 // Verify that Settings can free space used by abusive app 261 Utils.runDeviceTestsAsCurrentUser(getDevice(), PKG_A, CLASS, "testClearSpace"); 262 } finally { 263 getDevice().executeShellCommand("settings delete global hide_error_dialogs"); 264 } 265 } 266 waitForIdle()267 public void waitForIdle() throws Exception { 268 // Try getting all pending events flushed out 269 for (int i = 0; i < 4; i++) { 270 getDevice().executeShellCommand("am wait-for-broadcast-idle"); 271 RunUtil.getDefault().sleep(500); 272 } 273 } 274 runDeviceTests(String packageName, String testClassName, String testMethodName, int userId)275 public void runDeviceTests(String packageName, String testClassName, String testMethodName, 276 int userId) throws DeviceNotAvailableException { 277 runDeviceTests(packageName, testClassName, testMethodName, userId, false); 278 } 279 runDeviceTests(String packageName, String testClassName, String testMethodName, int userId, boolean disableIsolatedStorage)280 public void runDeviceTests(String packageName, String testClassName, String testMethodName, 281 int userId, boolean disableIsolatedStorage) throws DeviceNotAvailableException { 282 final DeviceTestRunOptions options = new DeviceTestRunOptions(packageName); 283 options.setDevice(getDevice()); 284 options.setTestClassName(testClassName); 285 options.setTestMethodName(testMethodName); 286 options.setUserId(userId); 287 options.setTestTimeoutMs(20 * 60 * 1000L); 288 options.setDisableIsolatedStorage(disableIsolatedStorage); 289 if (!runDeviceTests(options)) { 290 TestRunResult res = getLastDeviceRunResults(); 291 if (res != null) { 292 StringBuilder errorBuilder = new StringBuilder("on-device tests failed:\n"); 293 for (Map.Entry<TestDescription, TestResult> resultEntry : 294 res.getTestResults().entrySet()) { 295 if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) { 296 errorBuilder.append(resultEntry.getKey().toString()); 297 errorBuilder.append(":\n"); 298 errorBuilder.append(resultEntry.getValue().getStackTrace()); 299 } 300 } 301 throw new AssertionError(errorBuilder.toString()); 302 } else { 303 throw new AssertionFailedError("Error when running device tests."); 304 } 305 } 306 } 307 isWatch()308 private boolean isWatch() { 309 try { 310 return getDevice().hasFeature("feature:android.hardware.type.watch"); 311 } catch (DeviceNotAvailableException e) { 312 return false; 313 } 314 } 315 createCloneUserAndInstallDeviceTestApk()316 private int createCloneUserAndInstallDeviceTestApk() throws Exception { 317 // Create clone user. 318 String output = getDevice().executeShellCommand( 319 "pm create-user --profileOf 0 --user-type android.os.usertype.profile.CLONE " 320 + "testUser"); 321 String sCloneUserId = output.substring(output.lastIndexOf(' ') + 1).replaceAll("[^0-9]", 322 ""); 323 Truth.assertThat(sCloneUserId).isNotEmpty(); 324 // Start clone user. 325 CommandResult out = getDevice().executeShellV2Command("am start-user -w " + sCloneUserId); 326 Truth.assertThat(isSuccessful(out)).isTrue(); 327 328 Integer mCloneUserIdInt = Integer.parseInt(sCloneUserId); 329 String sCloneUserStoragePath = String.format(EXTERNAL_STORAGE_PATH, 330 Integer.parseInt(sCloneUserId)); 331 // Check that the clone user directories have been created 332 eventually(() -> getDevice().doesFileExist(sCloneUserStoragePath, mCloneUserIdInt), 333 CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS); 334 // Install the DeviceTest APK for Clone User. 335 installPackage(APK_STATS, "--user all"); 336 return mCloneUserIdInt; 337 } 338 eventually(ThrowingRunnable r, long timeoutMillis)339 private void eventually(ThrowingRunnable r, long timeoutMillis) { 340 long start = System.currentTimeMillis(); 341 342 while (true) { 343 try { 344 r.run(); 345 return; 346 } catch (Throwable e) { 347 if (System.currentTimeMillis() - start < timeoutMillis) { 348 try { 349 RunUtil.getDefault().sleep(100); 350 } catch (RunInterruptedException ignored) { 351 throw new RuntimeException(e); 352 } 353 } else { 354 throw new RuntimeException(e); 355 } 356 } 357 } 358 } 359 isSuccessful(CommandResult result)360 private boolean isSuccessful(CommandResult result) { 361 if (!CommandStatus.SUCCESS.equals(result.getStatus())) { 362 return false; 363 } 364 String stdout = result.getStdout(); 365 if (stdout.contains(ERROR_MESSAGE_TAG)) { 366 return false; 367 } 368 String stderr = result.getStderr(); 369 return (stderr == null || stderr.trim().isEmpty()); 370 } 371 372 private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> { InstallMultiple()373 public InstallMultiple() { 374 super(getDevice(), getBuild(), getAbi()); 375 } 376 } 377 } 378