diff options
| author | 2023-01-12 00:57:52 +0000 | |
|---|---|---|
| committer | 2023-01-12 00:57:52 +0000 | |
| commit | 843cd7b139c20df0dcc8cfeb9dfde7dcaea8a3fb (patch) | |
| tree | 161b69fa5aae9ec8724f421acb76755c5d3096c5 | |
| parent | befaeff53f589af65e1a9981dc877a3ad8a04502 (diff) | |
| parent | c0def4a57dff271e9dee3e60ac53fcb5de4ad55e (diff) | |
Merge "Install constraints API"
4 files changed, 132 insertions, 2 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 19123bba2b5d..825e73bd4aac 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -11876,6 +11876,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(); @@ -11920,6 +11921,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 } diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl index c9a56324de79..9dd9c0f757d5 100644 --- a/core/java/android/content/pm/IPackageInstallerSession.aidl +++ b/core/java/android/content/pm/IPackageInstallerSession.aidl @@ -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); diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 812b5b314582..df1340d318d8 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -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. @@ -441,6 +446,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. * * @see #EXTRA_DATA_LOADER_TYPE @@ -1016,6 +1028,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> * A typical session lifecycle looks like this: diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 8ea77889acc7..350f5ef7439c 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -1842,6 +1842,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. @@ -2095,6 +2149,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. * |