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.android.cts.shim.lib.ShimPackage.SHIM_APEX_PACKAGE_NAME;
20 
21 import static com.google.common.truth.Truth.assertThat;
22 
23 import static org.hamcrest.CoreMatchers.endsWith;
24 import static org.hamcrest.CoreMatchers.equalTo;
25 import static org.hamcrest.CoreMatchers.not;
26 import static org.junit.Assume.assumeFalse;
27 import static org.junit.Assume.assumeThat;
28 import static org.junit.Assume.assumeTrue;
29 
30 import android.cts.install.lib.host.InstallUtilsHost;
31 import android.platform.test.annotations.LargeTest;
32 
33 import com.android.apex.ApexInfo;
34 import com.android.apex.XmlParser;
35 import com.android.ddmlib.Log;
36 import com.android.tradefed.device.DeviceNotAvailableException;
37 import com.android.tradefed.device.ITestDevice;
38 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
39 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
40 
41 
42 import org.junit.After;
43 import org.junit.Before;
44 import org.junit.Rule;
45 import org.junit.Test;
46 import org.junit.rules.TestWatcher;
47 import org.junit.runner.Description;
48 import org.junit.runner.RunWith;
49 
50 import java.io.File;
51 import java.io.FileInputStream;
52 import java.util.List;
53 import java.util.Set;
54 import java.util.stream.Collectors;
55 
56 @RunWith(DeviceJUnit4ClassRunner.class)
57 public class StagedInstallTest extends BaseHostJUnit4Test {
58 
59     private static final String TAG = "StagedInstallTest";
60 
61     private static final String PACKAGE_NAME = "com.android.tests.stagedinstall";
62 
63     private static final String BROADCAST_RECEIVER_COMPONENT = PACKAGE_NAME + "/"
64             + PACKAGE_NAME + ".LauncherActivity";
65 
66     private final InstallUtilsHost mHostUtils = new InstallUtilsHost(this);
67 
68     private String mDefaultLauncher = null;
69 
70     @Rule
71     public final FailedTestLogHook mFailedTestLogHook = new FailedTestLogHook(this);
72 
73     /**
74      * Runs the given phase of a test by calling into the device.
75      * Throws an exception if the test phase fails.
76      * <p>
77      * For example, <code>runPhase("testInstallStagedApkCommit");</code>
78      */
runPhase(String phase)79     private void runPhase(String phase) throws Exception {
80         assertThat(runDeviceTests(PACKAGE_NAME,
81                 "com.android.tests.stagedinstall.StagedInstallTest",
82                 phase)).isTrue();
83     }
84 
85     // We do not assert the success of cleanup phase since it might fail due to flaky reasons.
cleanUp()86     private void cleanUp() throws Exception {
87         try {
88             runDeviceTests(PACKAGE_NAME,
89                     "com.android.tests.stagedinstall.StagedInstallTest",
90                     "cleanUp");
91         } catch (AssertionError e) {
92             Log.e(TAG, e);
93         }
94     }
95 
96     @Before
setUp()97     public void setUp() throws Exception {
98         cleanUp();
99         mHostUtils.uninstallShimApexIfNecessary();
100         storeDefaultLauncher();
101     }
102 
103     @After
tearDown()104     public void tearDown() throws Exception {
105         cleanUp();
106         mHostUtils.uninstallShimApexIfNecessary();
107         setDefaultLauncher(mDefaultLauncher);
108     }
109 
110     /**
111      * Tests for staged install involving only one apk.
112      */
113     @Test
114     @LargeTest
testInstallStagedApk()115     public void testInstallStagedApk() throws Exception {
116         assumeSystemUser();
117 
118         setDefaultLauncher(BROADCAST_RECEIVER_COMPONENT);
119         runPhase("testInstallStagedApk_Commit");
120         getDevice().reboot();
121         runPhase("testInstallStagedApk_VerifyPostReboot");
122         runPhase("testInstallStagedApk_AbandonSessionIsNoop");
123     }
124 
125     @Test
testFailInstallIfNoPermission()126     public void testFailInstallIfNoPermission() throws Exception {
127         runPhase("testFailInstallIfNoPermission");
128     }
129 
130     @Test
131     @LargeTest
testAbandonStagedApkBeforeReboot()132     public void testAbandonStagedApkBeforeReboot() throws Exception {
133         runPhase("testAbandonStagedApkBeforeReboot_CommitAndAbandon");
134         getDevice().reboot();
135         runPhase("testAbandonStagedApkBeforeReboot_VerifyPostReboot");
136     }
137 
138     @Test
139     @LargeTest
testAbandonStagedApkBeforeReady()140     public void testAbandonStagedApkBeforeReady() throws Exception {
141         runPhase("testAbandonStagedApkBeforeReady_CommitAndAbandon");
142         getDevice().reboot();
143         runPhase("testAbandonStagedApkBeforeReady_VerifyPostReboot");
144     }
145 
146     @Test
testStageAnotherSessionImmediatelyAfterAbandon()147     public void testStageAnotherSessionImmediatelyAfterAbandon() throws Exception {
148         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
149         runPhase("testStageAnotherSessionImmediatelyAfterAbandon");
150     }
151 
152     @Test
testStageAnotherSessionImmediatelyAfterAbandonMultiPackage()153     public void testStageAnotherSessionImmediatelyAfterAbandonMultiPackage() throws Exception {
154         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
155         runPhase("testStageAnotherSessionImmediatelyAfterAbandonMultiPackage");
156     }
157 
158     @Test
testNoSessionUpdatedBroadcastSentForStagedSessionAbandon()159     public void testNoSessionUpdatedBroadcastSentForStagedSessionAbandon() throws Exception {
160         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
161         runPhase("testNoSessionUpdatedBroadcastSentForStagedSessionAbandon");
162     }
163 
164     @Test
165     @LargeTest
testInstallMultipleStagedApks()166     public void testInstallMultipleStagedApks() throws Exception {
167         assumeSystemUser();
168 
169         setDefaultLauncher(BROADCAST_RECEIVER_COMPONENT);
170         runPhase("testInstallMultipleStagedApks_Commit");
171         getDevice().reboot();
172         runPhase("testInstallMultipleStagedApks_VerifyPostReboot");
173     }
174 
assumeSystemUser()175     private void assumeSystemUser() throws Exception {
176         String systemUser = "0";
177         assumeThat("Current user is not system user",
178                 getDevice().executeShellCommand("am get-current-user").trim(), equalTo(systemUser));
179     }
180 
181     @Test
testGetActiveStagedSessions()182     public void testGetActiveStagedSessions() throws Exception {
183         assumeTrue("Device does not support file-system checkpoint",
184                 mHostUtils.isCheckpointSupported());
185 
186         runPhase("testGetActiveStagedSessions");
187     }
188 
189     /**
190      * Verifies that active staged session fulfils conditions stated at
191      * {@link PackageInstaller.SessionInfo#isStagedSessionActive}
192      */
193     @Test
testIsStagedSessionActive()194     public void testIsStagedSessionActive() throws Exception {
195         runPhase("testIsStagedSessionActive");
196     }
197 
198     @Test
testGetActiveStagedSessionsNoSessionActive()199     public void testGetActiveStagedSessionsNoSessionActive() throws Exception {
200         runPhase("testGetActiveStagedSessionsNoSessionActive");
201     }
202 
203     @Test
testGetActiveStagedSessions_MultiApkSession()204     public void testGetActiveStagedSessions_MultiApkSession() throws Exception {
205         assumeTrue("Device does not support file-system checkpoint",
206                 mHostUtils.isCheckpointSupported());
207 
208         runPhase("testGetActiveStagedSessions_MultiApkSession");
209     }
210 
211     @Test
testStagedInstallDowngrade_DowngradeNotRequested_Fails()212     public void testStagedInstallDowngrade_DowngradeNotRequested_Fails() throws Exception {
213         runPhase("testStagedInstallDowngrade_DowngradeNotRequested_Fails_Commit");
214     }
215 
216     @Test
217     @LargeTest
testStagedInstallDowngrade_DowngradeRequested_DebugBuild()218     public void testStagedInstallDowngrade_DowngradeRequested_DebugBuild() throws Exception {
219         assumeThat(getDevice().getBuildFlavor(), not(endsWith("-user")));
220 
221         runPhase("testStagedInstallDowngrade_DowngradeRequested_Commit");
222         getDevice().reboot();
223         runPhase("testStagedInstallDowngrade_DowngradeRequested_DebugBuild_VerifyPostReboot");
224     }
225 
226     @Test
testStagedInstallDowngrade_DowngradeRequested_UserBuild()227     public void testStagedInstallDowngrade_DowngradeRequested_UserBuild() throws Exception {
228         assumeThat(getDevice().getBuildFlavor(), endsWith("-user"));
229         assumeFalse("Device is debuggable", isDebuggable());
230 
231         runPhase("testStagedInstallDowngrade_DowngradeRequested_Fails_Commit");
232     }
233 
234     @Test
testShimApexShouldPreInstalledIfUpdatingApexIsSupported()235     public void testShimApexShouldPreInstalledIfUpdatingApexIsSupported() throws Exception {
236         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
237 
238         final ITestDevice.ApexInfo shimApex = mHostUtils.getShimApex().orElseThrow(
239                 () -> new AssertionError("Can't find " + SHIM_APEX_PACKAGE_NAME)
240         );
241         assertThat(shimApex.versionCode).isEqualTo(1);
242     }
243 
244     @Test
245     @LargeTest
testInstallStagedApex()246     public void testInstallStagedApex() throws Exception {
247         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
248 
249         setDefaultLauncher(BROADCAST_RECEIVER_COMPONENT);
250         runPhase("testInstallStagedApex_Commit");
251         getDevice().reboot();
252         runPhase("testInstallStagedApex_VerifyPostReboot");
253     }
254 
255     @Test
256     // Don't mark as @LargeTest since we want at least one test to install apex during pre-submit.
testInstallStagedApexAndApk()257     public void testInstallStagedApexAndApk() throws Exception {
258         assumeSystemUser();
259         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
260 
261         setDefaultLauncher(BROADCAST_RECEIVER_COMPONENT);
262         runPhase("testInstallStagedApexAndApk_Commit");
263         getDevice().reboot();
264         runPhase("testInstallStagedApexAndApk_VerifyPostReboot");
265     }
266 
267     @Test
testsFailsNonStagedApexInstall()268     public void testsFailsNonStagedApexInstall() throws Exception {
269         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
270 
271         runPhase("testsFailsNonStagedApexInstall");
272     }
273 
274     @Test
testInstallStagedNonPreInstalledApex_Fails()275     public void testInstallStagedNonPreInstalledApex_Fails() throws Exception {
276         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
277 
278         runPhase("testInstallStagedNonPreInstalledApex_Fails");
279     }
280 
281     @Test
testInstallStagedDifferentPackageNameWithInstalledApex_Fails()282     public void testInstallStagedDifferentPackageNameWithInstalledApex_Fails() throws Exception {
283         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
284 
285         runPhase("testInstallStagedDifferentPackageNameWithInstalledApex_Fails");
286     }
287 
288     @Test
289     @LargeTest
testStageApkWithSameNameAsApexShouldFail()290     public void testStageApkWithSameNameAsApexShouldFail() throws Exception {
291         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
292 
293         runPhase("testStageApkWithSameNameAsApexShouldFail_Commit");
294         getDevice().reboot();
295         runPhase("testStageApkWithSameNameAsApexShouldFail_VerifyPostReboot");
296     }
297 
298     @Test
testNonStagedInstallApkWithSameNameAsApexShouldFail()299     public void testNonStagedInstallApkWithSameNameAsApexShouldFail() throws Exception {
300         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
301         runPhase("testNonStagedInstallApkWithSameNameAsApexShouldFail");
302     }
303 
304     @Test
305     @LargeTest
testStagedInstallDowngradeApex_DowngradeNotRequested_Fails()306     public void testStagedInstallDowngradeApex_DowngradeNotRequested_Fails() throws Exception {
307         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
308 
309         installV3Apex();
310         runPhase("testStagedInstallDowngradeApex_DowngradeNotRequested_Fails_Commit");
311         getDevice().reboot();
312         runPhase("testStagedInstallDowngradeApex_DowngradeNotRequested_Fails_VerifyPostReboot");
313     }
314 
315     @Test
316     @LargeTest
testStagedInstallDowngradeApex_DowngradeRequested_DebugBuild()317     public void testStagedInstallDowngradeApex_DowngradeRequested_DebugBuild() throws Exception {
318         assumeThat(getDevice().getBuildFlavor(), not(endsWith("-user")));
319         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
320 
321         installV3Apex();
322         runPhase("testStagedInstallDowngradeApex_DowngradeRequested_DebugBuild_Commit");
323         getDevice().reboot();
324         runPhase("testStagedInstallDowngradeApex_DowngradeRequested_DebugBuild_VerifyPostReboot");
325     }
326 
327     @Test
328     @LargeTest
testStagedInstallDowngradeApex_DowngradeRequested_UserBuild_Fails()329     public void testStagedInstallDowngradeApex_DowngradeRequested_UserBuild_Fails()
330             throws Exception {
331         assumeThat(getDevice().getBuildFlavor(), endsWith("-user"));
332         assumeFalse("Device is debuggable", isDebuggable());
333         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
334 
335         installV3Apex();
336         runPhase("testStagedInstallDowngradeApex_DowngradeRequested_UserBuild_Fails_Commit");
337         getDevice().reboot();
338         runPhase("testStagedInstallDowngradeApex_DowngradeRequested_UserBuild_Fails_"
339                 + "VerifyPostReboot");
340     }
341 
342     @Test
343     @LargeTest
testStagedInstallDowngradeApexToSystemVersion_DebugBuild()344     public void testStagedInstallDowngradeApexToSystemVersion_DebugBuild() throws Exception {
345         assumeThat(getDevice().getBuildFlavor(), not(endsWith("-user")));
346         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
347 
348         installV2Apex();
349         runPhase("testStagedInstallDowngradeApexToSystemVersion_DebugBuild_Commit");
350         getDevice().reboot();
351         runPhase("testStagedInstallDowngradeApexToSystemVersion_DebugBuild_VerifyPostReboot");
352     }
353 
354     @Test
355     @LargeTest
testInstallStagedApex_SameGrade()356     public void testInstallStagedApex_SameGrade() throws Exception {
357         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
358         installV3Apex();
359         installV3Apex();
360     }
361 
362     @Test
363     @LargeTest
testInstallStagedApex_SameGrade_NewOneWins()364     public void testInstallStagedApex_SameGrade_NewOneWins() throws Exception {
365         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
366 
367         installV2Apex();
368 
369         runPhase("testInstallStagedApex_SameGrade_NewOneWins_Commit");
370         getDevice().reboot();
371         runPhase("testInstallStagedApex_SameGrade_NewOneWins_VerifyPostReboot");
372     }
373 
374     @Test
testInstallApex_DeviceDoesNotSupportApex_Fails()375     public void testInstallApex_DeviceDoesNotSupportApex_Fails() throws Exception {
376         assumeFalse("Device supports updating APEX", mHostUtils.isApexUpdateSupported());
377 
378         runPhase("testInstallApex_DeviceDoesNotSupportApex_Fails");
379     }
380 
installV2Apex()381     private void installV2Apex()throws Exception {
382         runPhase("testInstallV2Apex_Commit");
383         getDevice().reboot();
384         runPhase("testInstallV2Apex_VerifyPostReboot");
385     }
386 
installV2SignedBobApex()387     private void installV2SignedBobApex() throws Exception {
388         runPhase("testInstallV2SignedBobApex_Commit");
389         getDevice().reboot();
390         runPhase("testInstallV2SignedBobApex_VerifyPostReboot");
391     }
392 
installV3Apex()393     private void installV3Apex()throws Exception {
394         runPhase("testInstallV3Apex_Commit");
395         getDevice().reboot();
396         runPhase("testInstallV3Apex_VerifyPostReboot");
397     }
398 
399     @Test
testFailsInvalidApexInstall()400     public void testFailsInvalidApexInstall() throws Exception {
401         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
402         runPhase("testFailsInvalidApexInstall_Commit");
403         runPhase("testFailsInvalidApexInstall_AbandonSessionIsNoop");
404     }
405 
406     @Test
testStagedApkSessionCallbacks()407     public void testStagedApkSessionCallbacks() throws Exception {
408         runPhase("testStagedApkSessionCallbacks");
409     }
410 
411     @Test
412     @LargeTest
testInstallStagedApexWithoutApexSuffix()413     public void testInstallStagedApexWithoutApexSuffix() throws Exception {
414         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
415 
416         runPhase("testInstallStagedApexWithoutApexSuffix_Commit");
417         getDevice().reboot();
418         runPhase("testInstallStagedApexWithoutApexSuffix_VerifyPostReboot");
419     }
420 
421     @Test
testRejectsApexDifferentCertificate()422     public void testRejectsApexDifferentCertificate() throws Exception {
423         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
424 
425         runPhase("testRejectsApexDifferentCertificate");
426     }
427 
428     /**
429      * Tests for staged install involving rotated keys.
430      *
431      * Here alice means the original default key that cts.shim.v1 package was signed with and
432      * bob is the new key alice rotates to. Where ambiguous, we will refer keys as alice and bob
433      * instead of "old key" and "new key".
434      *
435      * By default, rotated keys have rollback capability enabled for old keys. When we remove
436      * rollback capability from a key, it is called "Distrusting Event" and the distrusted key can
437      * not update the app anymore.
438      */
439 
440     // Should not be able to update with a key that has not been rotated.
441     @Test
testUpdateWithDifferentKeyButNoRotation()442     public void testUpdateWithDifferentKeyButNoRotation() throws Exception {
443         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
444 
445         runPhase("testUpdateWithDifferentKeyButNoRotation");
446     }
447 
448     // Should be able to update with a key that has been rotated.
449     @Test
450     @LargeTest
testUpdateWithDifferentKey()451     public void testUpdateWithDifferentKey() throws Exception {
452         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
453 
454         runPhase("testUpdateWithDifferentKey_Commit");
455         getDevice().reboot();
456         runPhase("testUpdateWithDifferentKey_VerifyPostReboot");
457     }
458 
459     // Should not be able to update with a key that is no longer trusted (i.e, has no
460     // rollback capability)
461     @Test
462     @LargeTest
testUntrustedOldKeyIsRejected()463     public void testUntrustedOldKeyIsRejected() throws Exception {
464         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
465 
466         installV2SignedBobApex();
467         runPhase("testUntrustedOldKeyIsRejected");
468     }
469 
470     // Should be able to update with an old key which is trusted
471     @Test
472     @LargeTest
testTrustedOldKeyIsAccepted()473     public void testTrustedOldKeyIsAccepted() throws Exception {
474         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
475 
476         runPhase("testTrustedOldKeyIsAccepted_Commit");
477         getDevice().reboot();
478         runPhase("testTrustedOldKeyIsAccepted_CommitPostReboot");
479         getDevice().reboot();
480         runPhase("testTrustedOldKeyIsAccepted_VerifyPostReboot");
481     }
482 
483     // Should be able to update further with rotated key
484     @Test
485     @LargeTest
testAfterRotationNewKeyCanUpdateFurther()486     public void testAfterRotationNewKeyCanUpdateFurther() throws Exception {
487         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
488 
489         installV2SignedBobApex();
490         runPhase("testAfterRotationNewKeyCanUpdateFurther_CommitPostReboot");
491         getDevice().reboot();
492         runPhase("testAfterRotationNewKeyCanUpdateFurther_VerifyPostReboot");
493     }
494 
495     @Test
496     @LargeTest
testAfterRotationNewKeyCanUpdateFurtherWithoutLineage()497     public void testAfterRotationNewKeyCanUpdateFurtherWithoutLineage() throws Exception {
498         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
499 
500         installV2SignedBobApex();
501         runPhase("testAfterRotationNewKeyCanUpdateFurtherWithoutLineage");
502     }
503 
504     /**
505      * Tests for staging and installing multiple staged sessions.
506      */
507 
508     // Should fail to stage multiple sessions when check-point is not available
509     @Test
testFailStagingMultipleSessionsIfNoCheckPoint()510     public void testFailStagingMultipleSessionsIfNoCheckPoint() throws Exception {
511         assumeFalse("Device supports file-system checkpoint",
512                 mHostUtils.isCheckpointSupported());
513 
514         runPhase("testFailStagingMultipleSessionsIfNoCheckPoint");
515     }
516 
517     @Test
testFailOverlappingMultipleStagedInstall_BothSinglePackage_Apk()518     public void testFailOverlappingMultipleStagedInstall_BothSinglePackage_Apk() throws Exception {
519         runPhase("testFailOverlappingMultipleStagedInstall_BothSinglePackage_Apk");
520     }
521 
522     @Test
testAllowNonOverlappingMultipleStagedInstall_MultiPackageSinglePackage_Apk()523     public void testAllowNonOverlappingMultipleStagedInstall_MultiPackageSinglePackage_Apk()
524             throws Exception {
525         assumeTrue("Device does not support file-system checkpoint",
526                 mHostUtils.isCheckpointSupported());
527 
528         runPhase("testAllowNonOverlappingMultipleStagedInstall_MultiPackageSinglePackage_Apk");
529     }
530 
531     @Test
testFailOverlappingMultipleStagedInstall_BothMultiPackage_Apk()532     public void testFailOverlappingMultipleStagedInstall_BothMultiPackage_Apk() throws Exception {
533         assumeTrue("Device does not support file-system checkpoint",
534                 mHostUtils.isCheckpointSupported());
535 
536         runPhase("testFailOverlappingMultipleStagedInstall_BothMultiPackage_Apk");
537     }
538 
539     // Test for installing multiple staged sessions at the same time
540     @Test
541     @LargeTest
testMultipleStagedInstall_ApkOnly()542     public void testMultipleStagedInstall_ApkOnly() throws Exception {
543         assumeTrue("Device does not support file-system checkpoint",
544                 mHostUtils.isCheckpointSupported());
545 
546         runPhase("testMultipleStagedInstall_ApkOnly_Commit");
547         getDevice().reboot();
548         runPhase("testMultipleStagedInstall_ApkOnly_VerifyPostReboot");
549     }
550 
551     // If apk installation fails in one staged session, then all staged session should fail.
552     @Test
553     @LargeTest
testInstallMultipleStagedSession_PartialFail_ApkOnly()554     public void testInstallMultipleStagedSession_PartialFail_ApkOnly() throws Exception {
555         assumeTrue("Device does not support file-system checkpoint",
556                 mHostUtils.isCheckpointSupported());
557 
558         runPhase("testInstallMultipleStagedSession_PartialFail_ApkOnly_Commit");
559         getDevice().reboot();
560         runPhase("testInstallMultipleStagedSession_PartialFail_ApkOnly_VerifyPostReboot");
561     }
562 
563     // Failure reason of staged install should be be persisted for single sessions
564     @Test
565     @LargeTest
testFailureReasonPersists_SingleSession()566     public void testFailureReasonPersists_SingleSession() throws Exception {
567         assumeTrue("Device does not support file-system checkpoint",
568                 mHostUtils.isCheckpointSupported());
569 
570         runPhase("testFailureReasonPersists_SingleSession_Commit");
571         getDevice().reboot();
572         runPhase("testFailureReasonPersists_SingleSession_VerifyPostReboot");
573     }
574 
575     // Failure reason of staged install should be be persisted for multi session staged install
576     @Test
577     @LargeTest
testFailureReasonPersists_MultiSession()578     public void testFailureReasonPersists_MultiSession() throws Exception {
579         assumeTrue("Device does not support file-system checkpoint",
580                 mHostUtils.isCheckpointSupported());
581 
582         runPhase("testFailureReasonPersists_MultipleSession_Commit");
583         getDevice().reboot();
584         runPhase("testFailureReasonPersists_MultipleSession_VerifyPostReboot");
585     }
586 
587     @Test
588     @LargeTest
testSamegradeSystemApex()589     public void testSamegradeSystemApex() throws Exception {
590         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
591 
592         runPhase("testSamegradeSystemApex_Commit");
593         getDevice().reboot();
594         runPhase("testSamegradeSystemApex_VerifyPostReboot");
595     }
596 
597     @Test
598     @LargeTest
testInstallApkChangingFingerprint()599     public void testInstallApkChangingFingerprint() throws Exception {
600         try {
601             getDevice().executeShellCommand("setprop persist.pm.mock-upgrade true");
602             runPhase("testInstallApkChangingFingerprint");
603             getDevice().reboot();
604             runPhase("testInstallApkChangingFingerprint_VerifyAborted");
605         } finally {
606             getDevice().executeShellCommand("setprop persist.pm.mock-upgrade false");
607         }
608     }
609 
610     @Test
611     @LargeTest
testInstallStagedNoHashtreeApex()612     public void testInstallStagedNoHashtreeApex() throws Exception {
613         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
614 
615         runPhase("testInstallStagedNoHashtreeApex_Commit");
616         getDevice().reboot();
617         runPhase("testInstallStagedNoHashtreeApex_VerifyPostReboot");
618     }
619 
620     /**
621      * Should fail to verify apex targeting older dev sdk
622      */
623     @Test
testApexTargetingOldDevSdkFailsVerification()624     public void testApexTargetingOldDevSdkFailsVerification() throws Exception {
625         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
626 
627         runPhase("testApexTargetingOldDevSdkFailsVerification");
628     }
629 
630     /**
631      * Apex should fail to install if apk-in-apex fails to get scanned
632      */
633     @Test
634     @LargeTest
testApexFailsToInstallIfApkInApexFailsToScan()635     public void testApexFailsToInstallIfApkInApexFailsToScan() throws Exception {
636         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
637 
638         runPhase("testApexFailsToInstallIfApkInApexFailsToScan_Commit");
639         getDevice().reboot();
640         runPhase("testApexFailsToInstallIfApkInApexFailsToScan_VerifyPostReboot");
641     }
642 
643     @Test
testCorruptedApexFailsVerification_b146895998()644     public void testCorruptedApexFailsVerification_b146895998() throws Exception {
645         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
646 
647         runPhase("testCorruptedApexFailsVerification_b146895998");
648     }
649 
650     /**
651      * Should fail to pass apk signature check
652      */
653     @Test
testApexWithUnsignedApkFailsVerification()654     public void testApexWithUnsignedApkFailsVerification() throws Exception {
655         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
656 
657         runPhase("testApexWithUnsignedApkFailsVerification");
658     }
659 
660     /**
661      * Should fail to verify apex signed payload with a different key
662      */
663     @Test
testApexSignPayloadWithDifferentKeyFailsVerification()664     public void testApexSignPayloadWithDifferentKeyFailsVerification() throws Exception {
665         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
666 
667         runPhase("testApexSignPayloadWithDifferentKeyFailsVerification");
668     }
669 
670     /**
671      * Should fail to verify apex with unsigned payload
672      */
673     @Test
testApexWithUnsignedPayloadFailsVerification()674     public void testApexWithUnsignedPayloadFailsVerification() throws Exception {
675         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
676 
677         runPhase("testApexWithUnsignedPayloadFailsVerification");
678     }
679 
680     @Test
681     @LargeTest
testApexSetsUpdatedSystemAppFlag()682     public void testApexSetsUpdatedSystemAppFlag() throws Exception {
683         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
684 
685         runPhase("testApexSetsUpdatedSystemAppFlag_preUpdate");
686         installV2Apex();
687         runPhase("testApexSetsUpdatedSystemAppFlag_postUpdate");
688     }
689 
690     /**
691      * Test non-priv apps cannot access /data/app-staging folder contents
692      */
693     @Test
testAppStagingDirCannotBeReadByNonPrivApps()694     public void testAppStagingDirCannotBeReadByNonPrivApps() throws Exception {
695         runPhase("testAppStagingDirCannotBeReadByNonPrivApps");
696     }
697 
698     @Test
699     @LargeTest
testUpdatedApexFromDataApexActiveCanBePulled()700     public void testUpdatedApexFromDataApexActiveCanBePulled() throws Exception {
701         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
702 
703         installV2Apex();
704 
705         final ITestDevice.ApexInfo shimApex = mHostUtils.getShimApex().orElseThrow(
706                 () -> new AssertionError("Can't find " + SHIM_APEX_PACKAGE_NAME)
707         );
708 
709         assertThat(shimApex.sourceDir).startsWith("/data/apex/active");
710         assertThat(getDevice().pullFile(shimApex.sourceDir)).isNotNull();
711     }
712 
713     @Test
testApexInfoList()714     public void testApexInfoList() throws Exception {
715         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
716 
717         // Check that content of /apex/apex-info-list.xml matches output of
718         // `adb shell pm list packages --apex-only --show-versioncode -f`.
719         List<ApexInfo> apexInfoList = readApexInfoList();
720         Set<ITestDevice.ApexInfo> activeApexes = getDevice().getActiveApexes();
721         assertThat(apexInfoList.size()).isEqualTo(activeApexes.size());
722         for (ITestDevice.ApexInfo apex : activeApexes) {
723             // Note: we can't assert equality of the apex.name and apexInfo.getModuleName() since
724             // they represent different concepts (the former is package name, while latter is apex
725             // module name)
726             List<ApexInfo> temp =
727                     apexInfoList.stream()
728                             .filter(a -> a.getModulePath().equals(apex.sourceDir))
729                             .collect(Collectors.toList());
730             assertThat(temp).hasSize(1);
731             ApexInfo apexInfo = temp.get(0);
732             assertThat(apexInfo.getModulePath()).isEqualTo(apex.sourceDir);
733             assertThat(apexInfo.getVersionCode()).isEqualTo(apex.versionCode);
734             assertThat(apexInfo.getIsActive()).isTrue();
735         }
736     }
737 
738     @Test
testApexInfoListAfterUpdate()739     public void testApexInfoListAfterUpdate() throws Exception {
740         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
741 
742         installV2Apex();
743 
744         List<ApexInfo> shimApexInfo =
745                 readApexInfoList().stream()
746                         .filter(a -> a.getModuleName().equals(SHIM_APEX_PACKAGE_NAME))
747                         .collect(Collectors.toList());
748 
749         assertThat(shimApexInfo).hasSize(2);
750 
751         ApexInfo factoryShimApexInfo =
752                 shimApexInfo.stream()
753                         .filter(ApexInfo::getIsFactory)
754                         .findAny()
755                         .orElseThrow(() ->
756                                 new AssertionError(
757                                         "No factory version of " + SHIM_APEX_PACKAGE_NAME
758                                                 + " found in /apex/apex-info-list.xml"));
759         assertThat(factoryShimApexInfo.getModuleName()).isEqualTo(SHIM_APEX_PACKAGE_NAME);
760         assertThat(factoryShimApexInfo.getIsActive()).isFalse();
761         assertThat(factoryShimApexInfo.getIsFactory()).isTrue();
762         assertThat(factoryShimApexInfo.getVersionCode()).isEqualTo(1);
763         assertThat(factoryShimApexInfo.getModulePath())
764                 .isEqualTo(factoryShimApexInfo.getPreinstalledModulePath());
765 
766         ApexInfo activeShimApexInfo =
767                 shimApexInfo.stream()
768                         .filter(ApexInfo::getIsActive)
769                         .findAny()
770                         .orElseThrow(() ->
771                                 new AssertionError(
772                                         "No active version of " + SHIM_APEX_PACKAGE_NAME
773                                                 + " found in /apex/apex-info-list.xml"));
774         assertThat(activeShimApexInfo.getModuleName()).isEqualTo(SHIM_APEX_PACKAGE_NAME);
775         assertThat(activeShimApexInfo.getIsActive()).isTrue();
776         assertThat(activeShimApexInfo.getIsFactory()).isFalse();
777         assertThat(activeShimApexInfo.getVersionCode()).isEqualTo(2);
778         assertThat(activeShimApexInfo.getPreinstalledModulePath())
779                 .isEqualTo(factoryShimApexInfo.getModulePath());
780     }
781 
readApexInfoList()782     private List<ApexInfo> readApexInfoList() throws Exception {
783         File file = getDevice().pullFile("/apex/apex-info-list.xml");
784         try (FileInputStream stream = new FileInputStream(file)) {
785             return XmlParser.readApexInfoList(stream).getApexInfo();
786         }
787     }
788 
789     /**
790      * Store the component name of the default launcher. This value will be used to reset the
791      * default launcher to its correct component upon test completion.
792      */
storeDefaultLauncher()793     private void storeDefaultLauncher() throws DeviceNotAvailableException {
794         final String PREFIX = "Launcher: ComponentInfo{";
795         final String POSTFIX = "}";
796         for (String s : getDevice().executeShellCommand("cmd shortcut get-default-launcher")
797                 .split("\n")) {
798             if (s.startsWith(PREFIX) && s.endsWith(POSTFIX)) {
799                 mDefaultLauncher = s.substring(PREFIX.length(), s.length() - POSTFIX.length());
800             }
801         }
802     }
803 
804     /**
805      * Set the default launcher to a given component.
806      * If set to the broadcast receiver component of this test app, this will allow the test app to
807      * receive SESSION_COMMITTED broadcasts.
808      */
setDefaultLauncher(String launcherComponent)809     private void setDefaultLauncher(String launcherComponent) throws DeviceNotAvailableException {
810         assertThat(launcherComponent).isNotEmpty();
811         int user = getDevice().getCurrentUser();
812         getDevice().executeShellCommand(
813                 "cmd package set-home-activity --user " + user + " " + launcherComponent);
814     }
815 
816     private static final class FailedTestLogHook extends TestWatcher {
817 
818         private final BaseHostJUnit4Test mInstance;
819         private String mStagedSessionsBeforeTest;
820 
FailedTestLogHook(BaseHostJUnit4Test instance)821         private FailedTestLogHook(BaseHostJUnit4Test instance) {
822             this.mInstance = instance;
823         }
824 
825         @Override
failed(Throwable e, Description description)826         protected void failed(Throwable e, Description description) {
827             String stagedSessionsAfterTest = getStagedSessions();
828             Log.e(TAG, "Test " + description + " failed.\n"
829                     + "Staged sessions before test started:\n" + mStagedSessionsBeforeTest + "\n"
830                     + "Staged sessions after test failed:\n" + stagedSessionsAfterTest);
831         }
832 
833         @Override
starting(Description description)834         protected void starting(Description description) {
835             mStagedSessionsBeforeTest = getStagedSessions();
836         }
837 
getStagedSessions()838         private String getStagedSessions() {
839             try {
840                 return mInstance.getDevice().executeShellV2Command("pm get-stagedsessions").getStdout();
841             } catch (DeviceNotAvailableException e) {
842                 Log.e(TAG, e);
843                 return "Failed to get staged sessions";
844             }
845         }
846     }
847 
isDebuggable()848     private boolean isDebuggable() throws Exception {
849         return getDevice().getIntProperty("ro.debuggable", 0) == 1;
850     }
851 }
852