blob: cd189064c87b4877c556ebc5220bfed54cc52b14 [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.cts.install.lib;
import static com.google.common.truth.Truth.assertThat;
import android.content.Intent;
import android.content.pm.PackageInstaller;
import com.android.compatibility.common.util.SystemUtil;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Builder class for installing test apps and creating install sessions.
*/
public class Install {
// The collection of apps to be installed with parameters inherited from parent Install object.
private final TestApp[] mTestApps;
// The collection of apps to be installed with parameters independent of parent Install object.
private final Install[] mChildInstalls;
// Indicates whether Install represents a multiPackage install.
private final boolean mIsMultiPackage;
// PackageInstaller.Session parameters.
private boolean mIsStaged = false;
private boolean mIsDowngrade = false;
private boolean mEnableRollback = false;
private int mRollbackDataPolicy = 0;
private int mSessionMode = PackageInstaller.SessionParams.MODE_FULL_INSTALL;
private int mInstallFlags = 0;
private Install(boolean isMultiPackage, TestApp... testApps) {
mIsMultiPackage = isMultiPackage;
mTestApps = testApps;
mChildInstalls = new Install[0];
}
private Install(boolean isMultiPackage, Install... installs) {
mIsMultiPackage = isMultiPackage;
mTestApps = new TestApp[0];
mChildInstalls = installs;
}
/**
* Creates an Install builder to install a single package.
*/
public static Install single(TestApp testApp) {
return new Install(false, testApp);
}
/**
* Creates an Install builder to install using multiPackage.
*/
public static Install multi(TestApp... testApps) {
return new Install(true, testApps);
}
/**
* Creates an Install builder from separate Install builders. The newly created builder
* will be responsible for building the parent session, while each one of the other builders
* will be responsible for building one of the child sessions.
*
* <p>Modifications to the parent install are not propagated to the child installs,
* and vice versa. This gives more control over a multi install session,
* e.g. can setStaged on a subset of the child sessions or setStaged on a child session but
* not on the parent session.
*
* <p>It's encouraged to use {@link #multi} that receives {@link TestApp}s
* instead of {@link Install}s. This variation of {@link #multi} should be used only if it's
* necessary to modify parameters in a subset of the installed sessions.
*/
public static Install multi(Install... installs) {
for (Install childInstall : installs) {
assertThat(childInstall.isMultiPackage()).isFalse();
}
Install install = new Install(true, installs);
return install;
}
/**
* Makes the install a staged install.
*/
public Install setStaged() {
mIsStaged = true;
return this;
}
/**
* Marks the install as a downgrade.
*/
public Install setRequestDowngrade() {
mIsDowngrade = true;
return this;
}
/**
* Enables rollback for the install.
*/
public Install setEnableRollback() {
mEnableRollback = true;
return this;
}
/**
* Enables rollback for the install with specified rollback data policy.
*/
public Install setEnableRollback(int dataPolicy) {
mEnableRollback = true;
mRollbackDataPolicy = dataPolicy;
return this;
}
/**
* Sets the session mode {@link PackageInstaller.SessionParams#MODE_INHERIT_EXISTING}.
* If it's not set, then the default session mode is
* {@link PackageInstaller.SessionParams#MODE_FULL_INSTALL}
*/
public Install setSessionMode(int sessionMode) {
mSessionMode = sessionMode;
return this;
}
/**
* Sets the session params.
*/
public Install addInstallFlags(int installFlags) {
mInstallFlags |= installFlags;
return this;
}
/**
* Commits the install.
*
* @return the session id of the install session, if the session is successful.
* @throws AssertionError if the install doesn't succeed.
*/
public int commit() throws IOException, InterruptedException {
int sessionId = createSession();
try (PackageInstaller.Session session =
InstallUtils.openPackageInstallerSession(sessionId)) {
session.commit(LocalIntentSender.getIntentSender());
Intent result = LocalIntentSender.getIntentSenderResult();
int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
PackageInstaller.STATUS_FAILURE);
if (status == -1) {
throw new AssertionError("PENDING USER ACTION");
} else if (status > 0) {
String message = result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
throw new AssertionError(message == null ? "UNKNOWN FAILURE" : message);
}
if (mIsStaged) {
InstallUtils.waitForSessionReady(sessionId);
}
return sessionId;
}
}
/**
* Kicks off an install flow by creating an install session
* and, in the case of a multiPackage install, child install sessions.
*
* @return the session id of the install session, if the session is successful.
*/
public int createSession() throws IOException {
int sessionId;
if (isMultiPackage()) {
sessionId = createEmptyInstallSession(/*multiPackage*/ true, /*isApex*/false);
try (PackageInstaller.Session session =
InstallUtils.openPackageInstallerSession(sessionId)) {
for (Install subInstall : mChildInstalls) {
session.addChildSessionId(subInstall.createSession());
}
for (TestApp testApp : mTestApps) {
session.addChildSessionId(createSingleInstallSession(testApp));
}
}
} else {
assert mTestApps.length == 1;
sessionId = createSingleInstallSession(mTestApps[0]);
}
return sessionId;
}
/**
* Creates an empty install session with appropriate install params set.
*
* @return the session id of the newly created session
*/
private int createEmptyInstallSession(boolean multiPackage, boolean isApex)
throws IOException {
if (mIsStaged) {
SystemUtil.runShellCommandForNoOutput("pm bypass-staged-installer-check true");
}
try {
PackageInstaller.SessionParams params =
new PackageInstaller.SessionParams(mSessionMode);
if (multiPackage) {
params.setMultiPackage();
}
if (isApex) {
params.setInstallAsApex();
}
if (mIsStaged) {
params.setStaged();
}
params.setRequestDowngrade(mIsDowngrade);
params.setEnableRollback(mEnableRollback, mRollbackDataPolicy);
if (mInstallFlags != 0) {
InstallUtils.mutateInstallFlags(params, mInstallFlags);
}
return InstallUtils.getPackageInstaller().createSession(params);
} finally {
if (mIsStaged) {
SystemUtil.runShellCommandForNoOutput("pm bypass-staged-installer-check false");
}
}
}
/**
* Creates an install session for the given test app.
*
* @return the session id of the newly created session.
*/
private int createSingleInstallSession(TestApp app) throws IOException {
int sessionId = createEmptyInstallSession(/*multiPackage*/false, app.isApex());
try (PackageInstaller.Session session =
InstallUtils.getPackageInstaller().openSession(sessionId)) {
for (String resourceName : app.getResourceNames()) {
try (OutputStream os = session.openWrite(resourceName, 0, -1);
InputStream is = app.getResourceStream(resourceName);) {
if (is == null) {
throw new IOException("Resource " + resourceName + " not found");
}
byte[] buffer = new byte[4096];
int n;
while ((n = is.read(buffer)) >= 0) {
os.write(buffer, 0, n);
}
}
}
return sessionId;
}
}
private boolean isMultiPackage() {
return mIsMultiPackage;
}
}