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         runPhase("testStageAnotherSessionImmediatelyAfterAbandon");
149     }
150 
151     @Test
testStageAnotherSessionImmediatelyAfterAbandonMultiPackage()152     public void testStageAnotherSessionImmediatelyAfterAbandonMultiPackage() throws Exception {
153         runPhase("testStageAnotherSessionImmediatelyAfterAbandonMultiPackage");
154     }
155 
156     @Test
testNoSessionUpdatedBroadcastSentForStagedSessionAbandon()157     public void testNoSessionUpdatedBroadcastSentForStagedSessionAbandon() throws Exception {
158         runPhase("testNoSessionUpdatedBroadcastSentForStagedSessionAbandon");
159     }
160 
161     @Test
162     @LargeTest
testInstallMultipleStagedApks()163     public void testInstallMultipleStagedApks() throws Exception {
164         assumeSystemUser();
165 
166         setDefaultLauncher(BROADCAST_RECEIVER_COMPONENT);
167         runPhase("testInstallMultipleStagedApks_Commit");
168         getDevice().reboot();
169         runPhase("testInstallMultipleStagedApks_VerifyPostReboot");
170     }
171 
assumeSystemUser()172     private void assumeSystemUser() throws Exception {
173         String systemUser = "0";
174         assumeThat("Current user is not system user",
175                 getDevice().executeShellCommand("am get-current-user").trim(), equalTo(systemUser));
176     }
177 
178     @Test
testGetActiveStagedSessions()179     public void testGetActiveStagedSessions() throws Exception {
180         assumeTrue("Device does not support file-system checkpoint",
181                 mHostUtils.isCheckpointSupported());
182 
183         runPhase("testGetActiveStagedSessions");
184     }
185 
186     /**
187      * Verifies that active staged session fulfils conditions stated at
188      * {@link PackageInstaller.SessionInfo#isStagedSessionActive}
189      */
190     @Test
testIsStagedSessionActive()191     public void testIsStagedSessionActive() throws Exception {
192         runPhase("testIsStagedSessionActive");
193     }
194 
195     @Test
testGetActiveStagedSessionsNoSessionActive()196     public void testGetActiveStagedSessionsNoSessionActive() throws Exception {
197         runPhase("testGetActiveStagedSessionsNoSessionActive");
198     }
199 
200     @Test
testGetActiveStagedSessions_MultiApkSession()201     public void testGetActiveStagedSessions_MultiApkSession() throws Exception {
202         assumeTrue("Device does not support file-system checkpoint",
203                 mHostUtils.isCheckpointSupported());
204 
205         runPhase("testGetActiveStagedSessions_MultiApkSession");
206     }
207 
208     @Test
testStagedInstallDowngrade_DowngradeNotRequested_Fails()209     public void testStagedInstallDowngrade_DowngradeNotRequested_Fails() throws Exception {
210         runPhase("testStagedInstallDowngrade_DowngradeNotRequested_Fails_Commit");
211     }
212 
213     @Test
214     @LargeTest
testStagedInstallDowngrade_DowngradeRequested_DebugBuild()215     public void testStagedInstallDowngrade_DowngradeRequested_DebugBuild() throws Exception {
216         assumeThat(getDevice().getBuildFlavor(), not(endsWith("-user")));
217 
218         runPhase("testStagedInstallDowngrade_DowngradeRequested_Commit");
219         getDevice().reboot();
220         runPhase("testStagedInstallDowngrade_DowngradeRequested_DebugBuild_VerifyPostReboot");
221     }
222 
223     @Test
testStagedInstallDowngrade_DowngradeRequested_UserBuild()224     public void testStagedInstallDowngrade_DowngradeRequested_UserBuild() throws Exception {
225         assumeThat(getDevice().getBuildFlavor(), endsWith("-user"));
226         assumeFalse("Device is debuggable", isDebuggable());
227 
228         runPhase("testStagedInstallDowngrade_DowngradeRequested_Fails_Commit");
229     }
230 
231     @Test
testShimApexShouldPreInstalledIfUpdatingApexIsSupported()232     public void testShimApexShouldPreInstalledIfUpdatingApexIsSupported() throws Exception {
233         final ITestDevice.ApexInfo shimApex = mHostUtils.getShimApex().orElseThrow(
234                 () -> new AssertionError("Can't find " + SHIM_APEX_PACKAGE_NAME)
235         );
236         assertThat(shimApex.versionCode).isEqualTo(1);
237     }
238 
239     @Test
240     @LargeTest
testInstallStagedApex()241     public void testInstallStagedApex() throws Exception {
242         setDefaultLauncher(BROADCAST_RECEIVER_COMPONENT);
243         runPhase("testInstallStagedApex_Commit");
244         getDevice().reboot();
245         runPhase("testInstallStagedApex_VerifyPostReboot");
246     }
247 
248     @Test
249     // Don't mark as @LargeTest since we want at least one test to install apex during pre-submit.
testInstallStagedApexAndApk()250     public void testInstallStagedApexAndApk() throws Exception {
251         assumeSystemUser();
252 
253         setDefaultLauncher(BROADCAST_RECEIVER_COMPONENT);
254         runPhase("testInstallStagedApexAndApk_Commit");
255         getDevice().reboot();
256         runPhase("testInstallStagedApexAndApk_VerifyPostReboot");
257     }
258 
259     @Test
testsFailsNonStagedApexInstall()260     public void testsFailsNonStagedApexInstall() throws Exception {
261         runPhase("testsFailsNonStagedApexInstall");
262     }
263 
264     @Test
testInstallStagedNonPreInstalledApex_Fails()265     public void testInstallStagedNonPreInstalledApex_Fails() throws Exception {
266         runPhase("testInstallStagedNonPreInstalledApex_Fails");
267     }
268 
269     @Test
testInstallStagedDifferentPackageNameWithInstalledApex_Fails()270     public void testInstallStagedDifferentPackageNameWithInstalledApex_Fails() throws Exception {
271         runPhase("testInstallStagedDifferentPackageNameWithInstalledApex_Fails");
272     }
273 
274     @Test
275     @LargeTest
testStageApkWithSameNameAsApexShouldFail()276     public void testStageApkWithSameNameAsApexShouldFail() throws Exception {
277         runPhase("testStageApkWithSameNameAsApexShouldFail_Commit");
278         getDevice().reboot();
279         runPhase("testStageApkWithSameNameAsApexShouldFail_VerifyPostReboot");
280     }
281 
282     @Test
testNonStagedInstallApkWithSameNameAsApexShouldFail()283     public void testNonStagedInstallApkWithSameNameAsApexShouldFail() throws Exception {
284         runPhase("testNonStagedInstallApkWithSameNameAsApexShouldFail");
285     }
286 
287     @Test
288     @LargeTest
testStagedInstallDowngradeApex_DowngradeNotRequested_Fails()289     public void testStagedInstallDowngradeApex_DowngradeNotRequested_Fails() throws Exception {
290         installV3Apex();
291         runPhase("testStagedInstallDowngradeApex_DowngradeNotRequested_Fails_Commit");
292         getDevice().reboot();
293         runPhase("testStagedInstallDowngradeApex_DowngradeNotRequested_Fails_VerifyPostReboot");
294     }
295 
296     @Test
297     @LargeTest
testStagedInstallDowngradeApex_DowngradeRequested_DebugBuild()298     public void testStagedInstallDowngradeApex_DowngradeRequested_DebugBuild() throws Exception {
299         assumeThat(getDevice().getBuildFlavor(), not(endsWith("-user")));
300 
301         installV3Apex();
302         runPhase("testStagedInstallDowngradeApex_DowngradeRequested_DebugBuild_Commit");
303         getDevice().reboot();
304         runPhase("testStagedInstallDowngradeApex_DowngradeRequested_DebugBuild_VerifyPostReboot");
305     }
306 
307     @Test
308     @LargeTest
testStagedInstallDowngradeApex_DowngradeRequested_UserBuild_Fails()309     public void testStagedInstallDowngradeApex_DowngradeRequested_UserBuild_Fails()
310             throws Exception {
311         assumeThat(getDevice().getBuildFlavor(), endsWith("-user"));
312         assumeFalse("Device is debuggable", isDebuggable());
313 
314         installV3Apex();
315         runPhase("testStagedInstallDowngradeApex_DowngradeRequested_UserBuild_Fails_Commit");
316         getDevice().reboot();
317         runPhase("testStagedInstallDowngradeApex_DowngradeRequested_UserBuild_Fails_"
318                 + "VerifyPostReboot");
319     }
320 
321     @Test
322     @LargeTest
testStagedInstallDowngradeApexToSystemVersion_DebugBuild()323     public void testStagedInstallDowngradeApexToSystemVersion_DebugBuild() throws Exception {
324         assumeThat(getDevice().getBuildFlavor(), not(endsWith("-user")));
325 
326         installV2Apex();
327         runPhase("testStagedInstallDowngradeApexToSystemVersion_DebugBuild_Commit");
328         getDevice().reboot();
329         runPhase("testStagedInstallDowngradeApexToSystemVersion_DebugBuild_VerifyPostReboot");
330     }
331 
332     @Test
333     @LargeTest
testInstallStagedApex_SameGrade()334     public void testInstallStagedApex_SameGrade() throws Exception {
335         installV3Apex();
336         ApexInfo shim1 =
337                 readApexInfoList().stream()
338                         .filter(a -> a.getModuleName().equals(SHIM_APEX_PACKAGE_NAME))
339                         .filter(ApexInfo::getIsActive)
340                         .findAny()
341                         .orElseThrow(() ->
342                                 new AssertionError(
343                                         "No active version of " + SHIM_APEX_PACKAGE_NAME
344                                                 + " found in /apex/apex-info-list.xml"));
345 
346         installV3Apex();
347         ApexInfo shim2 =
348                 readApexInfoList().stream()
349                         .filter(a -> a.getModuleName().equals(SHIM_APEX_PACKAGE_NAME))
350                         .filter(ApexInfo::getIsActive)
351                         .findAny()
352                         .orElseThrow(() ->
353                                 new AssertionError(
354                                         "No active version of " + SHIM_APEX_PACKAGE_NAME
355                                                 + " found in /apex/apex-info-list.xml"));
356         assertThat(shim1.getLastUpdateMillis()).isNotEqualTo(shim2.getLastUpdateMillis());
357     }
358 
359     @Test
360     @LargeTest
testInstallStagedApex_SameGrade_NewOneWins()361     public void testInstallStagedApex_SameGrade_NewOneWins() throws Exception {
362         installV2Apex();
363 
364         runPhase("testInstallStagedApex_SameGrade_NewOneWins_Commit");
365         getDevice().reboot();
366         runPhase("testInstallStagedApex_SameGrade_NewOneWins_VerifyPostReboot");
367     }
368 
installV2Apex()369     private void installV2Apex()throws Exception {
370         runPhase("testInstallV2Apex_Commit");
371         getDevice().reboot();
372         runPhase("testInstallV2Apex_VerifyPostReboot");
373     }
374 
installV2SignedBobApex()375     private void installV2SignedBobApex() throws Exception {
376         runPhase("testInstallV2SignedBobApex_Commit");
377         getDevice().reboot();
378         runPhase("testInstallV2SignedBobApex_VerifyPostReboot");
379     }
380 
installV3Apex()381     private void installV3Apex()throws Exception {
382         runPhase("testInstallV3Apex_Commit");
383         getDevice().reboot();
384         runPhase("testInstallV3Apex_VerifyPostReboot");
385     }
386 
387     @Test
testFailsInvalidApexInstall()388     public void testFailsInvalidApexInstall() throws Exception {
389         runPhase("testFailsInvalidApexInstall_Commit");
390         runPhase("testFailsInvalidApexInstall_AbandonSessionIsNoop");
391     }
392 
393     @Test
testStagedApkSessionCallbacks()394     public void testStagedApkSessionCallbacks() throws Exception {
395         runPhase("testStagedApkSessionCallbacks");
396     }
397 
398     @Test
399     @LargeTest
testInstallStagedApexWithoutApexSuffix()400     public void testInstallStagedApexWithoutApexSuffix() throws Exception {
401         runPhase("testInstallStagedApexWithoutApexSuffix_Commit");
402         getDevice().reboot();
403         runPhase("testInstallStagedApexWithoutApexSuffix_VerifyPostReboot");
404     }
405 
406     @Test
testRejectsApexDifferentCertificate()407     public void testRejectsApexDifferentCertificate() throws Exception {
408         runPhase("testRejectsApexDifferentCertificate");
409     }
410 
411     /**
412      * Tests for staged install involving rotated keys.
413      *
414      * Here alice means the original default key that cts.shim.v1 package was signed with and
415      * bob is the new key alice rotates to. Where ambiguous, we will refer keys as alice and bob
416      * instead of "old key" and "new key".
417      *
418      * By default, rotated keys have rollback capability enabled for old keys. When we remove
419      * rollback capability from a key, it is called "Distrusting Event" and the distrusted key can
420      * not update the app anymore.
421      */
422 
423     // Should not be able to update with a key that has not been rotated.
424     @Test
testUpdateWithDifferentKeyButNoRotation()425     public void testUpdateWithDifferentKeyButNoRotation() throws Exception {
426         runPhase("testUpdateWithDifferentKeyButNoRotation");
427     }
428 
429     // Should be able to update with a key that has been rotated.
430     @Test
431     @LargeTest
testUpdateWithDifferentKey()432     public void testUpdateWithDifferentKey() throws Exception {
433         runPhase("testUpdateWithDifferentKey_Commit");
434         getDevice().reboot();
435         runPhase("testUpdateWithDifferentKey_VerifyPostReboot");
436     }
437 
438     // Should not be able to update with a key that is no longer trusted (i.e, has no
439     // rollback capability)
440     @Test
441     @LargeTest
testUntrustedOldKeyIsRejected()442     public void testUntrustedOldKeyIsRejected() throws Exception {
443         installV2SignedBobApex();
444         runPhase("testUntrustedOldKeyIsRejected");
445     }
446 
447     // Should be able to update with an old key which is trusted
448     @Test
449     @LargeTest
testTrustedOldKeyIsAccepted()450     public void testTrustedOldKeyIsAccepted() throws Exception {
451         runPhase("testTrustedOldKeyIsAccepted_Commit");
452         getDevice().reboot();
453         runPhase("testTrustedOldKeyIsAccepted_CommitPostReboot");
454         getDevice().reboot();
455         runPhase("testTrustedOldKeyIsAccepted_VerifyPostReboot");
456     }
457 
458     // Should be able to update further with rotated key
459     @Test
460     @LargeTest
testAfterRotationNewKeyCanUpdateFurther()461     public void testAfterRotationNewKeyCanUpdateFurther() throws Exception {
462         installV2SignedBobApex();
463         runPhase("testAfterRotationNewKeyCanUpdateFurther_CommitPostReboot");
464         getDevice().reboot();
465         runPhase("testAfterRotationNewKeyCanUpdateFurther_VerifyPostReboot");
466     }
467 
468     @Test
469     @LargeTest
testAfterRotationNewKeyCanUpdateFurtherWithoutLineage()470     public void testAfterRotationNewKeyCanUpdateFurtherWithoutLineage() throws Exception {
471         installV2SignedBobApex();
472         runPhase("testAfterRotationNewKeyCanUpdateFurtherWithoutLineage");
473     }
474 
475     /**
476      * Tests for staging and installing multiple staged sessions.
477      */
478 
479     // Should fail to stage multiple sessions when check-point is not available
480     @Test
testFailStagingMultipleSessionsIfNoCheckPoint()481     public void testFailStagingMultipleSessionsIfNoCheckPoint() throws Exception {
482         assumeFalse("Device supports file-system checkpoint",
483                 mHostUtils.isCheckpointSupported());
484 
485         runPhase("testFailStagingMultipleSessionsIfNoCheckPoint");
486     }
487 
488     @Test
testFailOverlappingMultipleStagedInstall_BothSinglePackage_Apk()489     public void testFailOverlappingMultipleStagedInstall_BothSinglePackage_Apk() throws Exception {
490         assumeTrue("Device does not support file-system checkpoint",
491                 mHostUtils.isCheckpointSupported());
492 
493         runPhase("testFailOverlappingMultipleStagedInstall_BothSinglePackage_Apk");
494     }
495 
496     @Test
testAllowNonOverlappingMultipleStagedInstall_MultiPackageSinglePackage_Apk()497     public void testAllowNonOverlappingMultipleStagedInstall_MultiPackageSinglePackage_Apk()
498             throws Exception {
499         assumeTrue("Device does not support file-system checkpoint",
500                 mHostUtils.isCheckpointSupported());
501 
502         runPhase("testAllowNonOverlappingMultipleStagedInstall_MultiPackageSinglePackage_Apk");
503     }
504 
505     @Test
testFailOverlappingMultipleStagedInstall_BothMultiPackage_Apk()506     public void testFailOverlappingMultipleStagedInstall_BothMultiPackage_Apk() throws Exception {
507         assumeTrue("Device does not support file-system checkpoint",
508                 mHostUtils.isCheckpointSupported());
509 
510         runPhase("testFailOverlappingMultipleStagedInstall_BothMultiPackage_Apk");
511     }
512 
513     // Test for installing multiple staged sessions at the same time
514     @Test
515     @LargeTest
testMultipleStagedInstall_ApkOnly()516     public void testMultipleStagedInstall_ApkOnly() throws Exception {
517         assumeTrue("Device does not support file-system checkpoint",
518                 mHostUtils.isCheckpointSupported());
519 
520         runPhase("testMultipleStagedInstall_ApkOnly_Commit");
521         getDevice().reboot();
522         runPhase("testMultipleStagedInstall_ApkOnly_VerifyPostReboot");
523     }
524 
525     // If apk installation fails in one staged session, then all staged session should fail.
526     @Test
527     @LargeTest
testInstallMultipleStagedSession_PartialFail_ApkOnly()528     public void testInstallMultipleStagedSession_PartialFail_ApkOnly() throws Exception {
529         assumeTrue("Device does not support file-system checkpoint",
530                 mHostUtils.isCheckpointSupported());
531 
532         runPhase("testInstallMultipleStagedSession_PartialFail_ApkOnly_Commit");
533         getDevice().reboot();
534         runPhase("testInstallMultipleStagedSession_PartialFail_ApkOnly_VerifyPostReboot");
535     }
536 
537     // Failure reason of staged install should be be persisted for single sessions
538     @Test
539     @LargeTest
testFailureReasonPersists_SingleSession()540     public void testFailureReasonPersists_SingleSession() throws Exception {
541         assumeTrue("Device does not support file-system checkpoint",
542                 mHostUtils.isCheckpointSupported());
543 
544         runPhase("testFailureReasonPersists_SingleSession_Commit");
545         getDevice().reboot();
546         runPhase("testFailureReasonPersists_SingleSession_VerifyPostReboot");
547     }
548 
549     // Failure reason of staged install should be be persisted for multi session staged install
550     @Test
551     @LargeTest
testFailureReasonPersists_MultiSession()552     public void testFailureReasonPersists_MultiSession() throws Exception {
553         assumeTrue("Device does not support file-system checkpoint",
554                 mHostUtils.isCheckpointSupported());
555 
556         runPhase("testFailureReasonPersists_MultipleSession_Commit");
557         getDevice().reboot();
558         runPhase("testFailureReasonPersists_MultipleSession_VerifyPostReboot");
559     }
560 
561     @Test
562     @LargeTest
testSamegradeSystemApex()563     public void testSamegradeSystemApex() throws Exception {
564         runPhase("testSamegradeSystemApex_Commit");
565         getDevice().reboot();
566         runPhase("testSamegradeSystemApex_VerifyPostReboot");
567     }
568 
569     @Test
570     @LargeTest
testInstallApkChangingFingerprint()571     public void testInstallApkChangingFingerprint() throws Exception {
572         try {
573             getDevice().executeShellCommand("setprop persist.pm.mock-upgrade true");
574             runPhase("testInstallApkChangingFingerprint");
575             getDevice().reboot();
576             runPhase("testInstallApkChangingFingerprint_VerifyAborted");
577         } finally {
578             getDevice().executeShellCommand("setprop persist.pm.mock-upgrade false");
579         }
580     }
581 
582     @Test
583     @LargeTest
testInstallStagedNoHashtreeApex()584     public void testInstallStagedNoHashtreeApex() throws Exception {
585         runPhase("testInstallStagedNoHashtreeApex_Commit");
586         getDevice().reboot();
587         runPhase("testInstallStagedNoHashtreeApex_VerifyPostReboot");
588     }
589 
590     /**
591      * Should fail to verify apex targeting older dev sdk
592      */
593     @Test
testApexTargetingOldDevSdkFailsVerification()594     public void testApexTargetingOldDevSdkFailsVerification() throws Exception {
595         runPhase("testApexTargetingOldDevSdkFailsVerification");
596     }
597 
598     /**
599      * Apex should fail to install if apk-in-apex fails to get scanned
600      */
601     @Test
602     @LargeTest
testApexFailsToInstallIfApkInApexFailsToScan()603     public void testApexFailsToInstallIfApkInApexFailsToScan() throws Exception {
604         runPhase("testApexFailsToInstallIfApkInApexFailsToScan_Commit");
605         getDevice().reboot();
606         runPhase("testApexFailsToInstallIfApkInApexFailsToScan_VerifyPostReboot");
607     }
608 
609     @Test
testCorruptedApexFailsVerification_b146895998()610     public void testCorruptedApexFailsVerification_b146895998() throws Exception {
611         runPhase("testCorruptedApexFailsVerification_b146895998");
612     }
613 
614     /**
615      * Should fail to pass apk signature check
616      */
617     @Test
testApexWithUnsignedApkFailsVerification()618     public void testApexWithUnsignedApkFailsVerification() throws Exception {
619         runPhase("testApexWithUnsignedApkFailsVerification");
620     }
621 
622     /**
623      * Should fail to verify apex signed payload with a different key
624      */
625     @Test
testApexSignPayloadWithDifferentKeyFailsVerification()626     public void testApexSignPayloadWithDifferentKeyFailsVerification() throws Exception {
627         runPhase("testApexSignPayloadWithDifferentKeyFailsVerification");
628     }
629 
630     /**
631      * Should fail to verify apex with unsigned payload
632      */
633     @Test
testApexWithUnsignedPayloadFailsVerification()634     public void testApexWithUnsignedPayloadFailsVerification() throws Exception {
635         runPhase("testApexWithUnsignedPayloadFailsVerification");
636     }
637 
638     @Test
639     @LargeTest
testApexSetsUpdatedSystemAppFlag()640     public void testApexSetsUpdatedSystemAppFlag() throws Exception {
641         runPhase("testApexSetsUpdatedSystemAppFlag_preUpdate");
642         installV2Apex();
643         runPhase("testApexSetsUpdatedSystemAppFlag_postUpdate");
644     }
645 
646     /**
647      * Test non-priv apps cannot access /data/app-staging folder contents
648      */
649     @Test
testAppStagingDirCannotBeReadByNonPrivApps()650     public void testAppStagingDirCannotBeReadByNonPrivApps() throws Exception {
651         runPhase("testAppStagingDirCannotBeReadByNonPrivApps");
652     }
653 
654     @Test
655     @LargeTest
testUpdatedApexFromDataApexActiveCanBePulled()656     public void testUpdatedApexFromDataApexActiveCanBePulled() throws Exception {
657         installV2Apex();
658 
659         final ITestDevice.ApexInfo shimApex = mHostUtils.getShimApex().orElseThrow(
660                 () -> new AssertionError("Can't find " + SHIM_APEX_PACKAGE_NAME)
661         );
662 
663         assertThat(shimApex.sourceDir).startsWith("/data/apex/active");
664         assertThat(getDevice().pullFile(shimApex.sourceDir)).isNotNull();
665     }
666 
667     @Test
testApexInfoList()668     public void testApexInfoList() throws Exception {
669         // Check that content of /apex/apex-info-list.xml matches output of
670         // `adb shell pm list packages --apex-only --show-versioncode -f`.
671         List<ApexInfo> apexInfoList =
672                 readApexInfoList().stream()
673                     .filter(a -> a.getIsActive())
674                     .collect(Collectors.toList());
675         Set<ITestDevice.ApexInfo> activeApexes = getDevice().getActiveApexes();
676         assertThat(apexInfoList.size()).isEqualTo(activeApexes.size());
677         for (ITestDevice.ApexInfo apex : activeApexes) {
678             // Note: we can't assert equality of the apex.name and apexInfo.getModuleName() since
679             // they represent different concepts (the former is package name, while latter is apex
680             // module name)
681             List<ApexInfo> temp =
682                     apexInfoList.stream()
683                             .filter(a -> a.getModulePath().equals(apex.sourceDir))
684                             .collect(Collectors.toList());
685             assertThat(temp).hasSize(1);
686             ApexInfo apexInfo = temp.get(0);
687             assertThat(apexInfo.getModulePath()).isEqualTo(apex.sourceDir);
688             assertThat(apexInfo.getVersionCode()).isEqualTo(apex.versionCode);
689             assertThat(apexInfo.getIsActive()).isTrue();
690             assertThat(apexInfo.getLastUpdateMillis()).isGreaterThan(0);
691         }
692     }
693 
694     @Test
695     @LargeTest
testApexInfoListAfterUpdate()696     public void testApexInfoListAfterUpdate() throws Exception {
697         ApexInfo shimBeforeUpdate = getShimApexInfo();
698 
699         installV2Apex();
700 
701         List<ApexInfo> shimApexInfo =
702                 readApexInfoList().stream()
703                         .filter(a -> a.getModuleName().equals(SHIM_APEX_PACKAGE_NAME))
704                         .collect(Collectors.toList());
705 
706         assertThat(shimApexInfo).hasSize(2);
707 
708         ApexInfo factoryShimApexInfo =
709                 shimApexInfo.stream()
710                         .filter(ApexInfo::getIsFactory)
711                         .findAny()
712                         .orElseThrow(() ->
713                                 new AssertionError(
714                                         "No factory version of " + SHIM_APEX_PACKAGE_NAME
715                                                 + " found in /apex/apex-info-list.xml"));
716         assertThat(factoryShimApexInfo.getModuleName()).isEqualTo(SHIM_APEX_PACKAGE_NAME);
717         assertThat(factoryShimApexInfo.getIsActive()).isFalse();
718         assertThat(factoryShimApexInfo.getIsFactory()).isTrue();
719         assertThat(factoryShimApexInfo.getVersionCode()).isEqualTo(1);
720         assertThat(factoryShimApexInfo.getModulePath())
721                 .isEqualTo(factoryShimApexInfo.getPreinstalledModulePath());
722         assertThat(factoryShimApexInfo.getLastUpdateMillis())
723                 .isEqualTo(shimBeforeUpdate.getLastUpdateMillis());
724 
725         ApexInfo activeShimApexInfo =
726                 shimApexInfo.stream()
727                         .filter(ApexInfo::getIsActive)
728                         .findAny()
729                         .orElseThrow(() ->
730                                 new AssertionError(
731                                         "No active version of " + SHIM_APEX_PACKAGE_NAME
732                                                 + " found in /apex/apex-info-list.xml"));
733         assertThat(activeShimApexInfo.getModuleName()).isEqualTo(SHIM_APEX_PACKAGE_NAME);
734         assertThat(activeShimApexInfo.getIsActive()).isTrue();
735         assertThat(activeShimApexInfo.getIsFactory()).isFalse();
736         assertThat(activeShimApexInfo.getVersionCode()).isEqualTo(2);
737         assertThat(activeShimApexInfo.getPreinstalledModulePath())
738                 .isEqualTo(factoryShimApexInfo.getModulePath());
739         assertThat(activeShimApexInfo.getLastUpdateMillis())
740                 .isNotEqualTo(shimBeforeUpdate.getLastUpdateMillis());
741     }
742 
743     @Test
744     @LargeTest
testRebootlessUpdate()745     public void testRebootlessUpdate() throws Exception {
746         runPhase("testRebootlessUpdate");
747         ApexInfo activeShimApexInfo = getActiveShimApexInfo();
748         assertThat(activeShimApexInfo.getModuleName()).isEqualTo(SHIM_APEX_PACKAGE_NAME);
749         assertThat(activeShimApexInfo.getIsActive()).isTrue();
750         assertThat(activeShimApexInfo.getIsFactory()).isFalse();
751         assertThat(activeShimApexInfo.getVersionCode()).isEqualTo(2);
752     }
753 
754     @Test
755     @LargeTest
testRebootlessUpdate_fromV2ToV3_sameBoot()756     public void testRebootlessUpdate_fromV2ToV3_sameBoot() throws Exception {
757         runPhase("testRebootlessUpdate");
758         runPhase("testRebootlessUpdate_installV3");
759         ApexInfo activeShimApexInfo = getActiveShimApexInfo();
760         assertThat(activeShimApexInfo.getModuleName()).isEqualTo(SHIM_APEX_PACKAGE_NAME);
761         assertThat(activeShimApexInfo.getIsActive()).isTrue();
762         assertThat(activeShimApexInfo.getIsFactory()).isFalse();
763         assertThat(activeShimApexInfo.getVersionCode()).isEqualTo(3);
764     }
765 
766     @Test
767     @LargeTest
testRebootlessUpdate_fromV2ToV3_rebootInBetween()768     public void testRebootlessUpdate_fromV2ToV3_rebootInBetween() throws Exception {
769         runPhase("testRebootlessUpdate");
770         getDevice().reboot();
771         runPhase("testRebootlessUpdate_installV3");
772         ApexInfo activeShimApexInfo = getActiveShimApexInfo();
773         assertThat(activeShimApexInfo.getModuleName()).isEqualTo(SHIM_APEX_PACKAGE_NAME);
774         assertThat(activeShimApexInfo.getIsActive()).isTrue();
775         assertThat(activeShimApexInfo.getIsFactory()).isFalse();
776         assertThat(activeShimApexInfo.getVersionCode()).isEqualTo(3);
777     }
778 
779     @Test
780     @LargeTest
testRebootlessUpdate_downgrage_fails()781     public void testRebootlessUpdate_downgrage_fails() throws Exception {
782         runPhase("testRebootlessUpdate_installV3");
783         runPhase("testRebootlessUpdate_downgradeToV2_fails");
784     }
785 
786     @Test
testRebootlessUpdate_noPermission_fails()787     public void testRebootlessUpdate_noPermission_fails() throws Exception {
788         runPhase("testRebootlessUpdate_noPermission_fails");
789     }
790 
791     @Test
testRebootlessUpdate_noPreInstalledApex_fails()792     public void testRebootlessUpdate_noPreInstalledApex_fails() throws Exception {
793         runPhase("testRebootlessUpdate_noPreInstalledApex_fails");
794     }
795 
796     @Test
testRebootlessUpdate_unsignedPayload_fails()797     public void testRebootlessUpdate_unsignedPayload_fails() throws Exception {
798         runPhase("testRebootlessUpdate_unsignedPayload_fails");
799     }
800 
801     @Test
testRebootlessUpdate_payloadSignedWithDifferentKey_fails()802     public void testRebootlessUpdate_payloadSignedWithDifferentKey_fails() throws Exception {
803         runPhase("testRebootlessUpdate_payloadSignedWithDifferentKey_fails");
804     }
805 
806     @Test
testRebootlessUpdate_outerContainerSignedWithDifferentCert_fails()807     public void testRebootlessUpdate_outerContainerSignedWithDifferentCert_fails()
808             throws Exception {
809         runPhase("testRebootlessUpdate_outerContainerSignedWithDifferentCert_fails");
810     }
811 
812     @Test
testRebootlessUpdate_outerContainerUnsigned_fails()813     public void testRebootlessUpdate_outerContainerUnsigned_fails() throws Exception {
814         runPhase("testRebootlessUpdate_outerContainerUnsigned_fails");
815     }
816 
817     @Test
testRebootlessUpdate_targetsOlderSdk_fails()818     public void testRebootlessUpdate_targetsOlderSdk_fails() throws Exception {
819         runPhase("testRebootlessUpdate_targetsOlderSdk_fails");
820     }
821 
822     @Test
823     @LargeTest
testGetInactiveApexFactoryPackagesAfterApexInstall_containsNoDuplicates()824     public void testGetInactiveApexFactoryPackagesAfterApexInstall_containsNoDuplicates()
825             throws Exception {
826         installV2Apex();
827         runPhase("testGetInactiveApexFactoryPackagesAfterApexInstall_containsNoDuplicates");
828     }
829 
readApexInfoList()830     private List<ApexInfo> readApexInfoList() throws Exception {
831         File file = getDevice().pullFile("/apex/apex-info-list.xml");
832         try (FileInputStream stream = new FileInputStream(file)) {
833             return XmlParser.readApexInfoList(stream).getApexInfo();
834         }
835     }
836 
getShimApexInfo()837     private ApexInfo getShimApexInfo() throws Exception {
838         List<ApexInfo> temp =
839                 readApexInfoList().stream()
840                             .filter(a -> a.getModuleName().equals(SHIM_APEX_PACKAGE_NAME))
841                             .collect(Collectors.toList());
842         assertThat(temp).hasSize(1);
843         return temp.get(0);
844     }
845 
getActiveShimApexInfo()846     private ApexInfo getActiveShimApexInfo() throws Exception {
847         return readApexInfoList().stream()
848                     .filter(a -> a.getModuleName().equals(SHIM_APEX_PACKAGE_NAME))
849                     .filter(ApexInfo::getIsActive)
850                     .findAny()
851                     .orElseThrow(() ->
852                             new AssertionError(
853                                     "No active version of " + SHIM_APEX_PACKAGE_NAME
854                                             + " found in /apex/apex-info-list.xml"));
855     }
856 
857     /**
858      * Store the component name of the default launcher. This value will be used to reset the
859      * default launcher to its correct component upon test completion.
860      */
storeDefaultLauncher()861     private void storeDefaultLauncher() throws DeviceNotAvailableException {
862         final String PREFIX = "Launcher: ComponentInfo{";
863         final String POSTFIX = "}";
864         for (String s : getDevice().executeShellCommand("cmd shortcut get-default-launcher")
865                 .split("\n")) {
866             if (s.startsWith(PREFIX) && s.endsWith(POSTFIX)) {
867                 mDefaultLauncher = s.substring(PREFIX.length(), s.length() - POSTFIX.length());
868             }
869         }
870     }
871 
872     /**
873      * Set the default launcher to a given component.
874      * If set to the broadcast receiver component of this test app, this will allow the test app to
875      * receive SESSION_COMMITTED broadcasts.
876      */
setDefaultLauncher(String launcherComponent)877     private void setDefaultLauncher(String launcherComponent) throws DeviceNotAvailableException {
878         assertThat(launcherComponent).isNotEmpty();
879         int user = getDevice().getCurrentUser();
880         getDevice().executeShellCommand(
881                 "cmd package set-home-activity --user " + user + " " + launcherComponent);
882     }
883 
884     private static final class FailedTestLogHook extends TestWatcher {
885 
886         private final BaseHostJUnit4Test mInstance;
887         private String mStagedSessionsBeforeTest;
888 
FailedTestLogHook(BaseHostJUnit4Test instance)889         private FailedTestLogHook(BaseHostJUnit4Test instance) {
890             this.mInstance = instance;
891         }
892 
893         @Override
failed(Throwable e, Description description)894         protected void failed(Throwable e, Description description) {
895             String stagedSessionsAfterTest = getStagedSessions();
896             Log.e(TAG, "Test " + description + " failed.\n"
897                     + "Staged sessions before test started:\n" + mStagedSessionsBeforeTest + "\n"
898                     + "Staged sessions after test failed:\n" + stagedSessionsAfterTest);
899         }
900 
901         @Override
starting(Description description)902         protected void starting(Description description) {
903             mStagedSessionsBeforeTest = getStagedSessions();
904         }
905 
getStagedSessions()906         private String getStagedSessions() {
907             try {
908                 return mInstance.getDevice().executeShellV2Command("pm get-stagedsessions").getStdout();
909             } catch (DeviceNotAvailableException e) {
910                 Log.e(TAG, e);
911                 return "Failed to get staged sessions";
912             }
913         }
914     }
915 
isDebuggable()916     private boolean isDebuggable() throws Exception {
917         return getDevice().getIntProperty("ro.debuggable", 0) == 1;
918     }
919 }
920