• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 com.android.cts_root.rollback.host;
18 
19 import static com.android.cts.shim.lib.ShimPackage.SHIM_APEX_PACKAGE_NAME;
20 import static com.android.cts.shim.lib.ShimPackage.SHIM_PACKAGE_NAME;
21 import static com.android.cts_root.rollback.host.WatchdogEventLogger.Subject.assertThat;
22 
23 import static com.google.common.truth.Truth.assertThat;
24 import static com.google.common.truth.Truth.assertWithMessage;
25 
26 import static org.junit.Assert.fail;
27 import static org.junit.Assume.assumeTrue;
28 
29 import android.cts.install.lib.host.InstallUtilsHost;
30 
31 import com.android.ddmlib.Log;
32 import com.android.tradefed.device.DeviceNotAvailableException;
33 import com.android.tradefed.device.IFileEntry;
34 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
35 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
36 
37 import org.junit.After;
38 import org.junit.Before;
39 import org.junit.Test;
40 import org.junit.runner.RunWith;
41 
42 import java.time.Instant;
43 import java.util.Collections;
44 import java.util.Date;
45 import java.util.List;
46 import java.util.concurrent.TimeUnit;
47 import java.util.stream.Collectors;
48 
49 /**
50  * CTS-root host tests for RollbackManager APIs.
51  */
52 @RunWith(DeviceJUnit4ClassRunner.class)
53 public class RollbackManagerHostTest extends BaseHostJUnit4Test {
54     private static final String TAG = "RollbackManagerHostTest";
55 
56     private static final int NATIVE_CRASHES_THRESHOLD = 5;
57 
58     private static final String REASON_APP_CRASH = "REASON_APP_CRASH";
59     private static final String REASON_NATIVE_CRASH = "REASON_NATIVE_CRASH";
60     private static final String ROLLBACK_INITIATE = "ROLLBACK_INITIATE";
61     private static final String ROLLBACK_BOOT_TRIGGERED = "ROLLBACK_BOOT_TRIGGERED";
62     private static final String ROLLBACK_SUCCESS = "ROLLBACK_SUCCESS";
63 
64     private static final String TESTAPP_A = "com.android.cts.install.lib.testapp.A";
65     private static final String TEST_SUBDIR = "/subdir/";
66     private static final String TEST_FILENAME_1 = "test_file.txt";
67     private static final String TEST_STRING_1 = "hello this is a test";
68     private static final String TEST_FILENAME_2 = "another_file.txt";
69     private static final String TEST_STRING_2 = "this is a different file";
70     private static final String TEST_FILENAME_3 = "also.xyz";
71     private static final String TEST_STRING_3 = "also\n a\n test\n string";
72     private static final String TEST_FILENAME_4 = "one_more.test";
73     private static final String TEST_STRING_4 = "once more unto the test";
74 
75     // Expiry time for staged sessions that have not changed state in this time
76     private static final long MAX_TIME_SINCE_UPDATE_MILLIS = TimeUnit.DAYS.toMillis(21);
77 
78     private final InstallUtilsHost mHostUtils = new InstallUtilsHost(this);
79     private WatchdogEventLogger mLogger = new WatchdogEventLogger();
80 
run(String method)81     private void run(String method) throws Exception {
82         assertThat(runDeviceTests("com.android.cts_root.rollback.host.app",
83                 "com.android.cts_root.rollback.host.app.HostTestHelper",
84                 method)).isTrue();
85     }
86 
87     @Before
88     @After
cleanUp()89     public void cleanUp() throws Exception {
90         getDevice().enableAdbRoot();
91         getDevice().executeShellCommand("for i in $(pm list staged-sessions --only-sessionid "
92                 + "--only-parent); do pm install-abandon $i; done");
93         getDevice().uninstallPackage("com.android.cts.install.lib.testapp.A");
94         getDevice().uninstallPackage("com.android.cts.install.lib.testapp.B");
95         getDevice().uninstallPackage("com.android.cts.install.lib.testapp.C");
96         run("cleanUp");
97         mHostUtils.uninstallShimApexIfNecessary();
98     }
99 
100     @Before
setUp()101     public void setUp() throws Exception {
102         getDevice().enableAdbRoot();
103         mLogger.start(getDevice());
104     }
105 
106     @After
tearDown()107     public void tearDown() throws Exception {
108         getDevice().enableAdbRoot();
109         mLogger.stop();
110     }
111 
112     /**
113      * Tests user data is restored according to the preset rollback data policy.
114      */
115     @Test
testRollbackDataPolicy()116     public void testRollbackDataPolicy() throws Exception {
117         List<String> before = getSnapshotDirectories("/data/misc_ce/0/rollback");
118 
119         run("testRollbackDataPolicy_Phase1_Install");
120         getDevice().reboot();
121         run("testRollbackDataPolicy_Phase2_Rollback");
122         getDevice().reboot();
123         run("testRollbackDataPolicy_Phase3_VerifyRollback");
124 
125         // Verify snapshots are deleted after restoration
126         List<String> after = getSnapshotDirectories("/data/misc_ce/0/rollback");
127         // Only check directories newly created during the test
128         after.removeAll(before);
129         // There should be only one /data/misc_ce/0/rollback/<rollbackId> created during test
130         assertThat(after).hasSize(1);
131         assertDirectoryIsEmpty(after.get(0));
132     }
133 
134     /**
135      * Tests that userdata of apk-in-apex is restored when apex is rolled back.
136      */
137     @Test
testRollbackApkInApexDataDirectories_Ce()138     public void testRollbackApkInApexDataDirectories_Ce() throws Exception {
139         // Push files to apk data directory
140         String oldFilePath1 = apkDataDirCe(
141                 SHIM_PACKAGE_NAME, 0) + "/" + TEST_FILENAME_1;
142         String oldFilePath2 = apkDataDirCe(
143                 SHIM_PACKAGE_NAME, 0) + TEST_SUBDIR + TEST_FILENAME_2;
144         pushString(TEST_STRING_1, oldFilePath1);
145         pushString(TEST_STRING_2, oldFilePath2);
146 
147         // Install new version of the APEX with rollback enabled
148         run("testRollbackApexDataDirectories_Phase1_Install");
149         getDevice().reboot();
150 
151         // Replace files in data directory
152         String newFilePath3 = apkDataDirCe(
153                 SHIM_PACKAGE_NAME, 0) + "/" + TEST_FILENAME_3;
154         String newFilePath4 = apkDataDirCe(
155                 SHIM_PACKAGE_NAME, 0) + TEST_SUBDIR + TEST_FILENAME_4;
156         getDevice().deleteFile(oldFilePath1);
157         getDevice().deleteFile(oldFilePath2);
158         pushString(TEST_STRING_3, newFilePath3);
159         pushString(TEST_STRING_4, newFilePath4);
160 
161         // Roll back the APEX
162         getDevice().executeShellCommand("pm rollback-app " + SHIM_APEX_PACKAGE_NAME);
163         getDevice().reboot();
164 
165         // Verify that old files have been restored and new files are gone
166         assertFileContents(TEST_STRING_1, oldFilePath1);
167         assertFileContents(TEST_STRING_2, oldFilePath2);
168         assertFileNotExists(newFilePath3);
169         assertFileNotExists(newFilePath4);
170     }
171 
172     /**
173      * Tests that data in DE apk data directory is restored when apk is rolled back.
174      */
175     @Test
testRollbackApkDataDirectories_De()176     public void testRollbackApkDataDirectories_De() throws Exception {
177         // Install version 1 of TESTAPP_A
178         run("testRollbackApkDataDirectories_Phase1_InstallV1");
179 
180         // Push files to apk data directory
181         String oldFilePath1 = apkDataDirDe(TESTAPP_A, 0) + "/" + TEST_FILENAME_1;
182         String oldFilePath2 = apkDataDirDe(TESTAPP_A, 0) + TEST_SUBDIR + TEST_FILENAME_2;
183         pushString(TEST_STRING_1, oldFilePath1);
184         pushString(TEST_STRING_2, oldFilePath2);
185 
186         // Install version 2 of TESTAPP_A with rollback enabled
187         run("testRollbackApkDataDirectories_Phase2_InstallV2");
188         getDevice().reboot();
189 
190         // Replace files in data directory
191         String newFilePath3 = apkDataDirDe(TESTAPP_A, 0) + "/" + TEST_FILENAME_3;
192         String newFilePath4 = apkDataDirDe(TESTAPP_A, 0) + TEST_SUBDIR + TEST_FILENAME_4;
193         getDevice().deleteFile(oldFilePath1);
194         getDevice().deleteFile(oldFilePath2);
195         pushString(TEST_STRING_3, newFilePath3);
196         pushString(TEST_STRING_4, newFilePath4);
197 
198         // Roll back the APK
199         run("testRollbackApkDataDirectories_Phase3_Rollback");
200         getDevice().reboot();
201 
202         // Verify that old files have been restored and new files are gone
203         assertFileContents(TEST_STRING_1, oldFilePath1);
204         assertFileContents(TEST_STRING_2, oldFilePath2);
205         assertFileNotExists(newFilePath3);
206         assertFileNotExists(newFilePath4);
207     }
208 
209     /**
210      * Tests that data in CE apex data directory is restored when apex is rolled back.
211      */
212     @Test
testRollbackApexDataDirectories_Ce()213     public void testRollbackApexDataDirectories_Ce() throws Exception {
214         List<String> before = getSnapshotDirectories("/data/misc_ce/0/apexrollback");
215 
216         // Push files to apex data directory
217         String oldFilePath1 = apexDataDirCe(
218                 SHIM_APEX_PACKAGE_NAME, 0) + "/" + TEST_FILENAME_1;
219         String oldFilePath2 = apexDataDirCe(
220                 SHIM_APEX_PACKAGE_NAME, 0) + TEST_SUBDIR + TEST_FILENAME_2;
221         pushString(TEST_STRING_1, oldFilePath1);
222         pushString(TEST_STRING_2, oldFilePath2);
223 
224         // Install new version of the APEX with rollback enabled
225         run("testRollbackApexDataDirectories_Phase1_Install");
226         getDevice().reboot();
227 
228         // Replace files in data directory
229         String newFilePath3 = apexDataDirCe(
230                 SHIM_APEX_PACKAGE_NAME, 0) + "/" + TEST_FILENAME_3;
231         String newFilePath4 = apexDataDirCe(
232                 SHIM_APEX_PACKAGE_NAME, 0) + TEST_SUBDIR + TEST_FILENAME_4;
233         getDevice().deleteFile(oldFilePath1);
234         getDevice().deleteFile(oldFilePath2);
235         pushString(TEST_STRING_3, newFilePath3);
236         pushString(TEST_STRING_4, newFilePath4);
237 
238         // Roll back the APEX
239         getDevice().executeShellCommand("pm rollback-app " + SHIM_APEX_PACKAGE_NAME);
240         getDevice().reboot();
241 
242         // Verify that old files have been restored and new files are gone
243         assertFileContents(TEST_STRING_1, oldFilePath1);
244         assertFileContents(TEST_STRING_2, oldFilePath2);
245         assertFileNotExists(newFilePath3);
246         assertFileNotExists(newFilePath4);
247 
248         // Verify snapshots are deleted after restoration
249         List<String> after = getSnapshotDirectories("/data/misc_ce/0/apexrollback");
250         // Only check directories newly created during the test
251         after.removeAll(before);
252         // There should be only one /data/misc_ce/0/apexrollback/<rollbackId> created during test
253         assertThat(after).hasSize(1);
254         assertDirectoryIsEmpty(after.get(0));
255     }
256 
257     /**
258      * Tests that data in DE (user) apex data directory is restored when apex is rolled back.
259      */
260     @Test
testRollbackApexDataDirectories_DeUser()261     public void testRollbackApexDataDirectories_DeUser() throws Exception {
262         List<String> before = getSnapshotDirectories("/data/misc_de/0/apexrollback");
263 
264         // Push files to apex data directory
265         String oldFilePath1 = apexDataDirDeUser(
266                 SHIM_APEX_PACKAGE_NAME, 0) + "/" + TEST_FILENAME_1;
267         String oldFilePath2 = apexDataDirDeUser(
268                 SHIM_APEX_PACKAGE_NAME, 0) + TEST_SUBDIR + TEST_FILENAME_2;
269         pushString(TEST_STRING_1, oldFilePath1);
270         pushString(TEST_STRING_2, oldFilePath2);
271 
272         // Install new version of the APEX with rollback enabled
273         run("testRollbackApexDataDirectories_Phase1_Install");
274         getDevice().reboot();
275 
276         // Replace files in data directory
277         String newFilePath3 = apexDataDirDeUser(
278                 SHIM_APEX_PACKAGE_NAME, 0) + "/" + TEST_FILENAME_3;
279         String newFilePath4 = apexDataDirDeUser(
280                 SHIM_APEX_PACKAGE_NAME, 0) + TEST_SUBDIR + TEST_FILENAME_4;
281         getDevice().deleteFile(oldFilePath1);
282         getDevice().deleteFile(oldFilePath2);
283         pushString(TEST_STRING_3, newFilePath3);
284         pushString(TEST_STRING_4, newFilePath4);
285 
286         // Roll back the APEX
287         getDevice().executeShellCommand("pm rollback-app " + SHIM_APEX_PACKAGE_NAME);
288         getDevice().reboot();
289 
290         // Verify that old files have been restored and new files are gone
291         assertFileContents(TEST_STRING_1, oldFilePath1);
292         assertFileContents(TEST_STRING_2, oldFilePath2);
293         assertFileNotExists(newFilePath3);
294         assertFileNotExists(newFilePath4);
295 
296         // Verify snapshots are deleted after restoration
297         List<String> after = getSnapshotDirectories("/data/misc_de/0/apexrollback");
298         // Only check directories newly created during the test
299         after.removeAll(before);
300         // There should be only one /data/misc_de/0/apexrollback/<rollbackId> created during test
301         assertThat(after).hasSize(1);
302         assertDirectoryIsEmpty(after.get(0));
303     }
304 
305     /**
306      * Tests that data in DE_sys apex data directory is restored when apex is rolled back.
307      */
308     @Test
testRollbackApexDataDirectories_DeSys()309     public void testRollbackApexDataDirectories_DeSys() throws Exception {
310         List<String> before = getSnapshotDirectories("/data/misc/apexrollback");
311 
312         // Push files to apex data directory
313         String oldFilePath1 = apexDataDirDeSys(
314                 SHIM_APEX_PACKAGE_NAME) + "/" + TEST_FILENAME_1;
315         String oldFilePath2 = apexDataDirDeSys(
316                 SHIM_APEX_PACKAGE_NAME) + TEST_SUBDIR + TEST_FILENAME_2;
317         pushString(TEST_STRING_1, oldFilePath1);
318         pushString(TEST_STRING_2, oldFilePath2);
319 
320         // Install new version of the APEX with rollback enabled
321         run("testRollbackApexDataDirectories_Phase1_Install");
322         getDevice().reboot();
323 
324         // Replace files in data directory
325         String newFilePath3 = apexDataDirDeSys(
326                 SHIM_APEX_PACKAGE_NAME) + "/" + TEST_FILENAME_3;
327         String newFilePath4 = apexDataDirDeSys(
328                 SHIM_APEX_PACKAGE_NAME) + TEST_SUBDIR + TEST_FILENAME_4;
329         getDevice().deleteFile(oldFilePath1);
330         getDevice().deleteFile(oldFilePath2);
331         pushString(TEST_STRING_3, newFilePath3);
332         pushString(TEST_STRING_4, newFilePath4);
333 
334         // Roll back the APEX
335         getDevice().executeShellCommand("pm rollback-app " + SHIM_APEX_PACKAGE_NAME);
336         getDevice().reboot();
337 
338         // Verify that old files have been restored and new files are gone
339         assertFileContents(TEST_STRING_1, oldFilePath1);
340         assertFileContents(TEST_STRING_2, oldFilePath2);
341         assertFileNotExists(newFilePath3);
342         assertFileNotExists(newFilePath4);
343 
344         // Verify snapshots are deleted after restoration
345         List<String> after = getSnapshotDirectories("/data/misc/apexrollback");
346         // Only check directories newly created during the test
347         after.removeAll(before);
348         // There should be only one /data/misc/apexrollback/<rollbackId> created during test
349         assertThat(after).hasSize(1);
350         assertDirectoryIsEmpty(after.get(0));
351     }
352 
353     /**
354      * Tests that apex CE snapshots are deleted when its rollback is deleted.
355      */
356     @Test
testExpireApexRollback()357     public void testExpireApexRollback() throws Exception {
358         List<String> before = getSnapshotDirectories("/data/misc_ce/0/apexrollback");
359 
360         // Push files to apex data directory
361         String oldFilePath1 = apexDataDirCe(
362                 SHIM_APEX_PACKAGE_NAME, 0) + "/" + TEST_FILENAME_1;
363         String oldFilePath2 = apexDataDirCe(
364                 SHIM_APEX_PACKAGE_NAME, 0) + TEST_SUBDIR + TEST_FILENAME_2;
365         pushString(TEST_STRING_1, oldFilePath1);
366         pushString(TEST_STRING_2, oldFilePath2);
367 
368         // Install new version of the APEX with rollback enabled
369         run("testRollbackApexDataDirectories_Phase1_Install");
370         getDevice().reboot();
371 
372         List<String> after = getSnapshotDirectories("/data/misc_ce/0/apexrollback");
373         // Only check directories newly created during the test
374         after.removeAll(before);
375         // There should be only one /data/misc_ce/0/apexrollback/<rollbackId> created during test
376         assertThat(after).hasSize(1);
377         // Expire all rollbacks and check CE snapshot directories are deleted
378         run("cleanUp");
379         assertFileNotExists(after.get(0));
380     }
381 
382     /**
383      * Tests an available rollback shouldn't be deleted when its session expires.
384      */
385     @Test
testExpireSession()386     public void testExpireSession() throws Exception {
387         run("testExpireSession_Phase1_Install");
388         getDevice().reboot();
389         run("testExpireSession_Phase2_VerifyInstall");
390 
391         // Advance system clock by MAX_TIME_SINCE_UPDATE_MILLIS to expire the staged session
392         Instant t1 = Instant.ofEpochMilli(getDevice().getDeviceDate());
393         Instant t2 = t1.plusMillis(MAX_TIME_SINCE_UPDATE_MILLIS);
394 
395         try {
396             getDevice().setDate(Date.from(t2));
397             // Send the broadcast to ensure the time change is properly propagated
398             getDevice().executeShellCommand("am broadcast -a android.intent.action.TIME_SET");
399             // TODO(b/197298469): Time change will be lost after reboot and sessions won't be
400             //  expired correctly. We restart system server so sessions will be reloaded and expired
401             //  as a workaround.
402             getDevice().executeShellCommand("stop");
403             getDevice().executeShellCommand("start");
404             getDevice().waitForDeviceAvailable();
405             run("testExpireSession_Phase3_VerifyRollback");
406         } finally {
407             // Restore system clock
408             getDevice().setDate(Date.from(t1));
409             getDevice().executeShellCommand("am broadcast -a android.intent.action.TIME_SET");
410         }
411     }
412 
413     /**
414      * Tests watchdog triggered staged rollbacks involving only apks.
415      */
416     @Test
testBadApkOnly()417     public void testBadApkOnly() throws Exception {
418         run("testBadApkOnly_Phase1_Install");
419         getDevice().reboot();
420         run("testBadApkOnly_Phase2_VerifyInstall");
421 
422         // Launch the app to crash to trigger rollback
423         startActivity(TESTAPP_A);
424         // Wait for reboot to happen
425         waitForDeviceNotAvailable(2, TimeUnit.MINUTES);
426 
427         getDevice().waitForDeviceAvailable();
428 
429         run("testBadApkOnly_Phase3_VerifyRollback");
430 
431         assertThat(mLogger).eventOccurred(ROLLBACK_INITIATE, null, REASON_APP_CRASH, TESTAPP_A);
432         assertThat(mLogger).eventOccurred(ROLLBACK_BOOT_TRIGGERED, null, null, null);
433         assertThat(mLogger).eventOccurred(ROLLBACK_SUCCESS, null, null, null);
434     }
435 
436     /**
437      * Tests rollbacks triggered by the native watchdog.
438      */
439     @Test
testNativeWatchdogTriggersRollback()440     public void testNativeWatchdogTriggersRollback() throws Exception {
441         run("testNativeWatchdogTriggersRollback_Phase1_Install");
442         getDevice().reboot();
443         run("testNativeWatchdogTriggersRollback_Phase2_VerifyInstall");
444 
445         // crash system_server enough times to trigger a rollback
446         crashProcess("system_server", NATIVE_CRASHES_THRESHOLD);
447 
448         // Rollback should be committed automatically now.
449         // Give time for rollback to be committed. This could take a while,
450         // because we need all of the following to happen:
451         // 1. system_server comes back up and boot completes.
452         // 2. Rollback health observer detects updatable crashing signal.
453         // 3. Staged rollback session becomes ready.
454         // 4. Device actually reboots.
455         // So we give a generous timeout here.
456         waitForDeviceNotAvailable(5, TimeUnit.MINUTES);
457         getDevice().waitForDeviceAvailable();
458 
459         // verify rollback committed
460         run("testNativeWatchdogTriggersRollback_Phase3_VerifyRollback");
461 
462         assertThat(mLogger).eventOccurred(ROLLBACK_INITIATE, null, REASON_NATIVE_CRASH, null);
463         assertThat(mLogger).eventOccurred(ROLLBACK_BOOT_TRIGGERED, null, null, null);
464         assertThat(mLogger).eventOccurred(ROLLBACK_SUCCESS, null, null, null);
465     }
466 
467     /**
468      * Tests rollbacks triggered by the native watchdog.
469      */
470     @Test
testNativeWatchdogTriggersRollbackForAll()471     public void testNativeWatchdogTriggersRollbackForAll() throws Exception {
472         // This test requires committing multiple staged rollbacks
473         assumeTrue(isCheckpointSupported());
474 
475         // Install 2 packages with rollback enabled.
476         run("testNativeWatchdogTriggersRollbackForAll_Phase1_Install");
477         getDevice().reboot();
478 
479         // Verify that we have 2 rollbacks available
480         run("testNativeWatchdogTriggersRollbackForAll_Phase2_VerifyInstall");
481 
482         // crash system_server enough times to trigger rollbacks
483         crashProcess("system_server", NATIVE_CRASHES_THRESHOLD);
484 
485         // Rollback should be committed automatically now.
486         // Give time for rollback to be committed. This could take a while,
487         // because we need all of the following to happen:
488         // 1. system_server comes back up and boot completes.
489         // 2. Rollback health observer detects updatable crashing signal.
490         // 3. Staged rollback session becomes ready.
491         // 4. Device actually reboots.
492         // So we give a generous timeout here.
493         waitForDeviceNotAvailable(5, TimeUnit.MINUTES);
494         getDevice().waitForDeviceAvailable();
495 
496         // verify all available rollbacks have been committed
497         run("testNativeWatchdogTriggersRollbackForAll_Phase3_VerifyRollback");
498 
499         assertThat(mLogger).eventOccurred(ROLLBACK_INITIATE, null, REASON_NATIVE_CRASH, null);
500         assertThat(mLogger).eventOccurred(ROLLBACK_BOOT_TRIGGERED, null, null, null);
501         assertThat(mLogger).eventOccurred(ROLLBACK_SUCCESS, null, null, null);
502     }
503 
getSnapshotDirectories(String baseDir)504     private List<String> getSnapshotDirectories(String baseDir) throws Exception {
505         IFileEntry f = getDevice().getFileEntry(baseDir);
506         if (f == null) {
507             Log.d(TAG, "baseDir doesn't exist: " + baseDir);
508             return Collections.EMPTY_LIST;
509         }
510         List<String> list = f.getChildren(false)
511                 .stream().filter(entry -> entry.getName().matches("\\d+(-prerestore)?"))
512                 .map(entry -> entry.getFullPath())
513                 .collect(Collectors.toList());
514         Log.d(TAG, "getSnapshotDirectories=" + list);
515         return list;
516     }
517 
assertDirectoryIsEmpty(String path)518     private void assertDirectoryIsEmpty(String path) {
519         try {
520             IFileEntry file = getDevice().getFileEntry(path);
521             assertWithMessage("Not a directory: " + path).that(file.isDirectory()).isTrue();
522             assertWithMessage("Directory not empty: " + path)
523                     .that(file.getChildren(false)).isEmpty();
524         } catch (DeviceNotAvailableException e) {
525             fail("Can't access directory: " + path);
526         }
527     }
528 
assertFileContents(String expectedContents, String path)529     private void assertFileContents(String expectedContents, String path) throws Exception {
530         String actualContents = getDevice().pullFileContents(path);
531         assertWithMessage("Failed to retrieve file=%s", path).that(actualContents).isNotNull();
532         assertWithMessage("Mismatched file contents, path=%s", path)
533                 .that(actualContents).isEqualTo(expectedContents);
534     }
535 
assertFileNotExists(String path)536     private void assertFileNotExists(String path) throws Exception {
537         assertWithMessage("File shouldn't exist, path=%s", path)
538                 .that(getDevice().getFileEntry(path)).isNull();
539     }
540 
apkDataDirCe(String apkName, int userId)541     private static String apkDataDirCe(String apkName, int userId) {
542         return String.format("/data/user/%d/%s", userId, apkName);
543     }
544 
apkDataDirDe(String apkName, int userId)545     private static String apkDataDirDe(String apkName, int userId) {
546         return String.format("/data/user_de/%d/%s", userId, apkName);
547     }
548 
apexDataDirCe(String apexName, int userId)549     private static String apexDataDirCe(String apexName, int userId) {
550         return String.format("/data/misc_ce/%d/apexdata/%s", userId, apexName);
551     }
552 
apexDataDirDeUser(String apexName, int userId)553     private static String apexDataDirDeUser(String apexName, int userId) {
554         return String.format("/data/misc_de/%d/apexdata/%s", userId, apexName);
555     }
556 
apexDataDirDeSys(String apexName)557     private static String apexDataDirDeSys(String apexName) {
558         return String.format("/data/misc/apexdata/%s", apexName);
559     }
560 
pushString(String contents, String path)561     private void pushString(String contents, String path) throws Exception {
562         assertWithMessage("Failed to push file to device, content=%s path=%s", contents, path)
563                 .that(getDevice().pushString(contents, path)).isTrue();
564     }
565 
waitForDeviceNotAvailable(long timeout, TimeUnit unit)566     private void waitForDeviceNotAvailable(long timeout, TimeUnit unit) {
567         assertWithMessage("waitForDeviceNotAvailable() timed out in %s %s", timeout, unit)
568                 .that(getDevice().waitForDeviceNotAvailable(unit.toMillis(timeout))).isTrue();
569     }
570 
startActivity(String packageName)571     private void startActivity(String packageName) throws Exception {
572         String cmd = "am start -S -a android.intent.action.MAIN "
573                 + "-c android.intent.category.LAUNCHER " + packageName;
574         getDevice().executeShellCommand(cmd);
575     }
576 
crashProcess(String processName, int numberOfCrashes)577     private void crashProcess(String processName, int numberOfCrashes) throws Exception {
578         String pid = "";
579         String lastPid = "invalid";
580         for (int i = 0; i < numberOfCrashes; ++i) {
581             // This condition makes sure before we kill the process, the process is running AND
582             // the last crash was finished.
583             while ("".equals(pid) || lastPid.equals(pid)) {
584                 pid = getDevice().executeShellCommand("pidof " + processName);
585             }
586             getDevice().executeShellCommand("kill " + pid);
587             lastPid = pid;
588         }
589     }
590 
isCheckpointSupported()591     private boolean isCheckpointSupported() throws Exception {
592         try {
593             run("isCheckpointSupported");
594             return true;
595         } catch (AssertionError ignore) {
596             return false;
597         }
598     }
599 }
600