blob: e7d419b4edd1216791b16fd6461298c869eec868 [file] [log] [blame]
/*
* 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);
}
}