/* * Copyright (C) 2014 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 android.content.pm; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_DEFAULT; import static android.app.AppOpsManager.MODE_IGNORED; import static android.content.pm.Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256; import static android.content.pm.Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512; import static android.content.pm.Checksum.TYPE_WHOLE_MD5; import static android.content.pm.Checksum.TYPE_WHOLE_MERKLE_ROOT_4K_SHA256; import static android.content.pm.Checksum.TYPE_WHOLE_SHA1; import static android.content.pm.Checksum.TYPE_WHOLE_SHA256; import static android.content.pm.Checksum.TYPE_WHOLE_SHA512; import static android.content.pm.PackageInfo.INSTALL_LOCATION_AUTO; import static android.content.pm.PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY; import static android.content.pm.PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL; import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.CurrentTimeMillisLong; import android.annotation.DurationMillisLong; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; 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.app.PendingIntent; import android.compat.annotation.UnsupportedAppUsage; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; import android.content.pm.PackageManager.DeleteFlags; import android.content.pm.PackageManager.InstallReason; import android.content.pm.PackageManager.InstallScenario; import android.content.pm.parsing.ApkLiteParseUtils; import android.content.pm.parsing.PackageLite; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.content.pm.verify.domain.DomainSet; import android.graphics.Bitmap; import android.icu.util.ULocale; import android.net.Uri; import android.os.Build; import android.os.FileBridge; import android.os.Handler; import android.os.HandlerExecutor; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; import android.os.ParcelableException; import android.os.PersistableBundle; import android.os.RemoteCallback; import android.os.RemoteException; import android.os.SystemProperties; import android.os.UserHandle; import android.system.ErrnoException; import android.system.Os; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.ExceptionUtils; import com.android.internal.content.InstallLocationUtils; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DataClass; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.internal.util.function.pooled.PooledLambda; import java.io.Closeable; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.security.MessageDigest; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; import java.util.function.Consumer; /** * Offers the ability to install, upgrade, and remove applications on the * device. This includes support for apps packaged either as a single * "monolithic" APK, or apps packaged as multiple "split" APKs. *

* An app is delivered for installation through a * {@link PackageInstaller.Session}, which any app can create. Once the session * is created, the installer can stream one or more APKs into place until it * decides to either commit or destroy the session. Committing may require user * intervention to complete the installation, unless the caller falls into one of the * following categories, in which case the installation will complete automatically. *

*

* Sessions can install brand new apps, upgrade existing apps, or add new splits * into an existing app. *

* Apps packaged as multiple split APKs always consist of a single "base" APK * (with a {@code null} split name) and zero or more "split" APKs (with unique * split names). Any subset of these APKs can be installed together, as long as * the following constraints are met: *

*

* The ApiDemos project contains examples of using this API: * ApiDemos/src/com/example/android/apis/content/InstallApk*.java. */ public class PackageInstaller { private static final String TAG = "PackageInstaller"; private static final String ACTION_WAIT_INSTALL_CONSTRAINTS = "android.content.pm.action.WAIT_INSTALL_CONSTRAINTS"; /** {@hide} */ public static final boolean ENABLE_REVOCABLE_FD = SystemProperties.getBoolean("fw.revocable_fd", false); /** * Activity Action: Show details about a particular install session. This * may surface actions such as pause, resume, or cancel. *

* This should always be scoped to the installer package that owns the * session. Clients should use {@link SessionInfo#createDetailsIntent()} to * build this intent correctly. *

* In some cases, a matching Activity may not exist, so ensure you safeguard * against this. *

* The session to show details for is defined in {@link #EXTRA_SESSION_ID}. */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_SESSION_DETAILS = "android.content.pm.action.SESSION_DETAILS"; /** * Broadcast Action: Explicit broadcast sent to the last known default launcher when a session * for a new install is committed. For managed profile, this is sent to the default launcher * of the primary profile. * For user-profiles that have items restricted on home screen, this broadcast is sent to * the default launcher of the primary profile, only if it has either * {@link Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL} or * {@link Manifest.permission.ACCESS_HIDDEN_PROFILES} permission. *

* The associated session is defined in {@link #EXTRA_SESSION} and the user for which this * session was created in {@link Intent#EXTRA_USER}. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_SESSION_COMMITTED = "android.content.pm.action.SESSION_COMMITTED"; /** * Broadcast Action: Send information about a staged install session when its state is updated. *

* The associated session information is defined in {@link #EXTRA_SESSION}. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_SESSION_UPDATED = "android.content.pm.action.SESSION_UPDATED"; /** * Intent action to indicate that user action is required for current install. This action can * be used only by system apps. * * @hide */ @SystemApi public static final String ACTION_CONFIRM_INSTALL = "android.content.pm.action.CONFIRM_INSTALL"; /** * Activity Action: Intent sent to the installer when a session for requesting * user pre-approval, and user needs to confirm the installation. * * @hide */ @SystemApi public static final String ACTION_CONFIRM_PRE_APPROVAL = "android.content.pm.action.CONFIRM_PRE_APPROVAL"; /** * An integer session ID that an operation is working with. * * @see Intent#getIntExtra(String, int) */ public static final String EXTRA_SESSION_ID = "android.content.pm.extra.SESSION_ID"; /** * {@link SessionInfo} that an operation is working with. * * @see Intent#getParcelableExtra(String) */ public static final String EXTRA_SESSION = "android.content.pm.extra.SESSION"; /** * Package name that an operation is working with. * * @see Intent#getStringExtra(String) */ public static final String EXTRA_PACKAGE_NAME = "android.content.pm.extra.PACKAGE_NAME"; /** * Current status of an operation. Will be one of * {@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}, * {@link #STATUS_FAILURE_STORAGE}, or {@link #STATUS_FAILURE_TIMEOUT}. *

* More information about a status may be available through additional * extras; see the individual status documentation for details. * * @see Intent#getIntExtra(String, int) */ public static final String EXTRA_STATUS = "android.content.pm.extra.STATUS"; /** * Indicate if the status is for a pre-approval request. * * If callers use the same {@link IntentSender} for both * {@link Session#requestUserPreapproval(PreapprovalDetails, IntentSender)} and * {@link Session#commit(IntentSender)}, they can use this to differentiate between them. * * @see Intent#getBooleanExtra(String, boolean) */ public static final String EXTRA_PRE_APPROVAL = "android.content.pm.extra.PRE_APPROVAL"; /** * Detailed string representation of the status, including raw details that * are useful for debugging. * * @see Intent#getStringExtra(String) */ public static final String EXTRA_STATUS_MESSAGE = "android.content.pm.extra.STATUS_MESSAGE"; /** * Another package name relevant to a status. This is typically the package * responsible for causing an operation failure. * * @see Intent#getStringExtra(String) */ public static final String EXTRA_OTHER_PACKAGE_NAME = "android.content.pm.extra.OTHER_PACKAGE_NAME"; /** * Storage path relevant to a status. * * @see Intent#getStringExtra(String) */ public static final String EXTRA_STORAGE_PATH = "android.content.pm.extra.STORAGE_PATH"; /** * The {@link InstallConstraints} object. * * @see Intent#getParcelableExtra(String, Class) * @see #waitForInstallConstraints(List, InstallConstraints, IntentSender, long) */ public static final String EXTRA_INSTALL_CONSTRAINTS = "android.content.pm.extra.INSTALL_CONSTRAINTS"; /** * The {@link InstallConstraintsResult} object. * * @see Intent#getParcelableExtra(String, Class) * @see #waitForInstallConstraints(List, InstallConstraints, IntentSender, long) */ public static final String EXTRA_INSTALL_CONSTRAINTS_RESULT = "android.content.pm.extra.INSTALL_CONSTRAINTS_RESULT"; /** {@hide} */ @Deprecated public static final String EXTRA_PACKAGE_NAMES = "android.content.pm.extra.PACKAGE_NAMES"; /** * The status as used internally in the package manager. Refer to {@link PackageManager} for * a list of all valid legacy statuses. * * @hide */ @SystemApi public static final String EXTRA_LEGACY_STATUS = "android.content.pm.extra.LEGACY_STATUS"; /** {@hide} */ public static final String EXTRA_LEGACY_BUNDLE = "android.content.pm.extra.LEGACY_BUNDLE"; /** * The callback to execute once an uninstall is completed (used for both successful and * unsuccessful uninstalls). * * @hide */ @SystemApi public static final String EXTRA_CALLBACK = "android.content.pm.extra.CALLBACK"; /** * Key for passing extra delete flags during archiving. * * @hide */ @SystemApi @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING) public static final String EXTRA_DELETE_FLAGS = "android.content.pm.extra.DELETE_FLAGS"; /** * Type of DataLoader for this session. Will be one of * {@link #DATA_LOADER_TYPE_NONE}, {@link #DATA_LOADER_TYPE_STREAMING}, * {@link #DATA_LOADER_TYPE_INCREMENTAL}. *

* See the individual types documentation for details. * * @see Intent#getIntExtra(String, int) * {@hide} */ @SystemApi public static final String EXTRA_DATA_LOADER_TYPE = "android.content.pm.extra.DATA_LOADER_TYPE"; /** * Path to the validated base APK for this session, which may point at an * APK inside the session (when the session defines the base), or it may * point at the existing base APK (when adding splits to an existing app). * * @hide * @deprecated Resolved base path of an install session should not be available to unauthorized * callers. Use {@link SessionInfo#getResolvedBaseApkPath()} instead. */ @Deprecated @SystemApi public static final String EXTRA_RESOLVED_BASE_PATH = "android.content.pm.extra.RESOLVED_BASE_PATH"; /** * Extra field for the package name of a package that is requested to be unarchived. Sent as * part of the {@link android.content.Intent#ACTION_UNARCHIVE_PACKAGE} intent. */ @FlaggedApi(Flags.FLAG_ARCHIVING) public static final String EXTRA_UNARCHIVE_PACKAGE_NAME = "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME"; /** * Extra field for the unarchive ID. Sent as * part of the {@link android.content.Intent#ACTION_UNARCHIVE_PACKAGE} intent. * * @see SessionParams#setUnarchiveId */ @FlaggedApi(Flags.FLAG_ARCHIVING) public static final String EXTRA_UNARCHIVE_ID = "android.content.pm.extra.UNARCHIVE_ID"; /** * If true, the requestor of the unarchival has specified that the app should be unarchived * for all users. Sent as part of the {@link android.content.Intent#ACTION_UNARCHIVE_PACKAGE} * intent. */ @FlaggedApi(Flags.FLAG_ARCHIVING) public static final String EXTRA_UNARCHIVE_ALL_USERS = "android.content.pm.extra.UNARCHIVE_ALL_USERS"; /** * Current status of an unarchive operation. Will be one of * {@link #UNARCHIVAL_OK}, {@link #UNARCHIVAL_ERROR_USER_ACTION_NEEDED}, * {@link #UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE}, {@link #UNARCHIVAL_ERROR_NO_CONNECTIVITY}, * {@link #UNARCHIVAL_GENERIC_ERROR}, {@link #UNARCHIVAL_ERROR_INSTALLER_DISABLED} or * {@link #UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED}. * *

If the status is not {@link #UNARCHIVAL_OK}, then {@link Intent#EXTRA_INTENT} will be set * with an intent for a corresponding follow-up action (e.g. storage clearing dialog) or a * failure dialog. * *

Used as part of {@link #requestUnarchive} to return the status of the unarchival through * the {@link IntentSender}. * * @see #requestUnarchive */ @FlaggedApi(Flags.FLAG_ARCHIVING) public static final String EXTRA_UNARCHIVE_STATUS = "android.content.pm.extra.UNARCHIVE_STATUS"; /** * A list of warnings that occurred during installation. * * @hide */ public static final String EXTRA_WARNINGS = "android.content.pm.extra.WARNINGS"; /** * Streaming installation pending. * Caller should make sure DataLoader is able to prepare image and reinitiate the operation. * * @see #EXTRA_SESSION_ID * {@hide} */ public static final int STATUS_PENDING_STREAMING = -2; /** * User action is currently required to proceed. You can launch the intent * activity described by {@link Intent#EXTRA_INTENT} to involve the user and * continue. *

* You may choose to immediately launch the intent if the user is actively * using your app. Otherwise, you should use a notification to guide the * user back into your app before launching. * * @see Intent#getParcelableExtra(String) */ public static final int STATUS_PENDING_USER_ACTION = -1; /** * The operation succeeded. */ public static final int STATUS_SUCCESS = 0; /** * The operation failed in a generic way. The system will always try to * provide a more specific failure reason, but in some rare cases this may * be delivered. * * @see #EXTRA_STATUS_MESSAGE */ public static final int STATUS_FAILURE = 1; /** * The operation failed because it was blocked. For example, a device policy * may be blocking the operation, a package verifier may have blocked the * operation, or the app may be required for core system operation. *

* The result may also contain {@link #EXTRA_OTHER_PACKAGE_NAME} with the * specific package blocking the install. * * @see #EXTRA_STATUS_MESSAGE * @see #EXTRA_OTHER_PACKAGE_NAME */ public static final int STATUS_FAILURE_BLOCKED = 2; /** * The operation failed because it was actively aborted. For example, the * user actively declined requested permissions, or the session was * abandoned. * * @see #EXTRA_STATUS_MESSAGE */ public static final int STATUS_FAILURE_ABORTED = 3; /** * The operation failed because one or more of the APKs was invalid. For * example, they might be malformed, corrupt, incorrectly signed, * mismatched, etc. * * @see #EXTRA_STATUS_MESSAGE */ public static final int STATUS_FAILURE_INVALID = 4; /** * The operation failed because it conflicts (or is inconsistent with) with * another package already installed on the device. For example, an existing * permission, incompatible certificates, etc. The user may be able to * uninstall another app to fix the issue. *

* The result may also contain {@link #EXTRA_OTHER_PACKAGE_NAME} with the * specific package identified as the cause of the conflict. * * @see #EXTRA_STATUS_MESSAGE * @see #EXTRA_OTHER_PACKAGE_NAME */ public static final int STATUS_FAILURE_CONFLICT = 5; /** * The operation failed because of storage issues. For example, the device * may be running low on space, or external media may be unavailable. The * user may be able to help free space or insert different external media. *

* The result may also contain {@link #EXTRA_STORAGE_PATH} with the path to * the storage device that caused the failure. * * @see #EXTRA_STATUS_MESSAGE * @see #EXTRA_STORAGE_PATH */ public static final int STATUS_FAILURE_STORAGE = 6; /** * The operation failed because it is fundamentally incompatible with this * device. For example, the app may require a hardware feature that doesn't * exist, it may be missing native code for the ABIs supported by the * device, or it requires a newer SDK version, etc. * * Starting in {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, an app with only 32-bit native * code can still be installed on a device that supports both 64-bit and 32-bit ABIs. * However, a warning dialog will be displayed when the app is launched. * * @see #EXTRA_STATUS_MESSAGE */ 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 * {@hide} */ @SystemApi public static final int DATA_LOADER_TYPE_NONE = DataLoaderType.NONE; /** * Streaming installation using data loader. * * @see #EXTRA_DATA_LOADER_TYPE * {@hide} */ @SystemApi public static final int DATA_LOADER_TYPE_STREAMING = DataLoaderType.STREAMING; /** * Streaming installation using Incremental FileSystem. * * @see #EXTRA_DATA_LOADER_TYPE * {@hide} */ @SystemApi public static final int DATA_LOADER_TYPE_INCREMENTAL = DataLoaderType.INCREMENTAL; /** * Target location for the file in installation session is /data/app/-. * This is the intended location for APKs. * Requires permission to install packages. * {@hide} */ @SystemApi public static final int LOCATION_DATA_APP = InstallationFileLocation.DATA_APP; /** * Target location for the file in installation session is * /data/media//Android/obb/. This is the intended location for OBBs. * {@hide} */ @SystemApi public static final int LOCATION_MEDIA_OBB = InstallationFileLocation.MEDIA_OBB; /** * Target location for the file in installation session is * /data/media//Android/data/. * This is the intended location for application data. * Can only be used by an app itself running under specific user. * {@hide} */ @SystemApi public static final int LOCATION_MEDIA_DATA = InstallationFileLocation.MEDIA_DATA; /** @hide */ @IntDef(prefix = { "LOCATION_" }, value = { LOCATION_DATA_APP, LOCATION_MEDIA_OBB, LOCATION_MEDIA_DATA}) @Retention(RetentionPolicy.SOURCE) public @interface FileLocation{} /** * The installer did not call {@link PackageInstaller.SessionParams#setPackageSource(int)} to * specify the package source. */ public static final int PACKAGE_SOURCE_UNSPECIFIED = 0; /** * Code indicating that the package being installed is from a source not reflected by any * other package source constant. */ public static final int PACKAGE_SOURCE_OTHER = 1; /** * Code indicating that the package being installed is from a store. An app store that * installs an app for the user would use this. */ public static final int PACKAGE_SOURCE_STORE = 2; /** * Code indicating that the package being installed comes from a local file on the device. A * file manager that is facilitating the installation of an APK file would use this. */ public static final int PACKAGE_SOURCE_LOCAL_FILE = 3; /** * Code indicating that the package being installed comes from a file that was downloaded to * the device by the user. For use in place of {@link #PACKAGE_SOURCE_LOCAL_FILE} when the * installer knows the package was downloaded. */ public static final int PACKAGE_SOURCE_DOWNLOADED_FILE = 4; /** @hide */ @IntDef(prefix = { "PACKAGE_SOURCE_" }, value = { PACKAGE_SOURCE_UNSPECIFIED, PACKAGE_SOURCE_STORE, PACKAGE_SOURCE_LOCAL_FILE, PACKAGE_SOURCE_DOWNLOADED_FILE, PACKAGE_SOURCE_OTHER }) @Retention(RetentionPolicy.SOURCE) @interface PackageSourceType{} /** * Indicate the user intervention is required when the installer attempts to commit the session. * This is the default case. * * @hide */ @SystemApi public static final int REASON_CONFIRM_PACKAGE_CHANGE = 0; /** * Indicate the user intervention is required because the update ownership enforcement is * enabled, and the update owner will change. * * @see PackageInstaller.SessionParams#setRequestUpdateOwnership * @see InstallSourceInfo#getUpdateOwnerPackageName * @hide */ @SystemApi public static final int REASON_OWNERSHIP_CHANGED = 1; /** * Indicate the user intervention is required because the update ownership enforcement is * enabled, and remind the update owner is a different package. * * @see PackageInstaller.SessionParams#setRequestUpdateOwnership * @see InstallSourceInfo#getUpdateOwnerPackageName * @hide */ @SystemApi public static final int REASON_REMIND_OWNERSHIP = 2; /** @hide */ @IntDef(prefix = { "REASON_" }, value = { REASON_CONFIRM_PACKAGE_CHANGE, REASON_OWNERSHIP_CHANGED, REASON_REMIND_OWNERSHIP, }) @Retention(RetentionPolicy.SOURCE) public @interface UserActionReason {} /** * The unarchival status is not set. * * @hide */ public static final int UNARCHIVAL_STATUS_UNSET = -1; /** * The unarchival is possible and will commence. * *

Note that this does not mean that the unarchival has completed. This status should be * sent before any longer asynchronous action (e.g. app download) is started. */ @FlaggedApi(Flags.FLAG_ARCHIVING) public static final int UNARCHIVAL_OK = 0; /** * The user needs to interact with the installer to enable the installation. * *

An example use case for this could be that the user needs to login to allow the * download for a paid app. */ @FlaggedApi(Flags.FLAG_ARCHIVING) public static final int UNARCHIVAL_ERROR_USER_ACTION_NEEDED = 1; /** * Not enough storage to unarchive the application. * *

The installer can optionally provide a {@code userActionIntent} for a space-clearing * dialog. If no action is provided, then a generic intent * {@link android.os.storage.StorageManager#ACTION_MANAGE_STORAGE} is started instead. */ @FlaggedApi(Flags.FLAG_ARCHIVING) public static final int UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE = 2; /** * The device is not connected to the internet */ @FlaggedApi(Flags.FLAG_ARCHIVING) public static final int UNARCHIVAL_ERROR_NO_CONNECTIVITY = 3; /** * The installer responsible for the unarchival is disabled. * *

The system will return this status if appropriate. Installers do not need to verify for * this error. */ @FlaggedApi(Flags.FLAG_ARCHIVING) public static final int UNARCHIVAL_ERROR_INSTALLER_DISABLED = 4; /** * The installer responsible for the unarchival has been uninstalled * *

The system will return this status if appropriate. Installers do not need to verify for * this error. */ @FlaggedApi(Flags.FLAG_ARCHIVING) public static final int UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED = 5; /** * Generic error: The app cannot be unarchived. */ @FlaggedApi(Flags.FLAG_ARCHIVING) public static final int UNARCHIVAL_GENERIC_ERROR = 100; /** * The set of error types that can be set for * {@link #reportUnarchivalState}. * * @hide */ @IntDef(value = { UNARCHIVAL_STATUS_UNSET, UNARCHIVAL_OK, UNARCHIVAL_ERROR_USER_ACTION_NEEDED, UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE, UNARCHIVAL_ERROR_NO_CONNECTIVITY, UNARCHIVAL_ERROR_INSTALLER_DISABLED, UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED, UNARCHIVAL_GENERIC_ERROR, }) @Retention(RetentionPolicy.SOURCE) public @interface UnarchivalStatus {} /** Default set of checksums - includes all available checksums. * @see Session#requestChecksums */ private static final int DEFAULT_CHECKSUMS = TYPE_WHOLE_MERKLE_ROOT_4K_SHA256 | TYPE_WHOLE_MD5 | TYPE_WHOLE_SHA1 | TYPE_WHOLE_SHA256 | TYPE_WHOLE_SHA512 | TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256 | TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512; private final IPackageInstaller mInstaller; private final int mUserId; private final String mInstallerPackageName; private final String mAttributionTag; private final ArrayList mDelegates = new ArrayList<>(); /** {@hide} */ public PackageInstaller(IPackageInstaller installer, String installerPackageName, String installerAttributionTag, int userId) { Objects.requireNonNull(installer, "installer cannot be null"); mInstaller = installer; mInstallerPackageName = installerPackageName; mAttributionTag = installerAttributionTag; mUserId = userId; } /** * Create a new session using the given parameters, returning a unique ID * that represents the session. Once created, the session can be opened * multiple times across multiple device boots. *

* The system may automatically destroy sessions that have not been * finalized (either committed or abandoned) within a reasonable period of * time, typically on the order of a day. * * @throws IOException if parameters were unsatisfiable, such as lack of * disk space or unavailable media. * @throws SecurityException when installation services are unavailable, * such as when called from a restricted user. * @throws IllegalArgumentException when {@link SessionParams} is invalid. * @return positive, non-zero unique ID that represents the created session. * This ID remains consistent across device reboots until the * session is finalized. IDs are not reused during a given boot. */ public int createSession(@NonNull SessionParams params) throws IOException { try { return mInstaller.createSession(params, mInstallerPackageName, mAttributionTag, mUserId); } catch (RuntimeException e) { ExceptionUtils.maybeUnwrapIOException(e); throw e; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Open an existing session to actively perform work. To succeed, the caller * must be the owner of the install session. * * @throws IOException if parameters were unsatisfiable, such as lack of * disk space or unavailable media. * @throws SecurityException when the caller does not own the session, or * the session is invalid. */ public @NonNull Session openSession(int sessionId) throws IOException { try { try { return new Session(mInstaller.openSession(sessionId)); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } catch (RuntimeException e) { ExceptionUtils.maybeUnwrapIOException(e); throw e; } } /** * Update the icon representing the app being installed in a specific * session. This should be roughly * {@link ActivityManager#getLauncherLargeIconSize()} in both dimensions. * * @throws SecurityException when the caller does not own the session, or * the session is invalid. */ public void updateSessionAppIcon(int sessionId, @Nullable Bitmap appIcon) { try { mInstaller.updateSessionAppIcon(sessionId, appIcon); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Update the label representing the app being installed in a specific * session. * * @throws SecurityException when the caller does not own the session, or * the session is invalid. */ public void updateSessionAppLabel(int sessionId, @Nullable CharSequence appLabel) { try { final String val = (appLabel != null) ? appLabel.toString() : null; mInstaller.updateSessionAppLabel(sessionId, val); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Completely abandon the given session, destroying all staged data and * rendering it invalid. Abandoned sessions will be reported to * {@link SessionCallback} listeners as failures. This is equivalent to * opening the session and calling {@link Session#abandon()}. * * @throws SecurityException when the caller does not own the session, or * the session is invalid. */ public void abandonSession(int sessionId) { try { mInstaller.abandonSession(sessionId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Return details for a specific session. Callers need to either declare <queries> * element with the specific package name in the app's manifest, have the * android.permission.QUERY_ALL_PACKAGES, or be the session owner to retrieve these details. * * @return details for the requested session, or {@code null} if the session * does not exist. */ public @Nullable SessionInfo getSessionInfo(int sessionId) { try { return mInstaller.getSessionInfo(sessionId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Return list of all known install sessions, regardless of the installer. Callers need to * either declare <queries> element with the specific package name in the app's manifest, * have the android.permission.QUERY_ALL_PACKAGES, or be the session owner to retrieve these * details. */ public @NonNull List getAllSessions() { try { return mInstaller.getAllSessions(mUserId).getList(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Return list of all known install sessions owned by the calling app. */ public @NonNull List getMySessions() { try { return mInstaller.getMySessions(mInstallerPackageName, mUserId).getList(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Return list of all staged install sessions. Callers need to either declare <queries> * element with the specific package name in the app's manifest, have the * android.permission.QUERY_ALL_PACKAGES, or be the session owner to retrieve these details. */ public @NonNull List getStagedSessions() { try { // TODO: limit this to the mUserId? return mInstaller.getStagedSessions().getList(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Returns first active staged session, or {@code null} if there is none. * *

For more information on what sessions are considered active see * {@link SessionInfo#isStagedSessionActive()}. * * @deprecated Use {@link #getActiveStagedSessions} as there can be more than one active staged * session */ @Deprecated public @Nullable SessionInfo getActiveStagedSession() { List activeSessions = getActiveStagedSessions(); return activeSessions.isEmpty() ? null : activeSessions.get(0); } /** * Returns list of active staged sessions. Returns empty list if there is none. * *

For more information on what sessions are considered active see * * {@link SessionInfo#isStagedSessionActive()}. */ public @NonNull List getActiveStagedSessions() { final List activeStagedSessions = new ArrayList<>(); final List stagedSessions = getStagedSessions(); for (int i = 0; i < stagedSessions.size(); i++) { final SessionInfo sessionInfo = stagedSessions.get(i); if (sessionInfo.isStagedSessionActive()) { activeStagedSessions.add(sessionInfo); } } return activeStagedSessions; } /** * Uninstall the given package, removing it completely from the device. This * method is available to: *

* * @param packageName The package to uninstall. * @param statusReceiver Where to deliver the result of the operation indicated by the extra * {@link #EXTRA_STATUS}. Refer to the individual status codes * on how to handle them. * * @see android.app.admin.DevicePolicyManager */ @RequiresPermission(anyOf = { Manifest.permission.DELETE_PACKAGES, Manifest.permission.REQUEST_DELETE_PACKAGES}) public void uninstall(@NonNull String packageName, @NonNull IntentSender statusReceiver) { uninstall(packageName, 0 /*flags*/, statusReceiver); } /** * Uninstall the given package, removing it completely from the device. This * method is only available to the current "installer of record" for the * package. * * @param packageName The package to uninstall. * @param flags Flags for uninstall. * @param statusReceiver Where to deliver the result of the operation indicated by the extra * {@link #EXTRA_STATUS}. Refer to the individual status codes * on how to handle them. * * @hide */ public void uninstall(@NonNull String packageName, @DeleteFlags int flags, @NonNull IntentSender statusReceiver) { uninstall(new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST), flags, statusReceiver); } /** * Uninstall the given package with a specific version code, removing it * completely from the device. If the version code of the package * does not match the one passed in the versioned package argument this * method is a no-op. Use {@link PackageManager#VERSION_CODE_HIGHEST} to * uninstall the latest version of the package. *

* This method is available to: *

* * @param versionedPackage The versioned package to uninstall. * @param statusReceiver Where to deliver the result of the operation indicated by the extra * {@link #EXTRA_STATUS}. Refer to the individual status codes * on how to handle them. * * @see android.app.admin.DevicePolicyManager */ @RequiresPermission(anyOf = { Manifest.permission.DELETE_PACKAGES, Manifest.permission.REQUEST_DELETE_PACKAGES}) public void uninstall(@NonNull VersionedPackage versionedPackage, @NonNull IntentSender statusReceiver) { uninstall(versionedPackage, 0 /*flags*/, statusReceiver); } /** * Uninstall the given package with a specific version code, removing it * completely from the device. This method is only available to the current * "installer of record" for the package. If the version code of the package * does not match the one passed in the versioned package argument this * method is a no-op. Use {@link PackageManager#VERSION_CODE_HIGHEST} to * uninstall the latest version of the package. * * @param versionedPackage The versioned package to uninstall. * @param flags Flags for uninstall. * @param statusReceiver Where to deliver the result of the operation indicated by the extra * {@link #EXTRA_STATUS}. Refer to the individual status codes * on how to handle them. */ @RequiresPermission(anyOf = { Manifest.permission.DELETE_PACKAGES, Manifest.permission.REQUEST_DELETE_PACKAGES}) public void uninstall(@NonNull VersionedPackage versionedPackage, @DeleteFlags int flags, @NonNull IntentSender statusReceiver) { Objects.requireNonNull(versionedPackage, "versionedPackage cannot be null"); try { mInstaller.uninstall(versionedPackage, mInstallerPackageName, flags, statusReceiver, mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Install the given package, which already exists on the device, for the user for which this * installer was created. * *

This will * {@link PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set) allowlist * all restricted permissions}. * * @param packageName The package to install. * @param installReason Reason for install. * @param statusReceiver Where to deliver the result of the operation indicated by the extra * {@link #EXTRA_STATUS}. Refer to the individual status codes * on how to handle them. */ @RequiresPermission(allOf = { Manifest.permission.INSTALL_PACKAGES, Manifest.permission.INSTALL_EXISTING_PACKAGES}) public void installExistingPackage(@NonNull String packageName, @InstallReason int installReason, @Nullable IntentSender statusReceiver) { Objects.requireNonNull(packageName, "packageName cannot be null"); try { mInstaller.installExistingPackage(packageName, PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS, installReason, statusReceiver, mUserId, null); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Uninstall the given package for the user for which this installer was created if the package * will still exist for other users on the device. * * @param packageName The package to uninstall. * @param statusReceiver Where to deliver the result of the operation indicated by the extra * {@link #EXTRA_STATUS}. Refer to the individual status codes * on how to handle them. */ @RequiresPermission(Manifest.permission.DELETE_PACKAGES) public void uninstallExistingPackage(@NonNull String packageName, @Nullable IntentSender statusReceiver) { Objects.requireNonNull(packageName, "packageName cannot be null"); try { mInstaller.uninstallExistingPackage( new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST), mInstallerPackageName, statusReceiver, mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Install package in an archived state. * * @param archivedPackageInfo archived package data such as package name, signature etc. * @param sessionParams used to create an underlying installation session * @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. * @see #createSession * @see PackageInstaller.Session#commit */ @RequiresPermission(Manifest.permission.INSTALL_PACKAGES) @FlaggedApi(Flags.FLAG_ARCHIVING) public void installPackageArchived(@NonNull ArchivedPackageInfo archivedPackageInfo, @NonNull SessionParams sessionParams, @NonNull IntentSender statusReceiver) { Objects.requireNonNull(archivedPackageInfo, "archivedPackageInfo cannot be null"); Objects.requireNonNull(sessionParams, "sessionParams cannot be null"); Objects.requireNonNull(statusReceiver, "statusReceiver cannot be null"); try { mInstaller.installPackageArchived( archivedPackageInfo.getParcel(), sessionParams, statusReceiver, mInstallerPackageName, new UserHandle(mUserId)); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** {@hide} */ @SystemApi @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setPermissionsResult(int sessionId, boolean accepted) { try { mInstaller.setPermissionsResult(sessionId, accepted); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Check if install constraints are satisfied for the given packages. * * Note this query result is just a hint and subject to race because system states could * change anytime in-between this query and committing the session. * * The result is returned by a callback because some constraints might take a long time * to evaluate. * * @param packageNames a list of package names to check the constraints for installation * @param constraints the constraints for installation. * @param executor the {@link Executor} on which to invoke the callback * @param callback called when the {@link InstallConstraintsResult} is ready * * @throws SecurityException if the given packages' installer of record doesn't match the * caller's own package name or the installerPackageName set by the caller doesn't * match the caller's own package name. */ public void checkInstallConstraints(@NonNull List packageNames, @NonNull InstallConstraints constraints, @NonNull @CallbackExecutor Executor executor, @NonNull Consumer callback) { Objects.requireNonNull(executor); Objects.requireNonNull(callback); try { var remoteCallback = new RemoteCallback(b -> { executor.execute(() -> { callback.accept(b.getParcelable("result", InstallConstraintsResult.class)); }); }); mInstaller.checkInstallConstraints( mInstallerPackageName, packageNames, constraints, remoteCallback); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Similar to {@link #checkInstallConstraints(List, InstallConstraints, Executor, Consumer)}, * but the callback is invoked only when the constraints are satisfied or after timeout. *

* Note: the device idle constraint might take a long time to evaluate. The system will * ensure the constraint is evaluated completely before handling timeout. * * @param packageNames a list of package names to check the constraints for installation * @param constraints the constraints for installation. * @param callback Called when the constraints are satisfied or after timeout. * Intents sent to this callback contain: * {@link Intent#EXTRA_PACKAGES} for the input package names, * {@link #EXTRA_INSTALL_CONSTRAINTS} for the input constraints, * {@link #EXTRA_INSTALL_CONSTRAINTS_RESULT} for the result. * @param timeoutMillis The maximum time to wait, in milliseconds until the constraints are * satisfied. Valid range is from 0 to one week. {@code 0} means the * callback will be invoked immediately no matter constraints are * satisfied or not. * @throws SecurityException if the given packages' installer of record doesn't match the * caller's own package name or the installerPackageName set by the caller doesn't * match the caller's own package name. */ public void waitForInstallConstraints(@NonNull List packageNames, @NonNull InstallConstraints constraints, @NonNull IntentSender callback, @DurationMillisLong long timeoutMillis) { try { mInstaller.waitForInstallConstraints( mInstallerPackageName, packageNames, constraints, callback, timeoutMillis); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * 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)}. *

* 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 sessionId the session ID to commit when all constraints are satisfied. * @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. * @throws IllegalArgumentException if the {@code statusReceiver} from an immutable * {@link android.app.PendingIntent} when caller has a target SDK of API * 35 or above. */ 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 context = ActivityThread.currentApplication(); var localIntentSender = new LocalIntentSender(context, sessionId, session, statusReceiver); waitForInstallConstraints(packageNames, constraints, localIntentSender.getIntentSender(), timeoutMillis); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } private static final class LocalIntentSender extends BroadcastReceiver { private final Context mContext; private final IntentSender mStatusReceiver; private final int mSessionId; private final IPackageInstallerSession mSession; LocalIntentSender(Context context, int sessionId, IPackageInstallerSession session, IntentSender statusReceiver) { mContext = context; mSessionId = sessionId; mSession = session; mStatusReceiver = statusReceiver; } private IntentSender getIntentSender() { Intent intent = new Intent(ACTION_WAIT_INSTALL_CONSTRAINTS).setPackage( mContext.getPackageName()); mContext.registerReceiver(this, new IntentFilter(ACTION_WAIT_INSTALL_CONSTRAINTS), Context.RECEIVER_EXPORTED); PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_MUTABLE); return pendingIntent.getIntentSender(); } @Override public void onReceive(Context context, Intent intent) { InstallConstraintsResult result = intent.getParcelableExtra( PackageInstaller.EXTRA_INSTALL_CONSTRAINTS_RESULT, InstallConstraintsResult.class); try { if (result.areAllConstraintsSatisfied()) { mSession.commit(mStatusReceiver, false); } else { // timeout final Intent fillIn = new Intent(); fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, mSessionId); fillIn.putExtra(PackageInstaller.EXTRA_STATUS, STATUS_FAILURE_TIMEOUT); fillIn.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, "Install constraints not satisfied within timeout"); mStatusReceiver.sendIntent(ActivityThread.currentApplication(), 0, fillIn, null, null); } } catch (Exception ignore) { // no-op } finally { unregisterReceiver(); } } private void unregisterReceiver() { mContext.unregisterReceiver(this); } } /** * Events for observing session lifecycle. *

* A typical session lifecycle looks like this: *

*/ public static abstract class SessionCallback { /** * New session has been created. Details about the session can be * obtained from {@link PackageInstaller#getSessionInfo(int)}. */ public abstract void onCreated(int sessionId); /** * Badging details for an existing session has changed. For example, the * app icon or label has been updated. */ public abstract void onBadgingChanged(int sessionId); /** * Active state for session has been changed. *

* A session is considered active whenever there is ongoing forward * progress being made, such as the installer holding an open * {@link Session} instance while streaming data into place, or the * system optimizing code as the result of * {@link Session#commit(IntentSender)}. *

* If the installer closes the {@link Session} without committing, the * session is considered inactive until the installer opens the session * again. */ public abstract void onActiveChanged(int sessionId, boolean active); /** * Progress for given session has been updated. *

* Note that this progress may not directly correspond to the value * reported by * {@link PackageInstaller.Session#setStagingProgress(float)}, as the * system may carve out a portion of the overall progress to represent * its own internal installation work. */ public abstract void onProgressChanged(int sessionId, float progress); /** * Session has completely finished, either with success or failure. */ public abstract void onFinished(int sessionId, boolean success); } /** {@hide} */ static class SessionCallbackDelegate extends IPackageInstallerCallback.Stub { private static final int MSG_SESSION_CREATED = 1; private static final int MSG_SESSION_BADGING_CHANGED = 2; private static final int MSG_SESSION_ACTIVE_CHANGED = 3; private static final int MSG_SESSION_PROGRESS_CHANGED = 4; private static final int MSG_SESSION_FINISHED = 5; final SessionCallback mCallback; final Executor mExecutor; SessionCallbackDelegate(SessionCallback callback, Executor executor) { mCallback = callback; mExecutor = executor; } @Override public void onSessionCreated(int sessionId) { mExecutor.execute(PooledLambda.obtainRunnable(SessionCallback::onCreated, mCallback, sessionId).recycleOnUse()); } @Override public void onSessionBadgingChanged(int sessionId) { mExecutor.execute(PooledLambda.obtainRunnable(SessionCallback::onBadgingChanged, mCallback, sessionId).recycleOnUse()); } @Override public void onSessionActiveChanged(int sessionId, boolean active) { mExecutor.execute(PooledLambda.obtainRunnable(SessionCallback::onActiveChanged, mCallback, sessionId, active).recycleOnUse()); } @Override public void onSessionProgressChanged(int sessionId, float progress) { mExecutor.execute(PooledLambda.obtainRunnable(SessionCallback::onProgressChanged, mCallback, sessionId, progress).recycleOnUse()); } @Override public void onSessionFinished(int sessionId, boolean success) { mExecutor.execute(PooledLambda.obtainRunnable(SessionCallback::onFinished, mCallback, sessionId, success).recycleOnUse()); } } /** {@hide} */ @Deprecated public void addSessionCallback(@NonNull SessionCallback callback) { registerSessionCallback(callback); } /** * Register to watch for session lifecycle events. The callers need to be the session * owner or have the android.permission.QUERY_ALL_PACKAGES to watch for these events. */ public void registerSessionCallback(@NonNull SessionCallback callback) { registerSessionCallback(callback, new Handler()); } /** {@hide} */ @Deprecated public void addSessionCallback(@NonNull SessionCallback callback, @NonNull Handler handler) { registerSessionCallback(callback, handler); } /** * Register to watch for session lifecycle events. No special permissions * are required to watch for these events. * * @param handler to dispatch callback events through, otherwise uses * calling thread. */ public void registerSessionCallback(@NonNull SessionCallback callback, @NonNull Handler handler) { synchronized (mDelegates) { final SessionCallbackDelegate delegate = new SessionCallbackDelegate(callback, new HandlerExecutor(handler)); try { mInstaller.registerCallback(delegate, mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } mDelegates.add(delegate); } } /** {@hide} */ @Deprecated public void removeSessionCallback(@NonNull SessionCallback callback) { unregisterSessionCallback(callback); } /** * Unregister a previously registered callback. */ public void unregisterSessionCallback(@NonNull SessionCallback callback) { synchronized (mDelegates) { for (Iterator i = mDelegates.iterator(); i.hasNext();) { final SessionCallbackDelegate delegate = i.next(); if (delegate.mCallback == callback) { try { mInstaller.unregisterCallback(delegate); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } i.remove(); } } } } /** * An installation that is being actively staged. For an install to succeed, * all existing and new packages must have identical package names, version * codes, and signing certificates. *

* A session may contain any number of split packages. If the application * does not yet exist, this session must include a base package. *

* 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. *

* In such a case that multiple packages need to be committed simultaneously, * multiple sessions can be referenced by a single multi-package session. * This session is created with no package name and calling * {@link SessionParams#setMultiPackage()}. 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 { /** {@hide} */ protected final IPackageInstallerSession mSession; /** {@hide} */ public Session(IPackageInstallerSession session) { mSession = session; } /** {@hide} */ @Deprecated public void setProgress(float progress) { setStagingProgress(progress); } /** * Set current progress of staging this session. Valid values are * anywhere between 0 and 1. *

* Note that this progress may not directly correspond to the value * reported by {@link SessionCallback#onProgressChanged(int, float)}, as * the system may carve out a portion of the overall progress to * represent its own internal installation work. */ public void setStagingProgress(float progress) { try { mSession.setClientProgress(progress); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** {@hide} */ @UnsupportedAppUsage public void addProgress(float progress) { try { mSession.addClientProgress(progress); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Open a stream to write an APK file into the session. *

* The returned stream will start writing data at the requested offset * in the underlying file, which can be used to resume a partially * written file. If a valid file length is specified, the system will * preallocate the underlying disk space to optimize placement on disk. * It's strongly recommended to provide a valid file length when known. *

* You can write data into the returned stream, optionally call * {@link #fsync(OutputStream)} as needed to ensure bytes have been * persisted to disk, and then close when finished. All streams must be * closed before calling {@link #commit(IntentSender)}. * * @param name arbitrary, unique name of your choosing to identify the * APK being written. You can open a file again for * additional writes (such as after a reboot) by using the * same name. This name is only meaningful within the context * of a single install session. * @param offsetBytes offset into the file to begin writing at, or 0 to * start at the beginning of the file. * @param lengthBytes total size of the file being written, used to * preallocate the underlying disk space, or -1 if unknown. * The system may clear various caches as needed to allocate * this space. * @throws IOException if trouble opening the file for writing, such as * lack of disk space or unavailable media. * @throws SecurityException if called after the session has been * sealed or abandoned */ public @NonNull OutputStream openWrite(@NonNull String name, long offsetBytes, long lengthBytes) throws IOException { try { if (ENABLE_REVOCABLE_FD) { return new ParcelFileDescriptor.AutoCloseOutputStream( mSession.openWrite(name, offsetBytes, lengthBytes)); } else { final ParcelFileDescriptor clientSocket = mSession.openWrite(name, offsetBytes, lengthBytes); return new FileBridge.FileBridgeOutputStream(clientSocket); } } catch (RuntimeException e) { ExceptionUtils.maybeUnwrapIOException(e); throw e; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** {@hide} */ public void write(@NonNull String name, long offsetBytes, long lengthBytes, @NonNull ParcelFileDescriptor fd) throws IOException { try { mSession.write(name, offsetBytes, lengthBytes, fd); } catch (RuntimeException e) { ExceptionUtils.maybeUnwrapIOException(e); throw e; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Populate an APK file by creating a hard link to avoid the need to copy. *

* Note this API is used by RollbackManager only and can only be called from system_server. * {@code target} will be relabeled if link is created successfully. RollbackManager has * to delete {@code target} when the session is committed successfully to avoid SELinux * label conflicts. *

* Note No more bytes should be written to the file once the link is created successfully. * * @param target the path of the link target * * @hide */ public void stageViaHardLink(String target) throws IOException { try { mSession.stageViaHardLink(target); } catch (RuntimeException e) { ExceptionUtils.maybeUnwrapIOException(e); throw e; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Ensure that any outstanding data for given stream has been committed * to disk. This is only valid for streams returned from * {@link #openWrite(String, long, long)}. */ public void fsync(@NonNull OutputStream out) throws IOException { if (ENABLE_REVOCABLE_FD) { if (out instanceof ParcelFileDescriptor.AutoCloseOutputStream) { try { Os.fsync(((ParcelFileDescriptor.AutoCloseOutputStream) out).getFD()); } catch (ErrnoException e) { throw e.rethrowAsIOException(); } } else { throw new IllegalArgumentException("Unrecognized stream"); } } else { if (out instanceof FileBridge.FileBridgeOutputStream) { ((FileBridge.FileBridgeOutputStream) out).fsync(); } else { throw new IllegalArgumentException("Unrecognized stream"); } } } /** * Return all APK names contained in this session. *

* This returns all names which have been previously written through * {@link #openWrite(String, long, long)} as part of this session. * * @throws SecurityException if called after the session has been abandoned. */ public @NonNull String[] getNames() throws IOException { try { return mSession.getNames(); } catch (RuntimeException e) { ExceptionUtils.maybeUnwrapIOException(e); throw e; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Open a stream to read an APK file from the session. *

* This is only valid for names which have been previously written * through {@link #openWrite(String, long, long)} as part of this * session. For example, this stream may be used to calculate a * {@link MessageDigest} of a written APK before committing. * * @throws SecurityException if called after the session has been * committed or abandoned. */ public @NonNull InputStream openRead(@NonNull String name) throws IOException { try { final ParcelFileDescriptor pfd = mSession.openRead(name); return new ParcelFileDescriptor.AutoCloseInputStream(pfd); } catch (RuntimeException e) { ExceptionUtils.maybeUnwrapIOException(e); throw e; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Removes a split. *

* Split removals occur prior to adding new APKs. If upgrading a feature * split, it is not expected nor desirable to remove the split prior to * upgrading. *

* When split removal is bundled with new APKs, the packageName must be * identical. */ public void removeSplit(@NonNull String splitName) throws IOException { try { mSession.removeSplit(splitName); } catch (RuntimeException e) { ExceptionUtils.maybeUnwrapIOException(e); throw e; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * @return data loader params or null if the session is not using one. * {@hide} */ @SystemApi @RequiresPermission(android.Manifest.permission.USE_INSTALLER_V2) public @Nullable DataLoaderParams getDataLoaderParams() { try { DataLoaderParamsParcel data = mSession.getDataLoaderParams(); if (data == null) { return null; } return new DataLoaderParams(data); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Adds a file to session. On commit this file will be pulled from DataLoader {@code * android.service.dataloader.DataLoaderService.DataLoader}. * * @param location target location for the file. Possible values: * {@link #LOCATION_DATA_APP}, * {@link #LOCATION_MEDIA_OBB}, * {@link #LOCATION_MEDIA_DATA}. * @param name arbitrary, unique name of your choosing to identify the * APK being written. You can open a file again for * additional writes (such as after a reboot) by using the * same name. This name is only meaningful within the context * of a single install session. * @param lengthBytes total size of the file being written. * The system may clear various caches as needed to allocate * this space. * @param metadata additional info use by DataLoader to pull data for the file. * @param signature additional file signature, e.g. * APK Signature Scheme v4 * @throws SecurityException if called after the session has been * sealed or abandoned * @throws IllegalStateException if called for non-streaming session * * @see android.content.pm.InstallationFile * * {@hide} */ @SystemApi @RequiresPermission(android.Manifest.permission.USE_INSTALLER_V2) public void addFile(@FileLocation int location, @NonNull String name, long lengthBytes, @NonNull byte[] metadata, @Nullable byte[] signature) { try { mSession.addFile(location, name, lengthBytes, metadata, signature); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Removes a file. * * @param location target location for the file. Possible values: * {@link #LOCATION_DATA_APP}, * {@link #LOCATION_MEDIA_OBB}, * {@link #LOCATION_MEDIA_DATA}. * @param name name of a file, e.g. split. * @throws SecurityException if called after the session has been * sealed or abandoned * @throws IllegalStateException if called for non-DataLoader session * {@hide} */ @SystemApi @RequiresPermission(android.Manifest.permission.USE_INSTALLER_V2) public void removeFile(@FileLocation int location, @NonNull String name) { try { mSession.removeFile(location, name); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Sets installer-provided checksums for the APK file in session. * * @param name previously written as part of this session. * {@link #openWrite} * @param checksums installer intends to make available via * {@link PackageManager#requestChecksums} or {@link #requestChecksums}. * @param signature DER PKCS#7 detached signature bytes over binary serialized checksums * to enable integrity checking for the checksums or null for no integrity * checking. {@link PackageManager#requestChecksums} will return * the certificate used to create signature. * Binary format for checksums: *

{@code DataOutputStream dos;
         *                  dos.writeInt(checksum.getType());
         *                  dos.writeInt(checksum.getValue().length);
         *                  dos.write(checksum.getValue());}
* If using openssl cms, make sure to specify -binary -nosmimecap. * @see openssl cms * @throws SecurityException if called after the session has been * committed or abandoned. * @throws IllegalStateException if checksums for this file have already been added. * @deprecated do not use installer-provided checksums, * use platform-enforced checksums * e.g. {@link Checksum#TYPE_WHOLE_MERKLE_ROOT_4K_SHA256} * in {@link PackageManager#requestChecksums}. */ @Deprecated public void setChecksums(@NonNull String name, @NonNull List checksums, @Nullable byte[] signature) throws IOException { Objects.requireNonNull(name); Objects.requireNonNull(checksums); try { mSession.setChecksums(name, checksums.toArray(new Checksum[checksums.size()]), signature); } catch (RuntimeException e) { ExceptionUtils.maybeUnwrapIOException(e); throw e; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } private static List encodeCertificates(List certs) throws CertificateEncodingException { if (certs == null) { return null; } List result = new ArrayList<>(certs.size()); for (Certificate cert : certs) { if (!(cert instanceof X509Certificate)) { throw new CertificateEncodingException("Only X509 certificates supported."); } result.add(cert.getEncoded()); } return result; } /** * Requests checksums for the APK file in session. *

* A possible use case is replying to {@link Intent#ACTION_PACKAGE_NEEDS_VERIFICATION} * broadcast. * The checksums will be returned asynchronously via onChecksumsReadyListener. *

* By default returns all readily available checksums: *

    *
  • enforced by platform, *
  • enforced by the installer. *
* If the caller needs a specific checksum type, they can specify it as required. *

* Caution: Android can not verify installer-provided checksums. Make sure you specify * trusted installers. *

* @param name previously written as part of this session. * {@link #openWrite} * @param required to explicitly request the checksum types. Will incur significant * CPU/memory/disk usage. * @param trustedInstallers for checksums enforced by installer, which installers are to be * trusted. * {@link PackageManager#TRUST_ALL} will return checksums from any * installer, * {@link PackageManager#TRUST_NONE} disables optimized * installer-enforced checksums, otherwise the list has to be * a non-empty list of certificates. * @param executor the {@link Executor} on which to invoke the callback * @param onChecksumsReadyListener called once when the results are available. * @throws CertificateEncodingException if an encoding error occurs for trustedInstallers. * @throws FileNotFoundException if the file does not exist. * @throws IllegalArgumentException if the list of trusted installer certificates is empty. */ public void requestChecksums(@NonNull String name, @Checksum.TypeMask int required, @NonNull List trustedInstallers, @NonNull @CallbackExecutor Executor executor, @NonNull PackageManager.OnChecksumsReadyListener onChecksumsReadyListener) throws CertificateEncodingException, FileNotFoundException { Objects.requireNonNull(name); Objects.requireNonNull(trustedInstallers); Objects.requireNonNull(executor); Objects.requireNonNull(onChecksumsReadyListener); if (trustedInstallers == PackageManager.TRUST_ALL) { trustedInstallers = null; } else if (trustedInstallers == PackageManager.TRUST_NONE) { trustedInstallers = Collections.emptyList(); } else if (trustedInstallers.isEmpty()) { throw new IllegalArgumentException( "trustedInstallers has to be one of TRUST_ALL/TRUST_NONE or a non-empty " + "list of certificates."); } try { IOnChecksumsReadyListener onChecksumsReadyListenerDelegate = new IOnChecksumsReadyListener.Stub() { @Override public void onChecksumsReady(List checksums) throws RemoteException { executor.execute( () -> onChecksumsReadyListener.onChecksumsReady(checksums)); } }; mSession.requestChecksums(name, DEFAULT_CHECKSUMS, required, encodeCertificates(trustedInstallers), onChecksumsReadyListenerDelegate); } catch (ParcelableException e) { e.maybeRethrow(FileNotFoundException.class); throw new RuntimeException(e); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Attempt to commit everything staged in this session. This may require * user intervention, and so it may not happen immediately. The final * result of the commit will be reported through the given callback. *

* Once this method is called, the session is sealed and no additional mutations may be * performed on the session. In case of device reboot or data loader transient failure * before the session has been finalized, you may commit the session again. *

* If the installer is the device owner, the affiliated profile owner, or has received * user pre-approval of this session, there will be no user intervention. * * @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. * * @throws SecurityException if streams opened through * {@link #openWrite(String, long, long)} are still open. * @throws IllegalArgumentException if the {@code statusReceiver} from an immutable * {@link android.app.PendingIntent} when caller has a target SDK of API * version 35 or above. * * @see android.app.admin.DevicePolicyManager * @see #requestUserPreapproval */ public void commit(@NonNull IntentSender statusReceiver) { try { mSession.commit(statusReceiver, false); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Attempt to commit a session that has been {@link #transfer(String) transferred}. * *

If the device reboots before the session has been finalized, you may commit the * session again. * *

The caller of this method is responsible to ensure the safety of the session. As the * session was created by another - usually less trusted - app, it is paramount that before * committing all public and system {@link SessionInfo properties of the session} * and all {@link #openRead(String) APKs} are verified by the caller. It might happen * that new properties are added to the session with a new API revision. In this case the * callers need to be updated. * * @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. * @throws IllegalArgumentException if the {@code statusReceiver} from an immutable * {@link android.app.PendingIntent} when caller has a target SDK of API * 35 or above. * * @hide */ @SystemApi @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void commitTransferred(@NonNull IntentSender statusReceiver) { try { mSession.commit(statusReceiver, true); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Transfer the session to a new owner. *

* Only sessions that update the installing app can be transferred. *

* After the transfer to a package with a different uid all method calls on the session * will cause {@link SecurityException}s. *

* Once this method is called, the session is sealed and no additional mutations beside * committing it may be performed on the session. * * @param packageName The package of the new owner. Needs to hold the INSTALL_PACKAGES * permission. * * @throws PackageManager.NameNotFoundException if the new owner could not be found. * @throws SecurityException if called after the session has been committed or abandoned. * @throws IllegalStateException if streams opened through * {@link #openWrite(String, long, long) are still open. * @throws IllegalArgumentException if {@code packageName} is invalid. */ public void transfer(@NonNull String packageName) throws PackageManager.NameNotFoundException { Preconditions.checkArgument(!TextUtils.isEmpty(packageName)); try { mSession.transfer(packageName); } catch (ParcelableException e) { e.maybeRethrow(PackageManager.NameNotFoundException.class); throw new RuntimeException(e); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Release this session object. You can open the session again if it * hasn't been finalized. */ @Override public void close() { try { mSession.close(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Completely abandon this session, destroying all staged data and * rendering it invalid. Abandoned sessions will be reported to * {@link SessionCallback} listeners as failures. This is equivalent to * {@link #abandonSession(int)}. *

If the parent is abandoned, all children will also be abandoned. Any written data * would be destroyed and the created {@link Session} information will be discarded.

*/ public void abandon() { try { mSession.abandon(); } catch (RemoteException e) { 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 {@code true} if this session will be staged and applied at next reboot. */ public boolean isStaged() { try { return mSession.isStaged(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * @return Session's {@link SessionParams#installFlags}. * @hide */ public int getInstallFlags() { try { return mSession.getInstallFlags(); } 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. * *

If the parent is staged or has rollback enabled, all children must have * the same properties.

*

If the parent is abandoned, all children will also be abandoned.

* * @param sessionId the session ID to add to this multi-package session. */ public void addChildSessionId(int sessionId) { try { mSession.addChildSessionId(sessionId); } catch (RemoteException e) { 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) { e.rethrowFromSystemServer(); } } /** * @return A PersistableBundle containing the app metadata set with * {@link Session#setAppMetadata(PersistableBundle)}. In the case where this data does not * exist, an empty PersistableBundle is returned. */ @NonNull public PersistableBundle getAppMetadata() { PersistableBundle data = null; try { ParcelFileDescriptor pfd = mSession.getAppMetadataFd(); if (pfd != null) { try (InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) { data = PersistableBundle.readFromStream(inputStream); } } } catch (RemoteException e) { e.rethrowFromSystemServer(); } catch (IOException e) { throw new RuntimeException(e); } return data != null ? data : new PersistableBundle(); } private OutputStream openWriteAppMetadata() throws IOException { try { if (ENABLE_REVOCABLE_FD) { return new ParcelFileDescriptor.AutoCloseOutputStream( mSession.openWriteAppMetadata()); } else { final ParcelFileDescriptor clientSocket = mSession.openWriteAppMetadata(); return new FileBridge.FileBridgeOutputStream(clientSocket); } } catch (RuntimeException e) { ExceptionUtils.maybeUnwrapIOException(e); throw e; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Optionally set the app metadata. The size of this data cannot exceed the maximum allowed. * Any existing data from the previous install will not be retained even if no data is set * for the current install session. Setting data to null or an empty PersistableBundle will * remove any metadata that has previously been set in the same session. * * @param data a PersistableBundle containing the app metadata. * @throws IOException if writing the data fails. */ public void setAppMetadata(@Nullable PersistableBundle data) throws IOException { if (data == null || data.isEmpty()) { try { mSession.removeAppMetadata(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } return; } Objects.requireNonNull(data); try (OutputStream outputStream = openWriteAppMetadata()) { data.writeToStream(outputStream); } } /** * Attempt to request the approval before committing this session. * * For installers that have been granted the * {@link android.Manifest.permission#REQUEST_INSTALL_PACKAGES REQUEST_INSTALL_PACKAGES} * permission, they can request the approval from users before * {@link Session#commit(IntentSender)} is called. This may require user intervention as * well. When user intervention is required, installers will receive a * {@link #STATUS_PENDING_USER_ACTION} callback, and {@link #STATUS_SUCCESS} otherwise. * In case that requesting user pre-approval is not available, installers will receive * {@link #STATUS_FAILURE_BLOCKED} instead. Note that if the users decline the request, * this session will be abandoned. * * If user intervention is required but never resolved, or requesting user * pre-approval is not available, you may still call {@link Session#commit(IntentSender)} * as the typical installation. * * @param details the adequate context to this session for requesting the approval from * users prior to commit. * @param statusReceiver called when the state of the session changes. * Intents sent to this receiver contain {@link #EXTRA_STATUS} * and the {@link #EXTRA_PRE_APPROVAL} would be {@code true}. * Refer to the individual status codes on how to handle them. * * @throws IllegalArgumentException when {@link PreapprovalDetails} is {@code null}. * @throws IllegalArgumentException if {@link IntentSender} is {@code null}. * @throws IllegalStateException if called on a multi-package session (no matter * the parent session or any of the children sessions). * @throws IllegalStateException if called again after this method has been called on * this session. * @throws SecurityException when the caller does not own this session. * @throws SecurityException if called after the session has been committed or abandoned. */ public void requestUserPreapproval(@NonNull PreapprovalDetails details, @NonNull IntentSender statusReceiver) { Preconditions.checkArgument(details != null, "preapprovalDetails cannot be null."); Preconditions.checkArgument(statusReceiver != null, "statusReceiver cannot be null."); try { mSession.requestUserPreapproval(details, statusReceiver); } catch (RemoteException e) { e.rethrowFromSystemServer(); } } /** * @return {@code true} if this session will keep the existing application enabled setting * after installation. */ public boolean isApplicationEnabledSettingPersistent() { try { return mSession.isApplicationEnabledSettingPersistent(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * @return {@code true} if the installer requested the update ownership enforcement * for the packages in this session. * * @see PackageInstaller.SessionParams#setRequestUpdateOwnership */ public boolean isRequestUpdateOwnership() { try { return mSession.isRequestUpdateOwnership(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Sets the pre-verified domains for the app to be installed. By setting pre-verified * domains, the installer allows the app to be opened by the app links of these domains * immediately after it is installed. * *

The specified pre-verified domains should be a subset of the hostnames declared with * {@code android:host} and {@code android:autoVerify=true} in the intent filters of the * AndroidManifest.xml of the app. If some of the specified domains are not declared in * the manifest, they will be ignored.

*

If this API is called multiple times on the same {@link #Session}, the last call * overrides the previous ones.

*

The instant app installer is the only entity that may call this API. *

* * @param preVerifiedDomains domains that are already pre-verified by the installer. * * @throws IllegalArgumentException if the number or the total size of the pre-verified * domains exceeds the maximum allowed, or if the domain * names contain invalid characters. * @throws SecurityException if called from an installer that is not the instant app * installer of the device, or if called after the session has * been committed or abandoned. * * @hide */ @SystemApi @FlaggedApi(Flags.FLAG_SET_PRE_VERIFIED_DOMAINS) @RequiresPermission(Manifest.permission.ACCESS_INSTANT_APPS) public void setPreVerifiedDomains(@NonNull Set preVerifiedDomains) { Preconditions.checkArgument(preVerifiedDomains != null && !preVerifiedDomains.isEmpty(), "Provided pre-verified domains cannot be null or empty."); try { mSession.setPreVerifiedDomains(new DomainSet(preVerifiedDomains)); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Retrieve the pre-verified domains set in a session. * See {@link #setPreVerifiedDomains(Set)} for the definition of pre-verified domains. * * @throws SecurityException if called from an installer that is not the owner of the * session, or if called after the session has been committed or * abandoned. * @hide */ @SystemApi @FlaggedApi(Flags.FLAG_SET_PRE_VERIFIED_DOMAINS) @RequiresPermission(Manifest.permission.ACCESS_INSTANT_APPS) @NonNull public Set getPreVerifiedDomains() { try { DomainSet domainSet = mSession.getPreVerifiedDomains(); return domainSet != null ? domainSet.getDomains() : Collections.emptySet(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } } /** * Parse a single APK or a directory of APKs to get install relevant information about * the package wrapped in {@link InstallInfo}. * @throws PackageParsingException if the package source file(s) provided is(are) not valid, * or the parser isn't able to parse the supplied source(s). * @hide */ @SystemApi @NonNull public InstallInfo readInstallInfo(@NonNull File file, int flags) throws PackageParsingException { final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing(); final ParseResult result = ApkLiteParseUtils.parsePackageLite( input.reset(), file, flags); if (result.isError()) { throw new PackageParsingException(result.getErrorCode(), result.getErrorMessage()); } return new InstallInfo(result); } /** * Parse a single APK file passed as an FD to get install relevant information about * the package wrapped in {@link InstallInfo}. * @throws PackageParsingException if the package source file(s) provided is(are) not valid, * or the parser isn't able to parse the supplied source(s). * @hide */ @SystemApi @NonNull @FlaggedApi(Flags.FLAG_READ_INSTALL_INFO) public InstallInfo readInstallInfo(@NonNull ParcelFileDescriptor pfd, @Nullable String debugPathName, int flags) throws PackageParsingException { final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing(); final ParseResult result = ApkLiteParseUtils.parseMonolithicPackageLite(input, pfd.getFileDescriptor(), debugPathName, flags); if (result.isError()) { throw new PackageParsingException(result.getErrorCode(), result.getErrorMessage()); } return new InstallInfo(result); } /** * Requests to archive a package which is currently installed. * *

During the archival process, the apps APKs and cache are removed from the device while * the user data is kept. Through the {@link #requestUnarchive} call, apps * can be restored again through their responsible installer. * *

Archived apps are returned as displayable apps through the {@link LauncherApps} APIs and * will be displayed to users with UI treatment to highlight that said apps are archived. If * a user taps on an archived app, the app will be unarchived and the restoration process is * communicated. * * @param statusReceiver Callback used to notify when the operation is completed. * @throws PackageManager.NameNotFoundException If {@code packageName} isn't found or not * available to the caller or isn't archived. */ @RequiresPermission(anyOf = { Manifest.permission.DELETE_PACKAGES, Manifest.permission.REQUEST_DELETE_PACKAGES}) @FlaggedApi(Flags.FLAG_ARCHIVING) public void requestArchive(@NonNull String packageName, @NonNull IntentSender statusReceiver) throws PackageManager.NameNotFoundException { try { mInstaller.requestArchive(packageName, mInstallerPackageName, /*flags=*/ 0, statusReceiver, new UserHandle(mUserId)); } catch (ParcelableException e) { e.maybeRethrow(PackageManager.NameNotFoundException.class); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Requests to unarchive a currently archived package. * *

Sends a request to unarchive an app to the responsible installer. The installer is * determined by {@link InstallSourceInfo#getUpdateOwnerPackageName()}, or * {@link InstallSourceInfo#getInstallingPackageName()} if the former value is null. * *

The installation will happen asynchronously and can be observed through * {@link android.content.Intent#ACTION_PACKAGE_ADDED}. * * @param statusReceiver Callback used to notify whether the installer has accepted the * unarchival request or an error has occurred. The status update will be * sent though {@link #EXTRA_UNARCHIVE_STATUS}. Only one status will be * sent. * @throws PackageManager.NameNotFoundException If {@code packageName} isn't found or not * visible to the caller or if the package has no * installer on the device anymore to unarchive it. * @throws IOException If parameters were unsatisfiable, such as lack of disk space. */ @RequiresPermission(anyOf = { Manifest.permission.INSTALL_PACKAGES, Manifest.permission.REQUEST_INSTALL_PACKAGES}) @FlaggedApi(Flags.FLAG_ARCHIVING) public void requestUnarchive(@NonNull String packageName, @NonNull IntentSender statusReceiver) throws IOException, PackageManager.NameNotFoundException { try { mInstaller.requestUnarchive(packageName, mInstallerPackageName, statusReceiver, new UserHandle(mUserId)); } catch (ParcelableException e) { e.maybeRethrow(IOException.class); e.maybeRethrow(PackageManager.NameNotFoundException.class); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Reports the status of an unarchival to the system. * * @param unarchiveId the ID provided by the system as part of the * intent.action.UNARCHIVE broadcast with EXTRA_UNARCHIVE_ID. * @param status is used for the system to provide the user with necessary * follow-up steps or errors. * @param requiredStorageBytes If the error is UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE this field * should be set to specify how many additional bytes of storage * are required to unarchive the app. * @param userActionIntent Optional intent to start a follow up action required to * facilitate the unarchival flow (e.g. user needs to log in). * @throws PackageManager.NameNotFoundException if no unarchival with {@code unarchiveId} exists */ // TODO(b/314960798) Remove old API once it's unused @RequiresPermission(anyOf = { Manifest.permission.INSTALL_PACKAGES, Manifest.permission.REQUEST_INSTALL_PACKAGES}) @FlaggedApi(Flags.FLAG_ARCHIVING) public void reportUnarchivalStatus(int unarchiveId, @UnarchivalStatus int status, long requiredStorageBytes, @Nullable PendingIntent userActionIntent) throws PackageManager.NameNotFoundException { try { mInstaller.reportUnarchivalStatus(unarchiveId, status, requiredStorageBytes, userActionIntent, new UserHandle(mUserId)); } catch (ParcelableException e) { e.maybeRethrow(PackageManager.NameNotFoundException.class); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Reports the state of an unarchival to the system. * * @see UnarchivalState for the different state options. * @throws PackageManager.NameNotFoundException if no unarchival with {@code unarchiveId} exists */ @RequiresPermission(anyOf = { Manifest.permission.INSTALL_PACKAGES, Manifest.permission.REQUEST_INSTALL_PACKAGES}) @FlaggedApi(Flags.FLAG_ARCHIVING) public void reportUnarchivalState(@NonNull UnarchivalState unarchivalState) throws PackageManager.NameNotFoundException { Objects.requireNonNull(unarchivalState); try { mInstaller.reportUnarchivalStatus(unarchivalState.getUnarchiveId(), unarchivalState.getStatus(), unarchivalState.getRequiredStorageBytes(), unarchivalState.getUserActionIntent(), new UserHandle(mUserId)); } catch (ParcelableException e) { e.maybeRethrow(PackageManager.NameNotFoundException.class); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } // (b/239722738) This class serves as a bridge between the PackageLite class, which // is a hidden class, and the consumers of this class. (e.g. InstallInstalling.java) // This is a part of an effort to remove dependency on hidden APIs and use SystemAPIs or // public APIs. /** * Install related details from an APK or a folder of APK(s). * * @hide */ @SystemApi public static class InstallInfo { /** @hide */ @IntDef(prefix = { "INSTALL_LOCATION_" }, value = { INSTALL_LOCATION_AUTO, INSTALL_LOCATION_INTERNAL_ONLY, INSTALL_LOCATION_PREFER_EXTERNAL }) @Retention(RetentionPolicy.SOURCE) public @interface InstallLocation{} private PackageLite mPkg; InstallInfo(ParseResult result) { mPkg = result.getResult(); } /** * See {@link PackageLite#getPackageName()} */ @NonNull public String getPackageName() { return mPkg.getPackageName(); } /** * @return The default install location defined by an application in * {@link android.R.attr#installLocation} attribute. */ public @InstallLocation int getInstallLocation() { return mPkg.getInstallLocation(); } /** * @param params {@link SessionParams} of the installation * @return Total disk space occupied by an application after installation. * Includes the size of the raw APKs, possibly unpacked resources, raw dex metadata files, * and all relevant native code. * @throws IOException when size of native binaries cannot be calculated. */ public long calculateInstalledSize(@NonNull SessionParams params) throws IOException { return InstallLocationUtils.calculateInstalledSize(mPkg, params.abiOverride); } /** * @param params {@link SessionParams} of the installation * @param pfd of an APK opened for read * @return Total disk space occupied by an application after installation. * Includes the size of the raw APKs, possibly unpacked resources, raw dex metadata files, * and all relevant native code. * @throws IOException when size of native binaries cannot be calculated. */ @FlaggedApi(Flags.FLAG_READ_INSTALL_INFO) public long calculateInstalledSize(@NonNull SessionParams params, @NonNull ParcelFileDescriptor pfd) throws IOException { return InstallLocationUtils.calculateInstalledSize(mPkg, params.abiOverride, pfd.getFileDescriptor()); } } /** * Generic exception class for using with parsing operations. * * @hide */ @SystemApi public static class PackageParsingException extends Exception { private final int mErrorCode; /** {@hide} */ public PackageParsingException(int errorCode, @Nullable String detailedMessage) { super(detailedMessage); mErrorCode = errorCode; } public int getErrorCode() { return mErrorCode; } } /** * Parameters for creating a new {@link PackageInstaller.Session}. */ public static class SessionParams implements Parcelable { /** {@hide} */ public static final int MODE_INVALID = -1; /** * Mode for an install session whose staged APKs should fully replace any * existing APKs for the target app. */ public static final int MODE_FULL_INSTALL = 1; /** * Mode for an install session that should inherit any existing APKs for the * target app, unless they have been explicitly overridden (based on split * name) by the session. For example, this can be used to add one or more * split APKs to an existing installation. *

* If there are no existing APKs for the target app, this behaves like * {@link #MODE_FULL_INSTALL}. */ public static final int MODE_INHERIT_EXISTING = 2; /** * Special constant to refer to all restricted permissions. */ public static final @NonNull Set RESTRICTED_PERMISSIONS_ALL = new ArraySet<>(); /** {@hide} */ public static final int UID_UNKNOWN = -1; /** * This value is derived from the maximum file name length. No package above this limit * can ever be successfully installed on the device. * @hide */ public static final int MAX_PACKAGE_NAME_LENGTH = 255; /** @hide */ @IntDef(prefix = {"USER_ACTION_"}, value = { USER_ACTION_UNSPECIFIED, USER_ACTION_REQUIRED, USER_ACTION_NOT_REQUIRED }) @Retention(RetentionPolicy.SOURCE) public @interface UserActionRequirement {} /** * This value is passed by the installer to {@link SessionParams#setRequireUserAction(int)} * to indicate that user action is unspecified for this install. * {@code requireUserAction} also defaults to this value unless modified by * {@link SessionParams#setRequireUserAction(int)} */ public static final int USER_ACTION_UNSPECIFIED = 0; /** * This value is passed by the installer to {@link SessionParams#setRequireUserAction(int)} * to indicate that user action is required for this install. */ public static final int USER_ACTION_REQUIRED = 1; /** * This value is passed by the installer to {@link SessionParams#setRequireUserAction(int)} * to indicate that user action is not required for this install. */ public static final int USER_ACTION_NOT_REQUIRED = 2; /** @hide */ @IntDef(prefix = {"PERMISSION_STATE_"}, value = { PERMISSION_STATE_DEFAULT, PERMISSION_STATE_GRANTED, PERMISSION_STATE_DENIED, }) @Retention(RetentionPolicy.SOURCE) public @interface PermissionState {} /** * Value is passed by the installer to {@link #setPermissionState(String, int)} to set * the state of a permission. This indicates no preference by the installer, relying on * the device's default policy to set the grant state of the permission. */ public static final int PERMISSION_STATE_DEFAULT = 0; /** * Value is passed by the installer to {@link #setPermissionState(String, int)} to set * the state of a permission. This indicates the installers wants to automatically grant * the permission to the package being installed. The user and other actors in the system * may still be able to deny the permission after installation. */ public static final int PERMISSION_STATE_GRANTED = 1; /** * Value is passed by the installer to {@link #setPermissionState(String, int)} to set * the state of a permission. This indicates the installers wants to deny the permission * by default to the package being installed. The user and other actors in the system may * still be able to grant the permission after installation. */ public static final int PERMISSION_STATE_DENIED = 2; /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public int mode = MODE_INVALID; /** {@hide} */ @UnsupportedAppUsage public int installFlags = PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS; /** {@hide} */ public int installLocation = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY; /** {@hide} */ public @InstallReason int installReason = PackageManager.INSTALL_REASON_UNKNOWN; /** * {@hide} * * This flag indicates which installation scenario best describes this session. The system * may use this value when making decisions about how to handle the installation, such as * prioritizing system health or user experience. */ public @InstallScenario int installScenario = PackageManager.INSTALL_SCENARIO_DEFAULT; /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public long sizeBytes = -1; /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public String appPackageName; /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public Bitmap appIcon; /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public String appLabel; /** {@hide} */ public long appIconLastModified = -1; /** {@hide} */ public Uri originatingUri; /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public int originatingUid = UID_UNKNOWN; /** {@hide} */ public Uri referrerUri; /** {@hide} */ public String abiOverride; /** {@hide} */ public String volumeUuid; /** {@hide} */ public List whitelistedRestrictedPermissions; /** {@hide} */ public int autoRevokePermissionsMode = MODE_DEFAULT; /** {@hide} */ public String installerPackageName; /** {@hide} */ public boolean isMultiPackage; /** {@hide} */ public int packageSource = PACKAGE_SOURCE_UNSPECIFIED; /** {@hide} */ public boolean isStaged; /** {@hide} */ public long requiredInstalledVersionCode = PackageManager.VERSION_CODE_HIGHEST; /** {@hide} */ public DataLoaderParams dataLoaderParams; /** {@hide} */ public int rollbackDataPolicy = PackageManager.ROLLBACK_DATA_POLICY_RESTORE; /** @hide */ public long rollbackLifetimeMillis = 0; /** {@hide} */ public int rollbackImpactLevel = PackageManager.ROLLBACK_USER_IMPACT_LOW; /** {@hide} */ public boolean forceQueryableOverride; /** {@hide} */ public int requireUserAction = USER_ACTION_UNSPECIFIED; /** {@hide} */ public boolean applicationEnabledSettingPersistent = false; /** {@hide} */ public int developmentInstallFlags = 0; /** {@hide} */ public int unarchiveId = -1; private final ArrayMap mPermissionStates; /** * Construct parameters for a new package install session. * * @param mode one of {@link #MODE_FULL_INSTALL} or * {@link #MODE_INHERIT_EXISTING} describing how the session * should interact with an existing app. */ public SessionParams(int mode) { this.mode = mode; mPermissionStates = new ArrayMap<>(); } /** {@hide} */ public SessionParams(Parcel source) { mode = source.readInt(); installFlags = source.readInt(); installLocation = source.readInt(); installReason = source.readInt(); installScenario = source.readInt(); sizeBytes = source.readLong(); appPackageName = source.readString(); appIcon = source.readParcelable(null, android.graphics.Bitmap.class); appLabel = source.readString(); originatingUri = source.readParcelable(null, android.net.Uri.class); originatingUid = source.readInt(); referrerUri = source.readParcelable(null, android.net.Uri.class); abiOverride = source.readString(); volumeUuid = source.readString(); mPermissionStates = new ArrayMap<>(); source.readMap(mPermissionStates, null, String.class, Integer.class); whitelistedRestrictedPermissions = source.createStringArrayList(); autoRevokePermissionsMode = source.readInt(); installerPackageName = source.readString(); isMultiPackage = source.readBoolean(); isStaged = source.readBoolean(); forceQueryableOverride = source.readBoolean(); requiredInstalledVersionCode = source.readLong(); DataLoaderParamsParcel dataLoaderParamsParcel = source.readParcelable( DataLoaderParamsParcel.class.getClassLoader(), android.content.pm.DataLoaderParamsParcel.class); if (dataLoaderParamsParcel != null) { dataLoaderParams = new DataLoaderParams(dataLoaderParamsParcel); } rollbackDataPolicy = source.readInt(); rollbackLifetimeMillis = source.readLong(); rollbackImpactLevel = source.readInt(); requireUserAction = source.readInt(); packageSource = source.readInt(); applicationEnabledSettingPersistent = source.readBoolean(); developmentInstallFlags = source.readInt(); unarchiveId = source.readInt(); } /** {@hide} */ public SessionParams copy() { SessionParams ret = new SessionParams(mode); ret.installFlags = installFlags; ret.installLocation = installLocation; ret.installReason = installReason; ret.installScenario = installScenario; ret.sizeBytes = sizeBytes; ret.appPackageName = appPackageName; ret.appIcon = appIcon; // not a copy. ret.appLabel = appLabel; ret.originatingUri = originatingUri; // not a copy, but immutable. ret.originatingUid = originatingUid; ret.referrerUri = referrerUri; // not a copy, but immutable. ret.abiOverride = abiOverride; ret.volumeUuid = volumeUuid; ret.mPermissionStates.putAll(mPermissionStates); ret.whitelistedRestrictedPermissions = whitelistedRestrictedPermissions; ret.autoRevokePermissionsMode = autoRevokePermissionsMode; ret.installerPackageName = installerPackageName; ret.isMultiPackage = isMultiPackage; ret.isStaged = isStaged; ret.forceQueryableOverride = forceQueryableOverride; ret.requiredInstalledVersionCode = requiredInstalledVersionCode; ret.dataLoaderParams = dataLoaderParams; ret.rollbackDataPolicy = rollbackDataPolicy; ret.rollbackLifetimeMillis = rollbackLifetimeMillis; ret.rollbackImpactLevel = rollbackImpactLevel; ret.requireUserAction = requireUserAction; ret.packageSource = packageSource; ret.applicationEnabledSettingPersistent = applicationEnabledSettingPersistent; ret.developmentInstallFlags = developmentInstallFlags; ret.unarchiveId = unarchiveId; return ret; } /** * Check if there are hidden options set. * *

Hidden options are those options that cannot be verified via public or system-api * methods on {@link SessionInfo}. * * @return {@code true} if any hidden option is set. * * @hide */ public boolean areHiddenOptionsSet() { return (installFlags & (PackageManager.INSTALL_REQUEST_DOWNGRADE | PackageManager.INSTALL_ALLOW_DOWNGRADE | PackageManager.INSTALL_DONT_KILL_APP | PackageManager.INSTALL_INSTANT_APP | PackageManager.INSTALL_FULL_APP | PackageManager.INSTALL_VIRTUAL_PRELOAD | PackageManager.INSTALL_ALLOCATE_AGGRESSIVE)) != installFlags || abiOverride != null || volumeUuid != null; } /** * Provide value of {@link PackageInfo#installLocation}, which may be used * to determine where the app will be staged. Defaults to * {@link PackageInfo#INSTALL_LOCATION_INTERNAL_ONLY}. */ public void setInstallLocation(int installLocation) { this.installLocation = installLocation; } /** * Optionally indicate the total size (in bytes) of all APKs that will be * delivered in this session. The system may use this to ensure enough disk * space exists before proceeding, or to estimate container size for * installations living on external storage. * * @see PackageInfo#INSTALL_LOCATION_AUTO * @see PackageInfo#INSTALL_LOCATION_PREFER_EXTERNAL */ public void setSize(long sizeBytes) { this.sizeBytes = sizeBytes; } /** * Optionally set the package name of the app being installed. It's strongly * recommended that you provide this value when known, so that observers can * communicate installing apps to users. *

* If the APKs staged in the session aren't consistent with this package * name, the install will fail. Regardless of this value, all APKs in the * app must have the same package name. */ public void setAppPackageName(@Nullable String appPackageName) { this.appPackageName = appPackageName; } /** * Optionally set an icon representing the app being installed. This should * be roughly {@link ActivityManager#getLauncherLargeIconSize()} in both * dimensions. */ public void setAppIcon(@Nullable Bitmap appIcon) { this.appIcon = appIcon; } /** * Optionally set a label representing the app being installed. * * This value will be trimmed to the first 1000 characters. */ public void setAppLabel(@Nullable CharSequence appLabel) { this.appLabel = (appLabel != null) ? appLabel.toString() : null; } /** * Optionally set the URI where this package was downloaded from. This is * informational and may be used as a signal for anti-malware purposes. * * @see Intent#EXTRA_ORIGINATING_URI */ public void setOriginatingUri(@Nullable Uri originatingUri) { this.originatingUri = originatingUri; } /** * Sets the UID that initiated the package installation. This is informational * and may be used as a signal for anti-malware purposes. */ public void setOriginatingUid(int originatingUid) { this.originatingUid = originatingUid; } /** * Optionally set the URI that referred you to install this package. This is * informational and may be used as a signal for anti-malware purposes. * * @see Intent#EXTRA_REFERRER */ public void setReferrerUri(@Nullable Uri referrerUri) { this.referrerUri = referrerUri; } /** * Sets which runtime permissions to be granted to the package at installation. * * @param permissions The permissions to grant or null to grant all runtime * permissions. * * @deprecated Prefer {@link #setPermissionState(String, int)} instead starting in * {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}. * @hide */ @Deprecated @SystemApi @RequiresPermission(android.Manifest.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS) public void setGrantedRuntimePermissions(String[] permissions) { if (permissions == null) { // The new API has no mechanism to grant all requested permissions installFlags |= PackageManager.INSTALL_GRANT_ALL_REQUESTED_PERMISSIONS; mPermissionStates.clear(); } else { installFlags &= ~PackageManager.INSTALL_GRANT_ALL_REQUESTED_PERMISSIONS; // Otherwise call the new API to grant the permissions specified for (String permission : permissions) { setPermissionState(permission, PERMISSION_STATE_GRANTED); } } } /** * Sets the state of permissions for the package at installation. *

* Granting any runtime permissions require the * {@code android.Manifest.permission#INSTALL_GRANT_RUNTIME_PERMISSIONS} * permission to be held by the caller. Revoking runtime * permissions is not allowed, even during app update sessions. *

* Holders without the permission are allowed to change the following special permissions: *

* On platform {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE UPSIDE_DOWN_CAKE}: *

    *
  • {@link Manifest.permission#USE_FULL_SCREEN_INTENT}
  • *
* Install time permissions, which cannot be revoked by the user, cannot be changed by the * installer. *

* See * Permissions on Android for more information. * * @param permissionName The permission to change state for. * @param state Either {@link #PERMISSION_STATE_DEFAULT}, * {@link #PERMISSION_STATE_GRANTED}, * or {@link #PERMISSION_STATE_DENIED} to set the permission to. * * @return This object for easier chaining. */ @RequiresPermission(value = android.Manifest.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS, conditional = true) @NonNull public SessionParams setPermissionState(@NonNull String permissionName, @PermissionState int state) { if (TextUtils.isEmpty(permissionName)) { throw new IllegalArgumentException("Provided permissionName cannot be " + (permissionName == null ? "null" : "empty")); } switch (state) { case PERMISSION_STATE_DEFAULT: mPermissionStates.remove(permissionName); break; case PERMISSION_STATE_GRANTED: case PERMISSION_STATE_DENIED: mPermissionStates.put(permissionName, state); break; default: throw new IllegalArgumentException("Unexpected permission state int: " + state); } return this; } /** @hide */ public void setPermissionStates(Collection grantPermissions, Collection denyPermissions) { for (String grantPermission : grantPermissions) { mPermissionStates.put(grantPermission, PERMISSION_STATE_GRANTED); } for (String denyPermission : denyPermissions) { mPermissionStates.put(denyPermission, PERMISSION_STATE_DENIED); } } /** * Optionally indicate the package source of the app being installed. This is * informational and may be used as a signal by the system. * * An installer should specify {@link #PACKAGE_SOURCE_OTHER} if no other package source * constant adequately reflects the source for this session. * * The default value is {@link #PACKAGE_SOURCE_UNSPECIFIED}. */ public void setPackageSource(@PackageSourceType int packageSource) { this.packageSource = packageSource; } /** * Sets which restricted permissions to be allowlisted for the app. Allowlisting * is not granting the permissions, rather it allows the app to hold permissions * which are otherwise restricted. Allowlisting a non restricted permission has * no effect. * *

Permissions can be hard restricted which means that the app cannot hold * them or soft restricted where the app can hold the permission but in a weaker * form. Whether a permission is {@link PermissionInfo#FLAG_HARD_RESTRICTED hard * restricted} or {@link PermissionInfo#FLAG_SOFT_RESTRICTED soft restricted} * depends on the permission declaration. Allowlisting a hard restricted permission * allows the app to hold that permission and allowlisting a soft restricted * permission allows the app to hold the permission in its full, unrestricted form. * *

Permissions can also be immutably restricted which means that the allowlist * state of the permission can be determined only at install time and cannot be * changed on updated or at a later point via the package manager APIs. * *

Initially, all restricted permissions are allowlisted but you can change * which ones are allowlisted by calling this method or the corresponding ones * on the {@link PackageManager}. Only soft or hard restricted permissions on the current * Android version are supported and any invalid entries will be removed. * * @see PackageManager#addWhitelistedRestrictedPermission(String, String, int) * @see PackageManager#removeWhitelistedRestrictedPermission(String, String, int) */ public void setWhitelistedRestrictedPermissions(@Nullable Set permissions) { if (permissions == RESTRICTED_PERMISSIONS_ALL) { installFlags |= PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS; whitelistedRestrictedPermissions = null; } else { installFlags &= ~PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS; whitelistedRestrictedPermissions = (permissions != null) ? new ArrayList<>(permissions) : null; } } /** * Sets whether permissions should be auto-revoked if this package is unused for an * extended periodd of time. * * It's disabled by default but generally the installer should enable it for most packages, * excluding only those where doing so might cause breakage that cannot be easily addressed * by simply re-requesting the permission(s). * * If user explicitly enabled or disabled it via settings, this call is ignored. * * @param shouldAutoRevoke whether permissions should be auto-revoked. * * @deprecated No longer used */ @Deprecated public void setAutoRevokePermissionsMode(boolean shouldAutoRevoke) { autoRevokePermissionsMode = shouldAutoRevoke ? MODE_ALLOWED : MODE_IGNORED; } /** * Request that rollbacks be enabled or disabled for the given upgrade with rollback data * policy set to RESTORE. * *

If the parent session is staged or has rollback enabled, all children sessions * must have the same properties. * * @param enable set to {@code true} to enable, {@code false} to disable * @see SessionParams#setEnableRollback(boolean, int) * @hide */ @SystemApi public void setEnableRollback(boolean enable) { setEnableRollback(enable, PackageManager.ROLLBACK_DATA_POLICY_RESTORE); } /** * Request that rollbacks be enabled or disabled for the given upgrade. * *

If the parent session is staged or has rollback enabled, all children sessions * must have the same properties. * *

For a multi-package install, this method must be called on each child session to * specify rollback data policies explicitly. Note each child session is allowed to have * different policies. * * @param enable set to {@code true} to enable, {@code false} to disable * @param dataPolicy the rollback data policy for this session * @hide */ @SystemApi public void setEnableRollback(boolean enable, @PackageManager.RollbackDataPolicy int dataPolicy) { if (enable) { installFlags |= PackageManager.INSTALL_ENABLE_ROLLBACK; } else { installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK; rollbackLifetimeMillis = 0; } rollbackDataPolicy = dataPolicy; } /** * If rollback enabled for this session (via {@link #setEnableRollback}, set period * after which rollback files will be deleted due to expiration * {@link RollbackManagerServiceImpl#deleteRollback}. * *

For multi-package installs, this value must be set on the parent session. * Child session rollback lifetime will be ignored. * * @param lifetimeMillis period after which rollback expires * @throws IllegalArgumentException if lifetimeMillis is negative or rollback is not * enabled via setEnableRollback. * @hide */ @SystemApi @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @FlaggedApi(Flags.FLAG_ROLLBACK_LIFETIME) public void setRollbackLifetimeMillis(@DurationMillisLong long lifetimeMillis) { if (lifetimeMillis < 0) { throw new IllegalArgumentException("rollbackLifetimeMillis can't be negative."); } if ((installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) == 0) { throw new IllegalArgumentException( "Can't set rollbackLifetimeMillis when rollback is not enabled"); } rollbackLifetimeMillis = lifetimeMillis; } /** * rollbackImpactLevel is a measure of impact a rollback has on the user. This can take one * of 3 values: *

    *
  • {@link PackageManager#ROLLBACK_USER_IMPACT_LOW} (default)
  • *
  • {@link PackageManager#ROLLBACK_USER_IMPACT_HIGH} (1)
  • *
  • {@link PackageManager#ROLLBACK_USER_IMPACT_ONLY_MANUAL} (2)
  • *
* * @hide */ @SystemApi @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @FlaggedApi(Flags.FLAG_RECOVERABILITY_DETECTION) public void setRollbackImpactLevel(@PackageManager.RollbackImpactLevel int impactLevel) { if ((installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) == 0) { throw new IllegalArgumentException( "Can't set rollbackImpactLevel when rollback is not enabled"); } rollbackImpactLevel = impactLevel; } /** * @deprecated use {@link #setRequestDowngrade(boolean)}. * {@hide} */ @SystemApi @Deprecated public void setAllowDowngrade(boolean allowDowngrade) { setRequestDowngrade(allowDowngrade); } /** {@hide} */ @SystemApi public void setRequestDowngrade(boolean requestDowngrade) { if (requestDowngrade) { installFlags |= PackageManager.INSTALL_REQUEST_DOWNGRADE; } else { installFlags &= ~PackageManager.INSTALL_REQUEST_DOWNGRADE; } } /** * Require the given version of the package be installed. * The install will only be allowed if the existing version code of * the package installed on the device matches the given version code. * Use {@link * PackageManager#VERSION_CODE_HIGHEST} to allow * installation regardless of the currently installed package version. * * @hide */ public void setRequiredInstalledVersionCode(long versionCode) { requiredInstalledVersionCode = versionCode; } /** {@hide} */ public void setInstallFlagsForcePermissionPrompt() { installFlags |= PackageManager.INSTALL_FORCE_PERMISSION_PROMPT; } /** * Requests that the system not kill any of the package's running * processes as part of a {@link SessionParams#MODE_INHERIT_EXISTING} * session in which splits being added. By default, all installs will * result in the package's running processes being killed before the * install completes. * * @param dontKillApp set to {@code true} to request that the processes * belonging to the package not be killed as part of * this install. */ public void setDontKillApp(boolean dontKillApp) { if (dontKillApp) { installFlags |= PackageManager.INSTALL_DONT_KILL_APP; } else { installFlags &= ~PackageManager.INSTALL_DONT_KILL_APP; } } /** {@hide} */ @SystemApi public void setInstallAsInstantApp(boolean isInstantApp) { if (isInstantApp) { installFlags |= PackageManager.INSTALL_INSTANT_APP; installFlags &= ~PackageManager.INSTALL_FULL_APP; } else { installFlags &= ~PackageManager.INSTALL_INSTANT_APP; installFlags |= PackageManager.INSTALL_FULL_APP; } } /** * Sets the install as a virtual preload. Will only have effect when called * by the verifier. * {@hide} */ @SystemApi public void setInstallAsVirtualPreload() { installFlags |= PackageManager.INSTALL_VIRTUAL_PRELOAD; } /** * Set the reason for installing this package. *

* The install reason should be a pre-defined integer. The behavior is * undefined if other values are used. * * @see PackageManager#INSTALL_REASON_UNKNOWN * @see PackageManager#INSTALL_REASON_POLICY * @see PackageManager#INSTALL_REASON_DEVICE_RESTORE * @see PackageManager#INSTALL_REASON_DEVICE_SETUP * @see PackageManager#INSTALL_REASON_USER */ public void setInstallReason(@InstallReason int installReason) { this.installReason = installReason; } /** {@hide} */ @SystemApi @RequiresPermission(android.Manifest.permission.ALLOCATE_AGGRESSIVE) public void setAllocateAggressive(boolean allocateAggressive) { if (allocateAggressive) { installFlags |= PackageManager.INSTALL_ALLOCATE_AGGRESSIVE; } else { installFlags &= ~PackageManager.INSTALL_ALLOCATE_AGGRESSIVE; } } /** * @hide */ @TestApi public void setInstallFlagAllowTest() { installFlags |= PackageManager.INSTALL_ALLOW_TEST; } /** * Set the installer package for the app. * * By default this is the app that created the {@link PackageInstaller} object. * * Note: Only applications with {@link android.Manifest.permission#INSTALL_PACKAGES} * permission are allowed to set an installer that is not the caller's own installer * package name, otherwise it will cause a {@link SecurityException} when creating the * install session. * * @param installerPackageName The name of the installer package, its length must be less * than {@code 255}, otherwise it will be invalid. */ public void setInstallerPackageName(@Nullable String installerPackageName) { 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; } /** * Set this session to be staged to be installed at reboot. * * Staged sessions are scheduled to be installed at next reboot. Staged sessions can also be * multi-package. In that case, if any of the children sessions fail to install at reboot, * all the other children sessions are aborted as well. * *

If the parent session is staged or has rollback enabled, all children sessions * must have the same properties. * * {@hide} */ @SystemApi @RequiresPermission(Manifest.permission.INSTALL_PACKAGES) public void setStaged() { this.isStaged = true; } /** * Set this session to be installing an APEX package. * * {@hide} */ @SystemApi @RequiresPermission(Manifest.permission.INSTALL_PACKAGES) public void setInstallAsApex() { installFlags |= PackageManager.INSTALL_APEX; } /** @hide */ public boolean getEnableRollback() { return (installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0; } /** * Set the data loader params for the session. * This also switches installation into data loading mode and disallow direct writes into * staging folder. * * @see android.service.dataloader.DataLoaderService.DataLoader * * {@hide} */ @SystemApi @RequiresPermission(allOf = { Manifest.permission.INSTALL_PACKAGES, Manifest.permission.USE_INSTALLER_V2}) public void setDataLoaderParams(@NonNull DataLoaderParams dataLoaderParams) { this.dataLoaderParams = dataLoaderParams; } /** * * {@hide} */ public void setForceQueryable() { this.forceQueryableOverride = true; } /** * Optionally indicate whether user action should be required when the session is * committed. *

* Defaults to {@link #USER_ACTION_UNSPECIFIED} unless otherwise set. When unspecified for * installers using the * {@link android.Manifest.permission#REQUEST_INSTALL_PACKAGES REQUEST_INSTALL_PACKAGES} * permission will behave as if set to {@link #USER_ACTION_REQUIRED}, and * {@link #USER_ACTION_NOT_REQUIRED} otherwise. When {@code requireUserAction} is set to * {@link #USER_ACTION_REQUIRED}, installers will receive a * {@link #STATUS_PENDING_USER_ACTION} callback once the session is committed, indicating * that user action is required for the install to proceed. *

* For installers that have been granted the * {@link android.Manifest.permission#REQUEST_INSTALL_PACKAGES REQUEST_INSTALL_PACKAGES} * permission, user action will not be required when all of the following conditions are * met: * *

    *
  • {@code requireUserAction} is set to {@link #USER_ACTION_NOT_REQUIRED}.
  • *
  • The app being installed targets: *
      *
    • {@link android.os.Build.VERSION_CODES#Q API 29} or higher on * Android S ({@link android.os.Build.VERSION_CODES#S API 31})
    • *
    • {@link android.os.Build.VERSION_CODES#R API 30} or higher on * Android T ({@link android.os.Build.VERSION_CODES#TIRAMISU API 33})
    • *
    • {@link android.os.Build.VERSION_CODES#S API 31} or higher after * Android T ({@link android.os.Build.VERSION_CODES#TIRAMISU API 33})
    • *
    *
  • *
  • The installer is: *
      *
    • The {@link InstallSourceInfo#getUpdateOwnerPackageName() update owner} * of an existing version of the app (in other words, this install session is * an app update) if the update ownership enforcement is enabled.
    • *
    • The * {@link InstallSourceInfo#getInstallingPackageName() installer of record} * of an existing version of the app (in other words, this install * session is an app update) if the update ownership enforcement isn't * enabled.
    • *
    • Updating itself.
    • *
    *
  • *
  • The installer declares the * {@link android.Manifest.permission#UPDATE_PACKAGES_WITHOUT_USER_ACTION * UPDATE_PACKAGES_WITHOUT_USER_ACTION} permission.
  • *
*

* Note: The target API level requirement will advance in future Android versions. * Session owners should always be prepared to handle {@link #STATUS_PENDING_USER_ACTION}. * * @param requireUserAction whether user action should be required. */ public void setRequireUserAction( @SessionParams.UserActionRequirement int requireUserAction) { if (requireUserAction != USER_ACTION_UNSPECIFIED && requireUserAction != USER_ACTION_REQUIRED && requireUserAction != USER_ACTION_NOT_REQUIRED) { throw new IllegalArgumentException("requireUserAction set as invalid value of " + requireUserAction + ", but must be one of [" + "USER_ACTION_UNSPECIFIED, USER_ACTION_REQUIRED, USER_ACTION_NOT_REQUIRED" + "]"); } this.requireUserAction = requireUserAction; } /** * Sets the install scenario for this session, which describes the expected user journey. */ public void setInstallScenario(@InstallScenario int installScenario) { this.installScenario = installScenario; } /** * Request to keep the original application enabled setting. This will prevent the * application from being enabled if it was previously in a disabled state. */ public void setApplicationEnabledSettingPersistent() { this.applicationEnabledSettingPersistent = true; } /** * Optionally indicate whether the package being installed needs the update ownership * enforcement. Once the update ownership enforcement is enabled, the other installers * will need the user action to update the package even if the installers have been * granted the {@link android.Manifest.permission#INSTALL_PACKAGES INSTALL_PACKAGES} * permission. Default to {@code false}. * * The update ownership enforcement can only be enabled on initial installation. Set * this to {@code true} on package update is a no-op. * * Note: To enable the update ownership enforcement, the installer must have the * {@link android.Manifest.permission#ENFORCE_UPDATE_OWNERSHIP ENFORCE_UPDATE_OWNERSHIP} * permission. */ @RequiresPermission(Manifest.permission.ENFORCE_UPDATE_OWNERSHIP) public void setRequestUpdateOwnership(boolean enable) { if (enable) { this.installFlags |= PackageManager.INSTALL_REQUEST_UPDATE_OWNERSHIP; } else { this.installFlags &= ~PackageManager.INSTALL_REQUEST_UPDATE_OWNERSHIP; } } /** * Used to set the unarchive ID received as part of an * {@link Intent#ACTION_UNARCHIVE_PACKAGE}. * *

The ID should be retrieved from the unarchive intent and passed into the * session that's being created to unarchive the app in question. Used to link the unarchive * intent and the install session to disambiguate. */ @FlaggedApi(Flags.FLAG_ARCHIVING) public void setUnarchiveId(int unarchiveId) { this.unarchiveId = unarchiveId; } /** @hide */ @NonNull public ArrayMap getPermissionStates() { return mPermissionStates; } /** @hide */ @Nullable public String[] getLegacyGrantedRuntimePermissions() { if ((installFlags & PackageManager.INSTALL_GRANT_ALL_REQUESTED_PERMISSIONS) != 0) { return null; } var grantedPermissions = new ArrayList(); for (int index = 0; index < mPermissionStates.size(); index++) { var permissionName = mPermissionStates.keyAt(index); var state = mPermissionStates.valueAt(index); if (state == PERMISSION_STATE_GRANTED) { grantedPermissions.add(permissionName); } } return grantedPermissions.toArray(ArrayUtils.emptyArray(String.class)); } /** {@hide} */ public void dump(IndentingPrintWriter pw) { pw.printPair("mode", mode); pw.printHexPair("installFlags", installFlags); pw.printPair("installLocation", installLocation); pw.printPair("installReason", installReason); pw.printPair("installScenario", installScenario); pw.printPair("sizeBytes", sizeBytes); pw.printPair("appPackageName", appPackageName); pw.printPair("appIcon", (appIcon != null)); pw.printPair("appLabel", appLabel); pw.printPair("originatingUri", originatingUri); pw.printPair("originatingUid", originatingUid); pw.printPair("referrerUri", referrerUri); pw.printPair("abiOverride", abiOverride); pw.printPair("volumeUuid", volumeUuid); pw.printPair("mPermissionStates", mPermissionStates); pw.printPair("packageSource", packageSource); pw.printPair("whitelistedRestrictedPermissions", whitelistedRestrictedPermissions); pw.printPair("autoRevokePermissions", autoRevokePermissionsMode); pw.printPair("installerPackageName", installerPackageName); pw.printPair("isMultiPackage", isMultiPackage); pw.printPair("isStaged", isStaged); pw.printPair("forceQueryable", forceQueryableOverride); pw.printPair("requireUserAction", SessionInfo.userActionToString(requireUserAction)); pw.printPair("requiredInstalledVersionCode", requiredInstalledVersionCode); pw.printPair("dataLoaderParams", dataLoaderParams); pw.printPair("rollbackDataPolicy", rollbackDataPolicy); pw.printPair("rollbackLifetimeMillis", rollbackLifetimeMillis); pw.printPair("rollbackImpactLevel", rollbackImpactLevel); pw.printPair("applicationEnabledSettingPersistent", applicationEnabledSettingPersistent); pw.printHexPair("developmentInstallFlags", developmentInstallFlags); pw.printPair("unarchiveId", unarchiveId); pw.println(); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mode); dest.writeInt(installFlags); dest.writeInt(installLocation); dest.writeInt(installReason); dest.writeInt(installScenario); dest.writeLong(sizeBytes); dest.writeString(appPackageName); dest.writeParcelable(appIcon, flags); dest.writeString(appLabel); dest.writeParcelable(originatingUri, flags); dest.writeInt(originatingUid); dest.writeParcelable(referrerUri, flags); dest.writeString(abiOverride); dest.writeString(volumeUuid); dest.writeMap(mPermissionStates); dest.writeStringList(whitelistedRestrictedPermissions); dest.writeInt(autoRevokePermissionsMode); dest.writeString(installerPackageName); dest.writeBoolean(isMultiPackage); dest.writeBoolean(isStaged); dest.writeBoolean(forceQueryableOverride); dest.writeLong(requiredInstalledVersionCode); if (dataLoaderParams != null) { dest.writeParcelable(dataLoaderParams.getData(), flags); } else { dest.writeParcelable(null, flags); } dest.writeInt(rollbackDataPolicy); dest.writeLong(rollbackLifetimeMillis); dest.writeInt(rollbackImpactLevel); dest.writeInt(requireUserAction); dest.writeInt(packageSource); dest.writeBoolean(applicationEnabledSettingPersistent); dest.writeInt(developmentInstallFlags); dest.writeInt(unarchiveId); } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public SessionParams createFromParcel(Parcel p) { return new SessionParams(p); } @Override public SessionParams[] newArray(int size) { return new SessionParams[size]; } }; } /** * Details for an active install session. */ 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 = {}; /** * @deprecated use {@link #SESSION_NO_ERROR}. */ @Deprecated public static final int STAGED_SESSION_NO_ERROR = 0; /** * @deprecated use {@link #SESSION_VERIFICATION_FAILED}. */ @Deprecated public static final int STAGED_SESSION_VERIFICATION_FAILED = 1; /** * @deprecated use {@link #SESSION_ACTIVATION_FAILED}. */ @Deprecated public static final int STAGED_SESSION_ACTIVATION_FAILED = 2; /** * @deprecated use {@link #SESSION_UNKNOWN_ERROR}. */ @Deprecated public static final int STAGED_SESSION_UNKNOWN = 3; /** * @deprecated use {@link #SESSION_CONFLICT}. */ @Deprecated public static final int STAGED_SESSION_CONFLICT = 4; /** * Constant indicating that no error occurred during the preparation or the activation of * this session. */ public static final int SESSION_NO_ERROR = 0; /** * Constant indicating that an error occurred during the verification phase of * this session. */ public static final int SESSION_VERIFICATION_FAILED = 1; /** * Constant indicating that an error occurred during the activation phase of * this session. */ public static final int SESSION_ACTIVATION_FAILED = 2; /** * Constant indicating that an unknown error occurred while processing this session. */ public static final int SESSION_UNKNOWN_ERROR = 3; /** * Constant indicating that the session was in conflict with another session and had * to be sacrificed for resolution. */ public static final int SESSION_CONFLICT = 4; private static String userActionToString(int requireUserAction) { switch(requireUserAction) { case SessionParams.USER_ACTION_REQUIRED: return "REQUIRED"; case SessionParams.USER_ACTION_NOT_REQUIRED: return "NOT_REQUIRED"; default: return "UNSPECIFIED"; } } /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public int sessionId; /** {@hide} */ public int userId; /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public String installerPackageName; /** {@hide} */ public String installerAttributionTag; /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public String resolvedBaseCodePath; /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public float progress; /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean sealed; /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public boolean active; /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public int mode; /** {@hide} */ public @InstallReason int installReason; /** {@hide} */ public @InstallScenario int installScenario; /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public long sizeBytes; /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public String appPackageName; /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public Bitmap appIcon; /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public CharSequence appLabel; /** {@hide} */ public int installLocation; /** {@hide} */ public Uri originatingUri; /** {@hide} */ public int originatingUid; /** {@hide} */ public Uri referrerUri; /** {@hide} */ public String[] grantedRuntimePermissions; /** {@hide}*/ public List whitelistedRestrictedPermissions; /** {@hide}*/ public int autoRevokePermissionsMode = MODE_DEFAULT; /** {@hide} */ public int installFlags; /** {@hide} */ public boolean isMultiPackage; /** {@hide} */ public boolean isStaged; /** {@hide} */ public boolean forceQueryable; /** {@hide} */ public int parentSessionId = INVALID_ID; /** {@hide} */ public int[] childSessionIds = NO_SESSIONS; /** {@hide} */ public boolean isSessionApplied; /** {@hide} */ public boolean isSessionReady; /** {@hide} */ public boolean isSessionFailed; private int mSessionErrorCode; private String mSessionErrorMessage; /** {@hide} */ public boolean isCommitted; /** {@hide} */ public long createdMillis; /** {@hide} */ public long updatedMillis; /** {@hide} */ public int rollbackDataPolicy; /** @hide */ public long rollbackLifetimeMillis; /** {@hide} */ public int rollbackImpactLevel; /** {@hide} */ public int requireUserAction; /** {@hide} */ public int packageSource = PACKAGE_SOURCE_UNSPECIFIED; /** {@hide} */ public int installerUid; /** @hide */ public boolean isPreapprovalRequested; /** @hide */ public boolean applicationEnabledSettingPersistent; /** @hide */ public int pendingUserActionReason; /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public SessionInfo() { } /** {@hide} */ public SessionInfo(Parcel source) { sessionId = source.readInt(); userId = source.readInt(); installerPackageName = source.readString(); installerAttributionTag = source.readString(); resolvedBaseCodePath = source.readString(); progress = source.readFloat(); sealed = source.readInt() != 0; active = source.readInt() != 0; mode = source.readInt(); installReason = source.readInt(); installScenario = source.readInt(); sizeBytes = source.readLong(); appPackageName = source.readString(); appIcon = source.readParcelable(null, android.graphics.Bitmap.class); appLabel = source.readString(); installLocation = source.readInt(); originatingUri = source.readParcelable(null, android.net.Uri.class); originatingUid = source.readInt(); referrerUri = source.readParcelable(null, android.net.Uri.class); grantedRuntimePermissions = source.readStringArray(); whitelistedRestrictedPermissions = source.createStringArrayList(); autoRevokePermissionsMode = source.readInt(); installFlags = source.readInt(); isMultiPackage = source.readBoolean(); isStaged = source.readBoolean(); forceQueryable = source.readBoolean(); parentSessionId = source.readInt(); childSessionIds = source.createIntArray(); if (childSessionIds == null) { childSessionIds = NO_SESSIONS; } isSessionApplied = source.readBoolean(); isSessionReady = source.readBoolean(); isSessionFailed = source.readBoolean(); mSessionErrorCode = source.readInt(); mSessionErrorMessage = source.readString(); isCommitted = source.readBoolean(); isPreapprovalRequested = source.readBoolean(); rollbackDataPolicy = source.readInt(); rollbackLifetimeMillis = source.readLong(); rollbackImpactLevel = source.readInt(); createdMillis = source.readLong(); requireUserAction = source.readInt(); installerUid = source.readInt(); packageSource = source.readInt(); applicationEnabledSettingPersistent = source.readBoolean(); pendingUserActionReason = source.readInt(); } /** * Return the ID for this session. */ public int getSessionId() { return sessionId; } /** * Return the user associated with this session. */ public @NonNull UserHandle getUser() { return new UserHandle(userId); } /** * Return the package name of the app that owns this session. */ public @Nullable String getInstallerPackageName() { return installerPackageName; } /** * @return {@link android.content.Context#getAttributionTag attribution tag} of the context * that created this session */ public @Nullable String getInstallerAttributionTag() { return installerAttributionTag; } /** * Return current overall progress of this session, between 0 and 1. *

* Note that this progress may not directly correspond to the value * reported by * {@link PackageInstaller.Session#setStagingProgress(float)}, as the * system may carve out a portion of the overall progress to represent * its own internal installation work. */ public float getProgress() { return progress; } /** * Return if this session is currently active. *

* A session is considered active whenever there is ongoing forward * progress being made, such as the installer holding an open * {@link Session} instance while streaming data into place, or the * system optimizing code as the result of * {@link Session#commit(IntentSender)}. *

* If the installer closes the {@link Session} without committing, the * session is considered inactive until the installer opens the session * again. */ public boolean isActive() { return active; } /** * Return if this session is sealed. *

* Once sealed, no further changes may be made to the session. A session * is sealed the moment {@link Session#commit(IntentSender)} is called. */ public boolean isSealed() { return sealed; } /** * Return the reason for installing this package. * * @return The install reason. */ public @InstallReason int getInstallReason() { return installReason; } /** {@hide} */ @Deprecated public boolean isOpen() { return isActive(); } /** * Return the package name this session is working with. May be {@code null} * if unknown. */ public @Nullable String getAppPackageName() { return appPackageName; } /** * Return an icon representing the app being installed. May be {@code null} * if unavailable. */ public @Nullable Bitmap getAppIcon() { if (appIcon == null) { // Icon may have been omitted for calls that return bulk session // lists, so try fetching the specific icon. try { final SessionInfo info = AppGlobals.getPackageManager().getPackageInstaller() .getSessionInfo(sessionId); appIcon = (info != null) ? info.appIcon : null; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } return appIcon; } /** * Return a label representing the app being installed. May be {@code null} * if unavailable. */ public @Nullable CharSequence getAppLabel() { return appLabel; } /** * Return an Intent that can be started to view details about this install * session. This may surface actions such as pause, resume, or cancel. *

* In some cases, a matching Activity may not exist, so ensure you safeguard * against this. * * @see PackageInstaller#ACTION_SESSION_DETAILS */ public @Nullable Intent createDetailsIntent() { final Intent intent = new Intent(PackageInstaller.ACTION_SESSION_DETAILS); intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId); intent.setPackage(installerPackageName); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); return intent; } /** * Get the mode of the session as set in the constructor of the {@link SessionParams}. * * @return One of {@link SessionParams#MODE_FULL_INSTALL} * or {@link SessionParams#MODE_INHERIT_EXISTING} */ public int getMode() { return mode; } /** * Get the value set in {@link SessionParams#setInstallLocation(int)}. */ public int getInstallLocation() { return installLocation; } /** * Get the value as set in {@link SessionParams#setSize(long)}. * *

The value is a hint and does not have to match the actual size. */ public long getSize() { return sizeBytes; } /** * Get the value set in {@link SessionParams#setOriginatingUri(Uri)}. * Note: This value will only be non-null for the owner of the session. */ public @Nullable Uri getOriginatingUri() { return originatingUri; } /** * Get the value set in {@link SessionParams#setOriginatingUid(int)}. */ public int getOriginatingUid() { return originatingUid; } /** * Get the value set in {@link SessionParams#setReferrerUri(Uri)} * Note: This value will only be non-null for the owner of the session. */ public @Nullable Uri getReferrerUri() { return referrerUri; } /** * @return the path to the validated base APK for this session, which may point at an * APK inside the session (when the session defines the base), or it may * point at the existing base APK (when adding splits to an existing app). * * @hide */ @SystemApi @RequiresPermission(Manifest.permission.READ_INSTALLED_SESSION_PATHS) @FlaggedApi(Flags.FLAG_GET_RESOLVED_APK_PATH) public @Nullable String getResolvedBaseApkPath() { return resolvedBaseCodePath; } /** * Get the value set in {@link SessionParams#setGrantedRuntimePermissions(String[])}. * * @hide */ @SystemApi public @Nullable String[] getGrantedRuntimePermissions() { return grantedRuntimePermissions; } /** * Get the value set in {@link SessionParams#setWhitelistedRestrictedPermissions(Set)}. * Note that if all permissions are allowlisted this method returns {@link * SessionParams#RESTRICTED_PERMISSIONS_ALL}. * * @hide */ @SystemApi public @NonNull Set getWhitelistedRestrictedPermissions() { if ((installFlags & PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS) != 0) { return SessionParams.RESTRICTED_PERMISSIONS_ALL; } if (whitelistedRestrictedPermissions != null) { return new ArraySet<>(whitelistedRestrictedPermissions); } return Collections.emptySet(); } /** * Get the status of whether permission auto-revocation should be allowed, ignored, or * deferred to manifest data. * * @see android.app.AppOpsManager#MODE_ALLOWED * @see android.app.AppOpsManager#MODE_IGNORED * @see android.app.AppOpsManager#MODE_DEFAULT * * @return the status of auto-revoke for this package * * @hide */ @SystemApi public int getAutoRevokePermissionsMode() { return autoRevokePermissionsMode; } /** * Get the value set in {@link SessionParams#setAllowDowngrade(boolean)}. * * @deprecated use {@link #getRequestDowngrade()}. * @hide */ @SystemApi @Deprecated public boolean getAllowDowngrade() { return getRequestDowngrade(); } /** * Get the value set in {@link SessionParams#setRequestDowngrade(boolean)}. * * @hide */ @SystemApi public boolean getRequestDowngrade() { return (installFlags & PackageManager.INSTALL_REQUEST_DOWNGRADE) != 0; } /** * Get the value set in {@link SessionParams#setDontKillApp(boolean)}. */ public boolean getDontKillApp() { return (installFlags & PackageManager.INSTALL_DONT_KILL_APP) != 0; } /** * Get if this session is to be installed as Instant Apps. * * @param isInstantApp an unused parameter and is ignored. * @return {@code true} if {@link SessionParams#setInstallAsInstantApp(boolean)} was called * with {@code true}; {@code false} if it was called with {@code false} or if it was not * called. * * @see #getInstallAsFullApp * * @hide */ @SystemApi public boolean getInstallAsInstantApp(boolean isInstantApp) { return (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0; } /** * Get if this session is to be installed as full apps. * * @param isInstantApp an unused parameter and is ignored. * @return {@code true} if {@link SessionParams#setInstallAsInstantApp(boolean)} was called * with {@code false}; {code false} if it was called with {@code true} or if it was not * called. * * @see #getInstallAsInstantApp * * @hide */ @SystemApi public boolean getInstallAsFullApp(boolean isInstantApp) { return (installFlags & PackageManager.INSTALL_FULL_APP) != 0; } /** * Get if {@link SessionParams#setInstallAsVirtualPreload()} was called. * * @hide */ @SystemApi public boolean getInstallAsVirtualPreload() { return (installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0; } /** * Return whether rollback is enabled or disabled for the given upgrade. * * @hide */ @SystemApi public boolean getEnableRollback() { return (installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0; } /** * Get the value set in {@link SessionParams#setAllocateAggressive(boolean)}. * * @hide */ @SystemApi public boolean getAllocateAggressive() { return (installFlags & PackageManager.INSTALL_ALLOCATE_AGGRESSIVE) != 0; } /** {@hide} */ @Deprecated public @Nullable Intent getDetailsIntent() { return createDetailsIntent(); } /** * Get the package source that was set in * {@link PackageInstaller.SessionParams#setPackageSource(int)}. */ public @PackageSourceType int getPackageSource() { return packageSource; } /** * Returns true if this session is a multi-package session containing references to other * sessions. */ public boolean isMultiPackage() { return isMultiPackage; } /** * Returns true if this session is a staged session. */ public boolean isStaged() { return isStaged; } /** * Return the data policy associated with the rollback for the given upgrade. * * @hide */ @SystemApi @PackageManager.RollbackDataPolicy public int getRollbackDataPolicy() { return rollbackDataPolicy; } /** * Returns true if this session is marked as forceQueryable * {@hide} */ public boolean isForceQueryable() { return forceQueryable; } /** * Returns {@code true} if this session is an active staged session. * * We consider a session active if it has been committed and it is either pending * verification, or will be applied at next reboot. * *

Staged session is active iff: *

    *
  • It is committed, i.e. {@link SessionInfo#isCommitted()} is {@code true}, and *
  • it is not applied, i.e. {@link SessionInfo#isStagedSessionApplied()} is {@code * false}, and *
  • it is not failed, i.e. {@link SessionInfo#isStagedSessionFailed()} is * {@code false}. *
* *

In case of a multi-package session, reasoning above is applied to the parent session, * since that is the one that should have been {@link Session#commit committed}. */ public boolean isStagedSessionActive() { return isStaged && isCommitted && !isSessionApplied && !isSessionFailed && !hasParentSessionId(); } /** * Returns the parent multi-package session ID if this session belongs to one, * {@link #INVALID_ID} otherwise. */ public int getParentSessionId() { return parentSessionId; } /** * Returns true if session has a valid parent session, otherwise false. */ public boolean hasParentSessionId() { return parentSessionId != INVALID_ID; } /** * Returns the set of session IDs that will be committed when this session is committed if * this session is a multi-package session. */ @NonNull public int[] getChildSessionIds() { return childSessionIds; } private void checkSessionIsStaged() { if (!isStaged) { throw new IllegalStateException("Session is not marked as staged."); } } /** * Whether the staged session has been applied successfully, meaning that all of its * packages have been activated and no further action is required. * Only meaningful if {@code isStaged} is true. */ public boolean isStagedSessionApplied() { checkSessionIsStaged(); return isSessionApplied; } /** * Whether the staged session is ready to be applied at next reboot. Only meaningful if * {@code isStaged} is true. */ public boolean isStagedSessionReady() { checkSessionIsStaged(); return isSessionReady; } /** * Whether something went wrong and the staged session is declared as failed, meaning that * it will be ignored at next reboot. Only meaningful if {@code isStaged} is true. */ public boolean isStagedSessionFailed() { checkSessionIsStaged(); return isSessionFailed; } /** * If something went wrong with a staged session, clients can check this error code to * understand which kind of failure happened. Only meaningful if {@code isStaged} is true. */ public int getStagedSessionErrorCode() { checkSessionIsStaged(); return mSessionErrorCode; } /** * Text description of the error code returned by {@code getStagedSessionErrorCode}, or * empty string if no error was encountered. */ public @NonNull String getStagedSessionErrorMessage() { checkSessionIsStaged(); return mSessionErrorMessage; } /** {@hide} */ public void setSessionErrorCode(int errorCode, String errorMessage) { mSessionErrorCode = errorCode; mSessionErrorMessage = errorMessage; } /** * Returns {@code true} if {@link Session#commit(IntentSender)}} was called for this * session. */ public boolean isCommitted() { return isCommitted; } /** * The timestamp of the initial creation of the session. */ public long getCreatedMillis() { return createdMillis; } /** * The timestamp of the last update that occurred to the session, including changing of * states in case of staged sessions. */ @CurrentTimeMillisLong public long getUpdatedMillis() { return updatedMillis; } /** * Whether user action was required by the installer. * *

* Note: a return value of {@code USER_ACTION_NOT_REQUIRED} does not guarantee that the * install will not result in user action. * * @return {@link SessionParams#USER_ACTION_NOT_REQUIRED}, * {@link SessionParams#USER_ACTION_REQUIRED} or * {@link SessionParams#USER_ACTION_UNSPECIFIED} */ @SessionParams.UserActionRequirement public int getRequireUserAction() { return requireUserAction; } /** * Returns the Uid of the owner of the session. */ public int getInstallerUid() { return installerUid; } /** * Returns {@code true} if this session will keep the existing application enabled setting * after installation. */ public boolean isApplicationEnabledSettingPersistent() { return applicationEnabledSettingPersistent; } /** * Returns whether this session has requested user pre-approval. */ public boolean isPreApprovalRequested() { return isPreapprovalRequested; } /** * @return {@code true} if the installer requested the update ownership enforcement * for the packages in this session. * * @see PackageInstaller.SessionParams#setRequestUpdateOwnership */ public boolean isRequestUpdateOwnership() { return (installFlags & PackageManager.INSTALL_REQUEST_UPDATE_OWNERSHIP) != 0; } /** * Return the reason for requiring the user action. * @hide */ @SystemApi public @UserActionReason int getPendingUserActionReason() { return pendingUserActionReason; } /** * Returns true if the session is an unarchival. * * @see PackageInstaller#requestUnarchive */ @FlaggedApi(Flags.FLAG_ARCHIVING) public boolean isUnarchival() { return (installFlags & PackageManager.INSTALL_UNARCHIVE) != 0; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(sessionId); dest.writeInt(userId); dest.writeString(installerPackageName); dest.writeString(installerAttributionTag); dest.writeString(resolvedBaseCodePath); dest.writeFloat(progress); dest.writeInt(sealed ? 1 : 0); dest.writeInt(active ? 1 : 0); dest.writeInt(mode); dest.writeInt(installReason); dest.writeInt(installScenario); dest.writeLong(sizeBytes); dest.writeString(appPackageName); dest.writeParcelable(appIcon, flags); dest.writeString(appLabel != null ? appLabel.toString() : null); dest.writeInt(installLocation); dest.writeParcelable(originatingUri, flags); dest.writeInt(originatingUid); dest.writeParcelable(referrerUri, flags); dest.writeStringArray(grantedRuntimePermissions); dest.writeStringList(whitelistedRestrictedPermissions); dest.writeInt(autoRevokePermissionsMode); dest.writeInt(installFlags); dest.writeBoolean(isMultiPackage); dest.writeBoolean(isStaged); dest.writeBoolean(forceQueryable); dest.writeInt(parentSessionId); dest.writeIntArray(childSessionIds); dest.writeBoolean(isSessionApplied); dest.writeBoolean(isSessionReady); dest.writeBoolean(isSessionFailed); dest.writeInt(mSessionErrorCode); dest.writeString(mSessionErrorMessage); dest.writeBoolean(isCommitted); dest.writeBoolean(isPreapprovalRequested); dest.writeInt(rollbackDataPolicy); dest.writeLong(rollbackLifetimeMillis); dest.writeInt(rollbackImpactLevel); dest.writeLong(createdMillis); dest.writeInt(requireUserAction); dest.writeInt(installerUid); dest.writeInt(packageSource); dest.writeBoolean(applicationEnabledSettingPersistent); dest.writeInt(pendingUserActionReason); } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public SessionInfo createFromParcel(Parcel p) { return new SessionInfo(p); } @Override public SessionInfo[] newArray(int size) { return new SessionInfo[size]; } }; } /** * Details for requesting the pre-commit install approval. */ @DataClass(genConstructor = false, genToString = true) public static final class PreapprovalDetails implements Parcelable { /** * The icon representing the app to be installed. */ private final @Nullable Bitmap mIcon; /** * The label representing the app to be installed. */ private final @NonNull CharSequence mLabel; /** * The locale of the app label being used. */ private final @NonNull ULocale mLocale; /** * The package name of the app to be installed. */ private final @NonNull String mPackageName; /** * Creates a new PreapprovalDetails. * * @param icon * The icon representing the app to be installed. * @param label * The label representing the app to be installed. * @param locale * The locale is used to get the app label from the APKs (includes the base APK and * split APKs) related to the package to be installed. * @param packageName * The package name of the app to be installed. * @hide */ public PreapprovalDetails( @Nullable Bitmap icon, @NonNull CharSequence label, @NonNull ULocale locale, @NonNull String packageName) { mIcon = icon; mLabel = label; Preconditions.checkArgument(!TextUtils.isEmpty(mLabel), "App label cannot be empty."); mLocale = locale; Preconditions.checkArgument(!Objects.isNull(mLocale), "Locale cannot be null."); mPackageName = packageName; Preconditions.checkArgument(!TextUtils.isEmpty(mPackageName), "Package name cannot be empty."); } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { byte flg = 0; if (mIcon != null) flg |= 0x1; dest.writeByte(flg); if (mIcon != null) mIcon.writeToParcel(dest, flags); dest.writeCharSequence(mLabel); dest.writeString8(mLocale.toString()); dest.writeString8(mPackageName); } @Override public int describeContents() { return 0; } /** @hide */ /* package-private */ PreapprovalDetails(@NonNull Parcel in) { byte flg = in.readByte(); final Bitmap icon = (flg & 0x1) == 0 ? null : Bitmap.CREATOR.createFromParcel(in); final CharSequence label = in.readCharSequence(); final ULocale locale = new ULocale(in.readString8()); final String packageName = in.readString8(); mIcon = icon; mLabel = label; Preconditions.checkArgument(!TextUtils.isEmpty(mLabel), "App label cannot be empty."); mLocale = locale; Preconditions.checkArgument(!Objects.isNull(mLocale), "Locale cannot be null."); mPackageName = packageName; Preconditions.checkArgument(!TextUtils.isEmpty(mPackageName), "Package name cannot be empty."); } public static final @NonNull Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public PreapprovalDetails[] newArray(int size) { return new PreapprovalDetails[size]; } @Override public PreapprovalDetails createFromParcel(@NonNull Parcel in) { return new PreapprovalDetails(in); } }; /** * A builder for {@link PreapprovalDetails} */ public static final class Builder { private @Nullable Bitmap mIcon; private @NonNull CharSequence mLabel; private @NonNull ULocale mLocale; private @NonNull String mPackageName; private long mBuilderFieldsSet = 0L; /** * Creates a new Builder. */ public Builder() {} /** * The icon representing the app to be installed. */ public @NonNull Builder setIcon(@NonNull Bitmap value) { checkNotUsed(); mBuilderFieldsSet |= 0x1; mIcon = value; return this; } /** * The label representing the app to be installed. */ public @NonNull Builder setLabel(@NonNull CharSequence value) { checkNotUsed(); mBuilderFieldsSet |= 0x2; mLabel = value; return this; } /** * The locale is used to get the app label from the APKs (includes the base APK and * split APKs) related to the package to be installed. The caller needs to make sure * the app label is consistent with the app label of {@link PreapprovalDetails} when * validating the installation. Otherwise, the pre-approval install session will fail. */ public @NonNull Builder setLocale(@NonNull ULocale value) { checkNotUsed(); mBuilderFieldsSet |= 0x4; mLocale = value; return this; } /** * The package name of the app to be installed. */ public @NonNull Builder setPackageName(@NonNull String value) { checkNotUsed(); mBuilderFieldsSet |= 0x8; mPackageName = value; return this; } /** Builds the instance. This builder should not be touched after calling this! */ public @NonNull PreapprovalDetails build() { checkNotUsed(); mBuilderFieldsSet |= 0x10; // Mark builder used PreapprovalDetails o = new PreapprovalDetails( mIcon, mLabel, mLocale, mPackageName); return o; } private void checkNotUsed() { if ((mBuilderFieldsSet & 0x10) != 0) { throw new IllegalStateException("This Builder should not be reused. " + "Use a new Builder instance instead"); } } } // Code below generated by codegen v1.0.23. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code // // To regenerate run: // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/PackageInstaller.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control //@formatter:off /** * The icon representing the app to be installed. */ @DataClass.Generated.Member public @Nullable Bitmap getIcon() { return mIcon; } /** * The label representing the app to be installed. */ @DataClass.Generated.Member public @NonNull CharSequence getLabel() { return mLabel; } /** * The locale of the app label being used. */ @DataClass.Generated.Member public @NonNull ULocale getLocale() { return mLocale; } /** * The package name of the app to be installed. */ @DataClass.Generated.Member public @NonNull String getPackageName() { return mPackageName; } @Override @DataClass.Generated.Member public String toString() { // You can override field toString logic by defining methods like: // String fieldNameToString() { ... } return "PreapprovalDetails { " + "icon = " + mIcon + ", " + "label = " + mLabel + ", " + "locale = " + mLocale + ", " + "packageName = " + mPackageName + " }"; } @DataClass.Generated( time = 1676970504308L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java", inputSignatures = "private final @android.annotation.Nullable android.graphics.Bitmap mIcon\nprivate final @android.annotation.NonNull java.lang.CharSequence mLabel\nprivate final @android.annotation.NonNull android.icu.util.ULocale mLocale\nprivate final @android.annotation.NonNull java.lang.String mPackageName\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator CREATOR\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\npublic @java.lang.Override int describeContents()\nclass PreapprovalDetails extends java.lang.Object implements [android.os.Parcelable]\nprivate @android.annotation.Nullable android.graphics.Bitmap mIcon\nprivate @android.annotation.NonNull java.lang.CharSequence mLabel\nprivate @android.annotation.NonNull android.icu.util.ULocale mLocale\nprivate @android.annotation.NonNull java.lang.String mPackageName\nprivate long mBuilderFieldsSet\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.PreapprovalDetails.Builder setIcon(android.graphics.Bitmap)\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.PreapprovalDetails.Builder setLabel(java.lang.CharSequence)\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.PreapprovalDetails.Builder setLocale(android.icu.util.ULocale)\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.PreapprovalDetails.Builder setPackageName(java.lang.String)\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.PreapprovalDetails build()\nprivate void checkNotUsed()\nclass Builder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true)") @Deprecated private void __metadata() {} //@formatter:on // End of generated code } /** * The callback result of {@link #checkInstallConstraints(List, InstallConstraints, Executor, Consumer)}. */ @DataClass(genParcelable = true, genHiddenConstructor = true) @DataClass.Suppress("isAllConstraintsSatisfied") public static final class InstallConstraintsResult implements Parcelable { /** * True if all constraints are satisfied. */ private boolean mAllConstraintsSatisfied; /** * True if all constraints are satisfied. */ public boolean areAllConstraintsSatisfied() { return mAllConstraintsSatisfied; } // Code below generated by codegen v1.0.23. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code // // To regenerate run: // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/PackageInstaller.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control //@formatter:off /** * Creates a new InstallConstraintsResult. * * @param allConstraintsSatisfied * True if all constraints are satisfied. * @hide */ @DataClass.Generated.Member public InstallConstraintsResult( boolean allConstraintsSatisfied) { this.mAllConstraintsSatisfied = allConstraintsSatisfied; // onConstructed(); // You can define this method to get a callback } @Override @DataClass.Generated.Member public void writeToParcel(@NonNull Parcel dest, int flags) { // You can override field parcelling by defining methods like: // void parcelFieldName(Parcel dest, int flags) { ... } byte flg = 0; if (mAllConstraintsSatisfied) flg |= 0x1; dest.writeByte(flg); } @Override @DataClass.Generated.Member public int describeContents() { return 0; } /** @hide */ @SuppressWarnings({"unchecked", "RedundantCast"}) @DataClass.Generated.Member /* package-private */ InstallConstraintsResult(@NonNull Parcel in) { // You can override field unparcelling by defining methods like: // static FieldType unparcelFieldName(Parcel in) { ... } byte flg = in.readByte(); boolean allConstraintsSatisfied = (flg & 0x1) != 0; this.mAllConstraintsSatisfied = allConstraintsSatisfied; // onConstructed(); // You can define this method to get a callback } @DataClass.Generated.Member public static final @NonNull Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public InstallConstraintsResult[] newArray(int size) { return new InstallConstraintsResult[size]; } @Override public InstallConstraintsResult createFromParcel(@NonNull Parcel in) { return new InstallConstraintsResult(in); } }; @DataClass.Generated( time = 1676970504336L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java", inputSignatures = "private boolean mAllConstraintsSatisfied\npublic boolean areAllConstraintsSatisfied()\nclass InstallConstraintsResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true)") @Deprecated private void __metadata() {} //@formatter:on // End of generated code } /** * A class to encapsulate constraints for installation. * * When used with {@link #checkInstallConstraints(List, InstallConstraints, Executor, Consumer)}, it * specifies the conditions to check against for the packages in question. This can be used * by app stores to deliver auto updates without disrupting the user experience (referred as * gentle update) - for example, an app store might hold off updates when it find out the * app to update is interacting with the user. * * Use {@link Builder} to create a new instance and call mutator methods to add constraints. * If no mutators were called, default constraints will be generated which implies no * constraints. It is recommended to use preset constraints which are useful in most * cases. * * For the purpose of gentle update, it is recommended to always use {@link #GENTLE_UPDATE} * for the system knows best how to do it. It will also benefits the installer as the * platform evolves and add more constraints to improve the accuracy and efficiency of * gentle update. * * Note the constraints are applied transitively. If app Foo is used by app Bar (via shared * library or bounded service), the constraints will also be applied to Bar. */ @DataClass(genParcelable = true, genHiddenConstructor = true, genEqualsHashCode=true) public static final class InstallConstraints implements Parcelable { /** * Preset constraints suitable for gentle update. */ @NonNull public static final InstallConstraints GENTLE_UPDATE = new Builder().setAppNotInteractingRequired().build(); private final boolean mDeviceIdleRequired; private final boolean mAppNotForegroundRequired; private final boolean mAppNotInteractingRequired; private final boolean mAppNotTopVisibleRequired; private final boolean mNotInCallRequired; /** * Builder class for constructing {@link InstallConstraints}. */ public static final class Builder { private boolean mDeviceIdleRequired; private boolean mAppNotForegroundRequired; private boolean mAppNotInteractingRequired; private boolean mAppNotTopVisibleRequired; private boolean mNotInCallRequired; /** * This constraint requires the device is idle. */ @SuppressLint("MissingGetterMatchingBuilder") @NonNull public Builder setDeviceIdleRequired() { mDeviceIdleRequired = true; return this; } /** * This constraint requires the app in question is not in the foreground. */ @SuppressLint("MissingGetterMatchingBuilder") @NonNull public Builder setAppNotForegroundRequired() { mAppNotForegroundRequired = true; return this; } /** * This constraint requires the app in question is not interacting with the user. * User interaction includes: *

    *
  • playing or recording audio/video
  • *
  • sending or receiving network data
  • *
  • being visible to the user
  • *
*/ @SuppressLint("MissingGetterMatchingBuilder") @NonNull public Builder setAppNotInteractingRequired() { mAppNotInteractingRequired = true; return this; } /** * This constraint requires the app in question is not top-visible to the user. * A top-visible app is showing UI at the top of the screen that the user is * interacting with. * * Note this constraint is a subset of {@link #setAppNotForegroundRequired()} * because a top-visible app is also a foreground app. This is also a subset * of {@link #setAppNotInteractingRequired()} because a top-visible app is interacting * with the user. * * @see ActivityManager.RunningAppProcessInfo#IMPORTANCE_FOREGROUND */ @SuppressLint("MissingGetterMatchingBuilder") @NonNull public Builder setAppNotTopVisibleRequired() { mAppNotTopVisibleRequired = true; return this; } /** * This constraint requires there is no ongoing call in the device. */ @SuppressLint("MissingGetterMatchingBuilder") @NonNull public Builder setNotInCallRequired() { mNotInCallRequired = true; return this; } /** * Builds a new {@link InstallConstraints} instance. */ @NonNull public InstallConstraints build() { return new InstallConstraints(mDeviceIdleRequired, mAppNotForegroundRequired, mAppNotInteractingRequired, mAppNotTopVisibleRequired, mNotInCallRequired); } } // Code below generated by codegen v1.0.23. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code // // To regenerate run: // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/PackageInstaller.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control //@formatter:off /** * Creates a new InstallConstraints. * * @hide */ @DataClass.Generated.Member public InstallConstraints( boolean deviceIdleRequired, boolean appNotForegroundRequired, boolean appNotInteractingRequired, boolean appNotTopVisibleRequired, boolean notInCallRequired) { this.mDeviceIdleRequired = deviceIdleRequired; this.mAppNotForegroundRequired = appNotForegroundRequired; this.mAppNotInteractingRequired = appNotInteractingRequired; this.mAppNotTopVisibleRequired = appNotTopVisibleRequired; this.mNotInCallRequired = notInCallRequired; // onConstructed(); // You can define this method to get a callback } @DataClass.Generated.Member public boolean isDeviceIdleRequired() { return mDeviceIdleRequired; } @DataClass.Generated.Member public boolean isAppNotForegroundRequired() { return mAppNotForegroundRequired; } @DataClass.Generated.Member public boolean isAppNotInteractingRequired() { return mAppNotInteractingRequired; } @DataClass.Generated.Member public boolean isAppNotTopVisibleRequired() { return mAppNotTopVisibleRequired; } @DataClass.Generated.Member public boolean isNotInCallRequired() { return mNotInCallRequired; } @Override @DataClass.Generated.Member public boolean equals(@Nullable Object o) { // You can override field equality logic by defining either of the methods like: // boolean fieldNameEquals(InstallConstraints other) { ... } // boolean fieldNameEquals(FieldType otherValue) { ... } if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; @SuppressWarnings("unchecked") InstallConstraints that = (InstallConstraints) o; //noinspection PointlessBooleanExpression return true && mDeviceIdleRequired == that.mDeviceIdleRequired && mAppNotForegroundRequired == that.mAppNotForegroundRequired && mAppNotInteractingRequired == that.mAppNotInteractingRequired && mAppNotTopVisibleRequired == that.mAppNotTopVisibleRequired && mNotInCallRequired == that.mNotInCallRequired; } @Override @DataClass.Generated.Member public int hashCode() { // You can override field hashCode logic by defining methods like: // int fieldNameHashCode() { ... } int _hash = 1; _hash = 31 * _hash + Boolean.hashCode(mDeviceIdleRequired); _hash = 31 * _hash + Boolean.hashCode(mAppNotForegroundRequired); _hash = 31 * _hash + Boolean.hashCode(mAppNotInteractingRequired); _hash = 31 * _hash + Boolean.hashCode(mAppNotTopVisibleRequired); _hash = 31 * _hash + Boolean.hashCode(mNotInCallRequired); return _hash; } @Override @DataClass.Generated.Member public void writeToParcel(@NonNull Parcel dest, int flags) { // You can override field parcelling by defining methods like: // void parcelFieldName(Parcel dest, int flags) { ... } byte flg = 0; if (mDeviceIdleRequired) flg |= 0x1; if (mAppNotForegroundRequired) flg |= 0x2; if (mAppNotInteractingRequired) flg |= 0x4; if (mAppNotTopVisibleRequired) flg |= 0x8; if (mNotInCallRequired) flg |= 0x10; dest.writeByte(flg); } @Override @DataClass.Generated.Member public int describeContents() { return 0; } /** @hide */ @SuppressWarnings({"unchecked", "RedundantCast"}) @DataClass.Generated.Member /* package-private */ InstallConstraints(@NonNull Parcel in) { // You can override field unparcelling by defining methods like: // static FieldType unparcelFieldName(Parcel in) { ... } byte flg = in.readByte(); boolean deviceIdleRequired = (flg & 0x1) != 0; boolean appNotForegroundRequired = (flg & 0x2) != 0; boolean appNotInteractingRequired = (flg & 0x4) != 0; boolean appNotTopVisibleRequired = (flg & 0x8) != 0; boolean notInCallRequired = (flg & 0x10) != 0; this.mDeviceIdleRequired = deviceIdleRequired; this.mAppNotForegroundRequired = appNotForegroundRequired; this.mAppNotInteractingRequired = appNotInteractingRequired; this.mAppNotTopVisibleRequired = appNotTopVisibleRequired; this.mNotInCallRequired = notInCallRequired; // onConstructed(); // You can define this method to get a callback } @DataClass.Generated.Member public static final @NonNull Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public InstallConstraints[] newArray(int size) { return new InstallConstraints[size]; } @Override public InstallConstraints createFromParcel(@NonNull Parcel in) { return new InstallConstraints(in); } }; @DataClass.Generated( time = 1676970504352L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java", inputSignatures = "public static final @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints GENTLE_UPDATE\nprivate final boolean mDeviceIdleRequired\nprivate final boolean mAppNotForegroundRequired\nprivate final boolean mAppNotInteractingRequired\nprivate final boolean mAppNotTopVisibleRequired\nprivate final boolean mNotInCallRequired\nclass InstallConstraints extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mDeviceIdleRequired\nprivate boolean mAppNotForegroundRequired\nprivate boolean mAppNotInteractingRequired\nprivate boolean mAppNotTopVisibleRequired\nprivate boolean mNotInCallRequired\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setDeviceIdleRequired()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setAppNotForegroundRequired()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setAppNotInteractingRequired()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setAppNotTopVisibleRequired()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setNotInCallRequired()\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints build()\nclass Builder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true, genEqualsHashCode=true)") @Deprecated private void __metadata() {} //@formatter:on // End of generated code } /** * Used to communicate the unarchival state in {@link #reportUnarchivalState}. */ @FlaggedApi(Flags.FLAG_ARCHIVING) public static final class UnarchivalState { /** * The caller is able to facilitate the unarchival for the given {@code unarchiveId}. * * @param unarchiveId the ID provided by the system as part of the intent.action.UNARCHIVE * broadcast with EXTRA_UNARCHIVE_ID. */ @NonNull public static UnarchivalState createOkState(int unarchiveId) { return new UnarchivalState(unarchiveId, UNARCHIVAL_OK, /* requiredStorageBytes= */ -1, /* userActionIntent= */ null); } /** * User action is required before commencing with the unarchival for the given * {@code unarchiveId}. E.g., this could be used if it's necessary for the user to sign-in * first. * * @param unarchiveId the ID provided by the system as part of the * intent.action.UNARCHIVE * broadcast with EXTRA_UNARCHIVE_ID. * @param userActionIntent optional intent to start a follow up action required to * facilitate the unarchival flow (e.g. user needs to log in). */ @NonNull public static UnarchivalState createUserActionRequiredState(int unarchiveId, @NonNull PendingIntent userActionIntent) { Objects.requireNonNull(userActionIntent); return new UnarchivalState(unarchiveId, UNARCHIVAL_ERROR_USER_ACTION_NEEDED, /* requiredStorageBytes= */ -1, userActionIntent); } /** * There is not enough storage to start the unarchival for the given {@code unarchiveId}. * * @param unarchiveId the ID provided by the system as part of the * intent.action.UNARCHIVE * broadcast with EXTRA_UNARCHIVE_ID. * @param requiredStorageBytes ff the error is UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE this * field should be set to specify how many additional bytes of * storage are required to unarchive the app. * @param userActionIntent can optionally be set to provide a custom storage-clearing * action. */ @NonNull public static UnarchivalState createInsufficientStorageState(int unarchiveId, long requiredStorageBytes, @Nullable PendingIntent userActionIntent) { return new UnarchivalState(unarchiveId, UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE, requiredStorageBytes, userActionIntent); } /** * The device has no data connectivity and unarchival cannot be started for the given * {@code unarchiveId}. * * @param unarchiveId the ID provided by the system as part of the intent.action.UNARCHIVE * broadcast with EXTRA_UNARCHIVE_ID. */ @NonNull public static UnarchivalState createNoConnectivityState(int unarchiveId) { return new UnarchivalState(unarchiveId, UNARCHIVAL_ERROR_NO_CONNECTIVITY, /* requiredStorageBytes= */ -1,/* userActionIntent= */ null); } /** * Generic error state for all cases that are not covered by other methods in this class. * * @param unarchiveId the ID provided by the system as part of the intent.action.UNARCHIVE * broadcast with EXTRA_UNARCHIVE_ID. */ @NonNull public static UnarchivalState createGenericErrorState(int unarchiveId) { return new UnarchivalState(unarchiveId, UNARCHIVAL_GENERIC_ERROR, /* requiredStorageBytes= */ -1,/* userActionIntent= */ null); } /** * The ID provided by the system as part of the intent.action.UNARCHIVE broadcast with * EXTRA_UNARCHIVE_ID. */ private final int mUnarchiveId; /** Used for the system to provide the user with necessary follow-up steps or errors. */ @UnarchivalStatus private final int mStatus; /** * If the error is UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE this field should be set to specify * how many additional bytes of storage are required to unarchive the app. */ private final long mRequiredStorageBytes; /** * Optional intent to start a follow up action required to facilitate the unarchival flow * (e.g., user needs to log in). */ @Nullable private final PendingIntent mUserActionIntent; /** * Creates a new UnarchivalState. * * @param unarchiveId The ID provided by the system as part of the * intent.action.UNARCHIVE broadcast with * EXTRA_UNARCHIVE_ID. * @param status Used for the system to provide the user with necessary * follow-up steps or errors. * @param requiredStorageBytes If the error is UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE this * field should be set to specify * how many additional bytes of storage are required to * unarchive the app. * @param userActionIntent Optional intent to start a follow up action required to * facilitate the unarchival flow * (e.g,. user needs to log in). * @hide */ private UnarchivalState( int unarchiveId, @UnarchivalStatus int status, long requiredStorageBytes, @Nullable PendingIntent userActionIntent) { this.mUnarchiveId = unarchiveId; this.mStatus = status; com.android.internal.util.AnnotationValidations.validate( UnarchivalStatus.class, null, mStatus); this.mRequiredStorageBytes = requiredStorageBytes; this.mUserActionIntent = userActionIntent; } /** * The ID provided by the system as part of the intent.action.UNARCHIVE broadcast with * EXTRA_UNARCHIVE_ID. * * @hide */ int getUnarchiveId() { return mUnarchiveId; } /** * Used for the system to provide the user with necessary follow-up steps or errors. * * @hide */ @UnarchivalStatus int getStatus() { return mStatus; } /** * If the error is UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE this field should be set to specify * how many additional bytes of storage are required to unarchive the app. * * @hide */ long getRequiredStorageBytes() { return mRequiredStorageBytes; } /** * Optional intent to start a follow up action required to facilitate the unarchival flow * (e.g. user needs to log in). * * @hide */ @Nullable PendingIntent getUserActionIntent() { return mUserActionIntent; } } }