1 /*
2  * Copyright (C) 2019 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.tests.stagedinstall.host;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.hamcrest.CoreMatchers.endsWith;
22 import static org.hamcrest.CoreMatchers.equalTo;
23 import static org.hamcrest.CoreMatchers.not;
24 import static org.junit.Assume.assumeFalse;
25 import static org.junit.Assume.assumeThat;
26 import static org.junit.Assume.assumeTrue;
27 
28 import android.platform.test.annotations.LargeTest;
29 
30 import com.android.ddmlib.Log;
31 import com.android.tradefed.device.DeviceNotAvailableException;
32 import com.android.tradefed.device.ITestDevice;
33 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
34 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
35 
36 import org.junit.After;
37 import org.junit.Before;
38 import org.junit.Rule;
39 import org.junit.Test;
40 import org.junit.rules.TestWatcher;
41 import org.junit.runner.Description;
42 import org.junit.runner.RunWith;
43 
44 @RunWith(DeviceJUnit4ClassRunner.class)
45 public class StagedInstallTest extends BaseHostJUnit4Test {
46 
47     private static final String TAG = "StagedInstallTest";
48 
49     private static final String SHIM_APEX_PACKAGE_NAME = "com.android.apex.cts.shim";
50 
51     @Rule
52     public final FailedTestLogHook mFailedTestLogHook = new FailedTestLogHook(this);
53 
54     /**
55      * Runs the given phase of a test by calling into the device.
56      * Throws an exception if the test phase fails.
57      * <p>
58      * For example, <code>runPhase("testInstallStagedApkCommit");</code>
59      */
runPhase(String phase)60     private void runPhase(String phase) throws Exception {
61         assertThat(runDeviceTests("com.android.tests.stagedinstall",
62                 "com.android.tests.stagedinstall.StagedInstallTest",
63                 phase)).isTrue();
64     }
65 
66     // We do not assert the success of cleanup phase since it might fail due to flaky reasons.
cleanUp()67     private void cleanUp() throws Exception {
68         try {
69             runDeviceTests("com.android.tests.stagedinstall",
70                     "com.android.tests.stagedinstall.StagedInstallTest",
71                     "cleanUp");
72         } catch (AssertionError e) {
73             Log.e(TAG, e);
74         }
75     }
76 
77     @Before
setUp()78     public void setUp() throws Exception {
79         cleanUp();
80         uninstallShimApexIfNecessary();
81     }
82 
83     @After
tearDown()84     public void tearDown() throws Exception {
85         cleanUp();
86         uninstallShimApexIfNecessary();
87     }
88 
89     /**
90      * Tests for staged install involving only one apk.
91      */
92     @Test
93     @LargeTest
testInstallStagedApk()94     public void testInstallStagedApk() throws Exception {
95         assumeSystemUser();
96         runPhase("testInstallStagedApk_Commit");
97         getDevice().reboot();
98         runPhase("testInstallStagedApk_VerifyPostReboot");
99         runPhase("testInstallStagedApk_AbandonSessionIsNoop");
100     }
101 
102     @Test
testFailInstallIfNoPermission()103     public void testFailInstallIfNoPermission() throws Exception {
104         runPhase("testFailInstallIfNoPermission");
105     }
106 
107     @Test
108     @LargeTest
testAbandonStagedApkBeforeReboot()109     public void testAbandonStagedApkBeforeReboot() throws Exception {
110         runPhase("testAbandonStagedApkBeforeReboot_CommitAndAbandon");
111         getDevice().reboot();
112         runPhase("testAbandonStagedApkBeforeReboot_VerifyPostReboot");
113     }
114 
115     @Test
116     @LargeTest
testInstallMultipleStagedApks()117     public void testInstallMultipleStagedApks() throws Exception {
118         assumeSystemUser();
119         runPhase("testInstallMultipleStagedApks_Commit");
120         getDevice().reboot();
121         runPhase("testInstallMultipleStagedApks_VerifyPostReboot");
122     }
123 
assumeSystemUser()124     private void assumeSystemUser() throws Exception {
125         String systemUser = "0";
126         assumeThat("Current user is not system user",
127                 getDevice().executeShellCommand("am get-current-user").trim(), equalTo(systemUser));
128     }
129 
130     @Test
testGetActiveStagedSessions()131     public void testGetActiveStagedSessions() throws Exception {
132         assumeTrue(isCheckpointSupported());
133         runPhase("testGetActiveStagedSessions");
134     }
135 
136     /**
137      * Verifies that active staged session fulfils conditions stated at
138      * {@link PackageInstaller.SessionInfo#isStagedSessionActive}
139      */
140     @Test
testIsStagedSessionActive()141     public void testIsStagedSessionActive() throws Exception {
142         runPhase("testIsStagedSessionActive");
143     }
144 
145     @Test
testGetActiveStagedSessionsNoSessionActive()146     public void testGetActiveStagedSessionsNoSessionActive() throws Exception {
147         runPhase("testGetActiveStagedSessionsNoSessionActive");
148     }
149 
150     @Test
testGetActiveStagedSessions_MultiApkSession()151     public void testGetActiveStagedSessions_MultiApkSession() throws Exception {
152         assumeTrue(isCheckpointSupported());
153         runPhase("testGetActiveStagedSessions_MultiApkSession");
154     }
155 
156     @Test
testStagedInstallDowngrade_DowngradeNotRequested_Fails()157     public void testStagedInstallDowngrade_DowngradeNotRequested_Fails() throws Exception {
158         runPhase("testStagedInstallDowngrade_DowngradeNotRequested_Fails_Commit");
159     }
160 
161     @Test
162     @LargeTest
testStagedInstallDowngrade_DowngradeRequested_DebugBuild()163     public void testStagedInstallDowngrade_DowngradeRequested_DebugBuild() throws Exception {
164         assumeThat(getDevice().getBuildFlavor(), not(endsWith("-user")));
165 
166         runPhase("testStagedInstallDowngrade_DowngradeRequested_Commit");
167         getDevice().reboot();
168         runPhase("testStagedInstallDowngrade_DowngradeRequested_DebugBuild_VerifyPostReboot");
169     }
170 
171     @Test
testStagedInstallDowngrade_DowngradeRequested_UserBuild()172     public void testStagedInstallDowngrade_DowngradeRequested_UserBuild() throws Exception {
173         assumeThat(getDevice().getBuildFlavor(), endsWith("-user"));
174         runPhase("testStagedInstallDowngrade_DowngradeRequested_Fails_Commit");
175     }
176 
177     @Test
testShimApexShouldPreInstalledIfUpdatingApexIsSupported()178     public void testShimApexShouldPreInstalledIfUpdatingApexIsSupported() throws Exception {
179         assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
180 
181         final ITestDevice.ApexInfo shimApex = getShimApex();
182         assertThat(shimApex.versionCode).isEqualTo(1);
183     }
184 
185     @Test
186     @LargeTest
testInstallStagedApex()187     public void testInstallStagedApex() throws Exception {
188         assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
189 
190         runPhase("testInstallStagedApex_Commit");
191         getDevice().reboot();
192         runPhase("testInstallStagedApex_VerifyPostReboot");
193     }
194 
195     @Test
testInstallStagedApexAndApk()196     public void testInstallStagedApexAndApk() throws Exception {
197         assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
198 
199         runPhase("testInstallStagedApexAndApk_Commit");
200         getDevice().reboot();
201         runPhase("testInstallStagedApexAndApk_VerifyPostReboot");
202     }
203 
204     @Test
testsFailsNonStagedApexInstall()205     public void testsFailsNonStagedApexInstall() throws Exception {
206         assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
207 
208         runPhase("testsFailsNonStagedApexInstall");
209     }
210 
211     @Test
testInstallStagedNonPreInstalledApex_Fails()212     public void testInstallStagedNonPreInstalledApex_Fails() throws Exception {
213         assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
214 
215         runPhase("testInstallStagedNonPreInstalledApex_Fails");
216     }
217 
218     @Test
219     @LargeTest
testStageApkWithSameNameAsApexShouldFail()220     public void testStageApkWithSameNameAsApexShouldFail() throws Exception {
221         assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
222 
223         runPhase("testStageApkWithSameNameAsApexShouldFail_Commit");
224         getDevice().reboot();
225         runPhase("testStageApkWithSameNameAsApexShouldFail_VerifyPostReboot");
226     }
227 
228     @Test
testNonStagedInstallApkWithSameNameAsApexShouldFail()229     public void testNonStagedInstallApkWithSameNameAsApexShouldFail() throws Exception {
230         assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
231         runPhase("testNonStagedInstallApkWithSameNameAsApexShouldFail");
232     }
233 
234     @Test
235     @LargeTest
testStagedInstallDowngradeApex_DowngradeNotRequested_Fails()236     public void testStagedInstallDowngradeApex_DowngradeNotRequested_Fails() throws Exception {
237         assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
238 
239         installV3Apex();
240         runPhase("testStagedInstallDowngradeApex_DowngradeNotRequested_Fails_Commit");
241         getDevice().reboot();
242         runPhase("testStagedInstallDowngradeApex_DowngradeNotRequested_Fails_VerifyPostReboot");
243     }
244 
245     @Test
246     @LargeTest
testStagedInstallDowngradeApex_DowngradeRequested_DebugBuild()247     public void testStagedInstallDowngradeApex_DowngradeRequested_DebugBuild() throws Exception {
248         assumeThat(getDevice().getBuildFlavor(), not(endsWith("-user")));
249         assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
250 
251         installV3Apex();
252         runPhase("testStagedInstallDowngradeApex_DowngradeRequested_DebugBuild_Commit");
253         getDevice().reboot();
254         runPhase("testStagedInstallDowngradeApex_DowngradeRequested_DebugBuild_VerifyPostReboot");
255     }
256 
257     @Test
258     @LargeTest
testStagedInstallDowngradeApex_DowngradeRequested_UserBuild_Fails()259     public void testStagedInstallDowngradeApex_DowngradeRequested_UserBuild_Fails()
260             throws Exception {
261         assumeThat(getDevice().getBuildFlavor(), endsWith("-user"));
262         assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
263 
264         installV3Apex();
265         runPhase("testStagedInstallDowngradeApex_DowngradeRequested_UserBuild_Fails_Commit");
266         getDevice().reboot();
267         runPhase("testStagedInstallDowngradeApex_DowngradeRequested_UserBuild_Fails_"
268                 + "VerifyPostReboot");
269     }
270 
271     @Test
272     @LargeTest
testStagedInstallDowngradeApexToSystemVersion_DebugBuild()273     public void testStagedInstallDowngradeApexToSystemVersion_DebugBuild() throws Exception {
274         assumeThat(getDevice().getBuildFlavor(), not(endsWith("-user")));
275         assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
276 
277         installV2Apex();
278         runPhase("testStagedInstallDowngradeApexToSystemVersion_DebugBuild_Commit");
279         getDevice().reboot();
280         runPhase("testStagedInstallDowngradeApexToSystemVersion_DebugBuild_VerifyPostReboot");
281     }
282 
283     @Test
284     @LargeTest
testInstallStagedApex_SameGrade()285     public void testInstallStagedApex_SameGrade() throws Exception {
286         assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
287 
288         installV3Apex();
289         installV3Apex();
290     }
291 
292     @Test
testInstallApex_DeviceDoesNotSupportApex_Fails()293     public void testInstallApex_DeviceDoesNotSupportApex_Fails() throws Exception {
294         assumeFalse("Device supports updating APEX", isUpdatingApexSupported());
295 
296         runPhase("testInstallApex_DeviceDoesNotSupportApex_Fails");
297     }
298 
installV2Apex()299     private void installV2Apex()throws Exception {
300         runPhase("testInstallV2Apex_Commit");
301         getDevice().reboot();
302         runPhase("testInstallV2Apex_VerifyPostReboot");
303     }
304 
installV2SignedBobApex()305     private void installV2SignedBobApex() throws Exception {
306         runPhase("testInstallV2SignedBobApex_Commit");
307         getDevice().reboot();
308         runPhase("testInstallV2SignedBobApex_VerifyPostReboot");
309     }
310 
installV3Apex()311     private void installV3Apex()throws Exception {
312         runPhase("testInstallV3Apex_Commit");
313         getDevice().reboot();
314         runPhase("testInstallV3Apex_VerifyPostReboot");
315     }
316 
317     @Test
testFailsInvalidApexInstall()318     public void testFailsInvalidApexInstall() throws Exception {
319         assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
320         runPhase("testFailsInvalidApexInstall_Commit");
321         runPhase("testFailsInvalidApexInstall_AbandonSessionIsNoop");
322     }
323 
324     @Test
testStagedApkSessionCallbacks()325     public void testStagedApkSessionCallbacks() throws Exception {
326         runPhase("testStagedApkSessionCallbacks");
327     }
328 
329     @Test
330     @LargeTest
testInstallStagedApexWithoutApexSuffix()331     public void testInstallStagedApexWithoutApexSuffix() throws Exception {
332         assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
333 
334         runPhase("testInstallStagedApexWithoutApexSuffix_Commit");
335         getDevice().reboot();
336         runPhase("testInstallStagedApexWithoutApexSuffix_VerifyPostReboot");
337     }
338 
339     @Test
340     @LargeTest
testInstallStagedNoHashtreeApex()341     public void testInstallStagedNoHashtreeApex() throws Exception {
342         assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
343 
344         runPhase("testInstallStagedNoHashtreeApex_Commit");
345         getDevice().reboot();
346         runPhase("testInstallStagedNoHashtreeApex_VerifyPostReboot");
347     }
348 
349     @Test
testRejectsApexDifferentCertificate()350     public void testRejectsApexDifferentCertificate() throws Exception {
351         runPhase("testRejectsApexDifferentCertificate");
352     }
353 
354     /**
355      * Tests for staged install involving rotated keys.
356      *
357      * Here alice means the original default key that cts.shim.v1 package was signed with and
358      * bob is the new key alice rotates to. Where ambiguous, we will refer keys as alice and bob
359      * instead of "old key" and "new key".
360      *
361      * By default, rotated keys have rollback capability enabled for old keys. When we remove
362      * rollback capability from a key, it is called "Distrusting Event" and the distrusted key can
363      * not update the app anymore.
364      */
365 
366     // Should not be able to update with a key that has not been rotated.
367     @Test
testUpdateWithDifferentKeyButNoRotation()368     public void testUpdateWithDifferentKeyButNoRotation() throws Exception {
369         assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
370 
371         runPhase("testUpdateWithDifferentKeyButNoRotation");
372     }
373 
374     // Should be able to update with a key that has been rotated.
375     @Test
376     @LargeTest
testUpdateWithDifferentKey()377     public void testUpdateWithDifferentKey() throws Exception {
378         assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
379 
380         runPhase("testUpdateWithDifferentKey_Commit");
381         getDevice().reboot();
382         runPhase("testUpdateWithDifferentKey_VerifyPostReboot");
383     }
384 
385     // Should not be able to update with a key that is no longer trusted (i.e, has no
386     // rollback capability)
387     @Test
388     @LargeTest
testUntrustedOldKeyIsRejected()389     public void testUntrustedOldKeyIsRejected() throws Exception {
390         assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
391 
392         installV2SignedBobApex();
393         runPhase("testUntrustedOldKeyIsRejected");
394     }
395 
396     // Should be able to update with an old key which is trusted
397     @Test
398     @LargeTest
testTrustedOldKeyIsAccepted()399     public void testTrustedOldKeyIsAccepted() throws Exception {
400         assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
401 
402         runPhase("testTrustedOldKeyIsAccepted_Commit");
403         getDevice().reboot();
404         runPhase("testTrustedOldKeyIsAccepted_CommitPostReboot");
405         getDevice().reboot();
406         runPhase("testTrustedOldKeyIsAccepted_VerifyPostReboot");
407     }
408 
409     // Should be able to update further with rotated key
410     @Test
411     @LargeTest
testAfterRotationNewKeyCanUpdateFurther()412     public void testAfterRotationNewKeyCanUpdateFurther() throws Exception {
413         assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
414 
415         installV2SignedBobApex();
416         runPhase("testAfterRotationNewKeyCanUpdateFurther_CommitPostReboot");
417         getDevice().reboot();
418         runPhase("testAfterRotationNewKeyCanUpdateFurther_VerifyPostReboot");
419     }
420 
421     @Test
422     @LargeTest
testAfterRotationNewKeyCanUpdateFurtherWithoutLineage()423     public void testAfterRotationNewKeyCanUpdateFurtherWithoutLineage() throws Exception {
424         assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
425 
426         installV2SignedBobApex();
427         runPhase("testAfterRotationNewKeyCanUpdateFurtherWithoutLineage");
428     }
429 
430     /**
431      * Tests for staging and installing multiple staged sessions.
432      */
433 
434     // Should fail to stage multiple sessions when check-point is not available
435     @Test
testFailStagingMultipleSessionsIfNoCheckPoint()436     public void testFailStagingMultipleSessionsIfNoCheckPoint() throws Exception {
437         assumeFalse(isCheckpointSupported());
438         runPhase("testFailStagingMultipleSessionsIfNoCheckPoint");
439     }
440 
441     @Test
testFailOverlappingMultipleStagedInstall_BothSinglePackage_Apk()442     public void testFailOverlappingMultipleStagedInstall_BothSinglePackage_Apk() throws Exception {
443         runPhase("testFailOverlappingMultipleStagedInstall_BothSinglePackage_Apk");
444     }
445 
446     @Test
testAllowNonOverlappingMultipleStagedInstall_MultiPackageSinglePackage_Apk()447     public void testAllowNonOverlappingMultipleStagedInstall_MultiPackageSinglePackage_Apk()
448             throws Exception {
449         assumeTrue(isCheckpointSupported());
450         runPhase("testAllowNonOverlappingMultipleStagedInstall_MultiPackageSinglePackage_Apk");
451     }
452 
453     @Test
testFailOverlappingMultipleStagedInstall_BothMultiPackage_Apk()454     public void testFailOverlappingMultipleStagedInstall_BothMultiPackage_Apk() throws Exception {
455         assumeTrue(isCheckpointSupported());
456         runPhase("testFailOverlappingMultipleStagedInstall_BothMultiPackage_Apk");
457     }
458 
459     // Test for installing multiple staged sessions at the same time
460     @Test
461     @LargeTest
testMultipleStagedInstall_ApkOnly()462     public void testMultipleStagedInstall_ApkOnly() throws Exception {
463         assumeTrue(isCheckpointSupported());
464         runPhase("testMultipleStagedInstall_ApkOnly_Commit");
465         getDevice().reboot();
466         runPhase("testMultipleStagedInstall_ApkOnly_VerifyPostReboot");
467     }
468 
469     // If apk installation fails in one staged session, then all staged session should fail.
470     @Test
471     @LargeTest
testInstallMultipleStagedSession_PartialFail_ApkOnly()472     public void testInstallMultipleStagedSession_PartialFail_ApkOnly() throws Exception {
473         assumeTrue(isCheckpointSupported());
474         runPhase("testInstallMultipleStagedSession_PartialFail_ApkOnly_Commit");
475         getDevice().reboot();
476         runPhase("testInstallMultipleStagedSession_PartialFail_ApkOnly_VerifyPostReboot");
477     }
478 
479     @Test
480     @LargeTest
testSamegradeSystemApex()481     public void testSamegradeSystemApex() throws Exception {
482         assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
483 
484         runPhase("testSamegradeSystemApex_Commit");
485         getDevice().reboot();
486         runPhase("testSamegradeSystemApex_VerifyPostReboot");
487     }
488 
489     @Test
testInstallApkChangingFingerprint()490     public void testInstallApkChangingFingerprint() throws Exception {
491         assumeThat(getDevice().getBuildFlavor(), not(endsWith("-user")));
492 
493         try {
494             getDevice().executeShellCommand("setprop persist.pm.mock-upgrade true");
495             runPhase("testInstallApkChangingFingerprint");
496             getDevice().reboot();
497             runPhase("testInstallApkChangingFingerprint_VerifyAborted");
498         } finally {
499             getDevice().executeShellCommand("setprop persist.pm.mock-upgrade false");
500         }
501     }
502 
isUpdatingApexSupported()503     private boolean isUpdatingApexSupported() throws Exception {
504         final String updatable = getDevice().getProperty("ro.apex.updatable");
505         return updatable != null && updatable.equals("true");
506     }
507 
508     /**
509      * Uninstalls a shim apex only if it's latest version is installed on /data partition (i.e.
510      * it has a version higher than {@code 1}).
511      *
512      * <p>This is purely to optimize tests run time. Since uninstalling an apex requires a reboot,
513      * and only a small subset of tests successfully install an apex, this code avoids ~10
514      * unnecessary reboots.
515      */
uninstallShimApexIfNecessary()516     private void uninstallShimApexIfNecessary() throws Exception {
517         if (!isUpdatingApexSupported()) {
518             // Device doesn't support updating apex. Nothing to uninstall.
519             return;
520         }
521         final ITestDevice.ApexInfo shimApex = getShimApex();
522         if (shimApex.versionCode == 1) {
523             // System version is active, skipping uninstalling active apex and rebooting the device.
524             return;
525         }
526         // Non system version is active, need to uninstall it and reboot the device.
527         final String errorMessage = getDevice().uninstallPackage(SHIM_APEX_PACKAGE_NAME);
528         Log.i(TAG, "Uninstalling shim apex " + shimApex);
529         if (errorMessage != null) {
530             throw new AssertionError("Failed to uninstall " + shimApex);
531         }
532         getDevice().reboot();
533         assertThat(getShimApex().versionCode).isEqualTo(1L);
534     }
535 
getShimApex()536     private ITestDevice.ApexInfo getShimApex() throws DeviceNotAvailableException {
537         return getDevice().getActiveApexes().stream().filter(
538                 apex -> apex.name.equals(SHIM_APEX_PACKAGE_NAME)).findAny().orElseThrow(
539                 () -> new AssertionError("Can't find " + SHIM_APEX_PACKAGE_NAME));
540     }
541 
542     private static final class FailedTestLogHook extends TestWatcher {
543 
544         private final BaseHostJUnit4Test mInstance;
545         private String mStagedSessionsBeforeTest;
546 
FailedTestLogHook(BaseHostJUnit4Test instance)547         private FailedTestLogHook(BaseHostJUnit4Test instance) {
548             this.mInstance = instance;
549         }
550 
551         @Override
failed(Throwable e, Description description)552         protected void failed(Throwable e, Description description) {
553             String stagedSessionsAfterTest = getStagedSessions();
554             Log.e(TAG, "Test " + description + " failed.\n"
555                     + "Staged sessions before test started:\n" + mStagedSessionsBeforeTest + "\n"
556                     + "Staged sessions after test failed:\n" + stagedSessionsAfterTest);
557         }
558 
559         @Override
starting(Description description)560         protected void starting(Description description) {
561             mStagedSessionsBeforeTest = getStagedSessions();
562         }
563 
getStagedSessions()564         private String getStagedSessions() {
565             try {
566                 return mInstance.getDevice().executeShellV2Command("pm get-stagedsessions").getStdout();
567             } catch (DeviceNotAvailableException e) {
568                 Log.e(TAG, e);
569                 return "Failed to get staged sessions";
570             }
571         }
572     }
573 
isCheckpointSupported()574     private boolean isCheckpointSupported() throws Exception {
575         try {
576             runPhase("isCheckpointSupported");
577             return true;
578         } catch (AssertionError ignore) {
579             return false;
580         }
581     }
582 }
583