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