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;
18 
19 import static com.android.cts.install.lib.InstallUtils.assertStatusFailure;
20 import static com.android.cts.install.lib.InstallUtils.assertStatusSuccess;
21 import static com.android.cts.install.lib.InstallUtils.getPackageInstaller;
22 import static com.android.cts.install.lib.PackageInstallerSessionInfoSubject.assertThat;
23 import static com.android.cts.shim.lib.ShimPackage.DIFFERENT_APEX_PACKAGE_NAME;
24 import static com.android.cts.shim.lib.ShimPackage.NOT_PRE_INSTALL_APEX_PACKAGE_NAME;
25 import static com.android.cts.shim.lib.ShimPackage.SHIM_APEX_PACKAGE_NAME;
26 import static com.android.cts.shim.lib.ShimPackage.SHIM_PACKAGE_NAME;
27 
28 import static com.google.common.truth.Truth.assertThat;
29 import static com.google.common.truth.Truth.assertWithMessage;
30 
31 import static org.junit.Assert.fail;
32 
33 import android.Manifest;
34 import android.content.BroadcastReceiver;
35 import android.content.Context;
36 import android.content.Intent;
37 import android.content.IntentFilter;
38 import android.content.pm.ApplicationInfo;
39 import android.content.pm.PackageInfo;
40 import android.content.pm.PackageInstaller;
41 import android.content.pm.PackageManager;
42 import android.os.Handler;
43 import android.os.HandlerThread;
44 import android.util.Log;
45 
46 import androidx.test.platform.app.InstrumentationRegistry;
47 
48 import com.android.compatibility.common.util.SystemUtil;
49 import com.android.cts.install.lib.Install;
50 import com.android.cts.install.lib.InstallUtils;
51 import com.android.cts.install.lib.LocalIntentSender;
52 import com.android.cts.install.lib.TestApp;
53 import com.android.cts.install.lib.Uninstall;
54 
55 import org.junit.After;
56 import org.junit.Before;
57 import org.junit.Test;
58 import org.junit.runner.RunWith;
59 import org.junit.runners.JUnit4;
60 
61 import java.io.BufferedReader;
62 import java.io.BufferedWriter;
63 import java.io.File;
64 import java.io.FileReader;
65 import java.io.FileWriter;
66 import java.io.IOException;
67 import java.io.InputStream;
68 import java.nio.file.FileVisitResult;
69 import java.nio.file.Files;
70 import java.nio.file.Path;
71 import java.nio.file.Paths;
72 import java.nio.file.SimpleFileVisitor;
73 import java.nio.file.StandardCopyOption;
74 import java.nio.file.attribute.BasicFileAttributes;
75 import java.time.Duration;
76 import java.util.ArrayList;
77 import java.util.Arrays;
78 import java.util.HashSet;
79 import java.util.List;
80 import java.util.Set;
81 import java.util.concurrent.TimeUnit;
82 import java.util.concurrent.atomic.AtomicInteger;
83 import java.util.function.Consumer;
84 import java.util.stream.Collectors;
85 
86 /**
87  * This series of tests are meant to be driven by a host, since some of the interactions being
88  * tested require the device to be rebooted, and some assertions to be verified post-reboot.
89  * The convention used here (not enforced) is that the test methods in this file will be named
90  * the same way as the test methods in the "host" class (see e.g.
91  * {@code com.android.test.stagedinstall.host.StagedInstallTest}), with an optional suffix preceded
92  * by an underscore, in case of multiple phases.
93  * Example:
94  * - In {@code com.android.test.stagedinstall.host.StagedInstallTest}:
95  *
96  * @Test
97  * public void testInstallStagedApk() throws Exception {
98  *  runPhase("testInstallStagedApk_Commit");
99  *  getDevice().reboot();
100  *  runPhase("testInstallStagedApk_VerifyPostReboot");
101  * }
102  * - In this class:
103  * @Test public void testInstallStagedApk_Commit() throws Exception;
104  * @Test public void testInstallStagedApk_VerifyPostReboot() throws Exception;
105  */
106 @RunWith(JUnit4.class)
107 public class StagedInstallTest {
108 
109     private static final String TAG = "StagedInstallTest";
110 
111     private File mTestStateFile = new File(
112             InstrumentationRegistry.getInstrumentation().getContext().getFilesDir(),
113             "ctsstagedinstall_state");
114 
115     private static final Duration WAIT_FOR_SESSION_REMOVED_TTL = Duration.ofSeconds(10);
116     private static final Duration SLEEP_DURATION = Duration.ofMillis(200);
117 
118     private static final TestApp TESTAPP_SAME_NAME_AS_APEX = new TestApp(
119             "TestAppSamePackageNameAsApex", SHIM_APEX_PACKAGE_NAME, 1, /*isApex*/ false,
120             "StagedInstallTestAppSamePackageNameAsApex.apk");
121     private static final TestApp Apex2DifferentCertificate = new TestApp(
122             "Apex2DifferentCertificate", SHIM_APEX_PACKAGE_NAME, 2, /*isApex*/true,
123             "com.android.apex.cts.shim.v2_different_certificate.apex");
124     private static final TestApp Apex2DifferentPackageName = new TestApp(
125             "Apex2DifferentPackageName", DIFFERENT_APEX_PACKAGE_NAME, 2, /*isApex*/true,
126             "com.android.apex.cts.shim.v2_different_package_name.apex");
127     private static final TestApp Apex2SignedBob = new TestApp(
128             "Apex2SignedBob", SHIM_APEX_PACKAGE_NAME, 2, /*isApex*/true,
129             "com.android.apex.cts.shim.v2_signed_bob.apex");
130     private static final TestApp Apex2SignedBobRot = new TestApp(
131             "Apex2SignedBobRot", SHIM_APEX_PACKAGE_NAME, 2, /*isApex*/true,
132             "com.android.apex.cts.shim.v2_signed_bob_rot.apex");
133     private static final TestApp Apex2SignedBobRotRollback = new TestApp(
134             "Apex2SignedBobRotRollback", SHIM_APEX_PACKAGE_NAME, 2, /*isApex*/true,
135             "com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex");
136     private static final TestApp ApexNoHashtree2 = new TestApp(
137             "Apex2", SHIM_APEX_PACKAGE_NAME, 2, /*isApex*/true,
138             "com.android.apex.cts.shim.v2_no_hashtree.apex");
139     private static final TestApp ApexWrongSha2 = new TestApp(
140             "ApexWrongSha2", SHIM_APEX_PACKAGE_NAME, 2, /*isApex*/true,
141             "com.android.apex.cts.shim.v2_wrong_sha.apex");
142     private static final TestApp Apex2WithoutApkInApex = new TestApp(
143             "Apex2WithoutApkInApex", SHIM_APEX_PACKAGE_NAME, 2, /*isApex*/true,
144             "com.android.apex.cts.shim.v2_without_apk_in_apex.apex");
145     private static final TestApp Apex3SignedBob = new TestApp(
146             "Apex3SignedBob", SHIM_APEX_PACKAGE_NAME, 3, /*isApex*/true,
147             "com.android.apex.cts.shim.v3_signed_bob.apex");
148     private static final TestApp Apex3SignedBobRot = new TestApp(
149             "Apex3SignedBobRot", SHIM_APEX_PACKAGE_NAME, 3, /*isApex*/true,
150             "com.android.apex.cts.shim.v3_signed_bob_rot.apex");
151     private static final TestApp ApexNotPreInstalled = new TestApp(
152             "ApexNotPreInstalled", NOT_PRE_INSTALL_APEX_PACKAGE_NAME, 3, /*isApex*/true,
153             "com.android.apex.cts.shim_not_pre_installed.apex");
154     private static final TestApp Apex2SdkTargetP = new TestApp(
155             "StagedInstallTestApexV2_SdkTargetP", SHIM_APEX_PACKAGE_NAME, 2,
156             /*isApex*/true, "com.android.apex.cts.shim.v2_sdk_target_p.apex");
157     private static final TestApp Apex2ApkInApexSdkTargetP = new TestApp(
158             "StagedInstallTestApexV2_ApkInApexSdkTargetP", SHIM_APEX_PACKAGE_NAME, 2,
159             /*isApex*/true, "com.android.apex.cts.shim.v2_apk_in_apex_sdk_target_p.apex");
160     private static final TestApp CorruptedApex_b146895998 = new TestApp(
161             "StagedInstallTestCorruptedApex_b146895998", "", 1, true, "corrupted_b146895998.apex");
162     private static final TestApp Apex2NoApkSignature = new TestApp(
163             "StagedInstallTestApexV2_NoApkSignature", SHIM_APEX_PACKAGE_NAME, 1,
164             /*isApex*/true, "com.android.apex.cts.shim.v2_unsigned_apk_container.apex");
165     private static final TestApp Apex2UnsignedPayload = new TestApp(
166             "StagedInstallTestApexV2_UnsignedPayload", SHIM_APEX_PACKAGE_NAME, 1,
167             /*isApex*/true, "com.android.apex.cts.shim.v2_unsigned_payload.apex");
168     private static final TestApp Apex2SignPayloadWithDifferentKey = new TestApp(
169             "StagedInstallTestApexV2_SignPayloadWithDifferentKey", SHIM_APEX_PACKAGE_NAME, 1,
170             /*isApex*/true, "com.android.apex.cts.shim.v2_sign_payload_with_different_key.apex");
171     private static final TestApp Apex2Rebootless = new TestApp(
172             "StagedInstallTestApexV2_Rebootless", SHIM_APEX_PACKAGE_NAME, 2,
173             /*isApex*/true, "com.android.apex.cts.shim.v2_rebootless.apex");
174     private static final TestApp Apex3Rebootless = new TestApp(
175             "StagedInstallTestApexV3_Rebootless", SHIM_APEX_PACKAGE_NAME, 3,
176             /*isApex*/true, "com.android.apex.cts.shim.v3_rebootless.apex");
177     private static final TestApp Apex2AsApk = new TestApp("Apex2", SHIM_APEX_PACKAGE_NAME, 2,
178             /*isApex*/false, "com.android.apex.cts.shim.v2.apex");
179 
180     @Before
adoptShellPermissions()181     public void adoptShellPermissions() {
182         InstrumentationRegistry
183                 .getInstrumentation()
184                 .getUiAutomation()
185                 .adoptShellPermissionIdentity(
186                         Manifest.permission.PACKAGE_USAGE_STATS,
187                         Manifest.permission.INSTALL_PACKAGES,
188                         Manifest.permission.DELETE_PACKAGES);
189     }
190 
191     @After
dropShellPermissions()192     public void dropShellPermissions() {
193         InstrumentationRegistry
194                 .getInstrumentation()
195                 .getUiAutomation()
196                 .dropShellPermissionIdentity();
197     }
198 
199     // This is marked as @Test to take advantage of @Before/@After methods of this class. Actual
200     // purpose of this method to be called before and after each test case of
201     // com.android.test.stagedinstall.host.StagedInstallTest to reduce tests flakiness.
202     @Test
cleanUp()203     public void cleanUp() throws Exception {
204         PackageInstaller packageInstaller = getPackageInstaller();
205         List<PackageInstaller.SessionInfo> stagedSessions = packageInstaller.getStagedSessions();
206         for (PackageInstaller.SessionInfo sessionInfo : stagedSessions) {
207             if (sessionInfo.getParentSessionId() != PackageInstaller.SessionInfo.INVALID_ID
208                     || sessionInfo.isStagedSessionApplied()
209                     || sessionInfo.isStagedSessionFailed()) {
210                 // Cannot abandon a child session; no need to abandon terminated sessions
211                 continue;
212             }
213             try {
214                 Log.i(TAG, "abandoning session " + sessionInfo.getSessionId());
215                 packageInstaller.abandonSession(sessionInfo.getSessionId());
216             } catch (Exception e) {
217                 Log.e(TAG, "Failed to abandon session " + sessionInfo.getSessionId(), e);
218             }
219         }
220         Uninstall.packages(TestApp.A, TestApp.B);
221         Files.deleteIfExists(mTestStateFile.toPath());
222     }
223 
224     @Test
testFailInstallIfNoPermission()225     public void testFailInstallIfNoPermission() throws Exception {
226         dropShellPermissions();
227         try {
228             createStagedSession();
229             fail(); // Should have thrown SecurityException.
230         } catch (SecurityException e) {
231             // This would be a better version, but it requires a version of truth not present in the
232             // tree yet.
233             // assertThat(e).hasMessageThat().containsMatch(...);
234             assertThat(e.getMessage()).containsMatch(
235                     "Neither user [0-9]+ nor current process has "
236                     + "android.permission.INSTALL_PACKAGES");
237         }
238     }
239 
240     @Test
testInstallStagedApk_Commit()241     public void testInstallStagedApk_Commit() throws Exception {
242         BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_COMMITTED);
243         int sessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId();
244         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
245         assertSessionReady(sessionId);
246         storeSessionId(sessionId);
247         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
248         counter.assertNoBroadcastReceived();
249     }
250 
251     @Test
testInstallStagedApk_VerifyPostReboot()252     public void testInstallStagedApk_VerifyPostReboot() throws Exception {
253         BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_COMMITTED);
254         int sessionId = retrieveLastSessionId();
255         assertSessionApplied(sessionId);
256         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1);
257         counter.assertNoBroadcastReceived();
258     }
259 
260     @Test
testInstallStagedApk_AbandonSessionIsNoop()261     public void testInstallStagedApk_AbandonSessionIsNoop() throws Exception {
262         int sessionId = retrieveLastSessionId();
263         assertSessionApplied(sessionId);
264         // Session is in a final state. Test that abandoning the session doesn't remove it from the
265         // session database.
266         abandonSession(sessionId);
267         assertSessionApplied(sessionId);
268     }
269 
270     @Test
testInstallMultipleStagedApks_Commit()271     public void testInstallMultipleStagedApks_Commit() throws Exception {
272         BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_COMMITTED);
273         int sessionId = stageMultipleApks(TestApp.A1, TestApp.B1)
274                 .assertSuccessful().getSessionId();
275         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
276         assertThat(getInstalledVersion(TestApp.B)).isEqualTo(-1);
277         assertSessionReady(sessionId);
278         storeSessionId(sessionId);
279         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
280         assertThat(getInstalledVersion(TestApp.B)).isEqualTo(-1);
281         counter.assertNoBroadcastReceived();
282     }
283 
284     @Test
testInstallMultipleStagedApks_VerifyPostReboot()285     public void testInstallMultipleStagedApks_VerifyPostReboot() throws Exception {
286         BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_COMMITTED);
287         int sessionId = retrieveLastSessionId();
288         assertSessionApplied(sessionId);
289         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1);
290         assertThat(getInstalledVersion(TestApp.B)).isEqualTo(1);
291         counter.assertNoBroadcastReceived();
292     }
293 
294     @Test
testAbandonStagedApkBeforeReboot_CommitAndAbandon()295     public void testAbandonStagedApkBeforeReboot_CommitAndAbandon() throws Exception {
296         int sessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId();
297         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
298         PackageInstaller.SessionInfo session = InstallUtils.getStagedSessionInfo(sessionId);
299         assertSessionReady(sessionId);
300         abandonSession(sessionId);
301         InstallUtils.assertStagedSessionIsAbandoned(sessionId);
302         // Allow the session to be removed from PackageInstaller
303         Duration spentWaiting = Duration.ZERO;
304         while (spentWaiting.compareTo(WAIT_FOR_SESSION_REMOVED_TTL) < 0) {
305             session = getSessionInfo(sessionId);
306             if (session == null) {
307                 Log.i(TAG, "Done waiting after " + spentWaiting);
308                 break;
309             }
310             try {
311                 Thread.sleep(SLEEP_DURATION.toMillis());
312                 spentWaiting = spentWaiting.plus(SLEEP_DURATION);
313             } catch (InterruptedException e) {
314                 Thread.currentThread().interrupt();
315                 throw new RuntimeException(e);
316             }
317         }
318         assertThat(session).isNull();
319     }
320 
321     @Test
testAbandonStagedApkBeforeReboot_VerifyPostReboot()322     public void testAbandonStagedApkBeforeReboot_VerifyPostReboot() throws Exception {
323         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
324     }
325 
326     @Test
testAbandonStagedApkBeforeReady_CommitAndAbandon()327     public void testAbandonStagedApkBeforeReady_CommitAndAbandon() throws Exception {
328         int sessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId();
329         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
330         abandonSession(sessionId);
331         InstallUtils.assertStagedSessionIsAbandoned(sessionId);
332     }
333 
334     @Test
testAbandonStagedApkBeforeReady_VerifyPostReboot()335     public void testAbandonStagedApkBeforeReady_VerifyPostReboot() throws Exception {
336         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
337     }
338 
339     @Test
testStageAnotherSessionImmediatelyAfterAbandon()340     public void testStageAnotherSessionImmediatelyAfterAbandon() throws Exception {
341         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
342         int sessionId = stageSingleApk(TestApp.Apex2).assertSuccessful().getSessionId();
343         abandonSession(sessionId);
344         stageSingleApk(TestApp.Apex2).assertSuccessful();
345     }
346 
347     @Test
testStageAnotherSessionImmediatelyAfterAbandonMultiPackage()348     public void testStageAnotherSessionImmediatelyAfterAbandonMultiPackage() throws Exception {
349         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
350         int sessionId = stageMultipleApks(TestApp.Apex2, TestApp.A1, TestApp.B1)
351                 .assertSuccessful().getSessionId();
352         abandonSession(sessionId);
353         stageSingleApk(TestApp.Apex2).assertSuccessful();
354     }
355 
356     @Test
testNoSessionUpdatedBroadcastSentForStagedSessionAbandon()357     public void testNoSessionUpdatedBroadcastSentForStagedSessionAbandon() throws Exception {
358         BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_UPDATED);
359         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
360         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
361         // Using an apex in hopes that pre-reboot verification will take longer to complete
362         // and we will manage to abandon it before session becomes ready.
363         int sessionId = Install.multi(TestApp.A1, TestApp.Apex2).setStaged().createSession();
364         LocalIntentSender sender = new LocalIntentSender();
365         InstallUtils.openPackageInstallerSession(sessionId).commit(sender.getIntentSender());
366         abandonSession(sessionId);
367         InstallUtils.assertStagedSessionIsAbandoned(sessionId);
368         counter.assertNoBroadcastReceived();
369     }
370 
371     @Test
testGetActiveStagedSessions()372     public void testGetActiveStagedSessions() throws Exception {
373         PackageInstaller packageInstaller = getPackageInstaller();
374         int firstSessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId();
375         int secondSessionId = stageSingleApk(TestApp.B1).assertSuccessful().getSessionId();
376         List<Integer> stagedSessionIds = packageInstaller.getActiveStagedSessions()
377                 .stream().map(s -> s.getSessionId()).collect(Collectors.toList());
378         assertThat(stagedSessionIds).containsExactly(firstSessionId, secondSessionId);
379 
380         // Verify no other session is considered as active staged session
381         List<PackageInstaller.SessionInfo> allSessions = packageInstaller.getAllSessions();
382         for (PackageInstaller.SessionInfo session : allSessions) {
383             if (session.isStagedSessionActive()) {
384                 assertThat(stagedSessionIds).contains(session.getSessionId());
385             }
386         }
387     }
388 
389     /**
390      * Verifies that active staged session fulfils conditions stated at
391      * {@link PackageInstaller.SessionInfo#isStagedSessionActive}
392      */
393     @Test
testIsStagedSessionActive()394     public void testIsStagedSessionActive() throws Exception {
395         PackageInstaller packageInstaller = getPackageInstaller();
396         int sessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId();
397 
398         List<PackageInstaller.SessionInfo> allSessions = packageInstaller.getAllSessions();
399         boolean activeStagedSessionFound = false;
400         for (PackageInstaller.SessionInfo session : allSessions) {
401             // If it fulfils conditions, then it should be an active staged session
402             if (session.isStaged() && session.isCommitted() && !session.isStagedSessionApplied()
403                     && !session.isStagedSessionFailed()) {
404                 activeStagedSessionFound = true;
405                 assertThat(session.getSessionId()).isEqualTo(sessionId);
406                 assertThat(session.isStagedSessionActive()).isTrue();
407             } else {
408                 // Otherwise, it should not be marked as active staged session
409                 assertThat(session.getSessionId()).isNotEqualTo(sessionId);
410                 assertThat(session.isStagedSessionActive()).isFalse();
411             }
412         }
413         assertWithMessage("Did not find any active staged session")
414                 .that(activeStagedSessionFound).isTrue();
415     }
416 
417     @Test
testGetActiveStagedSessionsNoSessionActive()418     public void testGetActiveStagedSessionsNoSessionActive() throws Exception {
419         PackageInstaller packageInstaller = getPackageInstaller();
420         List<PackageInstaller.SessionInfo> sessions = packageInstaller.getActiveStagedSessions();
421         assertThat(sessions).isEmpty();
422     }
423 
424     @Test
testGetActiveStagedSessions_MultiApkSession()425     public void testGetActiveStagedSessions_MultiApkSession() throws Exception {
426         int firstSessionId = stageMultipleApks(TestApp.A1, TestApp.B1)
427                 .assertSuccessful().getSessionId();
428         int secondSessionId = stageMultipleApks(TestApp.C1)
429                 .assertSuccessful().getSessionId();
430         List<Integer> stagedSessionIds = getPackageInstaller().getActiveStagedSessions()
431                 .stream().map(s -> s.getSessionId()).collect(Collectors.toList());
432         assertThat(stagedSessionIds).containsExactly(firstSessionId, secondSessionId);
433     }
434 
435     @Test
testStagedInstallDowngrade_DowngradeNotRequested_Fails_Commit()436     public void testStagedInstallDowngrade_DowngradeNotRequested_Fails_Commit()  throws Exception {
437         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
438         Install.single(TestApp.A2).commit();
439         int sessionId = stageSingleApk(TestApp.A1).assertFailure().getSessionId();
440         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(2);
441         PackageInstaller.SessionInfo sessionInfo = getSessionInfo(sessionId);
442         assertThat(sessionInfo).isStagedSessionFailed();
443     }
444 
445     @Test
testStagedInstallDowngrade_DowngradeRequested_Commit()446     public void testStagedInstallDowngrade_DowngradeRequested_Commit() throws Exception {
447         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
448         Install.single(TestApp.A2).commit();
449         int sessionId = stageDowngradeSingleApk(TestApp.A1).assertSuccessful().getSessionId();
450         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(2);
451         assertSessionReady(sessionId);
452         storeSessionId(sessionId);
453     }
454 
455     @Test
testStagedInstallDowngrade_DowngradeRequested_Fails_Commit()456     public void testStagedInstallDowngrade_DowngradeRequested_Fails_Commit() throws Exception {
457         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
458         Install.single(TestApp.A2).commit();
459         stageDowngradeSingleApk(TestApp.A1).assertFailure();
460     }
461 
462     @Test
testStagedInstallDowngrade_DowngradeRequested_DebugBuild_VerifyPostReboot()463     public void testStagedInstallDowngrade_DowngradeRequested_DebugBuild_VerifyPostReboot()
464             throws Exception {
465         int sessionId = retrieveLastSessionId();
466         assertSessionApplied(sessionId);
467         // App should be downgraded.
468         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1);
469     }
470 
471     @Test
testInstallStagedApex_Commit()472     public void testInstallStagedApex_Commit() throws Exception {
473         BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_COMMITTED);
474         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
475         int sessionId = stageSingleApk(TestApp.Apex2).assertSuccessful().getSessionId();
476         assertSessionReady(sessionId);
477         storeSessionId(sessionId);
478         // Version shouldn't change before reboot.
479         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
480         counter.assertNoBroadcastReceived();
481     }
482 
483     @Test
testInstallStagedApex_VerifyPostReboot()484     public void testInstallStagedApex_VerifyPostReboot() throws Exception {
485         BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_COMMITTED);
486         int sessionId = retrieveLastSessionId();
487         assertSessionApplied(sessionId);
488         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
489         counter.assertNoBroadcastReceived();
490     }
491 
492     @Test
testInstallStagedApexAndApk_Commit()493     public void testInstallStagedApexAndApk_Commit() throws Exception {
494         BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_COMMITTED);
495         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
496         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
497         int sessionId = stageMultipleApks(TestApp.Apex2, TestApp.A1)
498                 .assertSuccessful().getSessionId();
499         assertSessionReady(sessionId);
500         storeSessionId(sessionId);
501         // Version shouldn't change before reboot.
502         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
503         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
504         counter.assertNoBroadcastReceived();
505     }
506 
507     @Test
testInstallStagedApexAndApk_VerifyPostReboot()508     public void testInstallStagedApexAndApk_VerifyPostReboot() throws Exception {
509         BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_COMMITTED);
510         int sessionId = retrieveLastSessionId();
511         assertSessionApplied(sessionId);
512         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
513         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1);
514         counter.assertNoBroadcastReceived();
515     }
516 
517     @Test
testsFailsNonStagedApexInstall()518     public void testsFailsNonStagedApexInstall() throws Exception {
519         try {
520             SystemUtil.runShellCommandForNoOutput("pm bypass-staged-installer-check true");
521             assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
522             TestApp apex = new TestApp(
523                     "Apex2", SHIM_APEX_PACKAGE_NAME, 2, /*isApex*/true,
524                     "com.android.apex.cts.shim.v2.apex");
525             InstallUtils.commitExpectingFailure(AssertionError.class,
526                     "does not support non-staged update",
527                     Install.single(apex));
528             assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
529         } finally {
530             SystemUtil.runShellCommandForNoOutput("pm bypass-staged-installer-check false");
531         }
532     }
533 
534     @Test
testInstallStagedNonPreInstalledApex_Fails()535     public void testInstallStagedNonPreInstalledApex_Fails() throws Exception {
536         assertThat(getInstalledVersion(NOT_PRE_INSTALL_APEX_PACKAGE_NAME)).isEqualTo(-1);
537         int sessionId = stageSingleApk(ApexNotPreInstalled).assertFailure().getSessionId();
538         PackageInstaller.SessionInfo sessionInfo = getSessionInfo(sessionId);
539         assertThat(sessionInfo).isStagedSessionFailed();
540     }
541 
542     @Test
testInstallStagedDifferentPackageNameWithInstalledApex_Fails()543     public void testInstallStagedDifferentPackageNameWithInstalledApex_Fails() throws Exception {
544         assertThat(getInstalledVersion(DIFFERENT_APEX_PACKAGE_NAME)).isEqualTo(-1);
545         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
546         int sessionId = stageSingleApk(Apex2DifferentPackageName).assertFailure().getSessionId();
547         PackageInstaller.SessionInfo sessionInfo = getSessionInfo(sessionId);
548         assertThat(sessionInfo.getStagedSessionErrorMessage()).contains(
549                 "Attempting to install new APEX package");
550     }
551 
552     @Test
testStageApkWithSameNameAsApexShouldFail_Commit()553     public void testStageApkWithSameNameAsApexShouldFail_Commit() throws Exception {
554         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
555         int sessionId = stageSingleApk(Apex2AsApk).assertSuccessful().getSessionId();
556         assertSessionReady(sessionId);
557         storeSessionId(sessionId);
558     }
559 
560     @Test
testStageApkWithSameNameAsApexShouldFail_VerifyPostReboot()561     public void testStageApkWithSameNameAsApexShouldFail_VerifyPostReboot() throws Exception {
562         int sessionId = retrieveLastSessionId();
563         assertSessionFailed(sessionId);
564         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
565         PackageInstaller.SessionInfo sessionInfo = getSessionInfo(sessionId);
566         assertThat(sessionInfo.getStagedSessionErrorMessage()).contains(
567                 "is an APEX package and can't be installed as an APK");
568     }
569 
570     @Test
testNonStagedInstallApkWithSameNameAsApexShouldFail()571     public void testNonStagedInstallApkWithSameNameAsApexShouldFail() throws Exception {
572         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
573         InstallUtils.commitExpectingFailure(AssertionError.class,
574                 "is an APEX package and can't be installed as an APK",
575                 Install.single(Apex2AsApk));
576         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
577     }
578 
579     @Test
testInstallV2Apex_Commit()580     public void testInstallV2Apex_Commit() throws Exception {
581         int sessionId = stageSingleApk(TestApp.Apex2).assertSuccessful().getSessionId();
582         assertSessionReady(sessionId);
583         storeSessionId(sessionId);
584     }
585 
586     @Test
testInstallV2Apex_VerifyPostReboot()587     public void testInstallV2Apex_VerifyPostReboot() throws Exception {
588         int sessionId = retrieveLastSessionId();
589         assertSessionApplied(sessionId);
590         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
591         assertThat(getInstalledVersion(SHIM_PACKAGE_NAME)).isNotEqualTo(-1);
592     }
593 
594     @Test
testInstallV2SignedBobApex_Commit()595     public void testInstallV2SignedBobApex_Commit() throws Exception {
596         int sessionId = stageSingleApk(Apex2SignedBobRot).assertSuccessful().getSessionId();
597         assertSessionReady(sessionId);
598         storeSessionId(sessionId);
599     }
600 
601     @Test
testInstallV2SignedBobApex_VerifyPostReboot()602     public void testInstallV2SignedBobApex_VerifyPostReboot() throws Exception {
603         int sessionId = retrieveLastSessionId();
604         assertSessionApplied(sessionId);
605         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
606     }
607 
608     @Test
testInstallV3Apex_Commit()609     public void testInstallV3Apex_Commit() throws Exception {
610         int sessionId = stageSingleApk(TestApp.Apex3).assertSuccessful().getSessionId();
611         assertSessionReady(sessionId);
612         storeSessionId(sessionId);
613     }
614 
615     @Test
testInstallV3Apex_VerifyPostReboot()616     public void testInstallV3Apex_VerifyPostReboot() throws Exception {
617         int sessionId = retrieveLastSessionId();
618         assertSessionApplied(sessionId);
619         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
620     }
621 
622     @Test
testInstallV3SignedBobApex_Commit()623     public void testInstallV3SignedBobApex_Commit() throws Exception {
624         int sessionId = stageSingleApk(Apex2SignedBobRot).assertSuccessful().getSessionId();
625         assertSessionReady(sessionId);
626         storeSessionId(sessionId);
627     }
628 
629     @Test
testInstallV3SignedBobApex_VerifyPostReboot()630     public void testInstallV3SignedBobApex_VerifyPostReboot() throws Exception {
631         int sessionId = retrieveLastSessionId();
632         assertSessionApplied(sessionId);
633         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
634     }
635 
636     @Test
testStagedInstallDowngradeApex_DowngradeNotRequested_Fails_Commit()637     public void testStagedInstallDowngradeApex_DowngradeNotRequested_Fails_Commit()
638             throws Exception {
639         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
640         int sessionId = stageSingleApk(TestApp.Apex2).assertFailure().getSessionId();
641         PackageInstaller.SessionInfo sessionInfo = getSessionInfo(sessionId);
642         assertThat(sessionInfo).isStagedSessionFailed();
643         // Also verify that correct session info is reported by PackageManager.
644         assertSessionFailed(sessionId);
645         storeSessionId(sessionId);
646     }
647 
648     @Test
testStagedInstallDowngradeApex_DowngradeNotRequested_Fails_VerifyPostReboot()649     public void testStagedInstallDowngradeApex_DowngradeNotRequested_Fails_VerifyPostReboot()
650             throws Exception {
651         int sessionId = retrieveLastSessionId();
652         assertSessionFailed(sessionId);
653         // INSTALL_REQUEST_DOWNGRADE wasn't set, so apex shouldn't be downgraded.
654         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
655     }
656 
657     @Test
testStagedInstallDowngradeApex_DowngradeRequested_DebugBuild_Commit()658     public void testStagedInstallDowngradeApex_DowngradeRequested_DebugBuild_Commit()
659             throws Exception {
660         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
661         int sessionId = stageDowngradeSingleApk(TestApp.Apex2).assertSuccessful().getSessionId();
662         assertSessionReady(sessionId);
663         storeSessionId(sessionId);
664     }
665 
666     @Test
testStagedInstallDowngradeApex_DowngradeRequested_DebugBuild_VerifyPostReboot()667     public void testStagedInstallDowngradeApex_DowngradeRequested_DebugBuild_VerifyPostReboot()
668             throws Exception {
669         int sessionId = retrieveLastSessionId();
670         assertSessionApplied(sessionId);
671         // Apex should be downgraded.
672         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
673     }
674 
675     @Test
testStagedInstallDowngradeApex_DowngradeRequested_UserBuild_Fails_Commit()676     public void testStagedInstallDowngradeApex_DowngradeRequested_UserBuild_Fails_Commit()
677             throws Exception {
678         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
679         int sessionId = stageDowngradeSingleApk(TestApp.Apex2).getSessionId();
680         PackageInstaller.SessionInfo sessionInfo = getSessionInfo(sessionId);
681         assertThat(sessionInfo).isStagedSessionFailed();
682         // Also verify that correct session info is reported by PackageManager.
683         assertSessionFailed(sessionId);
684         storeSessionId(sessionId);
685     }
686 
687     @Test
testStagedInstallDowngradeApex_DowngradeRequested_UserBuild_Fails_VerifyPostReboot()688     public void testStagedInstallDowngradeApex_DowngradeRequested_UserBuild_Fails_VerifyPostReboot()
689             throws Exception {
690         int sessionId = retrieveLastSessionId();
691         assertSessionFailed(sessionId);
692         // Apex shouldn't be downgraded.
693         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
694     }
695 
696     @Test
testStagedInstallDowngradeApexToSystemVersion_DebugBuild_Commit()697     public void testStagedInstallDowngradeApexToSystemVersion_DebugBuild_Commit()
698             throws Exception {
699         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
700         int sessionId = stageDowngradeSingleApk(TestApp.Apex1).assertSuccessful().getSessionId();
701         assertSessionReady(sessionId);
702         storeSessionId(sessionId);
703     }
704 
705     @Test
testStagedInstallDowngradeApexToSystemVersion_DebugBuild_VerifyPostReboot()706     public void testStagedInstallDowngradeApexToSystemVersion_DebugBuild_VerifyPostReboot()
707             throws Exception {
708         int sessionId = retrieveLastSessionId();
709         assertSessionApplied(sessionId);
710         // Apex should be downgraded.
711         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
712     }
713 
714     @Test
testFailsInvalidApexInstall_Commit()715     public void testFailsInvalidApexInstall_Commit() throws Exception {
716         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
717         int sessionId = stageSingleApk(ApexWrongSha2).assertFailure()
718                 .getSessionId();
719         assertSessionFailed(sessionId);
720         storeSessionId(sessionId);
721     }
722 
723     @Test
testFailsInvalidApexInstall_AbandonSessionIsNoop()724     public void testFailsInvalidApexInstall_AbandonSessionIsNoop() throws Exception {
725         int sessionId = retrieveLastSessionId();
726         assertSessionFailed(sessionId);
727         // Session is in a final state. Test that abandoning the session doesn't remove it from the
728         // session database.
729         abandonSession(sessionId);
730         assertSessionFailed(sessionId);
731     }
732 
733     @Test
testStagedApkSessionCallbacks()734     public void testStagedApkSessionCallbacks() throws Exception {
735 
736         List<Integer> created = new ArrayList<Integer>();
737 
738         HandlerThread handlerThread = new HandlerThread(
739                 "StagedApkSessionCallbacksTestHandlerThread");
740         handlerThread.start();
741         Handler handler = new Handler(handlerThread.getLooper());
742 
743         PackageInstaller.SessionCallback callback = new PackageInstaller.SessionCallback() {
744 
745             @Override
746             public void onCreated(int sessionId) {
747                 synchronized (created) {
748                     created.add(sessionId);
749                 }
750             }
751 
752             @Override public void onBadgingChanged(int sessionId) { }
753             @Override public void onActiveChanged(int sessionId, boolean active) { }
754             @Override public void onProgressChanged(int sessionId, float progress) { }
755             @Override public void onFinished(int sessionId, boolean success) { }
756         };
757 
758         Context context = InstrumentationRegistry.getInstrumentation().getContext();
759         PackageInstaller packageInstaller = getPackageInstaller();
760         packageInstaller.registerSessionCallback(callback, handler);
761 
762         int sessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId();
763 
764         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
765         assertSessionReady(sessionId);
766 
767         packageInstaller.unregisterSessionCallback(callback);
768 
769         handlerThread.quitSafely();
770         handlerThread.join();
771 
772         synchronized (created) {
773             assertThat(created).containsExactly(sessionId);
774         }
775         packageInstaller.abandonSession(sessionId);
776     }
777 
778     @Test
testInstallStagedApexWithoutApexSuffix_Commit()779     public void testInstallStagedApexWithoutApexSuffix_Commit() throws Exception {
780         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
781 
782         int sessionId = stageSingleApk("com.android.apex.cts.shim.v2.apex", "package")
783                 .assertSuccessful().getSessionId();
784         assertSessionReady(sessionId);
785         storeSessionId(sessionId);
786         // Version shouldn't change before reboot.
787         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
788     }
789 
790     @Test
testInstallStagedApexWithoutApexSuffix_VerifyPostReboot()791     public void testInstallStagedApexWithoutApexSuffix_VerifyPostReboot() throws Exception {
792         int sessionId = retrieveLastSessionId();
793         assertSessionApplied(sessionId);
794         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
795     }
796 
797     @Test
testRejectsApexDifferentCertificate()798     public void testRejectsApexDifferentCertificate() throws Exception {
799         int sessionId = stageSingleApk(Apex2DifferentCertificate)
800                 .assertFailure().getSessionId();
801         PackageInstaller.SessionInfo info = getSessionInfo(sessionId);
802         assertThat(info.getSessionId()).isEqualTo(sessionId);
803         assertThat(info).isStagedSessionFailed();
804         assertThat(info.getStagedSessionErrorMessage()).contains("is not compatible with the one "
805                 + "currently installed on device");
806     }
807 
808     /**
809      * Tests for staged install involving rotated keys.
810      *
811      * Here alice means the original default key that cts.shim.v1 package was signed with and
812      * bob is the new key alice rotates to. Where ambiguous, we will refer keys as alice and bob
813      * instead of "old key" and "new key".
814      */
815 
816     // The update should fail if it is signed with a different non-rotated key
817     @Test
testUpdateWithDifferentKeyButNoRotation()818     public void testUpdateWithDifferentKeyButNoRotation() throws Exception {
819         stageSingleApk(Apex2SignedBob).assertFailure().getSessionId();
820     }
821 
822     // The update should pass if it is signed with a proper rotated key
823     @Test
testUpdateWithDifferentKey_Commit()824     public void testUpdateWithDifferentKey_Commit() throws Exception {
825         stageSingleApk(Apex2SignedBobRot).assertSuccessful().getSessionId();
826     }
827 
828     @Test
testUpdateWithDifferentKey_VerifyPostReboot()829     public void testUpdateWithDifferentKey_VerifyPostReboot() throws Exception {
830         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
831     }
832 
833     // Once updated with a new rotated key (bob), further updates with old key (alice) should fail
834     @Test
testUntrustedOldKeyIsRejected()835     public void testUntrustedOldKeyIsRejected() throws Exception {
836         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
837         stageSingleApk(TestApp.Apex3).assertFailure().getSessionId();
838     }
839 
840     // Should be able to update with an old key which is trusted
841     @Test
testTrustedOldKeyIsAccepted_Commit()842     public void testTrustedOldKeyIsAccepted_Commit() throws Exception {
843         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
844         stageSingleApk(Apex2SignedBobRotRollback).assertSuccessful().getSessionId();
845     }
846 
847     @Test
testTrustedOldKeyIsAccepted_CommitPostReboot()848     public void testTrustedOldKeyIsAccepted_CommitPostReboot() throws Exception {
849         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
850         stageSingleApk(TestApp.Apex3).assertSuccessful().getSessionId();
851     }
852 
853     @Test
testTrustedOldKeyIsAccepted_VerifyPostReboot()854     public void testTrustedOldKeyIsAccepted_VerifyPostReboot() throws Exception {
855         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
856     }
857 
858     // Once updated with a new rotated key (bob), further updates with new key (bob) should pass
859     @Test
testAfterRotationNewKeyCanUpdateFurther_CommitPostReboot()860     public void testAfterRotationNewKeyCanUpdateFurther_CommitPostReboot() throws Exception {
861         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
862         stageSingleApk(Apex3SignedBobRot).assertSuccessful().getSessionId();
863     }
864 
865     @Test
testAfterRotationNewKeyCanUpdateFurther_VerifyPostReboot()866     public void testAfterRotationNewKeyCanUpdateFurther_VerifyPostReboot() throws Exception {
867         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
868     }
869 
870     // Once updated with a new rotated key (bob), further updates can be done with key only
871     @Test
testAfterRotationNewKeyCanUpdateFurtherWithoutLineage()872     public void testAfterRotationNewKeyCanUpdateFurtherWithoutLineage()
873             throws Exception {
874         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
875         stageSingleApk(Apex3SignedBob).assertSuccessful().getSessionId();
876     }
877 
878     /**
879      * Tests for staging and installing multiple staged sessions.
880      */
881 
882     // Should fail to stage multiple sessions when check-point is not available
883     @Test
testFailStagingMultipleSessionsIfNoCheckPoint()884     public void testFailStagingMultipleSessionsIfNoCheckPoint() throws Exception {
885         stageSingleApk(TestApp.A1).assertSuccessful();
886         int sessionId = stageSingleApk(TestApp.B1).assertFailure().getSessionId();
887         PackageInstaller.SessionInfo info = getSessionInfo(sessionId);
888         assertThat(info).isStagedSessionFailed();
889         assertThat(info.getStagedSessionErrorMessage()).contains(
890                 "Cannot stage multiple sessions without checkpoint support");
891     }
892 
893     @Test
testFailOverlappingMultipleStagedInstall_BothSinglePackage_Apk()894     public void testFailOverlappingMultipleStagedInstall_BothSinglePackage_Apk() throws Exception {
895         stageSingleApk(TestApp.A1).assertSuccessful().getSessionId();
896 
897         int sessionId = stageSingleApk(TestApp.A1).assertFailure().getSessionId();
898         PackageInstaller.SessionInfo info = getSessionInfo(sessionId);
899         assertThat(info).isStagedSessionFailed();
900         assertThat(info.getStagedSessionErrorMessage()).contains(
901                 "has been staged already by session");
902     }
903 
904     @Test
testAllowNonOverlappingMultipleStagedInstall_MultiPackageSinglePackage_Apk()905     public void testAllowNonOverlappingMultipleStagedInstall_MultiPackageSinglePackage_Apk()
906             throws Exception {
907         stageMultipleApks(TestApp.A1, TestApp.B1).assertSuccessful();
908         stageSingleApk(TestApp.C1).assertSuccessful();
909     }
910 
911     @Test
testFailOverlappingMultipleStagedInstall_BothMultiPackage_Apk()912     public void testFailOverlappingMultipleStagedInstall_BothMultiPackage_Apk() throws Exception {
913         stageMultipleApks(TestApp.A1, TestApp.B1).assertSuccessful().getSessionId();
914         int sessionId = stageMultipleApks(TestApp.A2, TestApp.C1).assertFailure().getSessionId();
915         PackageInstaller.SessionInfo info = getSessionInfo(sessionId);
916         assertThat(info).isStagedSessionFailed();
917         assertThat(info.getStagedSessionErrorMessage()).contains(
918                 "has been staged already by session");
919     }
920 
921     // Should succeed in installing multiple staged sessions together
922     @Test
testMultipleStagedInstall_ApkOnly_Commit()923     public void testMultipleStagedInstall_ApkOnly_Commit()
924             throws Exception {
925         int firstSessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId();
926         int secondSessionId = stageSingleApk(TestApp.B1).assertSuccessful().getSessionId();
927         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
928         assertThat(getInstalledVersion(TestApp.B)).isEqualTo(-1);
929         storeSessionIds(Arrays.asList(firstSessionId, secondSessionId));
930     }
931 
932     @Test
testMultipleStagedInstall_ApkOnly_VerifyPostReboot()933     public void testMultipleStagedInstall_ApkOnly_VerifyPostReboot()
934             throws Exception {
935         List<Integer> sessionIds = retrieveLastSessionIds();
936         for (int sessionId: sessionIds) {
937             assertSessionApplied(sessionId);
938         }
939         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1);
940         assertThat(getInstalledVersion(TestApp.B)).isEqualTo(1);
941     }
942 
943     // If apk installation fails in one staged session, then all staged session should fail.
944     @Test
testInstallMultipleStagedSession_PartialFail_ApkOnly_Commit()945     public void testInstallMultipleStagedSession_PartialFail_ApkOnly_Commit()
946             throws Exception {
947         int firstSessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId();
948         int secondSessionId = stageSingleApk(TestApp.B1).assertSuccessful().getSessionId();
949 
950         // Install TestApp.A2 so that after reboot TestApp.A1 fails to install as it is downgrade
951         Install.single(TestApp.A2).commit();
952 
953         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(2);
954         assertThat(getInstalledVersion(TestApp.B)).isEqualTo(-1);
955         storeSessionIds(Arrays.asList(firstSessionId, secondSessionId));
956     }
957 
958     @Test
testInstallMultipleStagedSession_PartialFail_ApkOnly_VerifyPostReboot()959     public void testInstallMultipleStagedSession_PartialFail_ApkOnly_VerifyPostReboot()
960             throws Exception {
961         List<Integer> sessionIds = retrieveLastSessionIds();
962         for (int sessionId: sessionIds) {
963             assertSessionFailed(sessionId);
964         }
965         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(2);
966         assertThat(getInstalledVersion(TestApp.B)).isEqualTo(-1);
967     }
968 
969     // Failure reason of staged install should be be persisted for single sessions
970     @Test
testFailureReasonPersists_SingleSession_Commit()971     public void testFailureReasonPersists_SingleSession_Commit() throws Exception {
972         int sessionId = Install.single(TestApp.A1).setStaged().commit();
973         // Install TestApp.A2 so that after reboot TestApp.A1 fails to install as it is downgrade
974         Install.single(TestApp.A2).commit();
975         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(2);
976         storeSessionId(sessionId);
977     }
978 
979     @Test
testFailureReasonPersists_SingleSession_VerifyPostReboot()980     public void testFailureReasonPersists_SingleSession_VerifyPostReboot() throws Exception {
981         int sessionId = retrieveLastSessionId();
982         assertSessionFailedWithMessage(sessionId, "Failed to install sessionId: " + sessionId);
983         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(2);
984     }
985 
986     // If apk installation fails in one staged session, then all staged session should fail.
987     @Test
testFailureReasonPersists_MultipleSession_Commit()988     public void testFailureReasonPersists_MultipleSession_Commit() throws Exception {
989         int firstSessionId = Install.single(TestApp.A1).setStaged().commit();
990         int secondSessionId = Install.single(TestApp.B1).setStaged().commit();
991         // Install TestApp.A2 so that after reboot TestApp.A1 fails to install as it is downgrade
992         Install.single(TestApp.A2).commit();
993         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(2);
994         assertThat(getInstalledVersion(TestApp.B)).isEqualTo(-1);
995         storeSessionIds(Arrays.asList(firstSessionId, secondSessionId));
996     }
997 
998     @Test
testFailureReasonPersists_MultipleSession_VerifyPostReboot()999     public void testFailureReasonPersists_MultipleSession_VerifyPostReboot() throws Exception {
1000         List<Integer> sessionIds = retrieveLastSessionIds();
1001         int failingSessionId = sessionIds.get(0);
1002         for (int sessionId: sessionIds) {
1003             assertSessionFailedWithMessage(sessionId, "Failed to install sessionId: "
1004                     + failingSessionId);
1005         }
1006         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(2);
1007         assertThat(getInstalledVersion(TestApp.B)).isEqualTo(-1);
1008     }
1009 
1010     @Test
testSamegradeSystemApex_Commit()1011     public void testSamegradeSystemApex_Commit() throws Exception {
1012         final PackageInfo shim =
1013                 InstrumentationRegistry.getInstrumentation().getContext().getPackageManager()
1014                         .getPackageInfo(SHIM_APEX_PACKAGE_NAME, PackageManager.MATCH_APEX);
1015         assertThat(shim.getLongVersionCode()).isEqualTo(1);
1016         assertThat(shim.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM).isEqualTo(
1017                 ApplicationInfo.FLAG_SYSTEM);
1018         assertThat(shim.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED).isEqualTo(
1019                 ApplicationInfo.FLAG_INSTALLED);
1020         int sessionId = stageDowngradeSingleApk(TestApp.Apex1).assertSuccessful().getSessionId();
1021         storeSessionId(sessionId);
1022     }
1023 
1024     @Test
testSamegradeSystemApex_VerifyPostReboot()1025     public void testSamegradeSystemApex_VerifyPostReboot() throws Exception {
1026         int sessionId = retrieveLastSessionId();
1027         assertSessionApplied(sessionId);
1028         final PackageInfo shim =
1029                 InstrumentationRegistry.getInstrumentation().getContext().getPackageManager()
1030                         .getPackageInfo(SHIM_APEX_PACKAGE_NAME, PackageManager.MATCH_APEX);
1031         assertThat(shim.getLongVersionCode()).isEqualTo(1);
1032         // Check that APEX on /data wins.
1033         assertThat(shim.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)
1034                 .isEqualTo(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP);
1035         assertThat(shim.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED).isEqualTo(
1036                 ApplicationInfo.FLAG_INSTALLED);
1037         assertThat(shim.applicationInfo.sourceDir)
1038                 .isEqualTo("/data/apex/active/com.android.apex.cts.shim@1.apex");
1039         assertThat(shim.applicationInfo.publicSourceDir).isEqualTo(shim.applicationInfo.sourceDir);
1040     }
1041 
1042     @Test
testInstallApkChangingFingerprint()1043     public void testInstallApkChangingFingerprint() throws Exception {
1044         int sessionId = Install.single(TestApp.A1).setStaged().commit();
1045         storeSessionId(sessionId);
1046     }
1047 
1048     @Test
testInstallApkChangingFingerprint_VerifyAborted()1049     public void testInstallApkChangingFingerprint_VerifyAborted() throws Exception {
1050         int sessionId = retrieveLastSessionId();
1051         assertSessionFailed(sessionId);
1052     }
1053 
1054     @Test
testInstallStagedNoHashtreeApex_Commit()1055     public void testInstallStagedNoHashtreeApex_Commit() throws Exception {
1056         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
1057         int sessionId = stageSingleApk(ApexNoHashtree2).assertSuccessful().getSessionId();
1058         assertSessionReady(sessionId);
1059         storeSessionId(sessionId);
1060         // Version shouldn't change before reboot.
1061         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
1062     }
1063 
1064     @Test
testInstallStagedNoHashtreeApex_VerifyPostReboot()1065     public void testInstallStagedNoHashtreeApex_VerifyPostReboot() throws Exception {
1066         int sessionId = retrieveLastSessionId();
1067         assertSessionApplied(sessionId);
1068         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
1069         // Read all files under /apex/com.android.apex.cts.shim to somewhat verify that hashtree
1070         // is not corrupted
1071         Files.walkFileTree(Paths.get("/apex/com.android.apex.cts.shim"),
1072                 new SimpleFileVisitor<Path>() {
1073 
1074                     @Override
1075                     public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
1076                             throws IOException {
1077                         Files.readAllBytes(file);
1078                         return FileVisitResult.CONTINUE;
1079                     }
1080 
1081                     @Override
1082                     public FileVisitResult visitFileFailed(Path file, IOException exc)
1083                             throws IOException {
1084                         if (file.endsWith("lost+found")) {
1085                             return FileVisitResult.CONTINUE;
1086                         }
1087                         throw exc;
1088                     }
1089                 });
1090     }
1091 
1092     @Test
testInstallStagedApex_SameGrade_NewOneWins_Commit()1093     public void testInstallStagedApex_SameGrade_NewOneWins_Commit() throws Exception {
1094         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
1095         assertThat(getInstalledVersion(SHIM_PACKAGE_NAME)).isNotEqualTo(-1);
1096         int sessionId = Install.single(Apex2WithoutApkInApex).setStaged().commit();
1097         assertSessionReady(sessionId);
1098         storeSessionId(sessionId);
1099     }
1100 
1101     @Test
testInstallStagedApex_SameGrade_NewOneWins_VerifyPostReboot()1102     public void testInstallStagedApex_SameGrade_NewOneWins_VerifyPostReboot() throws Exception {
1103         int sessionId = retrieveLastSessionId();
1104         assertSessionApplied(sessionId);
1105         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
1106         assertThat(getInstalledVersion(SHIM_PACKAGE_NAME)).isEqualTo(-1);
1107     }
1108 
1109     /**
1110      * Should fail to verify apex targeting older dev sdk
1111      */
1112     @Test
testApexTargetingOldDevSdkFailsVerification()1113     public void testApexTargetingOldDevSdkFailsVerification() throws Exception {
1114         stageSingleApk(Apex2SdkTargetP).assertFailure();
1115     }
1116 
1117     /**
1118      * Apex should fail to install if apk-in-apex fails to get scanned
1119      */
1120     @Test
testApexFailsToInstallIfApkInApexFailsToScan_Commit()1121     public void testApexFailsToInstallIfApkInApexFailsToScan_Commit() throws Exception {
1122         int sessionId = stageSingleApk(Apex2ApkInApexSdkTargetP).assertSuccessful().getSessionId();
1123         storeSessionId(sessionId);
1124     }
1125 
1126     @Test
testApexFailsToInstallIfApkInApexFailsToScan_VerifyPostReboot()1127     public void testApexFailsToInstallIfApkInApexFailsToScan_VerifyPostReboot() throws Exception {
1128         int sessionId = retrieveLastSessionId();
1129         assertSessionFailed(sessionId);
1130         assertSessionFailedWithMessage(sessionId, "Failed to parse "
1131                 + "/apex/com.android.apex.cts.shim/app/CtsShimTargetPSdk");
1132         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
1133     }
1134 
1135     @Test
testCorruptedApexFailsVerification_b146895998()1136     public void testCorruptedApexFailsVerification_b146895998() throws Exception {
1137         int sessionId = stageSingleApk(CorruptedApex_b146895998).assertFailure().getSessionId();
1138         PackageInstaller.SessionInfo sessionInfo = getSessionInfo(sessionId);
1139         assertThat(sessionInfo).isStagedSessionFailed();
1140     }
1141 
1142     /**
1143      * Should fail to pass apk signature check
1144      */
1145     @Test
testApexWithUnsignedApkFailsVerification()1146     public void testApexWithUnsignedApkFailsVerification() throws Exception {
1147         assertThat(stageSingleApk(Apex2NoApkSignature).getErrorMessage())
1148                 .contains("INSTALL_PARSE_FAILED_NO_CERTIFICATES");
1149     }
1150 
1151     /**
1152      * Should fail to verify apex with unsigned payload
1153      */
1154     @Test
testApexWithUnsignedPayloadFailsVerification()1155     public void testApexWithUnsignedPayloadFailsVerification() throws Exception {
1156         int sessionId = stageSingleApk(Apex2UnsignedPayload).assertFailure().getSessionId();
1157         PackageInstaller.SessionInfo sessionInfo = getSessionInfo(sessionId);
1158         assertThat(sessionInfo).isStagedSessionFailed();
1159         assertThat(sessionInfo.getStagedSessionErrorMessage())
1160                 .contains("AVB footer verification failed");
1161     }
1162 
1163     /**
1164      * Should fail to verify apex signed payload with a different key
1165      */
1166     @Test
testApexSignPayloadWithDifferentKeyFailsVerification()1167     public void testApexSignPayloadWithDifferentKeyFailsVerification() throws Exception {
1168         int sessionId = stageSingleApk(
1169                 Apex2SignPayloadWithDifferentKey).assertFailure().getSessionId();
1170         PackageInstaller.SessionInfo sessionInfo = getSessionInfo(sessionId);
1171         assertThat(sessionInfo).isStagedSessionFailed();
1172         assertThat(sessionInfo.getStagedSessionErrorMessage())
1173                 .contains("public key doesn't match the pre-installed one");
1174     }
1175 
1176     /**
1177      * Test non-priv apps cannot access /data/app-staging folder contents
1178      */
1179     @Test
testAppStagingDirCannotBeReadByNonPrivApps()1180     public void testAppStagingDirCannotBeReadByNonPrivApps() throws Exception {
1181         final int sessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId();
1182         // Non-priv apps should not be able to view contents of app-staging directory
1183         final File appStagingDir = new File("/data/app-staging");
1184         assertThat(appStagingDir.exists()).isTrue();
1185         assertThat(appStagingDir.listFiles()).isNull();
1186         // Non-owner user should not be able to access sub-dirs of app-staging directory
1187         final File appStagingSubDir = new File("/data/app-staging/session_" + sessionId);
1188         assertThat(appStagingSubDir.exists()).isFalse();
1189         assertThat(appStagingDir.listFiles()).isNull();
1190     }
1191 
getInstalledVersion(String packageName)1192     private static long getInstalledVersion(String packageName) {
1193         Context context = InstrumentationRegistry.getInstrumentation().getContext();
1194         PackageManager pm = context.getPackageManager();
1195         try {
1196             PackageInfo info = pm.getPackageInfo(packageName, PackageManager.MATCH_APEX);
1197             return info.getLongVersionCode();
1198         } catch (PackageManager.NameNotFoundException e) {
1199             return -1;
1200         }
1201     }
1202 
1203     @Test
testApexSetsUpdatedSystemAppFlag_preUpdate()1204     public void testApexSetsUpdatedSystemAppFlag_preUpdate() throws Exception {
1205         final PackageInfo info = InstallUtils.getPackageInfo(SHIM_APEX_PACKAGE_NAME);
1206         assertThat(info).isNotNull();
1207         boolean isSystemApp = (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
1208         boolean isUpdatedSystemApp =
1209                 (info.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
1210         assertThat(isSystemApp).isTrue();
1211         assertThat(isUpdatedSystemApp).isFalse();
1212     }
1213 
1214     @Test
testApexSetsUpdatedSystemAppFlag_postUpdate()1215     public void testApexSetsUpdatedSystemAppFlag_postUpdate() throws Exception {
1216         final PackageInfo info = InstallUtils.getPackageInfo(SHIM_APEX_PACKAGE_NAME);
1217         assertThat(info).isNotNull();
1218         boolean isSystemApp = (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
1219         boolean isUpdatedSystemApp =
1220                 (info.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
1221         assertThat(isSystemApp).isTrue();
1222         assertThat(isUpdatedSystemApp).isTrue();
1223     }
1224 
1225     @Test
testRebootlessUpdate()1226     public void testRebootlessUpdate() throws Exception {
1227         InstallUtils.dropShellPermissionIdentity();
1228         InstallUtils.adoptShellPermissionIdentity(Manifest.permission.INSTALL_PACKAGE_UPDATES);
1229 
1230         final PackageManager pm =
1231                 InstrumentationRegistry.getInstrumentation().getContext().getPackageManager();
1232         {
1233             PackageInfo apex = pm.getPackageInfo(SHIM_APEX_PACKAGE_NAME, PackageManager.MATCH_APEX);
1234             assertThat(apex.getLongVersionCode()).isEqualTo(1);
1235             assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
1236                     .isEqualTo(ApplicationInfo.FLAG_SYSTEM);
1237             assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED)
1238                     .isEqualTo(ApplicationInfo.FLAG_INSTALLED);
1239             assertThat(apex.applicationInfo.sourceDir).startsWith("/system/apex");
1240         }
1241 
1242         Install.single(Apex2Rebootless).commit();
1243         {
1244             PackageInfo apex = pm.getPackageInfo(SHIM_APEX_PACKAGE_NAME, PackageManager.MATCH_APEX);
1245             assertThat(apex.getLongVersionCode()).isEqualTo(2);
1246             assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)
1247                     .isEqualTo(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP);
1248             assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED)
1249                     .isEqualTo(ApplicationInfo.FLAG_INSTALLED);
1250             assertThat(apex.applicationInfo.sourceDir).startsWith("/data/apex/active");
1251         }
1252         {
1253             PackageInfo apex = pm.getPackageInfo(SHIM_APEX_PACKAGE_NAME,
1254                     PackageManager.MATCH_APEX | PackageManager.MATCH_FACTORY_ONLY);
1255             assertThat(apex.getLongVersionCode()).isEqualTo(1);
1256             assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
1257                     .isEqualTo(ApplicationInfo.FLAG_SYSTEM);
1258             assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED)
1259                     .isEqualTo(ApplicationInfo.FLAG_INSTALLED);
1260             assertThat(apex.applicationInfo.sourceDir).startsWith("/system/apex");
1261         }
1262     }
1263 
1264     @Test
testRebootlessUpdate_installV3()1265     public void testRebootlessUpdate_installV3() throws Exception {
1266         InstallUtils.dropShellPermissionIdentity();
1267         InstallUtils.adoptShellPermissionIdentity(Manifest.permission.INSTALL_PACKAGE_UPDATES);
1268 
1269         final PackageManager pm =
1270                 InstrumentationRegistry.getInstrumentation().getContext().getPackageManager();
1271 
1272         Install.single(Apex3Rebootless).commit();
1273         {
1274             PackageInfo apex = pm.getPackageInfo(SHIM_APEX_PACKAGE_NAME, PackageManager.MATCH_APEX);
1275             assertThat(apex.getLongVersionCode()).isEqualTo(3);
1276             assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)
1277                     .isEqualTo(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP);
1278             assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED)
1279                     .isEqualTo(ApplicationInfo.FLAG_INSTALLED);
1280             assertThat(apex.applicationInfo.sourceDir).startsWith("/data/apex/active");
1281         }
1282         {
1283             PackageInfo apex = pm.getPackageInfo(SHIM_APEX_PACKAGE_NAME,
1284                     PackageManager.MATCH_APEX | PackageManager.MATCH_FACTORY_ONLY);
1285             assertThat(apex.getLongVersionCode()).isEqualTo(1);
1286             assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
1287                     .isEqualTo(ApplicationInfo.FLAG_SYSTEM);
1288             assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED)
1289                     .isEqualTo(ApplicationInfo.FLAG_INSTALLED);
1290             assertThat(apex.applicationInfo.sourceDir).startsWith("/system/apex");
1291         }
1292     }
1293 
1294     @Test
testRebootlessUpdate_downgradeToV2_fails()1295     public void testRebootlessUpdate_downgradeToV2_fails() throws Exception {
1296         InstallUtils.dropShellPermissionIdentity();
1297         InstallUtils.adoptShellPermissionIdentity(Manifest.permission.INSTALL_PACKAGE_UPDATES);
1298 
1299         final PackageManager pm =
1300                 InstrumentationRegistry.getInstrumentation().getContext().getPackageManager();
1301 
1302         {
1303             PackageInfo apex = pm.getPackageInfo(SHIM_APEX_PACKAGE_NAME, PackageManager.MATCH_APEX);
1304             assertThat(apex.getLongVersionCode()).isEqualTo(3);
1305             assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)
1306                     .isEqualTo(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP);
1307             assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED)
1308                     .isEqualTo(ApplicationInfo.FLAG_INSTALLED);
1309             assertThat(apex.applicationInfo.sourceDir).startsWith("/data/apex/active");
1310         }
1311 
1312         InstallUtils.commitExpectingFailure(
1313                     AssertionError.class,
1314                     "INSTALL_FAILED_VERSION_DOWNGRADE",
1315                     Install.single(Apex2Rebootless));
1316         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
1317     }
1318 
1319     @Test
testRebootlessUpdate_noPermission_fails()1320     public void testRebootlessUpdate_noPermission_fails() throws Exception {
1321         InstallUtils.dropShellPermissionIdentity();
1322 
1323         InstallUtils.commitExpectingFailure(SecurityException.class,
1324                     "Not allowed to perform APEX updates",
1325                     Install.single(Apex2Rebootless));
1326         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
1327     }
1328 
1329     @Test
testRebootlessUpdate_noPreInstalledApex_fails()1330     public void testRebootlessUpdate_noPreInstalledApex_fails() throws Exception {
1331         assertThat(getInstalledVersion(DIFFERENT_APEX_PACKAGE_NAME)).isEqualTo(-1);
1332         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
1333 
1334         InstallUtils.commitExpectingFailure(
1335                 AssertionError.class,
1336                 "Attempting to install new APEX package",
1337                 Install.single(Apex2DifferentPackageName));
1338         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
1339     }
1340 
1341     @Test
testRebootlessUpdate_unsignedPayload_fails()1342     public void testRebootlessUpdate_unsignedPayload_fails() throws Exception {
1343         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
1344 
1345         InstallUtils.commitExpectingFailure(
1346                 AssertionError.class,
1347                 "AVB footer verification failed",
1348                 Install.single(Apex2UnsignedPayload));
1349         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
1350     }
1351 
1352     @Test
testRebootlessUpdate_payloadSignedWithDifferentKey_fails()1353     public void testRebootlessUpdate_payloadSignedWithDifferentKey_fails() throws Exception {
1354         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
1355 
1356         InstallUtils.commitExpectingFailure(
1357                 AssertionError.class,
1358                 "public key doesn't match the pre-installed one",
1359                 Install.single(Apex2SignPayloadWithDifferentKey));
1360         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
1361     }
1362 
1363     @Test
testRebootlessUpdate_outerContainerSignedWithDifferentCert_fails()1364     public void testRebootlessUpdate_outerContainerSignedWithDifferentCert_fails()
1365             throws Exception {
1366         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
1367 
1368         InstallUtils.commitExpectingFailure(
1369                 AssertionError.class,
1370                 "APK container signature of .+ is not compatible with the one currently installed",
1371                 Install.single(Apex2DifferentCertificate));
1372         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
1373     }
1374 
1375     @Test
testRebootlessUpdate_outerContainerUnsigned_fails()1376     public void testRebootlessUpdate_outerContainerUnsigned_fails() throws Exception {
1377         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
1378 
1379         InstallUtils.commitExpectingFailure(
1380                 AssertionError.class,
1381                 "Failed to collect certificates from",
1382                 Install.single(Apex2NoApkSignature));
1383         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
1384     }
1385 
1386     @Test
testRebootlessUpdate_targetsOlderSdk_fails()1387     public void testRebootlessUpdate_targetsOlderSdk_fails() throws Exception {
1388         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
1389 
1390         InstallUtils.commitExpectingFailure(
1391                 AssertionError.class,
1392                 "Requires development platform P",
1393                 Install.single(Apex2SdkTargetP));
1394         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
1395     }
1396 
1397     @Test
testGetInactiveApexFactoryPackagesAfterApexInstall_containsNoDuplicates()1398     public void testGetInactiveApexFactoryPackagesAfterApexInstall_containsNoDuplicates()
1399             throws Exception {
1400         int flags = (PackageManager.MATCH_APEX | PackageManager.MATCH_FACTORY_ONLY
1401                 | PackageManager.MATCH_UNINSTALLED_PACKAGES);
1402         List<PackageInfo> packageInfos =
1403                 InstrumentationRegistry.getInstrumentation().getContext().getPackageManager()
1404                         .getInstalledPackages(flags);
1405         Set<String> foundPackages = new HashSet<>();
1406         for (PackageInfo pi : packageInfos) {
1407             if (foundPackages.contains(pi.packageName)) {
1408                 throw new AssertionError(pi.packageName + " is listed at least twice.");
1409             }
1410             foundPackages.add(pi.packageName);
1411         }
1412     }
1413 
1414     // It becomes harder to maintain this variety of install-related helper methods.
1415     // TODO(ioffe): refactor install-related helper methods into a separate utility.
createStagedSession()1416     private static int createStagedSession() throws Exception {
1417         return Install.single(TestApp.A1).setStaged().createSession();
1418     }
1419 
commitSession(int sessionId)1420     private static Intent commitSession(int sessionId) throws IOException, InterruptedException {
1421         LocalIntentSender sender = new LocalIntentSender();
1422         InstallUtils.openPackageInstallerSession(sessionId)
1423                 .commit(sender.getIntentSender());
1424         var result = sender.pollResult(5, TimeUnit.MINUTES);
1425         if (result == null) {
1426             throw new AssertionError("Install timeout, sessionId=" + sessionId);
1427         }
1428         return result;
1429     }
1430 
stageDowngradeSingleApk(TestApp testApp)1431     private static StageSessionResult stageDowngradeSingleApk(TestApp testApp) throws Exception {
1432         Log.i(TAG, "Staging a downgrade of " + testApp);
1433         int sessionId = Install.single(testApp).setStaged().setRequestDowngrade().createSession();
1434         // Commit the session (this will start the installation workflow).
1435         Log.i(TAG, "Committing downgrade session for apk: " + testApp);
1436         Intent result = commitSession(sessionId);
1437         return new StageSessionResult(sessionId, result);
1438     }
1439 
stageSingleApk(String apkFileName, String outputFileName)1440     private static StageSessionResult stageSingleApk(String apkFileName, String outputFileName)
1441             throws Exception {
1442         File tmpFile = File.createTempFile(outputFileName, null);
1443         try (InputStream is =
1444                      StagedInstallTest.class.getClassLoader().getResourceAsStream(apkFileName)) {
1445             Files.copy(is, tmpFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
1446         }
1447         TestApp testApp = new TestApp(tmpFile.getName(), null, -1,
1448                 apkFileName.endsWith(".apex"), tmpFile);
1449         return stageSingleApk(testApp);
1450     }
1451 
stageSingleApk(TestApp testApp)1452     private static StageSessionResult stageSingleApk(TestApp testApp) throws Exception {
1453         Log.i(TAG, "Staging an install of " + testApp);
1454         int sessionId = Install.single(testApp).setStaged().createSession();
1455         // Commit the session (this will start the installation workflow).
1456         Log.i(TAG, "Committing session for apk: " + testApp);
1457         Intent result = commitSession(sessionId);
1458         return new StageSessionResult(sessionId, result);
1459     }
1460 
stageMultipleApks(TestApp... testApps)1461     private static StageSessionResult stageMultipleApks(TestApp... testApps) throws Exception {
1462         Log.i(TAG, "Staging an install of " + Arrays.toString(testApps));
1463         int multiPackageSessionId = Install.multi(testApps).setStaged().createSession();
1464         Intent result = commitSession(multiPackageSessionId);
1465         return new StageSessionResult(multiPackageSessionId, result);
1466     }
1467 
assertSessionApplied(int sessionId)1468     private static void assertSessionApplied(int sessionId) {
1469         assertSessionState(sessionId,
1470                 (session) -> assertThat(session).isStagedSessionApplied());
1471     }
1472 
assertSessionReady(int sessionId)1473     private static void assertSessionReady(int sessionId) {
1474         assertSessionState(sessionId,
1475                 (session) -> assertThat(session).isStagedSessionReady());
1476     }
1477 
assertSessionFailed(int sessionId)1478     private static void assertSessionFailed(int sessionId) {
1479         assertSessionState(sessionId,
1480                 (session) -> assertThat(session).isStagedSessionFailed());
1481     }
1482 
assertSessionFailedWithMessage(int sessionId, String msg)1483     private static void assertSessionFailedWithMessage(int sessionId, String msg) {
1484         assertSessionState(sessionId, (session) -> {
1485             assertThat(session).isStagedSessionFailed();
1486             assertThat(session.getStagedSessionErrorMessage()).contains(msg);
1487         });
1488     }
1489 
assertSessionState( int sessionId, Consumer<PackageInstaller.SessionInfo> assertion)1490     private static void assertSessionState(
1491             int sessionId, Consumer<PackageInstaller.SessionInfo> assertion) {
1492         PackageInstaller packageInstaller = getPackageInstaller();
1493 
1494         List<PackageInstaller.SessionInfo> sessions = packageInstaller.getStagedSessions();
1495         boolean found = false;
1496         for (PackageInstaller.SessionInfo session : sessions) {
1497             if (session.getSessionId() == sessionId) {
1498                 assertion.accept(session);
1499                 found = true;
1500             }
1501         }
1502         assertWithMessage("Expecting to find session in getStagedSession()")
1503                 .that(found).isTrue();
1504 
1505         // Test also that getSessionInfo correctly returns the session.
1506         PackageInstaller.SessionInfo sessionInfo = packageInstaller.getSessionInfo(sessionId);
1507         assertion.accept(sessionInfo);
1508     }
1509 
storeSessionId(int sessionId)1510     private void storeSessionId(int sessionId) throws Exception {
1511         try (BufferedWriter writer = new BufferedWriter(new FileWriter(mTestStateFile))) {
1512             writer.write("" + sessionId);
1513         }
1514     }
1515 
retrieveLastSessionId()1516     private int retrieveLastSessionId() throws Exception {
1517         try (BufferedReader reader = new BufferedReader(new FileReader(mTestStateFile))) {
1518             return Integer.parseInt(reader.readLine());
1519         }
1520     }
1521 
storeSessionIds(List<Integer> sessionIds)1522     private void storeSessionIds(List<Integer> sessionIds) throws Exception {
1523         try (BufferedWriter writer = new BufferedWriter(new FileWriter(mTestStateFile))) {
1524             writer.write(sessionIds.toString());
1525         }
1526     }
1527 
retrieveLastSessionIds()1528     private List<Integer> retrieveLastSessionIds() throws Exception {
1529         try (BufferedReader reader = new BufferedReader(new FileReader(mTestStateFile))) {
1530             String line = reader.readLine();
1531             String[] sessionIdsStr = line.substring(1, line.length() - 1).split(", ");
1532             ArrayList<Integer> result = new ArrayList<>();
1533             for (String sessionIdStr: sessionIdsStr) {
1534                 result.add(Integer.parseInt(sessionIdStr));
1535             }
1536             return result;
1537         }
1538     }
1539 
1540     // TODO(ioffe): not really-tailored to staged install, rename to InstallResult?
1541     private static final class StageSessionResult {
1542         private final int sessionId;
1543         private final Intent result;
1544 
StageSessionResult(int sessionId, Intent result)1545         private StageSessionResult(int sessionId, Intent result) {
1546             this.sessionId = sessionId;
1547             this.result = result;
1548         }
1549 
getSessionId()1550         public int getSessionId() {
1551             return sessionId;
1552         }
1553 
getErrorMessage()1554         public String getErrorMessage() {
1555             return extractErrorMessage(result);
1556         }
1557 
assertSuccessful()1558         public StageSessionResult assertSuccessful() {
1559             assertStatusSuccess(result);
1560             return this;
1561         }
1562 
assertFailure()1563         public StageSessionResult assertFailure() {
1564             assertStatusFailure(result);
1565             return this;
1566         }
1567     }
1568 
1569     /**
1570      * Counts the number of broadcast intents received for a given type during the test.
1571      * Used by to check no broadcast intents were received during the test.
1572      */
1573     private static class BroadcastCounter extends BroadcastReceiver {
1574         private final Context mContext;
1575         private final AtomicInteger mNumBroadcastReceived = new AtomicInteger();
1576 
BroadcastCounter(String action)1577         BroadcastCounter(String action) {
1578             mContext = InstrumentationRegistry.getInstrumentation().getContext();
1579             mContext.registerReceiver(this, new IntentFilter(action),
1580                     Context.RECEIVER_EXPORTED_UNAUDITED);
1581         }
1582 
1583         @Override
onReceive(Context context, Intent intent)1584         public void onReceive(Context context, Intent intent) {
1585             mNumBroadcastReceived.incrementAndGet();
1586         }
1587 
1588         /**
1589          * Waits for a while and checks no broadcasts are received.
1590          */
assertNoBroadcastReceived()1591         void assertNoBroadcastReceived() {
1592             try {
1593                 // Sleep for a reasonable amount of time and check no broadcast is received
1594                 Thread.sleep(TimeUnit.SECONDS.toMillis(10));
1595             } catch (InterruptedException ignore) {
1596             }
1597             mContext.unregisterReceiver(this);
1598             assertThat(mNumBroadcastReceived.get()).isEqualTo(0);
1599         }
1600     }
1601 
extractErrorMessage(Intent result)1602     private static String extractErrorMessage(Intent result) {
1603         int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
1604                 PackageInstaller.STATUS_FAILURE);
1605         if (status == -1) {
1606             throw new AssertionError("PENDING USER ACTION");
1607         }
1608         if (status == 0) {
1609             throw new AssertionError("Result was successful");
1610         }
1611         return result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
1612     }
1613 
abandonSession(int sessionId)1614     private static void abandonSession(int sessionId) {
1615         getPackageInstaller().abandonSession(sessionId);
1616     }
1617 
getSessionInfo(int sessionId)1618     private static PackageInstaller.SessionInfo getSessionInfo(int sessionId) {
1619         return getPackageInstaller().getSessionInfo(sessionId);
1620     }
1621 
isAuto()1622     private static boolean isAuto() {
1623         var pm = InstrumentationRegistry.getInstrumentation().getContext().getPackageManager();
1624         return pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
1625     }
1626 }
1627