| /* |
| * Copyright (C) 2019 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.tests.stagedinstall; |
| |
| import static com.android.cts.install.lib.InstallUtils.assertStatusFailure; |
| import static com.android.cts.install.lib.InstallUtils.assertStatusSuccess; |
| import static com.android.cts.install.lib.InstallUtils.getPackageInstaller; |
| import static com.android.cts.shim.lib.ShimPackage.DIFFERENT_APEX_PACKAGE_NAME; |
| import static com.android.cts.shim.lib.ShimPackage.NOT_PRE_INSTALL_APEX_PACKAGE_NAME; |
| import static com.android.cts.shim.lib.ShimPackage.SHIM_APEX_PACKAGE_NAME; |
| import static com.android.cts.shim.lib.ShimPackage.SHIM_PACKAGE_NAME; |
| import static com.android.tests.stagedinstall.PackageInstallerSessionInfoSubject.assertThat; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.common.truth.Truth.assertWithMessage; |
| |
| import static org.junit.Assert.fail; |
| |
| import android.Manifest; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageInfo; |
| import android.content.pm.PackageInstaller; |
| import android.content.pm.PackageManager; |
| import android.os.Handler; |
| import android.os.HandlerThread; |
| import android.util.Log; |
| |
| import androidx.test.platform.app.InstrumentationRegistry; |
| |
| import com.android.compatibility.common.util.SystemUtil; |
| import com.android.cts.install.lib.Install; |
| import com.android.cts.install.lib.InstallUtils; |
| import com.android.cts.install.lib.LocalIntentSender; |
| import com.android.cts.install.lib.TestApp; |
| import com.android.cts.install.lib.Uninstall; |
| |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| import java.io.BufferedReader; |
| import java.io.BufferedWriter; |
| import java.io.File; |
| import java.io.FileReader; |
| import java.io.FileWriter; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.nio.file.FileVisitResult; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.nio.file.SimpleFileVisitor; |
| import java.nio.file.StandardCopyOption; |
| import java.nio.file.attribute.BasicFileAttributes; |
| import java.time.Duration; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.atomic.AtomicInteger; |
| import java.util.function.Consumer; |
| import java.util.stream.Collectors; |
| |
| /** |
| * This series of tests are meant to be driven by a host, since some of the interactions being |
| * tested require the device to be rebooted, and some assertions to be verified post-reboot. |
| * The convention used here (not enforced) is that the test methods in this file will be named |
| * the same way as the test methods in the "host" class (see e.g. |
| * {@code com.android.test.stagedinstall.host.StagedInstallTest}), with an optional suffix preceded |
| * by an underscore, in case of multiple phases. |
| * Example: |
| * - In {@code com.android.test.stagedinstall.host.StagedInstallTest}: |
| * |
| * @Test |
| * public void testInstallStagedApk() throws Exception { |
| * runPhase("testInstallStagedApk_Commit"); |
| * getDevice().reboot(); |
| * runPhase("testInstallStagedApk_VerifyPostReboot"); |
| * } |
| * - In this class: |
| * @Test public void testInstallStagedApk_Commit() throws Exception; |
| * @Test public void testInstallStagedApk_VerifyPostReboot() throws Exception; |
| */ |
| @RunWith(JUnit4.class) |
| public class StagedInstallTest { |
| |
| private static final String TAG = "StagedInstallTest"; |
| |
| private File mTestStateFile = new File( |
| InstrumentationRegistry.getInstrumentation().getContext().getFilesDir(), |
| "ctsstagedinstall_state"); |
| |
| private static final Duration WAIT_FOR_SESSION_REMOVED_TTL = Duration.ofSeconds(10); |
| private static final Duration SLEEP_DURATION = Duration.ofMillis(200); |
| |
| private static final TestApp TESTAPP_SAME_NAME_AS_APEX = new TestApp( |
| "TestAppSamePackageNameAsApex", SHIM_APEX_PACKAGE_NAME, 1, /*isApex*/ false, |
| "StagedInstallTestAppSamePackageNameAsApex.apk"); |
| private static final TestApp Apex2DifferentCertificate = new TestApp( |
| "Apex2DifferentCertificate", SHIM_APEX_PACKAGE_NAME, 2, /*isApex*/true, |
| "com.android.apex.cts.shim.v2_different_certificate.apex"); |
| private static final TestApp Apex2DifferentPackageName = new TestApp( |
| "Apex2DifferentPackageName", DIFFERENT_APEX_PACKAGE_NAME, 2, /*isApex*/true, |
| "com.android.apex.cts.shim.v2_different_package_name.apex"); |
| private static final TestApp Apex2SignedBob = new TestApp( |
| "Apex2SignedBob", SHIM_APEX_PACKAGE_NAME, 2, /*isApex*/true, |
| "com.android.apex.cts.shim.v2_signed_bob.apex"); |
| private static final TestApp Apex2SignedBobRot = new TestApp( |
| "Apex2SignedBobRot", SHIM_APEX_PACKAGE_NAME, 2, /*isApex*/true, |
| "com.android.apex.cts.shim.v2_signed_bob_rot.apex"); |
| private static final TestApp Apex2SignedBobRotRollback = new TestApp( |
| "Apex2SignedBobRotRollback", SHIM_APEX_PACKAGE_NAME, 2, /*isApex*/true, |
| "com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex"); |
| private static final TestApp ApexNoHashtree2 = new TestApp( |
| "Apex2", SHIM_APEX_PACKAGE_NAME, 2, /*isApex*/true, |
| "com.android.apex.cts.shim.v2_no_hashtree.apex"); |
| private static final TestApp ApexWrongSha2 = new TestApp( |
| "ApexWrongSha2", SHIM_APEX_PACKAGE_NAME, 2, /*isApex*/true, |
| "com.android.apex.cts.shim.v2_wrong_sha.apex"); |
| private static final TestApp Apex2WithoutApkInApex = new TestApp( |
| "Apex2WithoutApkInApex", SHIM_APEX_PACKAGE_NAME, 2, /*isApex*/true, |
| "com.android.apex.cts.shim.v2_without_apk_in_apex.apex"); |
| private static final TestApp Apex3SignedBob = new TestApp( |
| "Apex3SignedBob", SHIM_APEX_PACKAGE_NAME, 3, /*isApex*/true, |
| "com.android.apex.cts.shim.v3_signed_bob.apex"); |
| private static final TestApp Apex3SignedBobRot = new TestApp( |
| "Apex3SignedBobRot", SHIM_APEX_PACKAGE_NAME, 3, /*isApex*/true, |
| "com.android.apex.cts.shim.v3_signed_bob_rot.apex"); |
| private static final TestApp ApexNotPreInstalled = new TestApp( |
| "ApexNotPreInstalled", NOT_PRE_INSTALL_APEX_PACKAGE_NAME, 3, /*isApex*/true, |
| "com.android.apex.cts.shim_not_pre_installed.apex"); |
| private static final TestApp Apex2SdkTargetP = new TestApp( |
| "StagedInstallTestApexV2_SdkTargetP", SHIM_APEX_PACKAGE_NAME, 2, |
| /*isApex*/true, "com.android.apex.cts.shim.v2_sdk_target_p.apex"); |
| private static final TestApp Apex2ApkInApexSdkTargetP = new TestApp( |
| "StagedInstallTestApexV2_ApkInApexSdkTargetP", SHIM_APEX_PACKAGE_NAME, 2, |
| /*isApex*/true, "com.android.apex.cts.shim.v2_apk_in_apex_sdk_target_p.apex"); |
| private static final TestApp CorruptedApex_b146895998 = new TestApp( |
| "StagedInstallTestCorruptedApex_b146895998", "", 1, true, "corrupted_b146895998.apex"); |
| private static final TestApp Apex2NoApkSignature = new TestApp( |
| "StagedInstallTestApexV2_NoApkSignature", SHIM_APEX_PACKAGE_NAME, 1, |
| /*isApex*/true, "com.android.apex.cts.shim.v2_unsigned_apk_container.apex"); |
| private static final TestApp Apex2UnsignedPayload = new TestApp( |
| "StagedInstallTestApexV2_UnsignedPayload", SHIM_APEX_PACKAGE_NAME, 1, |
| /*isApex*/true, "com.android.apex.cts.shim.v2_unsigned_payload.apex"); |
| private static final TestApp Apex2SignPayloadWithDifferentKey = new TestApp( |
| "StagedInstallTestApexV2_SignPayloadWithDifferentKey", SHIM_APEX_PACKAGE_NAME, 1, |
| /*isApex*/true, "com.android.apex.cts.shim.v2_sign_payload_with_different_key.apex"); |
| private static final TestApp Apex2Rebootless = new TestApp( |
| "StagedInstallTestApexV2_Rebootless", SHIM_APEX_PACKAGE_NAME, 2, |
| /*isApex*/true, "com.android.apex.cts.shim.v2_rebootless.apex"); |
| private static final TestApp Apex3Rebootless = new TestApp( |
| "StagedInstallTestApexV3_Rebootless", SHIM_APEX_PACKAGE_NAME, 3, |
| /*isApex*/true, "com.android.apex.cts.shim.v3_rebootless.apex"); |
| |
| @Before |
| public void adoptShellPermissions() { |
| InstrumentationRegistry |
| .getInstrumentation() |
| .getUiAutomation() |
| .adoptShellPermissionIdentity( |
| Manifest.permission.INSTALL_PACKAGES, |
| Manifest.permission.DELETE_PACKAGES); |
| } |
| |
| @After |
| public void dropShellPermissions() { |
| InstrumentationRegistry |
| .getInstrumentation() |
| .getUiAutomation() |
| .dropShellPermissionIdentity(); |
| } |
| |
| // This is marked as @Test to take advantage of @Before/@After methods of this class. Actual |
| // purpose of this method to be called before and after each test case of |
| // com.android.test.stagedinstall.host.StagedInstallTest to reduce tests flakiness. |
| @Test |
| public void cleanUp() throws Exception { |
| PackageInstaller packageInstaller = getPackageInstaller(); |
| List<PackageInstaller.SessionInfo> stagedSessions = packageInstaller.getStagedSessions(); |
| for (PackageInstaller.SessionInfo sessionInfo : stagedSessions) { |
| if (sessionInfo.getParentSessionId() != PackageInstaller.SessionInfo.INVALID_ID |
| || sessionInfo.isStagedSessionApplied() |
| || sessionInfo.isStagedSessionFailed()) { |
| // Cannot abandon a child session; no need to abandon terminated sessions |
| continue; |
| } |
| try { |
| Log.i(TAG, "abandoning session " + sessionInfo.getSessionId()); |
| packageInstaller.abandonSession(sessionInfo.getSessionId()); |
| } catch (Exception e) { |
| Log.e(TAG, "Failed to abandon session " + sessionInfo.getSessionId(), e); |
| } |
| } |
| Uninstall.packages(TestApp.A, TestApp.B); |
| Files.deleteIfExists(mTestStateFile.toPath()); |
| } |
| |
| @Test |
| public void testFailInstallIfNoPermission() throws Exception { |
| dropShellPermissions(); |
| try { |
| createStagedSession(); |
| fail(); // Should have thrown SecurityException. |
| } catch (SecurityException e) { |
| // This would be a better version, but it requires a version of truth not present in the |
| // tree yet. |
| // assertThat(e).hasMessageThat().containsMatch(...); |
| assertThat(e.getMessage()).containsMatch( |
| "Neither user [0-9]+ nor current process has " |
| + "android.permission.INSTALL_PACKAGES"); |
| } |
| } |
| |
| @Test |
| public void testInstallStagedApk_Commit() throws Exception { |
| BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_COMMITTED); |
| int sessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId(); |
| assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1); |
| waitForIsReadyBroadcast(sessionId); |
| assertSessionReady(sessionId); |
| storeSessionId(sessionId); |
| assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1); |
| counter.assertNoBroadcastReceived(); |
| } |
| |
| @Test |
| public void testInstallStagedApk_VerifyPostReboot() throws Exception { |
| BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_COMMITTED); |
| int sessionId = retrieveLastSessionId(); |
| assertSessionApplied(sessionId); |
| assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1); |
| counter.assertNoBroadcastReceived(); |
| } |
| |
| @Test |
| public void testInstallStagedApk_AbandonSessionIsNoop() throws Exception { |
| int sessionId = retrieveLastSessionId(); |
| assertSessionApplied(sessionId); |
| // Session is in a final state. Test that abandoning the session doesn't remove it from the |
| // session database. |
| abandonSession(sessionId); |
| assertSessionApplied(sessionId); |
| } |
| |
| @Test |
| public void testInstallMultipleStagedApks_Commit() throws Exception { |
| BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_COMMITTED); |
| int sessionId = stageMultipleApks(TestApp.A1, TestApp.B1) |
| .assertSuccessful().getSessionId(); |
| assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1); |
| assertThat(getInstalledVersion(TestApp.B)).isEqualTo(-1); |
| waitForIsReadyBroadcast(sessionId); |
| assertSessionReady(sessionId); |
| storeSessionId(sessionId); |
| assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1); |
| assertThat(getInstalledVersion(TestApp.B)).isEqualTo(-1); |
| counter.assertNoBroadcastReceived(); |
| } |
| |
| @Test |
| public void testInstallMultipleStagedApks_VerifyPostReboot() throws Exception { |
| BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_COMMITTED); |
| int sessionId = retrieveLastSessionId(); |
| assertSessionApplied(sessionId); |
| assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1); |
| assertThat(getInstalledVersion(TestApp.B)).isEqualTo(1); |
| counter.assertNoBroadcastReceived(); |
| } |
| |
| @Test |
| public void testAbandonStagedApkBeforeReboot_CommitAndAbandon() throws Exception { |
| int sessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId(); |
| assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1); |
| waitForIsReadyBroadcast(sessionId); |
| PackageInstaller.SessionInfo session = InstallUtils.getStagedSessionInfo(sessionId); |
| assertSessionReady(sessionId); |
| abandonSession(sessionId); |
| InstallUtils.assertStagedSessionIsAbandoned(sessionId); |
| // Allow the session to be removed from PackageInstaller |
| Duration spentWaiting = Duration.ZERO; |
| while (spentWaiting.compareTo(WAIT_FOR_SESSION_REMOVED_TTL) < 0) { |
| session = getSessionInfo(sessionId); |
| if (session == null) { |
| Log.i(TAG, "Done waiting after " + spentWaiting); |
| break; |
| } |
| try { |
| Thread.sleep(SLEEP_DURATION.toMillis()); |
| spentWaiting = spentWaiting.plus(SLEEP_DURATION); |
| } catch (InterruptedException e) { |
| Thread.currentThread().interrupt(); |
| throw new RuntimeException(e); |
| } |
| } |
| assertThat(session).isNull(); |
| } |
| |
| @Test |
| public void testAbandonStagedApkBeforeReboot_VerifyPostReboot() throws Exception { |
| assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1); |
| } |
| |
| @Test |
| public void testAbandonStagedApkBeforeReady_CommitAndAbandon() throws Exception { |
| int sessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId(); |
| assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1); |
| abandonSession(sessionId); |
| InstallUtils.assertStagedSessionIsAbandoned(sessionId); |
| } |
| |
| @Test |
| public void testAbandonStagedApkBeforeReady_VerifyPostReboot() throws Exception { |
| assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1); |
| } |
| |
| @Test |
| public void testStageAnotherSessionImmediatelyAfterAbandon() throws Exception { |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| int sessionId = stageSingleApk(TestApp.Apex2).assertSuccessful().getSessionId(); |
| abandonSession(sessionId); |
| stageSingleApk(TestApp.Apex2).assertSuccessful(); |
| } |
| |
| @Test |
| public void testStageAnotherSessionImmediatelyAfterAbandonMultiPackage() throws Exception { |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| int sessionId = stageMultipleApks(TestApp.Apex2, TestApp.A1, TestApp.B1) |
| .assertSuccessful().getSessionId(); |
| abandonSession(sessionId); |
| stageSingleApk(TestApp.Apex2).assertSuccessful(); |
| } |
| |
| @Test |
| public void testNoSessionUpdatedBroadcastSentForStagedSessionAbandon() throws Exception { |
| BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_UPDATED); |
| assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1); |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| // Using an apex in hopes that pre-reboot verification will take longer to complete |
| // and we will manage to abandon it before session becomes ready. |
| int sessionId = stageMultipleApks(TestApp.A1, TestApp.Apex2).assertSuccessful() |
| .getSessionId(); |
| abandonSession(sessionId); |
| InstallUtils.assertStagedSessionIsAbandoned(sessionId); |
| counter.assertNoBroadcastReceived(); |
| } |
| |
| @Test |
| public void testGetActiveStagedSessions() throws Exception { |
| PackageInstaller packageInstaller = getPackageInstaller(); |
| int firstSessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId(); |
| int secondSessionId = stageSingleApk(TestApp.B1).assertSuccessful().getSessionId(); |
| List<Integer> stagedSessionIds = packageInstaller.getActiveStagedSessions() |
| .stream().map(s -> s.getSessionId()).collect(Collectors.toList()); |
| assertThat(stagedSessionIds).containsExactly(firstSessionId, secondSessionId); |
| |
| // Verify no other session is considered as active staged session |
| List<PackageInstaller.SessionInfo> allSessions = packageInstaller.getAllSessions(); |
| for (PackageInstaller.SessionInfo session : allSessions) { |
| if (session.isStagedSessionActive()) { |
| assertThat(stagedSessionIds).contains(session.getSessionId()); |
| } |
| } |
| } |
| |
| /** |
| * Verifies that active staged session fulfils conditions stated at |
| * {@link PackageInstaller.SessionInfo#isStagedSessionActive} |
| */ |
| @Test |
| public void testIsStagedSessionActive() throws Exception { |
| PackageInstaller packageInstaller = getPackageInstaller(); |
| int sessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId(); |
| |
| List<PackageInstaller.SessionInfo> allSessions = packageInstaller.getAllSessions(); |
| boolean activeStagedSessionFound = false; |
| for (PackageInstaller.SessionInfo session : allSessions) { |
| // If it fulfils conditions, then it should be an active staged session |
| if (session.isStaged() && session.isCommitted() && !session.isStagedSessionApplied() |
| && !session.isStagedSessionFailed()) { |
| activeStagedSessionFound = true; |
| assertThat(session.getSessionId()).isEqualTo(sessionId); |
| assertThat(session.isStagedSessionActive()).isTrue(); |
| } else { |
| // Otherwise, it should not be marked as active staged session |
| assertThat(session.getSessionId()).isNotEqualTo(sessionId); |
| assertThat(session.isStagedSessionActive()).isFalse(); |
| } |
| } |
| assertWithMessage("Did not find any active staged session") |
| .that(activeStagedSessionFound).isTrue(); |
| } |
| |
| @Test |
| public void testGetActiveStagedSessionsNoSessionActive() throws Exception { |
| PackageInstaller packageInstaller = getPackageInstaller(); |
| List<PackageInstaller.SessionInfo> sessions = packageInstaller.getActiveStagedSessions(); |
| assertThat(sessions).isEmpty(); |
| } |
| |
| @Test |
| public void testGetActiveStagedSessions_MultiApkSession() throws Exception { |
| int firstSessionId = stageMultipleApks(TestApp.A1, TestApp.B1) |
| .assertSuccessful().getSessionId(); |
| int secondSessionId = stageMultipleApks(TestApp.C1) |
| .assertSuccessful().getSessionId(); |
| List<Integer> stagedSessionIds = getPackageInstaller().getActiveStagedSessions() |
| .stream().map(s -> s.getSessionId()).collect(Collectors.toList()); |
| assertThat(stagedSessionIds).containsExactly(firstSessionId, secondSessionId); |
| } |
| |
| @Test |
| public void testStagedInstallDowngrade_DowngradeNotRequested_Fails_Commit() throws Exception { |
| assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1); |
| Install.single(TestApp.A2).commit(); |
| int sessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId(); |
| assertThat(getInstalledVersion(TestApp.A)).isEqualTo(2); |
| PackageInstaller.SessionInfo sessionInfo = waitForBroadcast(sessionId); |
| assertThat(sessionInfo).isStagedSessionFailed(); |
| } |
| |
| @Test |
| public void testStagedInstallDowngrade_DowngradeRequested_Commit() throws Exception { |
| assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1); |
| Install.single(TestApp.A2).commit(); |
| int sessionId = stageDowngradeSingleApk(TestApp.A1).assertSuccessful().getSessionId(); |
| assertThat(getInstalledVersion(TestApp.A)).isEqualTo(2); |
| waitForIsReadyBroadcast(sessionId); |
| assertSessionReady(sessionId); |
| storeSessionId(sessionId); |
| } |
| |
| @Test |
| public void testStagedInstallDowngrade_DowngradeRequested_Fails_Commit() throws Exception { |
| assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1); |
| Install.single(TestApp.A2).commit(); |
| int sessionId = stageDowngradeSingleApk(TestApp.A1).assertSuccessful().getSessionId(); |
| assertThat(getInstalledVersion(TestApp.A)).isEqualTo(2); |
| PackageInstaller.SessionInfo sessionInfo = waitForBroadcast(sessionId); |
| assertThat(sessionInfo).isStagedSessionFailed(); |
| } |
| |
| @Test |
| public void testStagedInstallDowngrade_DowngradeRequested_DebugBuild_VerifyPostReboot() |
| throws Exception { |
| int sessionId = retrieveLastSessionId(); |
| assertSessionApplied(sessionId); |
| // App should be downgraded. |
| assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1); |
| } |
| |
| @Test |
| public void testInstallStagedApex_Commit() throws Exception { |
| BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_COMMITTED); |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| int sessionId = stageSingleApk(TestApp.Apex2).assertSuccessful().getSessionId(); |
| waitForIsReadyBroadcast(sessionId); |
| assertSessionReady(sessionId); |
| storeSessionId(sessionId); |
| // Version shouldn't change before reboot. |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| counter.assertNoBroadcastReceived(); |
| } |
| |
| @Test |
| public void testInstallStagedApex_VerifyPostReboot() throws Exception { |
| BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_COMMITTED); |
| int sessionId = retrieveLastSessionId(); |
| assertSessionApplied(sessionId); |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2); |
| counter.assertNoBroadcastReceived(); |
| } |
| |
| @Test |
| public void testInstallStagedApexAndApk_Commit() throws Exception { |
| BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_COMMITTED); |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1); |
| int sessionId = stageMultipleApks(TestApp.Apex2, TestApp.A1) |
| .assertSuccessful().getSessionId(); |
| waitForIsReadyBroadcast(sessionId); |
| assertSessionReady(sessionId); |
| storeSessionId(sessionId); |
| // Version shouldn't change before reboot. |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1); |
| counter.assertNoBroadcastReceived(); |
| } |
| |
| @Test |
| public void testInstallStagedApexAndApk_VerifyPostReboot() throws Exception { |
| BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_COMMITTED); |
| int sessionId = retrieveLastSessionId(); |
| assertSessionApplied(sessionId); |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2); |
| assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1); |
| counter.assertNoBroadcastReceived(); |
| } |
| |
| @Test |
| public void testsFailsNonStagedApexInstall() throws Exception { |
| try { |
| SystemUtil.runShellCommandForNoOutput("pm bypass-staged-installer-check true"); |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| TestApp apex = new TestApp( |
| "Apex2", SHIM_APEX_PACKAGE_NAME, 2, /*isApex*/true, |
| "com.android.apex.cts.shim.v2.apex"); |
| InstallUtils.commitExpectingFailure(AssertionError.class, |
| "does not support non-staged update", |
| Install.single(apex)); |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| } finally { |
| SystemUtil.runShellCommandForNoOutput("pm bypass-staged-installer-check false"); |
| } |
| } |
| |
| @Test |
| public void testInstallStagedNonPreInstalledApex_Fails() throws Exception { |
| assertThat(getInstalledVersion(NOT_PRE_INSTALL_APEX_PACKAGE_NAME)).isEqualTo(-1); |
| int sessionId = stageSingleApk(ApexNotPreInstalled).assertSuccessful().getSessionId(); |
| PackageInstaller.SessionInfo sessionInfo = waitForBroadcast(sessionId); |
| assertThat(sessionInfo).isStagedSessionFailed(); |
| } |
| |
| @Test |
| public void testInstallStagedDifferentPackageNameWithInstalledApex_Fails() throws Exception { |
| assertThat(getInstalledVersion(DIFFERENT_APEX_PACKAGE_NAME)).isEqualTo(-1); |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| int sessionId = stageSingleApk(Apex2DifferentPackageName).assertSuccessful().getSessionId(); |
| PackageInstaller.SessionInfo sessionInfo = waitForBroadcast(sessionId); |
| assertThat(sessionInfo.getStagedSessionErrorMessage()).contains( |
| "It is forbidden to install new APEX packages."); |
| } |
| |
| @Test |
| public void testStageApkWithSameNameAsApexShouldFail_Commit() throws Exception { |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| int sessionId = stageSingleApk(TESTAPP_SAME_NAME_AS_APEX).assertSuccessful().getSessionId(); |
| waitForIsReadyBroadcast(sessionId); |
| assertSessionReady(sessionId); |
| storeSessionId(sessionId); |
| } |
| |
| @Test |
| public void testStageApkWithSameNameAsApexShouldFail_VerifyPostReboot() throws Exception { |
| int sessionId = retrieveLastSessionId(); |
| assertSessionFailed(sessionId); |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| } |
| |
| @Test |
| public void testNonStagedInstallApkWithSameNameAsApexShouldFail() throws Exception { |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| InstallUtils.commitExpectingFailure(AssertionError.class, |
| "is an APEX package and can't be installed as an APK", |
| Install.single(TESTAPP_SAME_NAME_AS_APEX)); |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| } |
| |
| @Test |
| public void testInstallV2Apex_Commit() throws Exception { |
| int sessionId = stageSingleApk(TestApp.Apex2).assertSuccessful().getSessionId(); |
| waitForIsReadyBroadcast(sessionId); |
| assertSessionReady(sessionId); |
| storeSessionId(sessionId); |
| } |
| |
| @Test |
| public void testInstallV2Apex_VerifyPostReboot() throws Exception { |
| int sessionId = retrieveLastSessionId(); |
| assertSessionApplied(sessionId); |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2); |
| assertThat(getInstalledVersion(SHIM_PACKAGE_NAME)).isNotEqualTo(-1); |
| } |
| |
| @Test |
| public void testInstallV2SignedBobApex_Commit() throws Exception { |
| int sessionId = stageSingleApk(Apex2SignedBobRot).assertSuccessful().getSessionId(); |
| waitForIsReadyBroadcast(sessionId); |
| assertSessionReady(sessionId); |
| storeSessionId(sessionId); |
| } |
| |
| @Test |
| public void testInstallV2SignedBobApex_VerifyPostReboot() throws Exception { |
| int sessionId = retrieveLastSessionId(); |
| assertSessionApplied(sessionId); |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2); |
| } |
| |
| @Test |
| public void testInstallV3Apex_Commit() throws Exception { |
| int sessionId = stageSingleApk(TestApp.Apex3).assertSuccessful().getSessionId(); |
| waitForIsReadyBroadcast(sessionId); |
| assertSessionReady(sessionId); |
| storeSessionId(sessionId); |
| } |
| |
| @Test |
| public void testInstallV3Apex_VerifyPostReboot() throws Exception { |
| int sessionId = retrieveLastSessionId(); |
| assertSessionApplied(sessionId); |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3); |
| } |
| |
| @Test |
| public void testInstallV3SignedBobApex_Commit() throws Exception { |
| int sessionId = stageSingleApk(Apex2SignedBobRot).assertSuccessful().getSessionId(); |
| waitForIsReadyBroadcast(sessionId); |
| assertSessionReady(sessionId); |
| storeSessionId(sessionId); |
| } |
| |
| @Test |
| public void testInstallV3SignedBobApex_VerifyPostReboot() throws Exception { |
| int sessionId = retrieveLastSessionId(); |
| assertSessionApplied(sessionId); |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2); |
| } |
| |
| @Test |
| public void testStagedInstallDowngradeApex_DowngradeNotRequested_Fails_Commit() |
| throws Exception { |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3); |
| int sessionId = stageSingleApk(TestApp.Apex2).assertSuccessful().getSessionId(); |
| PackageInstaller.SessionInfo sessionInfo = waitForBroadcast(sessionId); |
| assertThat(sessionInfo).isStagedSessionFailed(); |
| // Also verify that correct session info is reported by PackageManager. |
| assertSessionFailed(sessionId); |
| storeSessionId(sessionId); |
| } |
| |
| @Test |
| public void testStagedInstallDowngradeApex_DowngradeNotRequested_Fails_VerifyPostReboot() |
| throws Exception { |
| int sessionId = retrieveLastSessionId(); |
| assertSessionFailed(sessionId); |
| // INSTALL_REQUEST_DOWNGRADE wasn't set, so apex shouldn't be downgraded. |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3); |
| } |
| |
| @Test |
| public void testStagedInstallDowngradeApex_DowngradeRequested_DebugBuild_Commit() |
| throws Exception { |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3); |
| int sessionId = stageDowngradeSingleApk(TestApp.Apex2).assertSuccessful().getSessionId(); |
| waitForIsReadyBroadcast(sessionId); |
| assertSessionReady(sessionId); |
| storeSessionId(sessionId); |
| } |
| |
| @Test |
| public void testStagedInstallDowngradeApex_DowngradeRequested_DebugBuild_VerifyPostReboot() |
| throws Exception { |
| int sessionId = retrieveLastSessionId(); |
| assertSessionApplied(sessionId); |
| // Apex should be downgraded. |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2); |
| } |
| |
| @Test |
| public void testStagedInstallDowngradeApex_DowngradeRequested_UserBuild_Fails_Commit() |
| throws Exception { |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3); |
| int sessionId = stageDowngradeSingleApk(TestApp.Apex2).assertSuccessful().getSessionId(); |
| PackageInstaller.SessionInfo sessionInfo = waitForBroadcast(sessionId); |
| assertThat(sessionInfo).isStagedSessionFailed(); |
| // Also verify that correct session info is reported by PackageManager. |
| assertSessionFailed(sessionId); |
| storeSessionId(sessionId); |
| } |
| |
| @Test |
| public void testStagedInstallDowngradeApex_DowngradeRequested_UserBuild_Fails_VerifyPostReboot() |
| throws Exception { |
| int sessionId = retrieveLastSessionId(); |
| assertSessionFailed(sessionId); |
| // Apex shouldn't be downgraded. |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3); |
| } |
| |
| @Test |
| public void testStagedInstallDowngradeApexToSystemVersion_DebugBuild_Commit() |
| throws Exception { |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2); |
| int sessionId = stageDowngradeSingleApk(TestApp.Apex1).assertSuccessful().getSessionId(); |
| waitForIsReadyBroadcast(sessionId); |
| assertSessionReady(sessionId); |
| storeSessionId(sessionId); |
| } |
| |
| @Test |
| public void testStagedInstallDowngradeApexToSystemVersion_DebugBuild_VerifyPostReboot() |
| throws Exception { |
| int sessionId = retrieveLastSessionId(); |
| assertSessionApplied(sessionId); |
| // Apex should be downgraded. |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| } |
| |
| @Test |
| public void testInstallApex_DeviceDoesNotSupportApex_Fails() throws Exception { |
| InstallUtils.commitExpectingFailure(IllegalArgumentException.class, |
| "This device doesn't support the installation of APEX files", |
| Install.single(TestApp.Apex2).setStaged()); |
| } |
| |
| @Test |
| public void testFailsInvalidApexInstall_Commit() throws Exception { |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| int sessionId = stageSingleApk(ApexWrongSha2).assertSuccessful() |
| .getSessionId(); |
| waitForIsFailedBroadcast(sessionId); |
| assertSessionFailed(sessionId); |
| storeSessionId(sessionId); |
| } |
| |
| @Test |
| public void testFailsInvalidApexInstall_AbandonSessionIsNoop() throws Exception { |
| int sessionId = retrieveLastSessionId(); |
| assertSessionFailed(sessionId); |
| // Session is in a final state. Test that abandoning the session doesn't remove it from the |
| // session database. |
| abandonSession(sessionId); |
| assertSessionFailed(sessionId); |
| } |
| |
| @Test |
| public void testStagedApkSessionCallbacks() throws Exception { |
| |
| List<Integer> created = new ArrayList<Integer>(); |
| List<Integer> finished = new ArrayList<Integer>(); |
| |
| HandlerThread handlerThread = new HandlerThread( |
| "StagedApkSessionCallbacksTestHandlerThread"); |
| handlerThread.start(); |
| Handler handler = new Handler(handlerThread.getLooper()); |
| |
| PackageInstaller.SessionCallback callback = new PackageInstaller.SessionCallback() { |
| |
| @Override |
| public void onCreated(int sessionId) { |
| synchronized (created) { |
| created.add(sessionId); |
| } |
| } |
| |
| @Override public void onBadgingChanged(int sessionId) { } |
| @Override public void onActiveChanged(int sessionId, boolean active) { } |
| @Override public void onProgressChanged(int sessionId, float progress) { } |
| |
| @Override |
| public void onFinished(int sessionId, boolean success) { |
| synchronized (finished) { |
| finished.add(sessionId); |
| } |
| } |
| }; |
| |
| Context context = InstrumentationRegistry.getInstrumentation().getContext(); |
| PackageInstaller packageInstaller = getPackageInstaller(); |
| packageInstaller.registerSessionCallback(callback, handler); |
| |
| int sessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId(); |
| |
| assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1); |
| waitForIsReadyBroadcast(sessionId); |
| assertSessionReady(sessionId); |
| |
| packageInstaller.unregisterSessionCallback(callback); |
| |
| handlerThread.quitSafely(); |
| handlerThread.join(); |
| |
| synchronized (created) { |
| assertThat(created).containsExactly(sessionId); |
| } |
| synchronized (finished) { |
| assertThat(finished).containsExactly(sessionId); |
| } |
| packageInstaller.abandonSession(sessionId); |
| } |
| |
| @Test |
| public void testInstallStagedApexWithoutApexSuffix_Commit() throws Exception { |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| |
| int sessionId = stageSingleApk("com.android.apex.cts.shim.v2.apex", "package") |
| .assertSuccessful().getSessionId(); |
| waitForIsReadyBroadcast(sessionId); |
| assertSessionReady(sessionId); |
| storeSessionId(sessionId); |
| // Version shouldn't change before reboot. |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| } |
| |
| @Test |
| public void testInstallStagedApexWithoutApexSuffix_VerifyPostReboot() throws Exception { |
| int sessionId = retrieveLastSessionId(); |
| assertSessionApplied(sessionId); |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2); |
| } |
| |
| @Test |
| public void testRejectsApexDifferentCertificate() throws Exception { |
| int sessionId = stageSingleApk(Apex2DifferentCertificate) |
| .assertSuccessful().getSessionId(); |
| PackageInstaller.SessionInfo info = waitForBroadcast(sessionId); |
| assertThat(info.getSessionId()).isEqualTo(sessionId); |
| assertThat(info).isStagedSessionFailed(); |
| assertThat(info.getStagedSessionErrorMessage()).contains("is not compatible with the one " |
| + "currently installed on device"); |
| } |
| |
| /** |
| * Tests for staged install involving rotated keys. |
| * |
| * Here alice means the original default key that cts.shim.v1 package was signed with and |
| * bob is the new key alice rotates to. Where ambiguous, we will refer keys as alice and bob |
| * instead of "old key" and "new key". |
| */ |
| |
| // The update should fail if it is signed with a different non-rotated key |
| @Test |
| public void testUpdateWithDifferentKeyButNoRotation() throws Exception { |
| int sessionId = stageSingleApk(Apex2SignedBob).assertSuccessful().getSessionId(); |
| waitForIsFailedBroadcast(sessionId); |
| } |
| |
| // The update should pass if it is signed with a proper rotated key |
| @Test |
| public void testUpdateWithDifferentKey_Commit() throws Exception { |
| int sessionId = stageSingleApk(Apex2SignedBobRot).assertSuccessful().getSessionId(); |
| waitForIsReadyBroadcast(sessionId); |
| } |
| |
| @Test |
| public void testUpdateWithDifferentKey_VerifyPostReboot() throws Exception { |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2); |
| } |
| |
| // Once updated with a new rotated key (bob), further updates with old key (alice) should fail |
| @Test |
| public void testUntrustedOldKeyIsRejected() throws Exception { |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2); |
| int sessionId = stageSingleApk(TestApp.Apex3).assertSuccessful().getSessionId(); |
| waitForIsFailedBroadcast(sessionId); |
| } |
| |
| // Should be able to update with an old key which is trusted |
| @Test |
| public void testTrustedOldKeyIsAccepted_Commit() throws Exception { |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| int sessionId = stageSingleApk(Apex2SignedBobRotRollback).assertSuccessful().getSessionId(); |
| waitForIsReadyBroadcast(sessionId); |
| } |
| |
| @Test |
| public void testTrustedOldKeyIsAccepted_CommitPostReboot() throws Exception { |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2); |
| int sessionId = stageSingleApk(TestApp.Apex3).assertSuccessful().getSessionId(); |
| waitForIsReadyBroadcast(sessionId); |
| } |
| |
| @Test |
| public void testTrustedOldKeyIsAccepted_VerifyPostReboot() throws Exception { |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3); |
| } |
| |
| // Once updated with a new rotated key (bob), further updates with new key (bob) should pass |
| @Test |
| public void testAfterRotationNewKeyCanUpdateFurther_CommitPostReboot() throws Exception { |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2); |
| int sessionId = stageSingleApk(Apex3SignedBobRot).assertSuccessful().getSessionId(); |
| waitForIsReadyBroadcast(sessionId); |
| } |
| |
| @Test |
| public void testAfterRotationNewKeyCanUpdateFurther_VerifyPostReboot() throws Exception { |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3); |
| } |
| |
| // Once updated with a new rotated key (bob), further updates can be done with key only |
| @Test |
| public void testAfterRotationNewKeyCanUpdateFurtherWithoutLineage() |
| throws Exception { |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2); |
| int sessionId = stageSingleApk(Apex3SignedBob).assertSuccessful().getSessionId(); |
| waitForIsReadyBroadcast(sessionId); |
| } |
| |
| /** |
| * Tests for staging and installing multiple staged sessions. |
| */ |
| |
| // Should fail to stage multiple sessions when check-point is not available |
| @Test |
| public void testFailStagingMultipleSessionsIfNoCheckPoint() throws Exception { |
| stageSingleApk(TestApp.A1).assertSuccessful(); |
| int sessionId = stageSingleApk(TestApp.B1).assertSuccessful().getSessionId(); |
| PackageInstaller.SessionInfo info = waitForBroadcast(sessionId); |
| assertThat(info).isStagedSessionFailed(); |
| assertThat(info.getStagedSessionErrorMessage()).contains( |
| "Cannot stage multiple sessions without checkpoint support"); |
| } |
| |
| @Test |
| public void testFailOverlappingMultipleStagedInstall_BothSinglePackage_Apk() throws Exception { |
| int stagedSessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId(); |
| waitForIsReadyBroadcast(stagedSessionId); |
| |
| int sessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId(); |
| PackageInstaller.SessionInfo info = waitForBroadcast(sessionId); |
| assertThat(info).isStagedSessionFailed(); |
| assertThat(info.getStagedSessionErrorMessage()).contains( |
| "has been staged already by session"); |
| } |
| |
| @Test |
| public void testAllowNonOverlappingMultipleStagedInstall_MultiPackageSinglePackage_Apk() |
| throws Exception { |
| stageMultipleApks(TestApp.A1, TestApp.B1).assertSuccessful(); |
| stageSingleApk(TestApp.C1).assertSuccessful(); |
| } |
| |
| @Test |
| public void testFailOverlappingMultipleStagedInstall_BothMultiPackage_Apk() throws Exception { |
| int id = stageMultipleApks(TestApp.A1, TestApp.B1).assertSuccessful().getSessionId(); |
| waitForIsReadyBroadcast(id); |
| int sessionId = stageMultipleApks(TestApp.A2, TestApp.C1).assertSuccessful().getSessionId(); |
| PackageInstaller.SessionInfo info = waitForBroadcast(sessionId); |
| assertThat(info).isStagedSessionFailed(); |
| assertThat(info.getStagedSessionErrorMessage()).contains( |
| "has been staged already by session"); |
| } |
| |
| // Should succeed in installing multiple staged sessions together |
| @Test |
| public void testMultipleStagedInstall_ApkOnly_Commit() |
| throws Exception { |
| int firstSessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId(); |
| waitForIsReadyBroadcast(firstSessionId); |
| int secondSessionId = stageSingleApk(TestApp.B1).assertSuccessful().getSessionId(); |
| waitForIsReadyBroadcast(secondSessionId); |
| assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1); |
| assertThat(getInstalledVersion(TestApp.B)).isEqualTo(-1); |
| storeSessionIds(Arrays.asList(firstSessionId, secondSessionId)); |
| } |
| |
| @Test |
| public void testMultipleStagedInstall_ApkOnly_VerifyPostReboot() |
| throws Exception { |
| List<Integer> sessionIds = retrieveLastSessionIds(); |
| for (int sessionId: sessionIds) { |
| assertSessionApplied(sessionId); |
| } |
| assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1); |
| assertThat(getInstalledVersion(TestApp.B)).isEqualTo(1); |
| } |
| |
| // If apk installation fails in one staged session, then all staged session should fail. |
| @Test |
| public void testInstallMultipleStagedSession_PartialFail_ApkOnly_Commit() |
| throws Exception { |
| int firstSessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId(); |
| waitForIsReadyBroadcast(firstSessionId); |
| int secondSessionId = stageSingleApk(TestApp.B1).assertSuccessful().getSessionId(); |
| waitForIsReadyBroadcast(secondSessionId); |
| |
| // Install TestApp.A2 so that after reboot TestApp.A1 fails to install as it is downgrade |
| Install.single(TestApp.A2).commit(); |
| |
| assertThat(getInstalledVersion(TestApp.A)).isEqualTo(2); |
| assertThat(getInstalledVersion(TestApp.B)).isEqualTo(-1); |
| storeSessionIds(Arrays.asList(firstSessionId, secondSessionId)); |
| } |
| |
| @Test |
| public void testInstallMultipleStagedSession_PartialFail_ApkOnly_VerifyPostReboot() |
| throws Exception { |
| List<Integer> sessionIds = retrieveLastSessionIds(); |
| for (int sessionId: sessionIds) { |
| assertSessionFailed(sessionId); |
| } |
| assertThat(getInstalledVersion(TestApp.A)).isEqualTo(2); |
| assertThat(getInstalledVersion(TestApp.B)).isEqualTo(-1); |
| } |
| |
| // Failure reason of staged install should be be persisted for single sessions |
| @Test |
| public void testFailureReasonPersists_SingleSession_Commit() throws Exception { |
| int sessionId = Install.single(TestApp.A1).setStaged().commit(); |
| // Install TestApp.A2 so that after reboot TestApp.A1 fails to install as it is downgrade |
| Install.single(TestApp.A2).commit(); |
| assertThat(getInstalledVersion(TestApp.A)).isEqualTo(2); |
| storeSessionId(sessionId); |
| } |
| |
| @Test |
| public void testFailureReasonPersists_SingleSession_VerifyPostReboot() throws Exception { |
| int sessionId = retrieveLastSessionId(); |
| assertSessionFailedWithMessage(sessionId, "Failed to install sessionId: " + sessionId); |
| assertThat(getInstalledVersion(TestApp.A)).isEqualTo(2); |
| } |
| |
| // If apk installation fails in one staged session, then all staged session should fail. |
| @Test |
| public void testFailureReasonPersists_MultipleSession_Commit() throws Exception { |
| int firstSessionId = Install.single(TestApp.A1).setStaged().commit(); |
| int secondSessionId = Install.single(TestApp.B1).setStaged().commit(); |
| // Install TestApp.A2 so that after reboot TestApp.A1 fails to install as it is downgrade |
| Install.single(TestApp.A2).commit(); |
| assertThat(getInstalledVersion(TestApp.A)).isEqualTo(2); |
| assertThat(getInstalledVersion(TestApp.B)).isEqualTo(-1); |
| storeSessionIds(Arrays.asList(firstSessionId, secondSessionId)); |
| } |
| |
| @Test |
| public void testFailureReasonPersists_MultipleSession_VerifyPostReboot() throws Exception { |
| List<Integer> sessionIds = retrieveLastSessionIds(); |
| int failingSessionId = sessionIds.get(0); |
| for (int sessionId: sessionIds) { |
| assertSessionFailedWithMessage(sessionId, "Failed to install sessionId: " |
| + failingSessionId); |
| } |
| assertThat(getInstalledVersion(TestApp.A)).isEqualTo(2); |
| assertThat(getInstalledVersion(TestApp.B)).isEqualTo(-1); |
| } |
| |
| @Test |
| public void testSamegradeSystemApex_Commit() throws Exception { |
| final PackageInfo shim = |
| InstrumentationRegistry.getInstrumentation().getContext().getPackageManager() |
| .getPackageInfo(SHIM_APEX_PACKAGE_NAME, PackageManager.MATCH_APEX); |
| assertThat(shim.getLongVersionCode()).isEqualTo(1); |
| assertThat(shim.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM).isEqualTo( |
| ApplicationInfo.FLAG_SYSTEM); |
| assertThat(shim.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED).isEqualTo( |
| ApplicationInfo.FLAG_INSTALLED); |
| int sessionId = stageDowngradeSingleApk(TestApp.Apex1).assertSuccessful().getSessionId(); |
| waitForIsReadyBroadcast(sessionId); |
| storeSessionId(sessionId); |
| } |
| |
| @Test |
| public void testSamegradeSystemApex_VerifyPostReboot() throws Exception { |
| int sessionId = retrieveLastSessionId(); |
| assertSessionApplied(sessionId); |
| final PackageInfo shim = |
| InstrumentationRegistry.getInstrumentation().getContext().getPackageManager() |
| .getPackageInfo(SHIM_APEX_PACKAGE_NAME, PackageManager.MATCH_APEX); |
| assertThat(shim.getLongVersionCode()).isEqualTo(1); |
| // Check that APEX on /data wins. |
| assertThat(shim.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM).isEqualTo(0); |
| assertThat(shim.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED).isEqualTo( |
| ApplicationInfo.FLAG_INSTALLED); |
| assertThat(shim.applicationInfo.sourceDir) |
| .isEqualTo("/data/apex/active/com.android.apex.cts.shim@1.apex"); |
| assertThat(shim.applicationInfo.publicSourceDir).isEqualTo(shim.applicationInfo.sourceDir); |
| } |
| |
| @Test |
| public void testInstallApkChangingFingerprint() throws Exception { |
| int sessionId = Install.single(TestApp.A1).setStaged().commit(); |
| storeSessionId(sessionId); |
| } |
| |
| @Test |
| public void testInstallApkChangingFingerprint_VerifyAborted() throws Exception { |
| int sessionId = retrieveLastSessionId(); |
| assertSessionFailed(sessionId); |
| } |
| |
| @Test |
| public void testInstallStagedNoHashtreeApex_Commit() throws Exception { |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| int sessionId = stageSingleApk(ApexNoHashtree2).assertSuccessful().getSessionId(); |
| waitForIsReadyBroadcast(sessionId); |
| assertSessionReady(sessionId); |
| storeSessionId(sessionId); |
| // Version shouldn't change before reboot. |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| } |
| |
| @Test |
| public void testInstallStagedNoHashtreeApex_VerifyPostReboot() throws Exception { |
| int sessionId = retrieveLastSessionId(); |
| assertSessionApplied(sessionId); |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2); |
| // Read all files under /apex/com.android.apex.cts.shim to somewhat verify that hashtree |
| // is not corrupted |
| Files.walkFileTree(Paths.get("/apex/com.android.apex.cts.shim"), |
| new SimpleFileVisitor<Path>() { |
| |
| @Override |
| public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) |
| throws IOException { |
| Files.readAllBytes(file); |
| return FileVisitResult.CONTINUE; |
| } |
| |
| @Override |
| public FileVisitResult visitFileFailed(Path file, IOException exc) |
| throws IOException { |
| if (file.endsWith("lost+found")) { |
| return FileVisitResult.CONTINUE; |
| } |
| throw exc; |
| } |
| }); |
| } |
| |
| @Test |
| public void testInstallStagedApex_SameGrade_NewOneWins_Commit() throws Exception { |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2); |
| assertThat(getInstalledVersion(SHIM_PACKAGE_NAME)).isNotEqualTo(-1); |
| int sessionId = Install.single(Apex2WithoutApkInApex).setStaged().commit(); |
| assertSessionReady(sessionId); |
| storeSessionId(sessionId); |
| } |
| |
| @Test |
| public void testInstallStagedApex_SameGrade_NewOneWins_VerifyPostReboot() throws Exception { |
| int sessionId = retrieveLastSessionId(); |
| assertSessionApplied(sessionId); |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2); |
| assertThat(getInstalledVersion(SHIM_PACKAGE_NAME)).isEqualTo(-1); |
| } |
| |
| /** |
| * Should fail to verify apex targeting older dev sdk |
| */ |
| @Test |
| public void testApexTargetingOldDevSdkFailsVerification() throws Exception { |
| stageSingleApk(Apex2SdkTargetP).assertFailure(); |
| } |
| |
| /** |
| * Apex should fail to install if apk-in-apex fails to get scanned |
| */ |
| @Test |
| public void testApexFailsToInstallIfApkInApexFailsToScan_Commit() throws Exception { |
| int sessionId = stageSingleApk(Apex2ApkInApexSdkTargetP).assertSuccessful().getSessionId(); |
| waitForIsReadyBroadcast(sessionId); |
| storeSessionId(sessionId); |
| } |
| |
| @Test |
| public void testApexFailsToInstallIfApkInApexFailsToScan_VerifyPostReboot() throws Exception { |
| int sessionId = retrieveLastSessionId(); |
| assertSessionFailed(sessionId); |
| assertSessionFailedWithMessage(sessionId, "Failed to parse " |
| + "/apex/com.android.apex.cts.shim/app/CtsShimTargetPSdk"); |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| } |
| |
| @Test |
| public void testCorruptedApexFailsVerification_b146895998() throws Exception { |
| int sessionId = stageSingleApk(CorruptedApex_b146895998).assertSuccessful().getSessionId(); |
| PackageInstaller.SessionInfo sessionInfo = waitForBroadcast(sessionId); |
| assertThat(sessionInfo).isStagedSessionFailed(); |
| } |
| |
| /** |
| * Should fail to pass apk signature check |
| */ |
| @Test |
| public void testApexWithUnsignedApkFailsVerification() throws Exception { |
| assertThat(stageSingleApk(Apex2NoApkSignature).getErrorMessage()) |
| .contains("INSTALL_PARSE_FAILED_NO_CERTIFICATES"); |
| } |
| |
| /** |
| * Should fail to verify apex with unsigned payload |
| */ |
| @Test |
| public void testApexWithUnsignedPayloadFailsVerification() throws Exception { |
| int sessionId = stageSingleApk(Apex2UnsignedPayload).assertSuccessful().getSessionId(); |
| PackageInstaller.SessionInfo sessionInfo = waitForBroadcast(sessionId); |
| assertThat(sessionInfo).isStagedSessionFailed(); |
| assertThat(sessionInfo.getStagedSessionErrorMessage()) |
| .contains("AVB footer verification failed"); |
| } |
| |
| /** |
| * Should fail to verify apex signed payload with a different key |
| */ |
| @Test |
| public void testApexSignPayloadWithDifferentKeyFailsVerification() throws Exception { |
| int sessionId = stageSingleApk( |
| Apex2SignPayloadWithDifferentKey).assertSuccessful().getSessionId(); |
| PackageInstaller.SessionInfo sessionInfo = waitForBroadcast(sessionId); |
| assertThat(sessionInfo).isStagedSessionFailed(); |
| assertThat(sessionInfo.getStagedSessionErrorMessage()) |
| .contains("public key doesn't match the pre-installed one"); |
| } |
| |
| /** |
| * Test non-priv apps cannot access /data/app-staging folder contents |
| */ |
| @Test |
| public void testAppStagingDirCannotBeReadByNonPrivApps() throws Exception { |
| final int sessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId(); |
| // Non-priv apps should not be able to view contents of app-staging directory |
| final File appStagingDir = new File("/data/app-staging"); |
| assertThat(appStagingDir.exists()).isTrue(); |
| assertThat(appStagingDir.listFiles()).isNull(); |
| // Non-owner user should not be able to access sub-dirs of app-staging directory |
| final File appStagingSubDir = new File("/data/app-staging/session_" + sessionId); |
| assertThat(appStagingSubDir.exists()).isFalse(); |
| assertThat(appStagingDir.listFiles()).isNull(); |
| } |
| |
| private static long getInstalledVersion(String packageName) { |
| Context context = InstrumentationRegistry.getInstrumentation().getContext(); |
| PackageManager pm = context.getPackageManager(); |
| try { |
| PackageInfo info = pm.getPackageInfo(packageName, PackageManager.MATCH_APEX); |
| return info.getLongVersionCode(); |
| } catch (PackageManager.NameNotFoundException e) { |
| return -1; |
| } |
| } |
| |
| @Test |
| public void testApexSetsUpdatedSystemAppFlag_preUpdate() throws Exception { |
| final PackageInfo info = InstallUtils.getPackageInfo(SHIM_APEX_PACKAGE_NAME); |
| assertThat(info).isNotNull(); |
| boolean isSystemApp = (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; |
| boolean isUpdatedSystemApp = |
| (info.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; |
| assertThat(isSystemApp).isTrue(); |
| assertThat(isUpdatedSystemApp).isFalse(); |
| } |
| |
| @Test |
| public void testApexSetsUpdatedSystemAppFlag_postUpdate() throws Exception { |
| final PackageInfo info = InstallUtils.getPackageInfo(SHIM_APEX_PACKAGE_NAME); |
| assertThat(info).isNotNull(); |
| boolean isSystemApp = (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; |
| boolean isUpdatedSystemApp = |
| (info.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; |
| assertThat(isSystemApp).isFalse(); |
| assertThat(isUpdatedSystemApp).isTrue(); |
| } |
| |
| @Test |
| public void testRebootlessUpdate() throws Exception { |
| InstallUtils.dropShellPermissionIdentity(); |
| InstallUtils.adoptShellPermissionIdentity(Manifest.permission.INSTALL_PACKAGE_UPDATES); |
| |
| final PackageManager pm = |
| InstrumentationRegistry.getInstrumentation().getContext().getPackageManager(); |
| { |
| PackageInfo apex = pm.getPackageInfo(SHIM_APEX_PACKAGE_NAME, PackageManager.MATCH_APEX); |
| assertThat(apex.getLongVersionCode()).isEqualTo(1); |
| assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) |
| .isEqualTo(ApplicationInfo.FLAG_SYSTEM); |
| assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) |
| .isEqualTo(ApplicationInfo.FLAG_INSTALLED); |
| assertThat(apex.applicationInfo.sourceDir).startsWith("/system/apex"); |
| } |
| |
| Install.single(Apex2Rebootless).commit(); |
| { |
| PackageInfo apex = pm.getPackageInfo(SHIM_APEX_PACKAGE_NAME, PackageManager.MATCH_APEX); |
| assertThat(apex.getLongVersionCode()).isEqualTo(2); |
| assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM).isEqualTo(0); |
| assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) |
| .isEqualTo(ApplicationInfo.FLAG_INSTALLED); |
| assertThat(apex.applicationInfo.sourceDir).startsWith("/data/apex/active"); |
| } |
| { |
| PackageInfo apex = pm.getPackageInfo(SHIM_APEX_PACKAGE_NAME, |
| PackageManager.MATCH_APEX | PackageManager.MATCH_FACTORY_ONLY); |
| assertThat(apex.getLongVersionCode()).isEqualTo(1); |
| assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) |
| .isEqualTo(ApplicationInfo.FLAG_SYSTEM); |
| assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED).isEqualTo(0); |
| assertThat(apex.applicationInfo.sourceDir).startsWith("/system/apex"); |
| } |
| } |
| |
| @Test |
| public void testRebootlessUpdate_installV3() throws Exception { |
| InstallUtils.dropShellPermissionIdentity(); |
| InstallUtils.adoptShellPermissionIdentity(Manifest.permission.INSTALL_PACKAGE_UPDATES); |
| |
| final PackageManager pm = |
| InstrumentationRegistry.getInstrumentation().getContext().getPackageManager(); |
| |
| Install.single(Apex3Rebootless).commit(); |
| { |
| PackageInfo apex = pm.getPackageInfo(SHIM_APEX_PACKAGE_NAME, PackageManager.MATCH_APEX); |
| assertThat(apex.getLongVersionCode()).isEqualTo(3); |
| assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM).isEqualTo(0); |
| assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) |
| .isEqualTo(ApplicationInfo.FLAG_INSTALLED); |
| assertThat(apex.applicationInfo.sourceDir).startsWith("/data/apex/active"); |
| } |
| { |
| PackageInfo apex = pm.getPackageInfo(SHIM_APEX_PACKAGE_NAME, |
| PackageManager.MATCH_APEX | PackageManager.MATCH_FACTORY_ONLY); |
| assertThat(apex.getLongVersionCode()).isEqualTo(1); |
| assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) |
| .isEqualTo(ApplicationInfo.FLAG_SYSTEM); |
| assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED).isEqualTo(0); |
| assertThat(apex.applicationInfo.sourceDir).startsWith("/system/apex"); |
| } |
| } |
| |
| @Test |
| public void testRebootlessUpdate_downgradeToV2_fails() throws Exception { |
| InstallUtils.dropShellPermissionIdentity(); |
| InstallUtils.adoptShellPermissionIdentity(Manifest.permission.INSTALL_PACKAGE_UPDATES); |
| |
| final PackageManager pm = |
| InstrumentationRegistry.getInstrumentation().getContext().getPackageManager(); |
| |
| { |
| PackageInfo apex = pm.getPackageInfo(SHIM_APEX_PACKAGE_NAME, PackageManager.MATCH_APEX); |
| assertThat(apex.getLongVersionCode()).isEqualTo(3); |
| assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM).isEqualTo(0); |
| assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) |
| .isEqualTo(ApplicationInfo.FLAG_INSTALLED); |
| assertThat(apex.applicationInfo.sourceDir).startsWith("/data/apex/active"); |
| } |
| |
| InstallUtils.commitExpectingFailure( |
| AssertionError.class, |
| "Downgrade of APEX package com.android.apex.cts.shim is not allowed", |
| Install.single(Apex2Rebootless)); |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3); |
| } |
| |
| @Test |
| public void testRebootlessUpdate_noPermission_fails() throws Exception { |
| InstallUtils.dropShellPermissionIdentity(); |
| |
| InstallUtils.commitExpectingFailure(SecurityException.class, |
| "Not allowed to perform APEX updates", |
| Install.single(Apex2Rebootless)); |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| } |
| |
| @Test |
| public void testRebootlessUpdate_noPreInstalledApex_fails() throws Exception { |
| assertThat(getInstalledVersion(DIFFERENT_APEX_PACKAGE_NAME)).isEqualTo(-1); |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| |
| InstallUtils.commitExpectingFailure( |
| AssertionError.class, |
| "It is forbidden to install new APEX packages", |
| Install.single(Apex2DifferentPackageName)); |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| } |
| |
| @Test |
| public void testRebootlessUpdate_unsignedPayload_fails() throws Exception { |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| |
| InstallUtils.commitExpectingFailure( |
| AssertionError.class, |
| "AVB footer verification failed", |
| Install.single(Apex2UnsignedPayload)); |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| } |
| |
| @Test |
| public void testRebootlessUpdate_payloadSignedWithDifferentKey_fails() throws Exception { |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| |
| InstallUtils.commitExpectingFailure( |
| AssertionError.class, |
| "public key doesn't match the pre-installed one", |
| Install.single(Apex2SignPayloadWithDifferentKey)); |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| } |
| |
| @Test |
| public void testRebootlessUpdate_outerContainerSignedWithDifferentCert_fails() |
| throws Exception { |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| |
| InstallUtils.commitExpectingFailure( |
| AssertionError.class, |
| "APK container signature of .+ is not compatible with currently installed", |
| Install.single(Apex2DifferentCertificate)); |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| } |
| |
| @Test |
| public void testRebootlessUpdate_outerContainerUnsigned_fails() throws Exception { |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| |
| InstallUtils.commitExpectingFailure( |
| AssertionError.class, |
| "Failed collecting certificates for", |
| Install.single(Apex2NoApkSignature)); |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| } |
| |
| @Test |
| public void testRebootlessUpdate_targetsOlderSdk_fails() throws Exception { |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| |
| InstallUtils.commitExpectingFailure( |
| AssertionError.class, |
| "Requires development platform P", |
| Install.single(Apex2SdkTargetP)); |
| assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1); |
| } |
| |
| // It becomes harder to maintain this variety of install-related helper methods. |
| // TODO(ioffe): refactor install-related helper methods into a separate utility. |
| private static int createStagedSession() throws Exception { |
| return Install.single(TestApp.A1).setStaged().createSession(); |
| } |
| |
| private static Intent commitSession(int sessionId) throws IOException, InterruptedException { |
| LocalIntentSender sender = new LocalIntentSender(); |
| InstallUtils.openPackageInstallerSession(sessionId) |
| .commit(sender.getIntentSender()); |
| return sender.getResult(); |
| } |
| |
| private static StageSessionResult stageDowngradeSingleApk(TestApp testApp) throws Exception { |
| Log.i(TAG, "Staging a downgrade of " + testApp); |
| int sessionId = Install.single(testApp).setStaged().setRequestDowngrade().createSession(); |
| // Commit the session (this will start the installation workflow). |
| Log.i(TAG, "Committing downgrade session for apk: " + testApp); |
| Intent result = commitSession(sessionId); |
| return new StageSessionResult(sessionId, result); |
| } |
| |
| private static StageSessionResult stageSingleApk(String apkFileName, String outputFileName) |
| throws Exception { |
| File tmpFile = File.createTempFile(outputFileName, null); |
| try (InputStream is = |
| StagedInstallTest.class.getClassLoader().getResourceAsStream(apkFileName)) { |
| Files.copy(is, tmpFile.toPath(), StandardCopyOption.REPLACE_EXISTING); |
| } |
| TestApp testApp = new TestApp(tmpFile.getName(), null, -1, |
| apkFileName.endsWith(".apex"), tmpFile); |
| return stageSingleApk(testApp); |
| } |
| |
| private static StageSessionResult stageSingleApk(TestApp testApp) throws Exception { |
| Log.i(TAG, "Staging an install of " + testApp); |
| int sessionId = Install.single(testApp).setStaged().createSession(); |
| // Commit the session (this will start the installation workflow). |
| Log.i(TAG, "Committing session for apk: " + testApp); |
| Intent result = commitSession(sessionId); |
| return new StageSessionResult(sessionId, result); |
| } |
| |
| private static StageSessionResult stageMultipleApks(TestApp... testApps) throws Exception { |
| Log.i(TAG, "Staging an install of " + Arrays.toString(testApps)); |
| int multiPackageSessionId = Install.multi(testApps).setStaged().createSession(); |
| Intent result = commitSession(multiPackageSessionId); |
| return new StageSessionResult(multiPackageSessionId, result); |
| } |
| |
| private static void assertSessionApplied(int sessionId) { |
| assertSessionState(sessionId, |
| (session) -> assertThat(session).isStagedSessionApplied()); |
| } |
| |
| private static void assertSessionReady(int sessionId) { |
| assertSessionState(sessionId, |
| (session) -> assertThat(session).isStagedSessionReady()); |
| } |
| |
| private static void assertSessionFailed(int sessionId) { |
| assertSessionState(sessionId, |
| (session) -> assertThat(session).isStagedSessionFailed()); |
| } |
| |
| private static void assertSessionFailedWithMessage(int sessionId, String msg) { |
| assertSessionState(sessionId, (session) -> { |
| assertThat(session).isStagedSessionFailed(); |
| assertThat(session.getStagedSessionErrorMessage()).contains(msg); |
| }); |
| } |
| |
| private static void assertSessionState( |
| int sessionId, Consumer<PackageInstaller.SessionInfo> assertion) { |
| PackageInstaller packageInstaller = getPackageInstaller(); |
| |
| List<PackageInstaller.SessionInfo> sessions = packageInstaller.getStagedSessions(); |
| boolean found = false; |
| for (PackageInstaller.SessionInfo session : sessions) { |
| if (session.getSessionId() == sessionId) { |
| assertion.accept(session); |
| found = true; |
| } |
| } |
| assertWithMessage("Expecting to find session in getStagedSession()") |
| .that(found).isTrue(); |
| |
| // Test also that getSessionInfo correctly returns the session. |
| PackageInstaller.SessionInfo sessionInfo = packageInstaller.getSessionInfo(sessionId); |
| assertion.accept(sessionInfo); |
| } |
| |
| private void storeSessionId(int sessionId) throws Exception { |
| try (BufferedWriter writer = new BufferedWriter(new FileWriter(mTestStateFile))) { |
| writer.write("" + sessionId); |
| } |
| } |
| |
| private int retrieveLastSessionId() throws Exception { |
| try (BufferedReader reader = new BufferedReader(new FileReader(mTestStateFile))) { |
| return Integer.parseInt(reader.readLine()); |
| } |
| } |
| |
| private void storeSessionIds(List<Integer> sessionIds) throws Exception { |
| try (BufferedWriter writer = new BufferedWriter(new FileWriter(mTestStateFile))) { |
| writer.write(sessionIds.toString()); |
| } |
| } |
| |
| private List<Integer> retrieveLastSessionIds() throws Exception { |
| try (BufferedReader reader = new BufferedReader(new FileReader(mTestStateFile))) { |
| String line = reader.readLine(); |
| String[] sessionIdsStr = line.substring(1, line.length() - 1).split(", "); |
| ArrayList<Integer> result = new ArrayList<>(); |
| for (String sessionIdStr: sessionIdsStr) { |
| result.add(Integer.parseInt(sessionIdStr)); |
| } |
| return result; |
| } |
| } |
| |
| // TODO(ioffe): not really-tailored to staged install, rename to InstallResult? |
| private static final class StageSessionResult { |
| private final int sessionId; |
| private final Intent result; |
| |
| private StageSessionResult(int sessionId, Intent result) { |
| this.sessionId = sessionId; |
| this.result = result; |
| } |
| |
| public int getSessionId() { |
| return sessionId; |
| } |
| |
| public String getErrorMessage() { |
| return extractErrorMessage(result); |
| } |
| |
| public StageSessionResult assertSuccessful() { |
| assertStatusSuccess(result); |
| return this; |
| } |
| |
| public StageSessionResult assertFailure() { |
| assertStatusFailure(result); |
| return this; |
| } |
| } |
| |
| /** |
| * Counts the number of broadcast intents received for a given type during the test. |
| * Used by to check no broadcast intents were received during the test. |
| */ |
| private static class BroadcastCounter extends BroadcastReceiver { |
| private final Context mContext; |
| private final AtomicInteger mNumBroadcastReceived = new AtomicInteger(); |
| |
| BroadcastCounter(String action) { |
| mContext = InstrumentationRegistry.getInstrumentation().getContext(); |
| mContext.registerReceiver(this, new IntentFilter(action)); |
| } |
| |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| mNumBroadcastReceived.incrementAndGet(); |
| } |
| |
| /** |
| * Waits for a while and checks no broadcasts are received. |
| */ |
| void assertNoBroadcastReceived() { |
| try { |
| // Sleep for a reasonable amount of time and check no broadcast is received |
| Thread.sleep(TimeUnit.SECONDS.toMillis(10)); |
| } catch (InterruptedException ignore) { |
| } |
| mContext.unregisterReceiver(this); |
| assertThat(mNumBroadcastReceived.get()).isEqualTo(0); |
| } |
| } |
| |
| private static String extractErrorMessage(Intent result) { |
| int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, |
| PackageInstaller.STATUS_FAILURE); |
| if (status == -1) { |
| throw new AssertionError("PENDING USER ACTION"); |
| } |
| if (status == 0) { |
| throw new AssertionError("Result was successful"); |
| } |
| return result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE); |
| } |
| |
| private static void abandonSession(int sessionId) { |
| getPackageInstaller().abandonSession(sessionId); |
| } |
| |
| private static PackageInstaller.SessionInfo getSessionInfo(int sessionId) { |
| return getPackageInstaller().getSessionInfo(sessionId); |
| } |
| |
| private void waitForIsFailedBroadcast(int sessionId) { |
| Log.i(TAG, "Waiting for session " + sessionId + " to be marked as failed"); |
| PackageInstaller.SessionInfo info = waitForBroadcast(sessionId); |
| assertThat(info).isStagedSessionFailed(); |
| } |
| |
| private void waitForIsReadyBroadcast(int sessionId) { |
| Log.i(TAG, "Waiting for session " + sessionId + " to be ready"); |
| PackageInstaller.SessionInfo info = waitForBroadcast(sessionId); |
| assertThat(info).isStagedSessionReady(); |
| } |
| |
| private PackageInstaller.SessionInfo waitForBroadcast(int sessionId) { |
| return InstallUtils.waitForSession(sessionId); |
| } |
| } |