/* * 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/ 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
* 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 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 For more information on what sessions are considered active see
* * {@link SessionInfo#isStagedSessionActive()}.
*/
public @NonNull List
* This method is available to:
* 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
* 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
* 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:
*
* 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
* 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:
*
* 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:
*
* 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
* 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. 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. 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.
* 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
* 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 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.
* 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 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:
*
* 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:
*
*
* 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
* 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 Staged session is active iff:
* 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
*
*
* @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.
*
*
*
* @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.
*
*
*
*/
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.
* {@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
*
* If the caller needs a specific checksum type, they can specify it as required.
*
*
* 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
*
*
* @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.
*
*
*
*
*
*
*
*
*
*
*
*/
@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