Install constraints API

* commitSessionAfterInstallConstraintsAreMet

Bug: 235306967
Test: atest CtsStagedInstallHostTestCases

Change-Id: I11de4493eedf075a1957d2dacfffdb413b6cfa02
This commit is contained in:
JW Wang
2022-12-21 08:58:35 +08:00
parent 73246e5161
commit c0def4a57d
4 changed files with 132 additions and 2 deletions

View File

@@ -11845,6 +11845,7 @@ package android.content.pm {
public class PackageInstaller {
method public void abandonSession(int);
method public void checkInstallConstraints(@NonNull java.util.List<java.lang.String>, @NonNull android.content.pm.PackageInstaller.InstallConstraints, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.content.pm.PackageInstaller.InstallConstraintsResult>);
method public void commitSessionAfterInstallConstraintsAreMet(int, @NonNull android.content.IntentSender, @NonNull android.content.pm.PackageInstaller.InstallConstraints, long);
method public int createSession(@NonNull android.content.pm.PackageInstaller.SessionParams) throws java.io.IOException;
method @Deprecated @Nullable public android.content.pm.PackageInstaller.SessionInfo getActiveStagedSession();
method @NonNull public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getActiveStagedSessions();
@@ -11889,6 +11890,7 @@ package android.content.pm {
field public static final int STATUS_FAILURE_INCOMPATIBLE = 7; // 0x7
field public static final int STATUS_FAILURE_INVALID = 4; // 0x4
field public static final int STATUS_FAILURE_STORAGE = 6; // 0x6
field public static final int STATUS_FAILURE_TIMEOUT = 8; // 0x8
field public static final int STATUS_PENDING_USER_ACTION = -1; // 0xffffffff
field public static final int STATUS_SUCCESS = 0; // 0x0
}

View File

@@ -46,6 +46,8 @@ interface IPackageInstallerSession {
void commit(in IntentSender statusReceiver, boolean forTransferred);
void transfer(in String packageName);
void abandon();
void seal();
List<String> fetchPackageNames();
DataLoaderParamsParcel getDataLoaderParams();
void addFile(int location, String name, long lengthBytes, in byte[] metadata, in byte[] signature);

View File

@@ -44,8 +44,11 @@ import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.AppGlobals;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.IIntentReceiver;
import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageManager.DeleteFlags;
@@ -59,9 +62,11 @@ import android.graphics.Bitmap;
import android.icu.util.ULocale;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.FileBridge;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
@@ -226,8 +231,8 @@ public class PackageInstaller {
* {@link #STATUS_PENDING_USER_ACTION}, {@link #STATUS_SUCCESS},
* {@link #STATUS_FAILURE}, {@link #STATUS_FAILURE_ABORTED},
* {@link #STATUS_FAILURE_BLOCKED}, {@link #STATUS_FAILURE_CONFLICT},
* {@link #STATUS_FAILURE_INCOMPATIBLE}, {@link #STATUS_FAILURE_INVALID}, or
* {@link #STATUS_FAILURE_STORAGE}.
* {@link #STATUS_FAILURE_INCOMPATIBLE}, {@link #STATUS_FAILURE_INVALID},
* {@link #STATUS_FAILURE_STORAGE}, or {@link #STATUS_FAILURE_TIMEOUT}.
* <p>
* More information about a status may be available through additional
* extras; see the individual status documentation for details.
@@ -440,6 +445,13 @@ public class PackageInstaller {
*/
public static final int STATUS_FAILURE_INCOMPATIBLE = 7;
/**
* The operation failed because it didn't complete within the specified timeout.
*
* @see #EXTRA_STATUS_MESSAGE
*/
public static final int STATUS_FAILURE_TIMEOUT = 8;
/**
* Default value, non-streaming installation session.
*
@@ -975,6 +987,61 @@ public class PackageInstaller {
}
}
/**
* Commit the session when all constraints are satisfied. This is a convenient method to
* combine {@link #waitForInstallConstraints(List, InstallConstraints, IntentSender, long)}
* and {@link Session#commit(IntentSender)}.
* <p>
* Once this method is called, the session is sealed and no additional mutations
* may be performed on the session. In the case of timeout, you may commit the
* session again using this method or {@link Session#commit(IntentSender)} for retries.
*
* @param statusReceiver Called when the state of the session changes. Intents
* sent to this receiver contain {@link #EXTRA_STATUS}.
* Refer to the individual status codes on how to handle them.
* @param constraints The requirements to satisfy before committing the session.
* @param timeoutMillis The maximum time to wait, in milliseconds until the
* constraints are satisfied. The caller will be notified via
* {@code statusReceiver} if timeout happens before commit.
*/
public void commitSessionAfterInstallConstraintsAreMet(int sessionId,
@NonNull IntentSender statusReceiver, @NonNull InstallConstraints constraints,
@DurationMillisLong long timeoutMillis) {
try {
var session = mInstaller.openSession(sessionId);
session.seal();
var packageNames = session.fetchPackageNames();
var intentSender = new IntentSender((IIntentSender) new IIntentSender.Stub() {
@Override
public void send(int code, Intent intent, String resolvedType,
IBinder allowlistToken, IIntentReceiver finishedReceiver,
String requiredPermission, Bundle options) {
var result = intent.getParcelableExtra(
PackageInstaller.EXTRA_INSTALL_CONSTRAINTS_RESULT,
InstallConstraintsResult.class);
try {
if (result.isAllConstraintsSatisfied()) {
session.commit(statusReceiver, false);
} else {
// timeout
final Intent fillIn = new Intent();
fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
fillIn.putExtra(PackageInstaller.EXTRA_STATUS, STATUS_FAILURE_TIMEOUT);
fillIn.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE,
"Install constraints not satisfied within timeout");
statusReceiver.sendIntent(
ActivityThread.currentApplication(), 0, fillIn, null, null);
}
} catch (Exception ignore) {
}
}
});
waitForInstallConstraints(packageNames, constraints, intentSender, timeoutMillis);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Events for observing session lifecycle.
* <p>

View File

@@ -1796,6 +1796,60 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
dispatchSessionSealed();
}
@Override
public void seal() {
assertNotChild("seal");
assertCallerIsOwnerOrRoot();
try {
sealInternal();
for (var child : getChildSessions()) {
child.sealInternal();
}
} catch (PackageManagerException e) {
throw new IllegalStateException("Package is not valid", e);
}
}
private void sealInternal() throws PackageManagerException {
synchronized (mLock) {
sealLocked();
}
}
@Override
public List<String> fetchPackageNames() {
assertNotChild("fetchPackageNames");
assertCallerIsOwnerOrRoot();
var sessions = getSelfOrChildSessions();
var result = new ArrayList<String>(sessions.size());
for (var s : sessions) {
result.add(s.fetchPackageName());
}
return result;
}
private String fetchPackageName() {
assertSealed("fetchPackageName");
synchronized (mLock) {
final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
final List<File> addedFiles = getAddedApksLocked();
for (File addedFile : addedFiles) {
final ParseResult<ApkLite> result =
ApkLiteParseUtils.parseApkLite(input.reset(), addedFile, 0);
if (result.isError()) {
throw new IllegalStateException(
"Can't parse package for session=" + sessionId, result.getException());
}
final ApkLite apk = result.getResult();
var packageName = apk.getPackageName();
if (packageName != null) {
return packageName;
}
}
throw new IllegalStateException("Can't fetch package name for session=" + sessionId);
}
}
/**
* Kicks off the install flow. The first step is to persist 'sealed' flags
* to prevent mutations of hard links created later.
@@ -2049,6 +2103,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
}
@NonNull
private List<PackageInstallerSession> getSelfOrChildSessions() {
return isMultiPackage() ? getChildSessions() : Collections.singletonList(this);
}
/**
* Seal the session to prevent further modification.
*