diff options
9 files changed, 1038 insertions, 295 deletions
diff --git a/api/current.txt b/api/current.txt index dd4c781b6e7b..fd3ada24ca91 100755 --- a/api/current.txt +++ b/api/current.txt @@ -11176,12 +11176,17 @@ package android.content.pm { public static class PackageInstaller.Session implements java.io.Closeable { method public void abandon(); + method public void addChildSessionId(int); method public void close(); method public void commit(android.content.IntentSender); method public void fsync(java.io.OutputStream) throws java.io.IOException; + method public int[] getChildSessionIds(); method public java.lang.String[] getNames() throws java.io.IOException; + method public int getParentSessionId(); + method public boolean isMultiPackage(); method public java.io.InputStream openRead(java.lang.String) throws java.io.IOException; method public java.io.OutputStream openWrite(java.lang.String, long, long) throws java.io.IOException; + method public void removeChildSessionId(int); method public void removeSplit(java.lang.String) throws java.io.IOException; method public void setStagingProgress(float); method public void transfer(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException; @@ -11202,20 +11207,24 @@ package android.content.pm { method public android.graphics.Bitmap getAppIcon(); method public java.lang.CharSequence getAppLabel(); method public java.lang.String getAppPackageName(); + method public int[] getChildSessionIds(); method public int getInstallLocation(); method public int getInstallReason(); method public java.lang.String getInstallerPackageName(); method public int getMode(); method public int getOriginatingUid(); method public android.net.Uri getOriginatingUri(); + method public int getParentSessionId(); method public float getProgress(); method public android.net.Uri getReferrerUri(); method public int getSessionId(); method public long getSize(); method public boolean isActive(); + method public boolean isMultiPackage(); method public boolean isSealed(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.SessionInfo> CREATOR; + field public static final int INVALID_ID = -1; // 0xffffffff } public static class PackageInstaller.SessionParams implements android.os.Parcelable { @@ -11226,6 +11235,7 @@ package android.content.pm { method public void setAppPackageName(java.lang.String); method public void setInstallLocation(int); method public void setInstallReason(int); + method public void setMultiPackage(); method public void setOriginatingUid(int); method public void setOriginatingUri(android.net.Uri); method public void setReferrerUri(android.net.Uri); diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl index 8fddb99b35a8..cef21f607e5f 100644 --- a/core/java/android/content/pm/IPackageInstallerSession.aidl +++ b/core/java/android/content/pm/IPackageInstallerSession.aidl @@ -38,4 +38,9 @@ interface IPackageInstallerSession { void commit(in IntentSender statusReceiver, boolean forTransferred); void transfer(in String packageName); void abandon(); + boolean isMultiPackage(); + int[] getChildSessionIds(); + void addChildSessionId(in int sessionId); + void removeChildSessionId(in int sessionId); + int getParentSessionId(); } diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index e9cfa78deba6..38e1c4973cd9 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -365,12 +365,14 @@ public class PackageInstaller { */ public @NonNull Session openSession(int sessionId) throws IOException { try { - return new Session(mInstaller.openSession(sessionId)); + try { + return new Session(mInstaller.openSession(sessionId)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } catch (RuntimeException e) { ExceptionUtils.maybeUnwrapIOException(e); throw e; - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); } } @@ -769,9 +771,18 @@ public class PackageInstaller { * If an APK included in this session is already defined by the existing * installation (for example, the same split name), the APK in this session * will replace the existing APK. + * <p> + * In such a case that multiple packages need to be commited simultaneously, + * multiple sessions can be referenced by a single multi-package session. + * This session is created with no package name and calling + * {@link #setMultiPackage()} with {@code true}. The + * individual session IDs can be added with {@link #addChildSessionId(int)} + * and commit of the multi-package session will result in all child sessions + * being committed atomically. */ public static class Session implements Closeable { - private IPackageInstallerSession mSession; + /** {@hide} */ + protected final IPackageInstallerSession mSession; /** {@hide} */ public Session(IPackageInstallerSession session) { @@ -1080,6 +1091,71 @@ public class PackageInstaller { throw e.rethrowFromSystemServer(); } } + + /** + * @return {@code true} if this session will commit more than one package when it is + * committed. + */ + public boolean isMultiPackage() { + try { + return mSession.isMultiPackage(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @return the session ID of the multi-package session that this belongs to or + * {@link SessionInfo#INVALID_ID} if it does not belong to a multi-package session. + */ + public int getParentSessionId() { + try { + return mSession.getParentSessionId(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @return the set of session IDs that will be committed atomically when this session is + * committed if this is a multi-package session or null if none exist. + */ + @NonNull + public int[] getChildSessionIds() { + try { + return mSession.getChildSessionIds(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Adds a session ID to the set of sessions that will be committed atomically + * when this session is committed. + * + * @param sessionId the session ID to add to this multi-package session. + */ + public void addChildSessionId(int sessionId) { + try { + mSession.addChildSessionId(sessionId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Removes a session ID from the set of sessions that will be committed + * atomically when this session is committed. + * + * @param sessionId the session ID to remove from this multi-package session. + */ + public void removeChildSessionId(int sessionId) { + try { + mSession.removeChildSessionId(sessionId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } /** @@ -1149,6 +1225,8 @@ public class PackageInstaller { public String[] grantedRuntimePermissions; /** {@hide} */ public String installerPackageName; + /** {@hide} */ + public boolean isMultiPackage; /** * Construct parameters for a new package install session. @@ -1178,6 +1256,7 @@ public class PackageInstaller { volumeUuid = source.readString(); grantedRuntimePermissions = source.readStringArray(); installerPackageName = source.readString(); + isMultiPackage = source.readBoolean(); } /** @@ -1392,6 +1471,18 @@ public class PackageInstaller { this.installerPackageName = installerPackageName; } + /** + * Set this session to be the parent of a multi-package install. + * + * A multi-package install session contains no APKs and only references other install + * sessions via ID. When a multi-package session is committed, all of its children + * are committed to the system in an atomic manner. If any children fail to install, + * all of them do, including the multi-package session. + */ + public void setMultiPackage() { + this.isMultiPackage = true; + } + /** {@hide} */ public void dump(IndentingPrintWriter pw) { pw.printPair("mode", mode); @@ -1408,6 +1499,7 @@ public class PackageInstaller { pw.printPair("volumeUuid", volumeUuid); pw.printPair("grantedRuntimePermissions", grantedRuntimePermissions); pw.printPair("installerPackageName", installerPackageName); + pw.printPair("isMultiPackage", isMultiPackage); pw.println(); } @@ -1433,6 +1525,7 @@ public class PackageInstaller { dest.writeString(volumeUuid); dest.writeStringArray(grantedRuntimePermissions); dest.writeString(installerPackageName); + dest.writeBoolean(isMultiPackage); } public static final Parcelable.Creator<SessionParams> @@ -1454,6 +1547,12 @@ public class PackageInstaller { */ public static class SessionInfo implements Parcelable { + /** + * A session ID that does not exist or is invalid. + */ + public static final int INVALID_ID = -1; + /** {@hide} */ + private static final int[] NO_SESSIONS = {}; /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public int sessionId; @@ -1503,6 +1602,12 @@ public class PackageInstaller { public String[] grantedRuntimePermissions; /** {@hide} */ public int installFlags; + /** {@hide} */ + public boolean isMultiPackage; + /** {@hide} */ + public int parentSessionId = INVALID_ID; + /** {@hide} */ + public int[] childSessionIds = NO_SESSIONS; /** {@hide} */ @UnsupportedAppUsage @@ -1531,6 +1636,12 @@ public class PackageInstaller { referrerUri = source.readParcelable(null); grantedRuntimePermissions = source.readStringArray(); installFlags = source.readInt(); + isMultiPackage = source.readBoolean(); + parentSessionId = source.readInt(); + childSessionIds = source.createIntArray(); + if (childSessionIds == null) { + childSessionIds = NO_SESSIONS; + } } /** @@ -1784,6 +1895,30 @@ public class PackageInstaller { return createDetailsIntent(); } + /** + * Returns true if this session is a multi-package session containing references to other + * sessions. + */ + public boolean isMultiPackage() { + return isMultiPackage; + } + + /** + * Returns the parent multi-package session ID if this session belongs to one, + * {@link #INVALID_ID} otherwise. + */ + public int getParentSessionId() { + return parentSessionId; + } + + /** + * Returns the set of session IDs that will be committed when this session is commited if + * this session is a multi-package session. + */ + public int[] getChildSessionIds() { + return childSessionIds; + } + @Override public int describeContents() { return 0; @@ -1811,6 +1946,9 @@ public class PackageInstaller { dest.writeParcelable(referrerUri, flags); dest.writeStringArray(grantedRuntimePermissions); dest.writeInt(installFlags); + dest.writeBoolean(isMultiPackage); + dest.writeInt(parentSessionId); + dest.writeIntArray(childSessionIds); } public static final Parcelable.Creator<SessionInfo> diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 67b86c0e6e42..44b3fdaf9fd8 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -915,6 +915,11 @@ public abstract class PackageManager { public static final int INSTALL_REASON_USER = 4; /** + * @hide + */ + public static final int INSTALL_UNKNOWN = 0; + + /** * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS} * on success. * diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 6ccd0406bfcc..1a5b86cfbce0 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -107,7 +107,9 @@ import java.util.List; import java.util.Objects; import java.util.Random; -public class PackageInstallerService extends IPackageInstaller.Stub { +/** The service responsible for installing packages. */ +public class PackageInstallerService extends IPackageInstaller.Stub implements + PackageSessionProvider { private static final String TAG = "PackageInstaller"; private static final boolean LOGD = false; @@ -296,6 +298,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { in.setInput(fis, StandardCharsets.UTF_8.name()); int type; + PackageInstallerSession currentSession = null; while ((type = in.next()) != END_DOCUMENT) { if (type == START_TAG) { final String tag = in.getName(); @@ -303,8 +306,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub { final PackageInstallerSession session; try { session = PackageInstallerSession.readFromXml(in, mInternalCallback, - mContext, mPm, mInstallThread.getLooper(), mSessionsDir); + mContext, mPm, mInstallThread.getLooper(), mSessionsDir, this); + currentSession = session; } catch (Exception e) { + currentSession = null; Slog.e(TAG, "Could not read session", e); continue; } @@ -329,6 +334,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub { addHistoricalSessionLocked(session); } mAllocatedSessions.put(session.sessionId, true); + } else if (currentSession != null + && PackageInstallerSession.TAG_CHILD_SESSION.equals(tag)) { + currentSession.addChildSessionIdInternal( + PackageInstallerSession.readChildSessionIdFromXml(in)); } } } @@ -436,70 +445,72 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } } - // Only system components can circumvent runtime permissions when installing. - if ((params.installFlags & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0 - && mContext.checkCallingOrSelfPermission(Manifest.permission - .INSTALL_GRANT_RUNTIME_PERMISSIONS) == PackageManager.PERMISSION_DENIED) { - throw new SecurityException("You need the " - + "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS permission " - + "to use the PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS flag"); - } - - if ((params.installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0 - || (params.installFlags & PackageManager.INSTALL_EXTERNAL) != 0) { - throw new IllegalArgumentException( - "New installs into ASEC containers no longer supported"); - } - - // Defensively resize giant app icons - if (params.appIcon != null) { - final ActivityManager am = (ActivityManager) mContext.getSystemService( - Context.ACTIVITY_SERVICE); - final int iconSize = am.getLauncherLargeIconSize(); - if ((params.appIcon.getWidth() > iconSize * 2) - || (params.appIcon.getHeight() > iconSize * 2)) { - params.appIcon = Bitmap.createScaledBitmap(params.appIcon, iconSize, iconSize, - true); + if (!params.isMultiPackage) { + // Only system components can circumvent runtime permissions when installing. + if ((params.installFlags & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0 + && mContext.checkCallingOrSelfPermission(Manifest.permission + .INSTALL_GRANT_RUNTIME_PERMISSIONS) == PackageManager.PERMISSION_DENIED) { + throw new SecurityException("You need the " + + "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS permission " + + "to use the PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS flag"); } - } - switch (params.mode) { - case SessionParams.MODE_FULL_INSTALL: - case SessionParams.MODE_INHERIT_EXISTING: - break; - default: - throw new IllegalArgumentException("Invalid install mode: " + params.mode); - } + if ((params.installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0 + || (params.installFlags & PackageManager.INSTALL_EXTERNAL) != 0) { + throw new IllegalArgumentException( + "New installs into ASEC containers no longer supported"); + } - // If caller requested explicit location, sanity check it, otherwise - // resolve the best internal or adopted location. - if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) { - if (!PackageHelper.fitsOnInternal(mContext, params)) { - throw new IOException("No suitable internal storage available"); + // Defensively resize giant app icons + if (params.appIcon != null) { + final ActivityManager am = (ActivityManager) mContext.getSystemService( + Context.ACTIVITY_SERVICE); + final int iconSize = am.getLauncherLargeIconSize(); + if ((params.appIcon.getWidth() > iconSize * 2) + || (params.appIcon.getHeight() > iconSize * 2)) { + params.appIcon = Bitmap.createScaledBitmap(params.appIcon, iconSize, iconSize, + true); + } } - } else if ((params.installFlags & PackageManager.INSTALL_EXTERNAL) != 0) { - if (!PackageHelper.fitsOnExternal(mContext, params)) { - throw new IOException("No suitable external storage available"); + switch (params.mode) { + case SessionParams.MODE_FULL_INSTALL: + case SessionParams.MODE_INHERIT_EXISTING: + break; + default: + throw new IllegalArgumentException("Invalid install mode: " + params.mode); } - } else if ((params.installFlags & PackageManager.INSTALL_FORCE_VOLUME_UUID) != 0) { - // For now, installs to adopted media are treated as internal from - // an install flag point-of-view. - params.setInstallFlagsInternal(); + // If caller requested explicit location, sanity check it, otherwise + // resolve the best internal or adopted location. + if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) { + if (!PackageHelper.fitsOnInternal(mContext, params)) { + throw new IOException("No suitable internal storage available"); + } - } else { - // For now, installs to adopted media are treated as internal from - // an install flag point-of-view. - params.setInstallFlagsInternal(); + } else if ((params.installFlags & PackageManager.INSTALL_EXTERNAL) != 0) { + if (!PackageHelper.fitsOnExternal(mContext, params)) { + throw new IOException("No suitable external storage available"); + } - // Resolve best location for install, based on combination of - // requested install flags, delta size, and manifest settings. - final long ident = Binder.clearCallingIdentity(); - try { - params.volumeUuid = PackageHelper.resolveInstallVolume(mContext, params); - } finally { - Binder.restoreCallingIdentity(ident); + } else if ((params.installFlags & PackageManager.INSTALL_FORCE_VOLUME_UUID) != 0) { + // For now, installs to adopted media are treated as internal from + // an install flag point-of-view. + params.setInstallFlagsInternal(); + + } else { + // For now, installs to adopted media are treated as internal from + // an install flag point-of-view. + params.setInstallFlagsInternal(); + + // Resolve best location for install, based on combination of + // requested install flags, delta size, and manifest settings. + final long ident = Binder.clearCallingIdentity(); + try { + params.volumeUuid = PackageHelper.resolveInstallVolume(mContext, params); + } finally { + Binder.restoreCallingIdentity(ident); + } } } @@ -525,17 +536,19 @@ public class PackageInstallerService extends IPackageInstaller.Stub { // We're staging to exactly one location File stageDir = null; String stageCid = null; - if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) { - final boolean isInstant = - (params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0; - stageDir = buildStageDir(params.volumeUuid, sessionId, isInstant); - } else { - stageCid = buildExternalStageCid(sessionId); + if (!params.isMultiPackage) { + if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) { + final boolean isInstant = + (params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0; + stageDir = buildStageDir(params.volumeUuid, sessionId, isInstant); + } else { + stageCid = buildExternalStageCid(sessionId); + } } - - session = new PackageInstallerSession(mInternalCallback, mContext, mPm, - mInstallThread.getLooper(), sessionId, userId, installerPackageName, callingUid, - params, createdMillis, stageDir, stageCid, false, false); + session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this, + mInstallThread.getLooper(), sessionId, userId, installerPackageName, + callingUid, params, createdMillis, stageDir, stageCid, false, false, null, + SessionInfo.INVALID_ID); synchronized (mSessions) { mSessions.put(sessionId, session); @@ -678,7 +691,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { synchronized (mSessions) { for (int i = 0; i < mSessions.size(); i++) { final PackageInstallerSession session = mSessions.valueAt(i); - if (session.userId == userId) { + if (session.userId == userId && !session.hasParentSessionId()) { result.add(session.generateInfo(false)); } } @@ -699,7 +712,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { SessionInfo info = session.generateInfo(false); if (Objects.equals(info.getInstallerPackageName(), installerPackageName) - && session.userId == userId) { + && session.userId == userId && !session.hasParentSessionId()) { result.add(info); } } @@ -781,6 +794,13 @@ public class PackageInstallerService extends IPackageInstaller.Stub { mCallbacks.unregister(callback); } + @Override + public PackageInstallerSession getSession(int sessionId) { + synchronized (mSessions) { + return mSessions.get(sessionId); + } + } + private static int getSessionCount(SparseArray<PackageInstallerSession> sessions, int installerUid) { int count = 0; diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 6e450137185b..4e2c3c5ac025 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -47,6 +47,8 @@ import android.apex.IApexService; import android.app.admin.DeviceAdminInfo; import android.app.admin.DevicePolicyManagerInternal; import android.content.Context; +import android.content.IIntentReceiver; +import android.content.IIntentSender; import android.content.Intent; import android.content.IntentSender; import android.content.pm.ApplicationInfo; @@ -69,6 +71,7 @@ import android.os.Bundle; import android.os.FileBridge; import android.os.FileUtils; import android.os.Handler; +import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; @@ -90,6 +93,7 @@ import android.util.ArraySet; import android.util.ExceptionUtils; import android.util.MathUtils; import android.util.Slog; +import android.util.SparseIntArray; import android.util.apk.ApkSignatureVerifier; import com.android.internal.annotations.GuardedBy; @@ -130,6 +134,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { /** XML constants used for persisting a session */ static final String TAG_SESSION = "session"; + static final String TAG_CHILD_SESSION = "childSession"; private static final String TAG_GRANTED_RUNTIME_PERMISSION = "granted-runtime-permission"; private static final String ATTR_SESSION_ID = "sessionId"; private static final String ATTR_USER_ID = "userId"; @@ -140,6 +145,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private static final String ATTR_SESSION_STAGE_CID = "sessionStageCid"; private static final String ATTR_PREPARED = "prepared"; private static final String ATTR_SEALED = "sealed"; + private static final String ATTR_MULTI_PACKAGE = "multiPackage"; + private static final String ATTR_PARENT_SESSION_ID = "parentSessionId"; private static final String ATTR_MODE = "mode"; private static final String ATTR_INSTALL_FLAGS = "installFlags"; private static final String ATTR_INSTALL_LOCATION = "installLocation"; @@ -157,6 +164,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private static final String ATTR_INSTALL_REASON = "installRason"; private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill"; + private static final int[] EMPTY_CHILD_SESSION_ARRAY = {}; // TODO: enforce INSTALL_ALLOW_TEST // TODO: enforce INSTALL_ALLOW_DOWNGRADE @@ -165,6 +173,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private final Context mContext; private final PackageManagerService mPm; private final Handler mHandler; + private final PackageSessionProvider mSessionProvider; final int sessionId; final int userId; @@ -236,6 +245,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private long mVersionCode; @GuardedBy("mLock") private PackageParser.SigningDetails mSigningDetails; + @GuardedBy("mLock") + private SparseIntArray mChildSessionIds = new SparseIntArray(); + @GuardedBy("mLock") + private int mParentSessionId; /** * Path to the validated base APK for this session, which may point at an @@ -372,12 +385,16 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } public PackageInstallerSession(PackageInstallerService.InternalCallback callback, - Context context, PackageManagerService pm, Looper looper, int sessionId, int userId, + Context context, PackageManagerService pm, + PackageSessionProvider sessionProvider, Looper looper, + int sessionId, int userId, String installerPackageName, int installerUid, SessionParams params, long createdMillis, - File stageDir, String stageCid, boolean prepared, boolean sealed) { + File stageDir, String stageCid, boolean prepared, boolean sealed, + @Nullable int[] childSessionIds, int parentSessionId) { mCallback = callback; mContext = context; mPm = pm; + mSessionProvider = sessionProvider; mHandler = new Handler(looper, mHandlerCallback); this.sessionId = sessionId; @@ -389,8 +406,14 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { this.createdMillis = createdMillis; this.stageDir = stageDir; this.stageCid = stageCid; + if (childSessionIds != null) { + for (int childSessionId : childSessionIds) { + mChildSessionIds.put(childSessionId, 0); + } + } + this.mParentSessionId = parentSessionId; - if ((stageDir == null) == (stageCid == null)) { + if (!params.isMultiPackage && (stageDir == null) == (stageCid == null)) { throw new IllegalArgumentException( "Exactly one of stageDir or stageCid stage must be set"); } @@ -439,6 +462,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { info.referrerUri = params.referrerUri; info.grantedRuntimePermissions = params.grantedRuntimePermissions; info.installFlags = params.installFlags; + info.isMultiPackage = params.isMultiPackage; + info.parentSessionId = mParentSessionId; + info.childSessionIds = mChildSessionIds.copyKeys(); + if (info.childSessionIds == null) { + info.childSessionIds = EMPTY_CHILD_SESSION_ARRAY; + } } return info; } @@ -762,6 +791,92 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @Override public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) { + if (!markAsCommitted(statusReceiver, forTransfer /* enforce */)) { + return; + } + if (isMultiPackage()) { + + final SparseIntArray remainingSessions = mChildSessionIds.clone(); + final ChildStatusIntentReceiver localIntentReceiver = + new ChildStatusIntentReceiver(remainingSessions, statusReceiver); + for (int childSessionId : getChildSessionIds()) { + mSessionProvider.getSession(childSessionId) + .markAsCommitted(localIntentReceiver.getIntentSender(), forTransfer); + } + } + mHandler.obtainMessage(MSG_COMMIT).sendToTarget(); + } + + private class ChildStatusIntentReceiver { + private final SparseIntArray mChildSessionsRemaining; + private final IntentSender mStatusReceiver; + private final IIntentSender.Stub mLocalSender = new IIntentSender.Stub() { + @Override + public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken, + IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) { + statusUpdate(intent); + } + }; + + private ChildStatusIntentReceiver(SparseIntArray remainingSessions, + IntentSender statusReceiver) { + this.mChildSessionsRemaining = remainingSessions; + this.mStatusReceiver = statusReceiver; + } + + public IntentSender getIntentSender() { + return new IntentSender((IIntentSender) mLocalSender); + } + + public void statusUpdate(Intent intent) { + mHandler.post(() -> { + if (mChildSessionsRemaining.size() == 0) { + return; + } + final int sessionId = intent.getIntExtra( + PackageInstaller.EXTRA_SESSION_ID, 0); + final int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, + PackageInstaller.STATUS_FAILURE); + final int sessionIndex = mChildSessionsRemaining.indexOfKey(sessionId); + if (PackageInstaller.STATUS_SUCCESS == status) { + mChildSessionsRemaining.removeAt(sessionIndex); + if (mChildSessionsRemaining.size() == 0) { + try { + intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, + PackageInstallerSession.this.sessionId); + mStatusReceiver.sendIntent(mContext, 0, intent, null, null); + } catch (IntentSender.SendIntentException ignore) { + } + } + } else if (PackageInstaller.STATUS_PENDING_USER_ACTION == status) { + try { + mStatusReceiver.sendIntent(mContext, 0, intent, null, null); + } catch (IntentSender.SendIntentException ignore) { + } + } else { + intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, + PackageInstallerSession.this.sessionId); + mChildSessionsRemaining.clear(); // we're done. Don't send any more. + try { + mStatusReceiver.sendIntent(mContext, 0, intent, null, null); + } catch (IntentSender.SendIntentException ignore) { + } + } + }); + } + } + + + /** + * Do everything but actually commit the session. If this was not already called, the session + * will be sealed and marked as committed. The caller of this method is responsible for + * subsequently submitting this session for processing. + * + * This method may be called multiple times to update the status receiver validate caller + * permissions. + */ + public boolean markAsCommitted( + @NonNull IntentSender statusReceiver, boolean forTransfer) { Preconditions.checkNotNull(statusReceiver); final boolean wasSealed; @@ -786,6 +901,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + // After validations and updating the observer, we can skip re-sealing, etc. because we + // have already marked ourselves as committed. + if (mCommitted) { + return true; + } + wasSealed = mSealed; if (!mSealed) { try { @@ -796,7 +917,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // Do now throw an exception here to stay compatible with O and older destroyInternal(); dispatchSessionFinished(e.error, ExceptionUtils.getCompleteMessage(e), null); - return; + return false; } } @@ -809,7 +930,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mActiveCount.incrementAndGet(); mCommitted = true; - mHandler.obtainMessage(MSG_COMMIT).sendToTarget(); } if (!wasSealed) { @@ -818,6 +938,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // the session lock, since otherwise it's a lock inversion. mCallback.onSessionSealedBlocking(this); } + return true; } /** @@ -833,32 +954,37 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { assertNoWriteFileTransfersOpenLocked(); assertPreparedAndNotDestroyedLocked("sealing of session"); - final PackageInfo pkgInfo = mPm.getPackageInfo( - params.appPackageName, PackageManager.GET_SIGNATURES - | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId); - - resolveStageDirLocked(); - mSealed = true; - try { - if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) { - validateApexInstallLocked(pkgInfo); - } else { - // Verify that stage looks sane with respect to existing application. - // This currently only ensures packageName, versionCode, and certificate - // consistency. - validateApkInstallLocked(pkgInfo); - } - } catch (PackageManagerException e) { - throw e; - } catch (Throwable e) { - // Convert all exceptions into package manager exceptions as only those are handled - // in the code above - throw new PackageManagerException(e); - } // Read transfers from the original owner stay open, but as the session's data // cannot be modified anymore, there is no leak of information. + if (!params.isMultiPackage) { + final PackageInfo pkgInfo = mPm.getPackageInfo( + params.appPackageName, PackageManager.GET_SIGNATURES + | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId); + + resolveStageDirLocked(); + + // Verify that stage looks sane with respect to existing application. + // This currently only ensures packageName, versionCode, and certificate + // consistency. + try { + if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) { + validateApexInstallLocked(pkgInfo); + } else { + // Verify that stage looks sane with respect to existing application. + // This currently only ensures packageName, versionCode, and certificate + // consistency. + validateApkInstallLocked(pkgInfo); + } + } catch (PackageManagerException e) { + throw e; + } catch (Throwable e) { + // Convert all exceptions into package manager exceptions as only those are handled + // in the code above + throw new PackageManagerException(e); + } + } } @Override @@ -916,25 +1042,52 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @GuardedBy("mLock") private void commitLocked() throws PackageManagerException { - if (mRelinquished) { - Slog.d(TAG, "Ignoring commit after previous commit relinquished control"); + final PackageManagerService.ActiveInstallSession committingSession = + makeSessionActiveLocked(); + if (committingSession == null) { return; } - if (mDestroyed) { - throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session destroyed"); - } - if (!mSealed) { - throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session not sealed"); - } - - Preconditions.checkNotNull(mPackageName); - Preconditions.checkNotNull(mSigningDetails); - Preconditions.checkNotNull(mResolvedBaseFile); - - if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) { - commitApexLocked(); + if (isMultiPackage()) { + final int[] childSessionIds = getChildSessionIds(); + List<PackageManagerService.ActiveInstallSession> childSessions = + new ArrayList<>(childSessionIds.length); + boolean success = true; + PackageManagerException failure = null; + for (int childSessionId : getChildSessionIds()) { + final PackageInstallerSession session = mSessionProvider.getSession(childSessionId); + try { + final PackageManagerService.ActiveInstallSession activeSession = + session.makeSessionActiveLocked(); + if (activeSession != null) { + if ((activeSession.getSessionParams().installFlags + & PackageManager.INSTALL_APEX) != 0) { + // TODO(b/118865310): Add exception to this case for staged installs + throw new PackageManagerException( + PackageManager.INSTALL_FAILED_INTERNAL_ERROR, + "Atomic install is not supported for APEX packages."); + } + childSessions.add(activeSession); + } + } catch (PackageManagerException e) { + failure = e; + success = false; + } + } + if (!success) { + try { + mRemoteObserver.onPackageInstalled( + null, failure.error, failure.getLocalizedMessage(), null); + } catch (RemoteException ignored) { + } + return; + } + mPm.installStage(childSessions); } else { - commitApkLocked(); + if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) { + commitApexLocked(); + } else { + mPm.installStage(committingSession); + } } } @@ -954,81 +1107,105 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + /** + * Stages this session for install and returns a + * {@link PackageManagerService.ActiveInstallSession} representing this new staged state or null + * in case permissions need to be requested before install can proceed. + */ @GuardedBy("mLock") - private void commitApkLocked() throws PackageManagerException { - if (needToAskForPermissionsLocked()) { - // User needs to confirm installation; give installer an intent they can use to involve - // user. - final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_INSTALL); - intent.setPackage(mPm.getPackageInstallerPackageName()); - intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId); - try { - mRemoteObserver.onUserActionRequired(intent); - } catch (RemoteException ignored) { - } - - // Commit was keeping session marked as active until now; release - // that extra refcount so session appears idle. - closeInternal(false); - return; + private PackageManagerService.ActiveInstallSession makeSessionActiveLocked() + throws PackageManagerException { + if (mRelinquished) { + throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, + "Session relinquished"); + } + if (mDestroyed) { + throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session destroyed"); + } + if (!mSealed) { + throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session not sealed"); } - // Inherit any packages and native libraries from existing install that - // haven't been overridden. - if (params.mode == SessionParams.MODE_INHERIT_EXISTING) { - try { - final List<File> fromFiles = mResolvedInheritedFiles; - final File toDir = resolveStageDirLocked(); + if (!params.isMultiPackage) { + Preconditions.checkNotNull(mPackageName); + Preconditions.checkNotNull(mSigningDetails); + Preconditions.checkNotNull(mResolvedBaseFile); - if (LOGD) Slog.d(TAG, "Inherited files: " + mResolvedInheritedFiles); - if (!mResolvedInheritedFiles.isEmpty() && mInheritedFilesBase == null) { - throw new IllegalStateException("mInheritedFilesBase == null"); + if (needToAskForPermissionsLocked()) { + // User needs to confirm installation; + // give installer an intent they can use to involve + // user. + final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_INSTALL); + intent.setPackage(mPm.getPackageInstallerPackageName()); + intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId); + try { + mRemoteObserver.onUserActionRequired(intent); + } catch (RemoteException ignored) { } - if (isLinkPossible(fromFiles, toDir)) { - if (!mResolvedInstructionSets.isEmpty()) { - final File oatDir = new File(toDir, "oat"); - createOatDirs(mResolvedInstructionSets, oatDir); + // Commit was keeping session marked as active until now; release + // that extra refcount so session appears idle. + closeInternal(false); + return null; + } + + // Inherit any packages and native libraries from existing install that + // haven't been overridden. + if (params.mode == SessionParams.MODE_INHERIT_EXISTING) { + try { + final List<File> fromFiles = mResolvedInheritedFiles; + final File toDir = resolveStageDirLocked(); + + if (LOGD) Slog.d(TAG, "Inherited files: " + mResolvedInheritedFiles); + if (!mResolvedInheritedFiles.isEmpty() && mInheritedFilesBase == null) { + throw new IllegalStateException("mInheritedFilesBase == null"); } - // pre-create lib dirs for linking if necessary - if (!mResolvedNativeLibPaths.isEmpty()) { - for (String libPath : mResolvedNativeLibPaths) { - // "/lib/arm64" -> ["lib", "arm64"] - final int splitIndex = libPath.lastIndexOf('/'); - if (splitIndex < 0 || splitIndex >= libPath.length() - 1) { - Slog.e(TAG, "Skipping native library creation for linking due to " - + "invalid path: " + libPath); - continue; - } - final String libDirPath = libPath.substring(1, splitIndex); - final File libDir = new File(toDir, libDirPath); - if (!libDir.exists()) { - NativeLibraryHelper.createNativeLibrarySubdir(libDir); + + if (isLinkPossible(fromFiles, toDir)) { + if (!mResolvedInstructionSets.isEmpty()) { + final File oatDir = new File(toDir, "oat"); + createOatDirs(mResolvedInstructionSets, oatDir); + } + // pre-create lib dirs for linking if necessary + if (!mResolvedNativeLibPaths.isEmpty()) { + for (String libPath : mResolvedNativeLibPaths) { + // "/lib/arm64" -> ["lib", "arm64"] + final int splitIndex = libPath.lastIndexOf('/'); + if (splitIndex < 0 || splitIndex >= libPath.length() - 1) { + Slog.e(TAG, + "Skipping native library creation for linking due to " + + "invalid path: " + libPath); + continue; + } + final String libDirPath = libPath.substring(1, splitIndex); + final File libDir = new File(toDir, libDirPath); + if (!libDir.exists()) { + NativeLibraryHelper.createNativeLibrarySubdir(libDir); + } + final String archDirPath = libPath.substring(splitIndex + 1); + NativeLibraryHelper.createNativeLibrarySubdir( + new File(libDir, archDirPath)); } - final String archDirPath = libPath.substring(splitIndex + 1); - NativeLibraryHelper.createNativeLibrarySubdir( - new File(libDir, archDirPath)); } + linkFiles(fromFiles, toDir, mInheritedFilesBase); + } else { + // TODO: this should delegate to DCS so the system process + // avoids holding open FDs into containers. + copyFiles(fromFiles, toDir); } - linkFiles(fromFiles, toDir, mInheritedFilesBase); - } else { - // TODO: this should delegate to DCS so the system process - // avoids holding open FDs into containers. - copyFiles(fromFiles, toDir); + } catch (IOException e) { + throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE, + "Failed to inherit existing install", e); } - } catch (IOException e) { - throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE, - "Failed to inherit existing install", e); } - } - // TODO: surface more granular state from dexopt - mInternalProgress = 0.5f; - computeProgressLocked(true); - - // Unpack native libraries - extractNativeLibraries(mResolvedStageDir, params.abiOverride, mayInheritNativeLibs()); + // TODO: surface more granular state from dexopt + mInternalProgress = 0.5f; + computeProgressLocked(true); + // Unpack native libraries + extractNativeLibraries(mResolvedStageDir, params.abiOverride, mayInheritNativeLibs()); + } // We've reached point of no return; call into PMS to install the stage. // Regardless of success or failure we always destroy session. final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() { @@ -1053,8 +1230,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } mRelinquished = true; - mPm.installStage(mPackageName, stageDir, localObserver, params, - mInstallerPackageName, mInstallerUid, user, mSigningDetails); + final PackageManagerService.ActiveInstallSession activeInstallSession = + new PackageManagerService.ActiveInstallSession(mPackageName, stageDir, + localObserver, params, mInstallerPackageName, mInstallerUid, user, + mSigningDetails); + return activeInstallSession; } private static void maybeRenameFile(File from, File to) throws PackageManagerException { @@ -1556,6 +1736,14 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + /** + * Adds a child session ID without any safety / sanity checks. This should only be used to + * build a session from XML or similar. + */ + void addChildSessionIdInternal(int sessionId) { + mChildSessionIds.put(sessionId, 0); + } + public void open() throws IOException { if (mActiveCount.getAndIncrement() == 0) { mCallback.onSessionActiveChanged(this, true); @@ -1567,6 +1755,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (!mPrepared) { if (stageDir != null) { prepareStageDir(stageDir); + } else if (params.isMultiPackage) { + // it's all ok } else { throw new IllegalArgumentException("stageDir must be set"); } @@ -1615,6 +1805,81 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null); } + @Override + public boolean isMultiPackage() { + return params.isMultiPackage; + } + + @Override + public int[] getChildSessionIds() { + final int[] childSessionIds = mChildSessionIds.copyKeys(); + if (childSessionIds != null) { + return childSessionIds; + } + return EMPTY_CHILD_SESSION_ARRAY; + } + + @Override + public void addChildSessionId(int sessionId) { + final PackageInstallerSession session = mSessionProvider.getSession(sessionId); + if (session == null) { + throw new RemoteException("Unable to add child.", + new PackageManagerException("Child session " + sessionId + " does not exist"), + false, true).rethrowAsRuntimeException(); + } + synchronized (mLock) { + final int indexOfSession = mChildSessionIds.indexOfKey(sessionId); + if (indexOfSession >= 0) { + return; + } + session.setParentSessionId(this.sessionId); + addChildSessionIdInternal(sessionId); + } + } + + @Override + public void removeChildSessionId(int sessionId) { + final PackageInstallerSession session = mSessionProvider.getSession(sessionId); + synchronized (mLock) { + final int indexOfSession = mChildSessionIds.indexOfKey(sessionId); + if (session != null) { + session.setParentSessionId(SessionInfo.INVALID_ID); + } + if (indexOfSession < 0) { + // not added in the first place; no-op + return; + } + mChildSessionIds.removeAt(indexOfSession); + } + } + + /** + * Sets the parent session ID if not already set. + * If {@link SessionInfo#INVALID_ID} is passed, it will be unset. + */ + void setParentSessionId(int parentSessionId) { + synchronized (mLock) { + if (parentSessionId != SessionInfo.INVALID_ID + && mParentSessionId != SessionInfo.INVALID_ID) { + throw new RemoteException("Unable to set parent session.", + new PackageManagerException( + "The parent of " + sessionId + " is" + " already set to " + + mParentSessionId), false, + true).rethrowAsRuntimeException(); + } + this.mParentSessionId = parentSessionId; + } + } + + boolean hasParentSessionId() { + return mParentSessionId != SessionInfo.INVALID_ID; + } + + @Override + public int getParentSessionId() { + return mParentSessionId; + } + private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) { final IPackageInstallObserver2 observer; final String packageName; @@ -1704,6 +1969,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { pw.printPair("mBridges", mBridges.size()); pw.printPair("mFinalStatus", mFinalStatus); pw.printPair("mFinalMessage", mFinalMessage); + pw.printPair("params.isMultiPackage", params.isMultiPackage); pw.println(); pw.decreaseIndent(); @@ -1754,6 +2020,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { writeBooleanAttribute(out, ATTR_PREPARED, isPrepared()); writeBooleanAttribute(out, ATTR_SEALED, isSealed()); + writeBooleanAttribute(out, ATTR_MULTI_PACKAGE, params.isMultiPackage); + // TODO(patb,109941548): avoid writing to xml and instead infer / validate this after + // we've read all sessions. + writeIntAttribute(out, ATTR_PARENT_SESSION_ID, mParentSessionId); writeIntAttribute(out, ATTR_MODE, params.mode); writeIntAttribute(out, ATTR_INSTALL_FLAGS, params.installFlags); writeIntAttribute(out, ATTR_INSTALL_LOCATION, params.installLocation); @@ -1788,6 +2058,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { params.appIconLastModified = appIconFile.lastModified(); } + final int[] childSessionIds = getChildSessionIds(); + for (int childSessionId : childSessionIds) { + out.startTag(null, TAG_CHILD_SESSION); + writeIntAttribute(out, ATTR_SESSION_ID, childSessionId); + out.endTag(null, TAG_CHILD_SESSION); + } } out.endTag(null, TAG_SESSION); @@ -1832,11 +2108,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { * @param installerThread Thread to be used for callbacks of this session * @param sessionsDir The directory the sessions are stored in * + * @param sessionProvider * @return The newly created session */ + // TODO(patb,109941548): modify readFromXml to consume to the next tag session tag so we + // can have a complete session for the constructor public static PackageInstallerSession readFromXml(@NonNull XmlPullParser in, @NonNull PackageInstallerService.InternalCallback callback, @NonNull Context context, - @NonNull PackageManagerService pm, Looper installerThread, @NonNull File sessionsDir) + @NonNull PackageManagerService pm, Looper installerThread, @NonNull File sessionsDir, + @NonNull PackageSessionProvider sessionProvider) throws IOException, XmlPullParserException { final int sessionId = readIntAttribute(in, ATTR_SESSION_ID); final int userId = readIntAttribute(in, ATTR_USER_ID); @@ -1849,9 +2129,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final String stageCid = readStringAttribute(in, ATTR_SESSION_STAGE_CID); final boolean prepared = readBooleanAttribute(in, ATTR_PREPARED, true); final boolean sealed = readBooleanAttribute(in, ATTR_SEALED); + final int parentSessionId = readIntAttribute(in, ATTR_PARENT_SESSION_ID, + SessionInfo.INVALID_ID); final SessionParams params = new SessionParams( SessionParams.MODE_INVALID); + params.isMultiPackage = readBooleanAttribute(in, ATTR_MULTI_PACKAGE, false); params.mode = readIntAttribute(in, ATTR_MODE); params.installFlags = readIntAttribute(in, ATTR_INSTALL_FLAGS); params.installLocation = readIntAttribute(in, ATTR_INSTALL_LOCATION); @@ -1874,9 +2157,16 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { params.appIcon = BitmapFactory.decodeFile(appIconFile.getAbsolutePath()); params.appIconLastModified = appIconFile.lastModified(); } - - return new PackageInstallerSession(callback, context, pm, + return new PackageInstallerSession(callback, context, pm, sessionProvider, installerThread, sessionId, userId, installerPackageName, installerUid, - params, createdMillis, stageDir, stageCid, prepared, sealed); + params, createdMillis, stageDir, stageCid, prepared, sealed, + EMPTY_CHILD_SESSION_ARRAY, parentSessionId); + } + + /** + * Reads the session ID from a child session tag stored in the provided {@link XmlPullParser} + */ + static int readChildSessionIdFromXml(@NonNull XmlPullParser in) { + return readIntAttribute(in, ATTR_SESSION_ID, SessionInfo.INVALID_ID); } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 0e37bcafe5d0..c90f083c1c7e 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -60,6 +60,7 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_TEST_ONLY; import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE; import static android.content.pm.PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE; import static android.content.pm.PackageManager.INSTALL_INTERNAL; +import static android.content.pm.PackageManager.INSTALL_SUCCEEDED; import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS; import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK; import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK; @@ -12358,28 +12359,15 @@ public class PackageManagerService extends IPackageManager.Stub return installReason; } - void installStage(String packageName, File stagedDir, - IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams, - String installerPackageName, int installerUid, UserHandle user, - PackageParser.SigningDetails signingDetails) { + void installStage(ActiveInstallSession activeInstallSession) { if (DEBUG_INSTANT) { - if ((sessionParams.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) { - Slog.d(TAG, "Ephemeral install of " + packageName); + if ((activeInstallSession.getSessionParams().installFlags + & PackageManager.INSTALL_INSTANT_APP) != 0) { + Slog.d(TAG, "Ephemeral install of " + activeInstallSession.getPackageName()); } } - final VerificationInfo verificationInfo = new VerificationInfo( - sessionParams.originatingUri, sessionParams.referrerUri, - sessionParams.originatingUid, installerUid); - - final OriginInfo origin = OriginInfo.fromStagedFile(stagedDir); - final Message msg = mHandler.obtainMessage(INIT_COPY); - final int installReason = fixUpInstallReason(installerPackageName, installerUid, - sessionParams.installReason); - final InstallParams params = new InstallParams(origin, null, observer, - sessionParams.installFlags, installerPackageName, sessionParams.volumeUuid, - verificationInfo, user, sessionParams.abiOverride, - sessionParams.grantedRuntimePermissions, signingDetails, installReason); + final InstallParams params = new InstallParams(activeInstallSession); params.setTraceMethod("installStage").setTraceCookie(System.identityHashCode(params)); msg.obj = params; @@ -12391,6 +12379,22 @@ public class PackageManagerService extends IPackageManager.Stub mHandler.sendMessage(msg); } + void installStage(List<ActiveInstallSession> children) + throws PackageManagerException { + final Message msg = mHandler.obtainMessage(INIT_COPY); + final MultiPackageInstallParams params = + new MultiPackageInstallParams(UserHandle.ALL, children); + params.setTraceMethod("installStageMultiPackage") + .setTraceCookie(System.identityHashCode(params)); + msg.obj = params; + + Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "installStageMultiPackage", + System.identityHashCode(msg.obj)); + Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "queueInstall", + System.identityHashCode(msg.obj)); + mHandler.sendMessage(msg); + } + private void sendPackageAddedForUser(String packageName, PackageSetting pkgSetting, int userId) { final boolean isSystem = isSystemApp(pkgSetting) || isUpdatedSystemApp(pkgSetting); @@ -13568,86 +13572,111 @@ public class PackageManagerService extends IPackageManager.Stub } private void processPendingInstall(final InstallArgs args, final int currentStatus) { - // Queue up an async operation since the package installation may take a little while. - mHandler.post(new Runnable() { - public void run() { - mHandler.removeCallbacks(this); - // Result object to be returned - PackageInstalledInfo res = new PackageInstalledInfo(); - res.setReturnCode(currentStatus); - res.uid = -1; - res.pkg = null; - res.removedInfo = null; - if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) { - args.doPreInstall(res.returnCode); - synchronized (mInstallLock) { - installPackageTracedLI(args, res); - } - args.doPostInstall(res.returnCode, res.uid); - } - - // A restore should be performed at this point if (a) the install - // succeeded, (b) the operation is not an update, and (c) the new - // package has not opted out of backup participation. - final boolean update = res.removedInfo != null - && res.removedInfo.removedPackage != null; - final int flags = (res.pkg == null) ? 0 : res.pkg.applicationInfo.flags; - boolean doRestore = !update - && ((flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0); - - // Set up the post-install work request bookkeeping. This will be used - // and cleaned up by the post-install event handling regardless of whether - // there's a restore pass performed. Token values are >= 1. - int token; - if (mNextInstallToken < 0) mNextInstallToken = 1; - token = mNextInstallToken++; - - PostInstallData data = new PostInstallData(args, res); - mRunningInstalls.put(token, data); - if (DEBUG_INSTALL) Log.v(TAG, "+ starting restore round-trip " + token); - - if (res.returnCode == PackageManager.INSTALL_SUCCEEDED && doRestore) { - // Pass responsibility to the Backup Manager. It will perform a - // restore if appropriate, then pass responsibility back to the - // Package Manager to run the post-install observer callbacks - // and broadcasts. - IBackupManager bm = IBackupManager.Stub.asInterface( - ServiceManager.getService(Context.BACKUP_SERVICE)); - if (bm != null) { - if (DEBUG_INSTALL) Log.v(TAG, "token " + token - + " to BM for possible restore"); - Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "restore", token); - try { - // TODO: http://b/22388012 - if (bm.isBackupServiceActive(UserHandle.USER_SYSTEM)) { - bm.restoreAtInstall(res.pkg.applicationInfo.packageName, token); - } else { - doRestore = false; - } - } catch (RemoteException e) { - // can't happen; the backup manager is local - } catch (Exception e) { - Slog.e(TAG, "Exception trying to enqueue restore", e); - doRestore = false; - } + if (args.mMultiPackageInstallParams != null) { + args.mMultiPackageInstallParams.tryProcessInstallRequest(args, currentStatus); + } else { + PackageInstalledInfo res = createPackageInstalledInfo(currentStatus); + processInstallRequestsAsync( + res.returnCode == PackageManager.INSTALL_SUCCEEDED, + Collections.singletonList(new InstallRequest(args, res))); + } + } + + // Queue up an async operation since the package installation may take a little while. + private void processInstallRequestsAsync(boolean success, + List<InstallRequest> installRequests) { + mHandler.post(() -> { + if (success) { + for (InstallRequest request : installRequests) { + request.args.doPreInstall(request.installResult.returnCode); + } + synchronized (mInstallLock) { + installPackagesTracedLI(installRequests); + } + for (InstallRequest request : installRequests) { + request.args.doPostInstall( + request.installResult.returnCode, request.installResult.uid); + } + } + for (InstallRequest request : installRequests) { + resolvePackageInstalledInfo(request.args, + request.installResult); + } + }); + } + + private PackageInstalledInfo createPackageInstalledInfo( + int currentStatus) { + PackageInstalledInfo res = new PackageInstalledInfo(); + res.setReturnCode(currentStatus); + res.uid = -1; + res.pkg = null; + res.removedInfo = null; + return res; + } + + private void resolvePackageInstalledInfo(InstallArgs args, PackageInstalledInfo res) { + // A restore should be performed at this point if (a) the install + // succeeded, (b) the operation is not an update, and (c) the new + // package has not opted out of backup participation. + final boolean update = res.removedInfo != null + && res.removedInfo.removedPackage != null; + final int flags = (res.pkg == null) ? 0 : res.pkg.applicationInfo.flags; + boolean doRestore = !update + && ((flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0); + + // Set up the post-install work request bookkeeping. This will be used + // and cleaned up by the post-install event handling regardless of whether + // there's a restore pass performed. Token values are >= 1. + int token; + if (mNextInstallToken < 0) mNextInstallToken = 1; + token = mNextInstallToken++; + + PostInstallData data = new PostInstallData(args, res); + mRunningInstalls.put(token, data); + if (DEBUG_INSTALL) Log.v(TAG, "+ starting restore round-trip " + token); + + if (res.returnCode == PackageManager.INSTALL_SUCCEEDED && doRestore) { + // Pass responsibility to the Backup Manager. It will perform a + // restore if appropriate, then pass responsibility back to the + // Package Manager to run the post-install observer callbacks + // and broadcasts. + IBackupManager bm = IBackupManager.Stub.asInterface( + ServiceManager.getService(Context.BACKUP_SERVICE)); + if (bm != null) { + if (DEBUG_INSTALL) { + Log.v(TAG, "token " + token + " to BM for possible restore"); + } + Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "restore", token); + try { + // TODO: http://b/22388012 + if (bm.isBackupServiceActive(UserHandle.USER_SYSTEM)) { + bm.restoreAtInstall(res.pkg.applicationInfo.packageName, token); } else { - Slog.e(TAG, "Backup Manager not found!"); doRestore = false; } + } catch (RemoteException e) { + // can't happen; the backup manager is local + } catch (Exception e) { + Slog.e(TAG, "Exception trying to enqueue restore", e); + doRestore = false; } + } else { + Slog.e(TAG, "Backup Manager not found!"); + doRestore = false; + } + } - if (!doRestore) { - // No restore possible, or the Backup Manager was mysteriously not - // available -- just fire the post-install work request directly. - if (DEBUG_INSTALL) Log.v(TAG, "No restore - queue post-install for " + token); + if (!doRestore) { + // No restore possible, or the Backup Manager was mysteriously not + // available -- just fire the post-install work request directly. + if (DEBUG_INSTALL) Log.v(TAG, "No restore - queue post-install for " + token); - Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "postInstall", token); + Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "postInstall", token); - Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0); - mHandler.sendMessage(msg); - } - } - }); + Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0); + mHandler.sendMessage(msg); + } } /** @@ -13837,7 +13866,83 @@ public class PackageManagerService extends IPackageManager.Stub } } + /** + * Container for a multi-package install which refers to all install sessions and args being + * committed together. + */ + class MultiPackageInstallParams extends HandlerParams { + + private int mRet = INSTALL_SUCCEEDED; + @NonNull + private final ArrayList<InstallParams> mChildParams; + @NonNull + private final Map<InstallArgs, Integer> mVerifiedState; + + MultiPackageInstallParams( + @NonNull UserHandle user, + @NonNull List<ActiveInstallSession> activeInstallSessions) + throws PackageManagerException { + super(user); + if (activeInstallSessions.size() == 0) { + throw new PackageManagerException("No child sessions found!"); + } + mChildParams = new ArrayList<>(activeInstallSessions.size()); + for (int i = 0; i < activeInstallSessions.size(); i++) { + final InstallParams childParams = new InstallParams(activeInstallSessions.get(i)); + childParams.mParentInstallParams = this; + this.mChildParams.add(childParams); + } + this.mVerifiedState = new ArrayMap<>(mChildParams.size()); + } + + @Override + void handleStartCopy() { + for (InstallParams params : mChildParams) { + params.handleStartCopy(); + if (params.mRet != INSTALL_SUCCEEDED) { + mRet = params.mRet; + break; + } + } + } + + @Override + void handleReturnCode() { + for (InstallParams params : mChildParams) { + params.handleReturnCode(); + if (params.mRet != INSTALL_SUCCEEDED) { + mRet = params.mRet; + break; + } + } + } + + void tryProcessInstallRequest(InstallArgs args, int currentStatus) { + mVerifiedState.put(args, currentStatus); + boolean success = true; + if (mVerifiedState.size() != mChildParams.size()) { + return; + } + for (Integer status : mVerifiedState.values()) { + if (status == PackageManager.INSTALL_UNKNOWN) { + return; + } else if (status != PackageManager.INSTALL_SUCCEEDED) { + success = false; + break; + } + } + final List<InstallRequest> installRequests = new ArrayList<>(mVerifiedState.size()); + for (Map.Entry<InstallArgs, Integer> entry : mVerifiedState.entrySet()) { + installRequests.add(new InstallRequest(entry.getKey(), + createPackageInstalledInfo(entry.getValue()))); + } + processInstallRequestsAsync(success, installRequests); + } + } + class InstallParams extends HandlerParams { + // TODO: see if we can collapse this into ActiveInstallSession + final OriginInfo origin; final MoveInfo move; final IPackageInstallObserver2 observer; @@ -13845,17 +13950,20 @@ public class PackageManagerService extends IPackageManager.Stub final String installerPackageName; final String volumeUuid; private InstallArgs mArgs; - private int mRet; + int mRet; final String packageAbiOverride; final String[] grantedRuntimePermissions; final VerificationInfo verificationInfo; final PackageParser.SigningDetails signingDetails; final int installReason; + @Nullable + MultiPackageInstallParams mParentInstallParams; InstallParams(OriginInfo origin, MoveInfo move, IPackageInstallObserver2 observer, int installFlags, String installerPackageName, String volumeUuid, VerificationInfo verificationInfo, UserHandle user, String packageAbiOverride, - String[] grantedPermissions, PackageParser.SigningDetails signingDetails, int installReason) { + String[] grantedPermissions, PackageParser.SigningDetails signingDetails, + int installReason) { super(user); this.origin = origin; this.move = move; @@ -13870,6 +13978,34 @@ public class PackageManagerService extends IPackageManager.Stub this.installReason = installReason; } + InstallParams(ActiveInstallSession activeInstallSession) { + super(activeInstallSession.getUser()); + if (DEBUG_INSTANT) { + if ((activeInstallSession.getSessionParams().installFlags + & PackageManager.INSTALL_INSTANT_APP) != 0) { + Slog.d(TAG, "Ephemeral install of " + activeInstallSession.getPackageName()); + } + } + verificationInfo = new VerificationInfo( + activeInstallSession.getSessionParams().originatingUri, + activeInstallSession.getSessionParams().referrerUri, + activeInstallSession.getSessionParams().originatingUid, + activeInstallSession.getInstallerUid()); + origin = OriginInfo.fromStagedFile(activeInstallSession.getStagedDir()); + move = null; + installReason = fixUpInstallReason(activeInstallSession.getInstallerPackageName(), + activeInstallSession.getInstallerUid(), + activeInstallSession.getSessionParams().installReason); + observer = activeInstallSession.getObserver(); + installFlags = activeInstallSession.getSessionParams().installFlags; + installerPackageName = activeInstallSession.getInstallerPackageName(); + volumeUuid = activeInstallSession.getSessionParams().volumeUuid; + packageAbiOverride = activeInstallSession.getSessionParams().abiOverride; + grantedRuntimePermissions = + activeInstallSession.getSessionParams().grantedRuntimePermissions; + signingDetails = activeInstallSession.getSigningDetails(); + } + @Override public String toString() { return "InstallParams{" + Integer.toHexString(System.identityHashCode(this)) @@ -14291,6 +14427,7 @@ public class PackageManagerService extends IPackageManager.Stub final int traceCookie; final PackageParser.SigningDetails signingDetails; final int installReason; + @Nullable final MultiPackageInstallParams mMultiPackageInstallParams; // The list of instruction sets supported by this app. This is currently // only used during the rmdex() phase to clean up resources. We can get rid of this @@ -14301,8 +14438,9 @@ public class PackageManagerService extends IPackageManager.Stub int installFlags, String installerPackageName, String volumeUuid, UserHandle user, String[] instructionSets, String abiOverride, String[] installGrantPermissions, - String traceMethod, int traceCookie, PackageParser.SigningDetails signingDetails, - int installReason) { + String traceMethod, int traceCookie, SigningDetails signingDetails, + int installReason, + MultiPackageInstallParams multiPackageInstallParams) { this.origin = origin; this.move = move; this.installFlags = installFlags; @@ -14317,6 +14455,7 @@ public class PackageManagerService extends IPackageManager.Stub this.traceCookie = traceCookie; this.signingDetails = signingDetails; this.installReason = installReason; + this.mMultiPackageInstallParams = multiPackageInstallParams; } abstract int copyApk(); @@ -14412,7 +14551,7 @@ public class PackageManagerService extends IPackageManager.Stub params.getUser(), null /*instructionSets*/, params.packageAbiOverride, params.grantedRuntimePermissions, params.traceMethod, params.traceCookie, params.signingDetails, - params.installReason); + params.installReason, params.mParentInstallParams); if (isFwdLocked()) { throw new IllegalArgumentException("Forward locking only supported in ASEC"); } @@ -14422,7 +14561,7 @@ public class PackageManagerService extends IPackageManager.Stub FileInstallArgs(String codePath, String resourcePath, String[] instructionSets) { super(OriginInfo.fromNothing(), null, null, 0, null, null, null, instructionSets, null, null, null, 0, PackageParser.SigningDetails.UNKNOWN, - PackageManager.INSTALL_REASON_UNKNOWN); + PackageManager.INSTALL_REASON_UNKNOWN, null /* parent */); this.codeFile = (codePath != null) ? new File(codePath) : null; this.resourceFile = (resourcePath != null) ? new File(resourcePath) : null; } @@ -14614,7 +14753,7 @@ public class PackageManagerService extends IPackageManager.Stub params.getUser(), null /* instruction sets */, params.packageAbiOverride, params.grantedRuntimePermissions, params.traceMethod, params.traceCookie, params.signingDetails, - params.installReason); + params.installReason, params.mParentInstallParams); } int copyApk() { @@ -15019,10 +15158,10 @@ public class PackageManagerService extends IPackageManager.Stub } @GuardedBy({"mInstallLock", "mPackages"}) - private void installPackageTracedLI(InstallArgs args, PackageInstalledInfo installResult) { + private void installPackagesTracedLI(List<InstallRequest> requests) { try { - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackage"); - installPackagesLI(Collections.singletonList(new InstallRequest(args, installResult))); + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackages"); + installPackagesLI(requests); } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } @@ -15362,7 +15501,7 @@ public class PackageManagerService extends IPackageManager.Stub request.installResult.setError( PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE, "Duplicate package " + result.pkgSetting.pkg.packageName - + " in atomic install request."); + + " in multi-package install request."); return; } } @@ -23434,6 +23573,62 @@ public class PackageManagerService extends IPackageManager.Stub return mProtectedPackages.isPackageStateProtected(userId, packageName); } + + static class ActiveInstallSession { + private final String mPackageName; + private final File mStagedDir; + private final IPackageInstallObserver2 mObserver; + private final PackageInstaller.SessionParams mSessionParams; + private final String mInstallerPackageName; + private final int mInstallerUid; + private final UserHandle mUser; + private final SigningDetails mSigningDetails; + + ActiveInstallSession(String packageName, File stagedDir, IPackageInstallObserver2 observer, + PackageInstaller.SessionParams sessionParams, String installerPackageName, + int installerUid, UserHandle user, SigningDetails signingDetails) { + mPackageName = packageName; + mStagedDir = stagedDir; + mObserver = observer; + mSessionParams = sessionParams; + mInstallerPackageName = installerPackageName; + mInstallerUid = installerUid; + mUser = user; + mSigningDetails = signingDetails; + } + + public String getPackageName() { + return mPackageName; + } + + public File getStagedDir() { + return mStagedDir; + } + + public IPackageInstallObserver2 getObserver() { + return mObserver; + } + + public PackageInstaller.SessionParams getSessionParams() { + return mSessionParams; + } + + public String getInstallerPackageName() { + return mInstallerPackageName; + } + + public int getInstallerUid() { + return mInstallerUid; + } + + public UserHandle getUser() { + return mUser; + } + + public SigningDetails getSigningDetails() { + return mSigningDetails; + } + } } interface PackageSender { diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 38bd1722e61f..31711e524445 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -173,6 +173,8 @@ class PackageManagerShellCommand extends ShellCommand { return runSetInstallLocation(); case "get-install-location": return runGetInstallLocation(); + case "install-add-session": + return runInstallAddSession(); case "move-package": return runMovePackage(); case "move-primary-storage": @@ -983,6 +985,23 @@ class PackageManagerShellCommand extends ShellCommand { return doWriteSplit(sessionId, path, sizeBytes, splitName, true /*logSuccess*/); } + private int runInstallAddSession() throws RemoteException { + final PrintWriter pw = getOutPrintWriter(); + final int parentSessionId = Integer.parseInt(getNextArg()); + + List<Integer> otherSessionIds = new ArrayList<>(); + String opt; + while ((opt = getNextArg()) != null) { + otherSessionIds.add(Integer.parseInt(opt)); + } + if (otherSessionIds.size() == 0) { + pw.println("Error: At least two sessions are required."); + return 1; + } + return doInstallAddSession(parentSessionId, ArrayUtils.convertToIntArray(otherSessionIds), + true /*logSuccess*/); + } + private int runInstallRemove() throws RemoteException { final PrintWriter pw = getOutPrintWriter(); @@ -2268,6 +2287,9 @@ class PackageManagerShellCommand extends ShellCommand { case "--apex": sessionParams.installFlags |= PackageManager.INSTALL_APEX; break; + case "--multi-package": + sessionParams.setMultiPackage(); + break; default: throw new IllegalArgumentException("Unknown option " + opt); } @@ -2500,6 +2522,30 @@ class PackageManagerShellCommand extends ShellCommand { } } + private int doInstallAddSession(int parentId, int[] sessionIds, boolean logSuccess) + throws RemoteException { + final PrintWriter pw = getOutPrintWriter(); + PackageInstaller.Session session = null; + try { + session = new PackageInstaller.Session( + mInterface.getPackageInstaller().openSession(parentId)); + if (!session.isMultiPackage()) { + getErrPrintWriter().println( + "Error: parent session ID is not a multi-package session"); + return 1; + } + for (int i = 0; i < sessionIds.length; i++) { + session.addChildSessionId(sessionIds[i]); + } + if (logSuccess) { + pw.println("Success"); + } + return 0; + } finally { + IoUtils.closeQuietly(session); + } + } + private int doRemoveSplit(int sessionId, String splitName, boolean logSuccess) throws RemoteException { final PrintWriter pw = getOutPrintWriter(); @@ -2521,24 +2567,26 @@ class PackageManagerShellCommand extends ShellCommand { } } - private int doCommitSession(int sessionId, boolean logSuccess) throws RemoteException { + private int doCommitSession(int sessionId, boolean logSuccess) + throws RemoteException { + final PrintWriter pw = getOutPrintWriter(); PackageInstaller.Session session = null; try { session = new PackageInstaller.Session( mInterface.getPackageInstaller().openSession(sessionId)); - - // Sanity check that all .dm files match an apk. - // (The installer does not support standalone .dm files and will not process them.) - try { - DexMetadataHelper.validateDexPaths(session.getNames()); - } catch (IllegalStateException | IOException e) { - pw.println("Warning [Could not validate the dex paths: " + e.getMessage() + "]"); + if (!session.isMultiPackage()) { + // Sanity check that all .dm files match an apk. + // (The installer does not support standalone .dm files and will not process them.) + try { + DexMetadataHelper.validateDexPaths(session.getNames()); + } catch (IllegalStateException | IOException e) { + pw.println( + "Warning [Could not validate the dex paths: " + e.getMessage() + "]"); + } } - final LocalIntentReceiver receiver = new LocalIntentReceiver(); session.commit(receiver.getIntentSender()); - final Intent result = receiver.getResult(); final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE); @@ -2809,6 +2857,7 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" [--referrer URI] [--abi ABI_NAME] [--force-sdk]"); pw.println(" [--preload] [--instantapp] [--full] [--dont-kill]"); pw.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]"); + pw.println(" [--multi-package]"); pw.println(" Like \"install\", but starts an install session. Use \"install-write\""); pw.println(" to push data into the session, and \"install-commit\" to finish."); pw.println(""); @@ -2817,6 +2866,9 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" will be read from stdin. Options are:"); pw.println(" -S: size in bytes of package, required for stdin"); pw.println(""); + pw.println(" install-add-session MULTI_PACKAGE_SESSION_ID CHILD_SESSION_IDs"); + pw.println(" Add one or more session IDs to a multi-package session."); + pw.println(""); pw.println(" install-commit SESSION_ID"); pw.println(" Commit the given active install session, installing the app."); pw.println(""); diff --git a/services/core/java/com/android/server/pm/PackageSessionProvider.java b/services/core/java/com/android/server/pm/PackageSessionProvider.java new file mode 100644 index 000000000000..af11e77a510b --- /dev/null +++ b/services/core/java/com/android/server/pm/PackageSessionProvider.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2018 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.server.pm; + +/** Provides access to individual sessions managed by the install service */ +public interface PackageSessionProvider { + + /** + * Get the sessions for the provided session IDs. Null will be returned for session IDs that + * do not exist. + */ + PackageInstallerSession getSession(int sessionId); + +} |