1 /*
2  * Copyright (C) 2020 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.stagedinstallinternal.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.junit.Assert.assertTrue;
24 import static org.junit.Assert.fail;
25 import static org.junit.Assume.assumeTrue;
26 
27 import android.cts.install.lib.host.InstallUtilsHost;
28 import android.platform.test.annotations.LargeTest;
29 
30 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
31 import com.android.ddmlib.Log;
32 import com.android.tests.rollback.host.AbandonSessionsRule;
33 import com.android.tradefed.device.DeviceNotAvailableException;
34 import com.android.tradefed.device.PackageInfo;
35 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
36 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
37 import com.android.tradefed.util.CommandResult;
38 import com.android.tradefed.util.CommandStatus;
39 import com.android.tradefed.util.ProcessInfo;
40 
41 import org.junit.After;
42 import org.junit.Before;
43 import org.junit.Rule;
44 import org.junit.Test;
45 import org.junit.runner.RunWith;
46 
47 import java.io.BufferedWriter;
48 import java.io.File;
49 import java.io.FileWriter;
50 import java.util.List;
51 import java.util.stream.Collectors;
52 
53 @RunWith(DeviceJUnit4ClassRunner.class)
54 public class StagedInstallInternalTest extends BaseHostJUnit4Test {
55 
56     private static final String TAG = StagedInstallInternalTest.class.getSimpleName();
57     private static final long SYSTEM_SERVER_TIMEOUT_MS = 60 * 1000;
58 
59     @Rule
60     public AbandonSessionsRule mHostTestRule = new AbandonSessionsRule(this);
61     private static final String SHIM_V2 = "com.android.apex.cts.shim.v2.apex";
62     private static final String APEX_WRONG_SHA = "com.android.apex.cts.shim.v2_wrong_sha.apex";
63     private static final String APK_A = "TestAppAv1.apk";
64     private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test";
65     private static final String APEXD_TEST_APEX = "apex.apexd_test.apex";
66     private static final String FAKE_APEX_SYSTEM_SERVER_APEX = "test_com.android.server.apex";
67     private static final String REBOOTLESS_V1 = "test.rebootless_apex_v1.apex";
68     private static final String REBOOTLESS_V2 = "test.rebootless_apex_v2.apex";
69 
70     private static final String TEST_VENDOR_APEX_ALLOW_LIST =
71             "/vendor/etc/sysconfig/test-vendor-apex-allow-list.xml";
72 
73     private final InstallUtilsHost mHostUtils = new InstallUtilsHost(this);
74 
75     /**
76      * Runs the given phase of a test by calling into the device.
77      * Throws an exception if the test phase fails.
78      * <p>
79      * For example, <code>runPhase("testApkOnlyEnableRollback");</code>
80      */
runPhase(String phase)81     private void runPhase(String phase) throws Exception {
82         assertTrue(runDeviceTests("com.android.tests.stagedinstallinternal",
83                 "com.android.tests.stagedinstallinternal.StagedInstallInternalTest",
84                 phase));
85     }
86 
87     // We do not assert the success of cleanup phase since it might fail due to flaky reasons.
cleanUp()88     private void cleanUp() throws Exception {
89         try {
90             runDeviceTests("com.android.tests.stagedinstallinternal",
91                     "com.android.tests.stagedinstallinternal.StagedInstallInternalTest",
92                     "cleanUp");
93         } catch (AssertionError e) {
94             Log.e(TAG, e);
95         }
96         deleteFiles("/system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex",
97                 "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex",
98                 "/data/apex/active/" + SHIM_APEX_PACKAGE_NAME + "*.apex",
99                 "/system/apex/test.rebootless_apex_v*.apex",
100                 "/vendor/apex/test.rebootless_apex_v*.apex",
101                 "/data/apex/active/test.apex.rebootless*.apex",
102                 "/system/app/TestApp/TestAppAv1.apk",
103                 TEST_VENDOR_APEX_ALLOW_LIST);
104     }
105 
106     @Before
setUp()107     public void setUp() throws Exception {
108         cleanUp();
109     }
110 
111     @After
tearDown()112     public void tearDown() throws Exception {
113         cleanUp();
114     }
115 
116     /**
117      * Deletes files and reboots the device if necessary.
118      * @param files the paths of files which might contain wildcards
119      */
deleteFiles(String... files)120     private void deleteFiles(String... files) throws Exception {
121         if (!getDevice().isAdbRoot()) {
122             getDevice().enableAdbRoot();
123         }
124 
125         boolean found = false;
126         boolean remountSystem = false;
127         boolean remountVendor = false;
128         for (String file : files) {
129             CommandResult result = getDevice().executeShellV2Command("ls " + file);
130             if (result.getStatus() == CommandStatus.SUCCESS) {
131                 found = true;
132                 if (file.startsWith("/system")) {
133                     remountSystem = true;
134                 } else if (file.startsWith("/vendor")) {
135                     remountVendor = true;
136                 }
137             }
138         }
139 
140         if (found) {
141             if (remountSystem) {
142                 getDevice().remountSystemWritable();
143             }
144             if (remountVendor) {
145                 getDevice().remountVendorWritable();
146             }
147             for (String file : files) {
148                 getDevice().executeShellCommand("rm -rf " + file);
149             }
150             getDevice().reboot();
151         }
152     }
153 
pushTestApex(String fileName)154     private void pushTestApex(String fileName) throws Exception {
155         pushTestApex(fileName, "system");
156     }
157 
pushTestApex(String fileName, String partition)158     private void pushTestApex(String fileName, String partition) throws Exception {
159         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
160         final File apex = buildHelper.getTestFile(fileName);
161         if (!getDevice().isAdbRoot()) {
162             getDevice().enableAdbRoot();
163         }
164         if ("system".equals(partition)) {
165             getDevice().remountSystemWritable();
166         } else if ("vendor".equals(partition)) {
167             getDevice().remountVendorWritable();
168         }
169         assertTrue(getDevice().pushFile(apex, "/" + partition + "/apex/" + fileName));
170     }
171 
installTestApex(String fileName, String... extraArgs)172     private void installTestApex(String fileName, String... extraArgs) throws Exception {
173         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
174         final File apex = buildHelper.getTestFile(fileName);
175         getDevice().installPackage(apex, false, extraArgs);
176     }
177 
pushTestVendorApexAllowList(String installerPackageName)178     private void pushTestVendorApexAllowList(String installerPackageName) throws Exception {
179         if (!getDevice().isAdbRoot()) {
180             getDevice().enableAdbRoot();
181         }
182         getDevice().remountVendorWritable();
183         File file = File.createTempFile("test-vendor-apex-allow-list", ".xml");
184         try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
185             final String fmt =
186                     "<config>\n"
187                             + "    <allowed-vendor-apex package=\"test.apex.rebootless\" "
188                             + "       installerPackage=\"%s\" />\n"
189                             + "</config>";
190             writer.write(String.format(fmt, installerPackageName));
191         }
192         getDevice().pushFile(file, TEST_VENDOR_APEX_ALLOW_LIST);
193     }
194 
195     /**
196      * Tests app info flags are set correctly when updating a system app.
197      */
198     @Test
testUpdateSystemApp()199     public void testUpdateSystemApp() throws Exception {
200         // Push TestAppAv1.apk to /system
201         final File apkFile = mHostUtils.getTestFile(APK_A);
202         if (!getDevice().isAdbRoot()) {
203             getDevice().enableAdbRoot();
204         }
205         getDevice().remountSystemWritable();
206         assertTrue(getDevice().pushFile(apkFile, "/system/app/TestApp/" + apkFile.getName()));
207         getDevice().reboot();
208 
209         runPhase("testUpdateSystemApp_InstallV2");
210         getDevice().reboot();
211         runPhase("testUpdateSystemApp_PostInstallV2");
212     }
213 
214     /**
215      * Tests that duplicate packages in apk-in-apex and apk should fail to install.
216      */
217     @Test
218     @LargeTest
testDuplicateApkInApexShouldFail()219     public void testDuplicateApkInApexShouldFail() throws Exception {
220         pushTestApex(APK_IN_APEX_TESTAPEX_NAME + "_v1.apex");
221         getDevice().reboot();
222 
223         runPhase("testDuplicateApkInApexShouldFail_Commit");
224         getDevice().reboot();
225         runPhase("testDuplicateApkInApexShouldFail_Verify");
226     }
227 
228     /**
229      * Tests the cache of apk-in-apex is pruned and rebuilt correctly.
230      */
231     @Test
232     @LargeTest
testApkInApexPruneCache()233     public void testApkInApexPruneCache() throws Exception {
234         final String apkInApexPackageName = "com.android.cts.install.lib.testapp.A";
235 
236         pushTestApex(APK_IN_APEX_TESTAPEX_NAME + "_v1.apex");
237         getDevice().reboot();
238         PackageInfo pi = getDevice().getAppPackageInfo(apkInApexPackageName);
239         assertThat(Integer.parseInt(pi.getVersionCode())).isEqualTo(1);
240         assertThat(pi.getCodePath()).startsWith("/apex/" + APK_IN_APEX_TESTAPEX_NAME);
241 
242         installPackage(APK_IN_APEX_TESTAPEX_NAME + "_v2.apex", "--staged");
243         getDevice().reboot();
244         pi = getDevice().getAppPackageInfo(apkInApexPackageName);
245         // The version code of apk-in-apex will be stale if the cache is not rebuilt
246         assertThat(Integer.parseInt(pi.getVersionCode())).isEqualTo(2);
247         assertThat(pi.getCodePath()).startsWith("/apex/" + APK_IN_APEX_TESTAPEX_NAME);
248     }
249 
250     @Test
testSystemServerRestartDoesNotAffectStagedSessions()251     public void testSystemServerRestartDoesNotAffectStagedSessions() throws Exception {
252         runPhase("testSystemServerRestartDoesNotAffectStagedSessions_Commit");
253         restartSystemServer();
254         runPhase("testSystemServerRestartDoesNotAffectStagedSessions_Verify");
255     }
256 
257     // Test waiting time for staged session to be ready using adb staged install can be altered
258     @Test
testAdbStagdReadyTimeoutFlagWorks()259     public void testAdbStagdReadyTimeoutFlagWorks() throws Exception {
260         assumeTrue("Device does not support updating APEX",
261                 mHostUtils.isApexUpdateSupported());
262 
263         final File apexFile = mHostUtils.getTestFile(SHIM_V2);
264         final String output = getDevice().executeAdbCommand("install", "--staged",
265                 "--staged-ready-timeout", "60000", apexFile.getAbsolutePath());
266         assertThat(output).contains("Reboot device to apply staged session");
267         final String sessionId = getDevice().executeShellCommand(
268                 "pm get-stagedsessions --only-ready --only-parent --only-sessionid").trim();
269         assertThat(sessionId).isNotEmpty();
270     }
271 
272     // Test adb staged installation wait for session to be ready by default
273     @Test
testAdbStagedInstallWaitsTillReadyByDefault()274     public void testAdbStagedInstallWaitsTillReadyByDefault() throws Exception {
275         assumeTrue("Device does not support updating APEX",
276                 mHostUtils.isApexUpdateSupported());
277 
278         final File apexFile = mHostUtils.getTestFile(SHIM_V2);
279         final String output = getDevice().executeAdbCommand("install", "--staged",
280                 apexFile.getAbsolutePath());
281         assertThat(output).contains("Reboot device to apply staged session");
282         final String sessionId = getDevice().executeShellCommand(
283                 "pm get-stagedsessions --only-ready --only-parent --only-sessionid").trim();
284         assertThat(sessionId).isNotEmpty();
285     }
286 
287     // Test we can skip waiting for staged session to be ready
288     @Test
testAdbStagedReadyWaitCanBeSkipped()289     public void testAdbStagedReadyWaitCanBeSkipped() throws Exception {
290         assumeTrue("Device does not support updating APEX",
291                 mHostUtils.isApexUpdateSupported());
292 
293         final File apexFile = mHostUtils.getTestFile(SHIM_V2);
294         final String output = getDevice().executeAdbCommand("install", "--staged",
295                 "--staged-ready-timeout", "0", apexFile.getAbsolutePath());
296         assertThat(output).doesNotContain("Reboot device to apply staged session");
297         assertThat(output).contains("Success");
298         final String sessionId = getDevice().executeShellCommand(
299                 "pm get-stagedsessions --only-ready --only-parent --only-sessionid").trim();
300         assertThat(sessionId).isEmpty();
301     }
302 
303     // Test rollback-app command waits for staged sessions to be ready
304     @Test
305     @LargeTest
testAdbRollbackAppWaitsForStagedReady()306     public void testAdbRollbackAppWaitsForStagedReady() throws Exception {
307         assumeTrue("Device does not support updating APEX",
308                 mHostUtils.isApexUpdateSupported());
309 
310         final File apexFile = mHostUtils.getTestFile(SHIM_V2);
311         String output = getDevice().executeAdbCommand("install", "--staged",
312                 "--enable-rollback", apexFile.getAbsolutePath());
313         assertThat(output).contains("Reboot device to apply staged session");
314         getDevice().reboot();
315         output = getDevice().executeShellCommand("pm rollback-app " + SHIM_APEX_PACKAGE_NAME);
316         assertThat(output).contains("Reboot device to apply staged session");
317         final String sessionId = getDevice().executeShellCommand(
318                 "pm get-stagedsessions --only-ready --only-parent --only-sessionid").trim();
319         assertThat(sessionId).isNotEmpty();
320     }
321 
322     @Test
testAdbInstallMultiPackageCommandWorks()323     public void testAdbInstallMultiPackageCommandWorks() throws Exception {
324         assumeTrue("Device does not support updating APEX",
325                 mHostUtils.isApexUpdateSupported());
326 
327         final File apexFile = mHostUtils.getTestFile(SHIM_V2);
328         final File apkFile = mHostUtils.getTestFile(APK_A);
329         final String output = getDevice().executeAdbCommand("install-multi-package",
330                 apexFile.getAbsolutePath(), apkFile.getAbsolutePath());
331         assertThat(output).contains("Created parent session");
332         assertThat(output).contains("Created child session");
333         assertThat(output).contains("Success. Reboot device to apply staged session");
334 
335         // Ensure there is only one parent session
336         String[] sessionIds = getDevice().executeShellCommand(
337                 "pm get-stagedsessions --only-ready --only-parent --only-sessionid").split("\n");
338         assertThat(sessionIds.length).isEqualTo(1);
339         // Ensure there are two children session
340         sessionIds = getDevice().executeShellCommand(
341                 "pm get-stagedsessions --only-ready --only-sessionid").split("\n");
342         assertThat(sessionIds.length).isEqualTo(3);
343     }
344 
345     @Test
testAbandonStagedSessionShouldCleanUp()346     public void testAbandonStagedSessionShouldCleanUp() throws Exception {
347         List<String> before = getStagingDirectories();
348         runPhase("testAbandonStagedSessionShouldCleanUp");
349         List<String> after = getStagingDirectories();
350         // The staging directories generated during the test should be deleted
351         assertThat(after).isEqualTo(before);
352     }
353 
354     @Test
testStagedSessionShouldCleanUpOnVerificationFailure()355     public void testStagedSessionShouldCleanUpOnVerificationFailure() throws Exception {
356         assumeTrue("Device does not support updating APEX",
357                 mHostUtils.isApexUpdateSupported());
358         List<String> before = getStagingDirectories();
359         runPhase("testStagedSessionShouldCleanUpOnVerificationFailure");
360         List<String> after = getStagingDirectories();
361         assertThat(after).isEqualTo(before);
362     }
363 
364     @Test
365     @LargeTest
testStagedSessionShouldCleanUpOnOnSuccess()366     public void testStagedSessionShouldCleanUpOnOnSuccess() throws Exception {
367         List<String> before = getStagingDirectories();
368         runPhase("testStagedSessionShouldCleanUpOnOnSuccess_Commit");
369         assertThat(getStagingDirectories()).isNotEqualTo(before);
370         getDevice().reboot();
371         runPhase("testStagedSessionShouldCleanUpOnOnSuccess_Verify");
372         List<String> after = getStagingDirectories();
373         assertThat(after).isEqualTo(before);
374     }
375 
376     @Test
377     @LargeTest
testStagedSessionShouldCleanUpOnOnSuccessMultiPackage()378     public void testStagedSessionShouldCleanUpOnOnSuccessMultiPackage() throws Exception {
379         List<String> before = getStagingDirectories();
380         runPhase("testStagedSessionShouldCleanUpOnOnSuccessMultiPackage_Commit");
381         assertThat(getStagingDirectories()).isNotEqualTo(before);
382         getDevice().reboot();
383         runPhase("testStagedSessionShouldCleanUpOnOnSuccess_Verify");
384         List<String> after = getStagingDirectories();
385         assertThat(after).isEqualTo(before);
386     }
387 
388     @Test
testStagedInstallationShouldCleanUpOnValidationFailure()389     public void testStagedInstallationShouldCleanUpOnValidationFailure() throws Exception {
390         List<String> before = getStagingDirectories();
391         runPhase("testStagedInstallationShouldCleanUpOnValidationFailure");
392         List<String> after = getStagingDirectories();
393         assertThat(after).isEqualTo(before);
394     }
395 
396     @Test
testStagedInstallationShouldCleanUpOnValidationFailureMultiPackage()397     public void testStagedInstallationShouldCleanUpOnValidationFailureMultiPackage()
398             throws Exception {
399         List<String> before = getStagingDirectories();
400         runPhase("testStagedInstallationShouldCleanUpOnValidationFailureMultiPackage");
401         List<String> after = getStagingDirectories();
402         assertThat(after).isEqualTo(before);
403     }
404 
405     @Test
406     @LargeTest
testOrphanedStagingDirectoryGetsCleanedUpOnReboot()407     public void testOrphanedStagingDirectoryGetsCleanedUpOnReboot() throws Exception {
408         //create random directories in /data/app-staging folder
409         getDevice().enableAdbRoot();
410         getDevice().executeShellCommand("mkdir /data/app-staging/session_123");
411         getDevice().executeShellCommand("mkdir /data/app-staging/session_456");
412         getDevice().disableAdbRoot();
413 
414         assertThat(getStagingDirectories()).contains("session_123");
415         assertThat(getStagingDirectories()).contains("session_456");
416         getDevice().reboot();
417         assertThat(getStagingDirectories()).doesNotContain("session_123");
418         assertThat(getStagingDirectories()).doesNotContain("session_456");
419     }
420 
421     @Test
422     @LargeTest
testFailStagedSessionIfStagingDirectoryDeleted()423     public void testFailStagedSessionIfStagingDirectoryDeleted() throws Exception {
424         // Create a staged session
425         runPhase("testFailStagedSessionIfStagingDirectoryDeleted_Commit");
426 
427         // Delete the staging directory
428         getDevice().enableAdbRoot();
429         getDevice().executeShellCommand("rm -r /data/app-staging");
430         getDevice().disableAdbRoot();
431 
432         getDevice().reboot();
433 
434         runPhase("testFailStagedSessionIfStagingDirectoryDeleted_Verify");
435     }
436 
437     @Test
testApexActivationFailureIsCapturedInSession()438     public void testApexActivationFailureIsCapturedInSession() throws Exception {
439         // We initiate staging a normal apex update which passes pre-reboot verification.
440         // Then we replace the valid apex waiting in /data/app-staging with something
441         // that cannot be activated and reboot. The apex should fail to activate, which
442         // is what we want for this test.
443         runPhase("testApexActivationFailureIsCapturedInSession_Commit");
444         final String sessionId = getDevice().executeShellCommand(
445                 "pm get-stagedsessions --only-ready --only-parent --only-sessionid").trim();
446         assertThat(sessionId).isNotEmpty();
447         // Now replace the valid staged apex with something invalid
448         getDevice().enableAdbRoot();
449         getDevice().executeShellCommand("rm /data/app-staging/session_" + sessionId + "/*");
450         final File invalidApexFile = mHostUtils.getTestFile(APEX_WRONG_SHA);
451         getDevice().pushFile(invalidApexFile,
452                 "/data/app-staging/session_" + sessionId + "/base.apex");
453         getDevice().reboot();
454 
455         runPhase("testApexActivationFailureIsCapturedInSession_Verify");
456     }
457 
458     @Test
testActiveApexIsRevertedOnCheckpointRollback()459     public void testActiveApexIsRevertedOnCheckpointRollback() throws Exception {
460         assumeTrue("Device does not support updating APEX",
461                 mHostUtils.isApexUpdateSupported());
462         assumeTrue("Device does not support file-system checkpoint",
463                 mHostUtils.isCheckpointSupported());
464 
465         // Install something so that /data/apex/active is not empty
466         runPhase("testActiveApexIsRevertedOnCheckpointRollback_Prepare");
467         getDevice().reboot();
468 
469         // Stage another session which will be installed during fs-rollback mode
470         runPhase("testActiveApexIsRevertedOnCheckpointRollback_Commit");
471 
472         // Set checkpoint to 0 so that we enter fs-rollback mode immediately on reboot
473         getDevice().enableAdbRoot();
474         getDevice().executeShellCommand("vdc checkpoint startCheckpoint 0");
475         getDevice().disableAdbRoot();
476         getDevice().reboot();
477 
478         // Verify that session was reverted and we have fallen back to
479         // apex installed during preparation stage.
480         runPhase("testActiveApexIsRevertedOnCheckpointRollback_VerifyPostReboot");
481     }
482 
483     @Test
testApexIsNotActivatedIfNotInCheckpointMode()484     public void testApexIsNotActivatedIfNotInCheckpointMode() throws Exception {
485         assumeTrue("Device does not support updating APEX",
486                 mHostUtils.isApexUpdateSupported());
487         assumeTrue("Device does not support file-system checkpoint",
488                 mHostUtils.isCheckpointSupported());
489 
490         runPhase("testApexIsNotActivatedIfNotInCheckpointMode_Commit");
491         // Delete checkpoint file in /metadata so that device thinks
492         // fs-checkpointing was never activated
493         getDevice().enableAdbRoot();
494         getDevice().executeShellCommand("rm /metadata/vold/checkpoint");
495         getDevice().disableAdbRoot();
496         getDevice().reboot();
497         // Verify that session was not installed when not in fs-checkpoint mode
498         runPhase("testApexIsNotActivatedIfNotInCheckpointMode_VerifyPostReboot");
499     }
500 
501     @Test
testApexInstallerNotInAllowListCanNotInstall()502     public void testApexInstallerNotInAllowListCanNotInstall() throws Exception {
503         assumeTrue("Device does not support updating APEX",
504                 mHostUtils.isApexUpdateSupported());
505 
506         runPhase("testApexInstallerNotInAllowListCanNotInstall_staged");
507         runPhase("testApexInstallerNotInAllowListCanNotInstall_nonStaged");
508     }
509 
510     @Test
511     @LargeTest
testApexNotInAllowListCanNotInstall()512     public void testApexNotInAllowListCanNotInstall() throws Exception {
513         assumeTrue("Device does not support updating APEX",
514                 mHostUtils.isApexUpdateSupported());
515 
516         pushTestApex("test.rebootless_apex_v1.apex");
517         getDevice().reboot();
518 
519         runPhase("testApexNotInAllowListCanNotInstall_staged");
520         runPhase("testApexNotInAllowListCanNotInstall_nonStaged");
521     }
522 
523     @Test
524     @LargeTest
testVendorApexWrongInstaller()525     public void testVendorApexWrongInstaller() throws Exception {
526         assumeTrue("Device does not support updating APEX",
527                 mHostUtils.isApexUpdateSupported());
528 
529         pushTestVendorApexAllowList("com.wrong.installer");
530         pushTestApex("test.rebootless_apex_v1.apex");
531         getDevice().reboot();
532 
533         runPhase("testVendorApexWrongInstaller_staged");
534         runPhase("testVendorApexWrongInstaller_nonStaged");
535     }
536 
537     @Test
538     @LargeTest
testVendorApexCorrectInstaller()539     public void testVendorApexCorrectInstaller() throws Exception {
540         assumeTrue("Device does not support updating APEX",
541                 mHostUtils.isApexUpdateSupported());
542 
543         pushTestVendorApexAllowList("com.android.tests.stagedinstallinternal");
544         pushTestApex("test.rebootless_apex_v1.apex");
545         getDevice().reboot();
546 
547         runPhase("testVendorApexCorrectInstaller_staged");
548         runPhase("testVendorApexCorrectInstaller_nonStaged");
549     }
550 
551     /**
552      * Tests correctness of {@link android.content.pm.ApplicationInfo} for APEXes on /vendor.
553      */
554     @Test
555     @LargeTest
testVendorApex_Staged()556     public void testVendorApex_Staged() throws Exception {
557         pushTestApex(REBOOTLESS_V1, "vendor");
558         getDevice().reboot();
559         runPhase("testVendorApex_VerifyFactory");
560         installTestApex(REBOOTLESS_V2, "--staged");
561         getDevice().reboot();
562         runPhase("testVendorApex_VerifyData");
563     }
564 
565     /**
566      * Tests correctness of {@link android.content.pm.ApplicationInfo} for APEXes on /vendor.
567      */
568     @Test
569     @LargeTest
testVendorApex_NonStaged()570     public void testVendorApex_NonStaged() throws Exception {
571         pushTestApex(REBOOTLESS_V1, "vendor");
572         getDevice().reboot();
573         runPhase("testVendorApex_VerifyFactory");
574         installTestApex(REBOOTLESS_V2, "--force-non-staged");
575         runPhase("testVendorApex_VerifyData");
576     }
577 
578     @Test
testRebootlessUpdates()579     public void testRebootlessUpdates() throws Exception {
580         pushTestApex("test.rebootless_apex_v1.apex");
581         getDevice().reboot();
582 
583         runPhase("testRebootlessUpdates");
584     }
585 
586     @Test
testRebootlessUpdate_hasStagedSessionWithSameApex_fails()587     public void testRebootlessUpdate_hasStagedSessionWithSameApex_fails() throws Exception {
588         assumeTrue("Device does not support updating APEX",
589                 mHostUtils.isApexUpdateSupported());
590 
591         runPhase("testRebootlessUpdate_hasStagedSessionWithSameApex_fails");
592     }
593 
594     @Test
testGetStagedModuleNames()595     public void testGetStagedModuleNames() throws Exception {
596         assumeTrue("Device does not support updating APEX",
597                 mHostUtils.isApexUpdateSupported());
598 
599         runPhase("testGetStagedModuleNames");
600     }
601 
602     @Test
603     @LargeTest
testGetStagedApexInfo()604     public void testGetStagedApexInfo() throws Exception {
605         assumeTrue("Device does not support updating APEX",
606                 mHostUtils.isApexUpdateSupported());
607 
608         pushTestApex(APEXD_TEST_APEX);
609         getDevice().reboot();
610 
611         runPhase("testGetStagedApexInfo");
612     }
613 
614     @Test
615     @LargeTest
testGetAppInfo_flagTestOnlyIsSet()616     public void testGetAppInfo_flagTestOnlyIsSet() throws Exception {
617         assumeTrue("Device does not support updating APEX",
618                 mHostUtils.isApexUpdateSupported());
619 
620         pushTestApex(FAKE_APEX_SYSTEM_SERVER_APEX);
621         getDevice().reboot();
622 
623         runPhase("testGetAppInfo_flagTestOnlyIsSet");
624     }
625 
626     @Test
testStagedApexObserver()627     public void testStagedApexObserver() throws Exception {
628         assumeTrue("Device does not support updating APEX",
629                 mHostUtils.isApexUpdateSupported());
630 
631         runPhase("testStagedApexObserver");
632     }
633 
634     @Test
testRebootlessDowngrade()635     public void testRebootlessDowngrade() throws Exception {
636         pushTestApex("test.rebootless_apex_v2.apex");
637         getDevice().reboot();
638         runPhase("testRebootlessDowngrade");
639     }
640 
getStagingDirectories()641     private List<String> getStagingDirectories() throws DeviceNotAvailableException {
642         String baseDir = "/data/app-staging";
643         try {
644             getDevice().enableAdbRoot();
645             return getDevice().getFileEntry(baseDir).getChildren(false)
646                     .stream().filter(entry -> entry.getName().matches("session_\\d+"))
647                     .map(entry -> entry.getName())
648                     .collect(Collectors.toList());
649         } finally {
650             getDevice().disableAdbRoot();
651         }
652     }
653 
restartSystemServer()654     private void restartSystemServer() throws Exception {
655         // Restart the system server
656         final ProcessInfo oldPs = getDevice().getProcessByName("system_server");
657 
658         getDevice().enableAdbRoot(); // Need root to restart system server
659         assertThat(getDevice().executeShellCommand("am restart")).contains("Restart the system");
660         getDevice().disableAdbRoot();
661 
662         // Wait for new system server process to start
663         final long start = System.currentTimeMillis();
664         while (System.currentTimeMillis() < start + SYSTEM_SERVER_TIMEOUT_MS) {
665             final ProcessInfo newPs = getDevice().getProcessByName("system_server");
666             if (newPs != null) {
667                 if (newPs.getPid() != oldPs.getPid()) {
668                     getDevice().waitForDeviceAvailable();
669                     return;
670                 }
671             }
672             Thread.sleep(500);
673         }
674         fail("Timed out in restarting system server");
675     }
676 }
677