All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.android.content.pm.PackageInstaller Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 15-robolectric-12650502
Show newest version
/*
 * 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 android.Manifest;
import android.annotation.CurrentTimeMillisLong;
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.SystemApi;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageManager.DeleteFlags;
import android.content.pm.PackageManager.InstallReason;
import android.graphics.Bitmap;
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.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.system.ErrnoException;
import android.system.Os;
import android.util.ArraySet;
import android.util.ExceptionUtils;

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.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.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;

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

    *
  • the device owner *
  • the affiliated profile owner *
*

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

    *
  • All APKs must have the exact same package name, version code, and signing * certificates. *
  • All APKs must have unique split names. *
  • All installations must contain a single base APK. *
*

* The ApiDemos project contains examples of using this API: * ApiDemos/src/com/example/android/apis/content/InstallApk*.java. *

* On Android Q or above, an app installed notification will be posted * by system after a new app is installed. * To customize installer's notification icon, you should declare the following in the manifest * <application> as follows:

*
 * <meta-data android:name="com.android.packageinstaller.notification.smallIcon"
 * android:resource="@drawable/installer_notification_icon"/>
 * 
*
 * <meta-data android:name="com.android.packageinstaller.notification.color"
 * android:resource="@color/installer_notification_color"/>
 * 
*/ public class PackageInstaller { private static final String TAG = "PackageInstaller"; /** {@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. *

* 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"; /** {@hide} */ public static final String ACTION_CONFIRM_INSTALL = "android.content.pm.action.CONFIRM_INSTALL"; /** * 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}, or * {@link #STATUS_FAILURE_STORAGE}. *

* 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"; /** * 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"; /** {@hide} */ @Deprecated public static final String EXTRA_PACKAGE_NAMES = "android.content.pm.extra.PACKAGE_NAMES"; /** {@hide} */ 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"; /** {@hide} */ public static final String EXTRA_CALLBACK = "android.content.pm.extra.CALLBACK"; /** * 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. * * @see #EXTRA_STATUS_MESSAGE */ public static final int STATUS_FAILURE_INCOMPATIBLE = 7; private final IPackageInstaller mInstaller; private final int mUserId; private final String mInstallerPackageName; private final ArrayList mDelegates = new ArrayList<>(); /** {@hide} */ public PackageInstaller(IPackageInstaller installer, String installerPackageName, int userId) { mInstaller = installer; mInstallerPackageName = installerPackageName; mUserId = userId; } /** * Create a new session using the given parameters, returning a unique ID * that represents the session. Once created, the session can be opened * multiple times across multiple device boots. *

* The system may automatically destroy sessions that have not been * finalized (either committed or abandoned) within a reasonable period of * time, typically on the order of a day. * * @throws IOException if parameters were unsatisfiable, such as lack of * disk space or unavailable media. * @throws SecurityException when installation services are unavailable, * such as when called from a restricted user. * @throws IllegalArgumentException when {@link SessionParams} is invalid. * @return positive, non-zero unique ID that represents the created session. * This ID remains consistent across device reboots until the * session is finalized. IDs are not reused during a given boot. */ public int createSession(@NonNull SessionParams params) throws IOException { try { final String installerPackage; if (params.installerPackageName == null) { installerPackage = mInstallerPackageName; } else { installerPackage = params.installerPackageName; } return mInstaller.createSession(params, installerPackage, 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. No special permissions are * required 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. */ public @NonNull List getAllSessions() { try { return mInstaller.getAllSessions(mUserId).getList(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Return list of all known install sessions owned by the calling app. */ public @NonNull List getMySessions() { try { return mInstaller.getMySessions(mInstallerPackageName, mUserId).getList(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Return list of all staged install sessions. */ public @NonNull List getStagedSessions() { try { // TODO: limit this to the mUserId? return mInstaller.getStagedSessions().getList(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Returns an active staged session, or {@code null} if there is none. * *

Staged session is active iff: *

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

In case of a multi-apk session, reasoning above is applied to the parent session, since * that is the one that should been {@link Session#commit committed}. */ public @Nullable SessionInfo getActiveStagedSession() { final List stagedSessions = getStagedSessions(); for (SessionInfo s : stagedSessions) { if (s.isStagedSessionApplied() || s.isStagedSessionFailed()) { // Finalized session. continue; } if (s.getParentSessionId() != SessionInfo.INVALID_ID) { // Child session. continue; } if (s.isCommitted()) { return s; } } return null; } /** * Uninstall the given package, removing it completely from the device. This * method is available to: *

    *
  • the current "installer of record" for the package *
  • the device owner *
  • the affiliated profile owner *
* * @param packageName The package to uninstall. * @param statusReceiver Where to deliver the result. * * @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. * * @hide */ public void uninstall(@NonNull String packageName, @DeleteFlags int flags, @NonNull IntentSender statusReceiver) { uninstall(new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST), flags, statusReceiver); } /** * Uninstall the given package with a specific version code, removing it * completely from the device. If the version code of the package * does not match the one passed in the versioned package argument this * method is a no-op. Use {@link PackageManager#VERSION_CODE_HIGHEST} to * uninstall the latest version of the package. *

* This method is available to: *

    *
  • the current "installer of record" for the package *
  • the device owner *
  • the affiliated profile owner *
* * @param versionedPackage The versioned package to uninstall. * @param statusReceiver Where to deliver the result. * * @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. * * @hide */ @RequiresPermission(anyOf = { Manifest.permission.DELETE_PACKAGES, Manifest.permission.REQUEST_DELETE_PACKAGES}) public void uninstall(@NonNull VersionedPackage versionedPackage, @DeleteFlags int flags, @NonNull IntentSender statusReceiver) { Preconditions.checkNotNull(versionedPackage, "versionedPackage cannot be null"); try { mInstaller.uninstall(versionedPackage, mInstallerPackageName, flags, statusReceiver, mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Install the given package, which already exists on the device, for the user for which this * installer was created. * *

This will * {@link PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set) whitelist * all restricted permissions}. * * @param packageName The package to install. * @param installReason Reason for install. * @param statusReceiver Where to deliver the result. */ @RequiresPermission(allOf = { Manifest.permission.INSTALL_PACKAGES, Manifest.permission.INSTALL_EXISTING_PACKAGES}) public void installExistingPackage(@NonNull String packageName, @InstallReason int installReason, @Nullable IntentSender statusReceiver) { Preconditions.checkNotNull(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(); } } /** {@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(); } } /** * Events for observing session lifecycle. *

* A typical session lifecycle looks like this: *

    *
  • An installer creates a session to indicate pending app delivery. All * install details are available at this point. *
  • The installer opens the session to deliver APK data. Note that a * session may be opened and closed multiple times as network connectivity * changes. The installer may deliver periodic progress updates. *
  • The installer commits or abandons the session, resulting in the * session being finished. *
*/ public static abstract class SessionCallback { /** * New session has been created. Details about the session can be * obtained from {@link PackageInstaller#getSessionInfo(int)}. */ public abstract void onCreated(int sessionId); /** * Badging details for an existing session has changed. For example, the * app icon or label has been updated. */ public abstract void onBadgingChanged(int sessionId); /** * Active state for session has been changed. *

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

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

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

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

* If an APK included in this session is already defined by the existing * installation (for example, the same split name), the APK in this session * will replace the existing APK. *

* In such a case that multiple packages need to be committed simultaneously, * multiple sessions can be referenced by a single multi-package session. * This session is created with no package name and calling * {@link SessionParams#setMultiPackage()}. The individual session IDs can be * added with {@link #addChildSessionId(int)} and commit of the multi-package * session will result in all child sessions being committed atomically. */ public static class Session implements Closeable { /** {@hide} */ protected final IPackageInstallerSession mSession; /** {@hide} */ public Session(IPackageInstallerSession session) { mSession = session; } /** {@hide} */ @Deprecated public void setProgress(float progress) { setStagingProgress(progress); } /** * Set current progress of staging this session. Valid values are * anywhere between 0 and 1. *

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

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

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

* Once this method is called, the session is sealed and no additional * mutations may be performed on the session. If the device reboots * before the session has been finalized, you may commit the session again. *

* If the installer is the device owner or the affiliated profile owner, 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. * * @see android.app.admin.DevicePolicyManager */ 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. * * @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 SecurityException if the session does not update the original installer * @throws SecurityException if streams opened through * {@link #openWrite(String, long, long) are still open. */ public void transfer(@NonNull String packageName) throws PackageManager.NameNotFoundException { Preconditions.checkNotNull(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 * opening the session and calling {@link Session#abandon()}. */ public void abandon() { try { mSession.abandon(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * @return {@code true} if this session will commit more than one package when it is * committed. */ public boolean isMultiPackage() { try { return mSession.isMultiPackage(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * @return {@code true} if this session will be staged and applied at next reboot. */ public boolean isStaged() { try { return mSession.isStaged(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * @return the session ID of the multi-package session that this belongs to or * {@link SessionInfo#INVALID_ID} if it does not belong to a multi-package session. */ public int getParentSessionId() { try { return mSession.getParentSessionId(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * @return the set of session IDs that will be committed atomically when this session is * committed if this is a multi-package session or null if none exist. */ @NonNull public int[] getChildSessionIds() { try { return mSession.getChildSessionIds(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Adds a session ID to the set of sessions that will be committed atomically * when this session is committed. * *

If the parent is staged or has rollback enabled, all children must have * the same properties. * * @param sessionId the session ID to add to this multi-package session. */ public void addChildSessionId(int sessionId) { try { mSession.addChildSessionId(sessionId); } catch (RemoteException e) { e.rethrowFromSystemServer(); } } /** * Removes a session ID from the set of sessions that will be committed * atomically when this session is committed. * * @param sessionId the session ID to remove from this multi-package session. */ public void removeChildSessionId(int sessionId) { try { mSession.removeChildSessionId(sessionId); } catch (RemoteException e) { e.rethrowFromSystemServer(); } } } /** * Parameters for creating a new {@link PackageInstaller.Session}. */ public static class SessionParams implements Parcelable { /** {@hide} */ public static final int MODE_INVALID = -1; /** * Mode for an install session whose staged APKs should fully replace any * existing APKs for the target app. */ public static final int MODE_FULL_INSTALL = 1; /** * Mode for an install session that should inherit any existing APKs for the * target app, unless they have been explicitly overridden (based on split * name) by the session. For example, this can be used to add one or more * split APKs to an existing installation. *

* If there are no existing APKs for the target app, this behaves like * {@link #MODE_FULL_INSTALL}. */ public static final int MODE_INHERIT_EXISTING = 2; /** * Special constant to refer to all restricted permissions. */ public static final @NonNull Set RESTRICTED_PERMISSIONS_ALL = new ArraySet<>(); /** {@hide} */ public static final int UID_UNKNOWN = -1; /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public int mode = MODE_INVALID; /** {@hide} */ @UnsupportedAppUsage public int installFlags = PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS; /** {@hide} */ public int installLocation = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY; /** {@hide} */ public @InstallReason int installReason = PackageManager.INSTALL_REASON_UNKNOWN; /** {@hide} */ @UnsupportedAppUsage public long sizeBytes = -1; /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public String appPackageName; /** {@hide} */ @UnsupportedAppUsage public Bitmap appIcon; /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public String appLabel; /** {@hide} */ public long appIconLastModified = -1; /** {@hide} */ public Uri originatingUri; /** {@hide} */ @UnsupportedAppUsage public int originatingUid = UID_UNKNOWN; /** {@hide} */ public Uri referrerUri; /** {@hide} */ public String abiOverride; /** {@hide} */ public String volumeUuid; /** {@hide} */ public String[] grantedRuntimePermissions; /** {@hide} */ public List whitelistedRestrictedPermissions; /** {@hide} */ public String installerPackageName; /** {@hide} */ public boolean isMultiPackage; /** {@hide} */ public boolean isStaged; /** {@hide} */ public long requiredInstalledVersionCode = PackageManager.VERSION_CODE_HIGHEST; /** * Construct parameters for a new package install session. * * @param mode one of {@link #MODE_FULL_INSTALL} or * {@link #MODE_INHERIT_EXISTING} describing how the session * should interact with an existing app. */ public SessionParams(int mode) { this.mode = mode; } /** {@hide} */ public SessionParams(Parcel source) { mode = source.readInt(); installFlags = source.readInt(); installLocation = source.readInt(); installReason = source.readInt(); sizeBytes = source.readLong(); appPackageName = source.readString(); appIcon = source.readParcelable(null); appLabel = source.readString(); originatingUri = source.readParcelable(null); originatingUid = source.readInt(); referrerUri = source.readParcelable(null); abiOverride = source.readString(); volumeUuid = source.readString(); grantedRuntimePermissions = source.readStringArray(); whitelistedRestrictedPermissions = source.createStringArrayList(); installerPackageName = source.readString(); isMultiPackage = source.readBoolean(); isStaged = source.readBoolean(); requiredInstalledVersionCode = source.readLong(); } /** {@hide} */ public SessionParams copy() { SessionParams ret = new SessionParams(mode); ret.installFlags = installFlags; ret.installLocation = installLocation; ret.installReason = installReason; ret.sizeBytes = sizeBytes; ret.appPackageName = appPackageName; ret.appIcon = appIcon; // not a copy. ret.appLabel = appLabel; ret.originatingUri = originatingUri; // not a copy, but immutable. ret.originatingUid = originatingUid; ret.referrerUri = referrerUri; // not a copy, but immutable. ret.abiOverride = abiOverride; ret.volumeUuid = volumeUuid; ret.grantedRuntimePermissions = grantedRuntimePermissions; ret.whitelistedRestrictedPermissions = whitelistedRestrictedPermissions; ret.installerPackageName = installerPackageName; ret.isMultiPackage = isMultiPackage; ret.isStaged = isStaged; ret.requiredInstalledVersionCode = requiredInstalledVersionCode; return ret; } /** * Check if there are hidden options set. * *

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

* If the APKs staged in the session aren't consistent with this package * name, the install will fail. Regardless of this value, all APKs in the * app must have the same package name. */ public void setAppPackageName(@Nullable String appPackageName) { this.appPackageName = appPackageName; } /** * Optionally set an icon representing the app being installed. This should * be roughly {@link ActivityManager#getLauncherLargeIconSize()} in both * dimensions. */ public void setAppIcon(@Nullable Bitmap appIcon) { this.appIcon = appIcon; } /** * Optionally set a label representing the app being installed. */ 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. * * @hide */ @TestApi @SystemApi @RequiresPermission(android.Manifest.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS) public void setGrantedRuntimePermissions(String[] permissions) { installFlags |= PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS; this.grantedRuntimePermissions = permissions; } /** * Sets which restricted permissions to be whitelisted for the app. Whitelisting * is not granting the permissions, rather it allows the app to hold permissions * which are otherwise restricted. Whitelisting a non restricted permission has * no effect. * *

Permissions can be hard restricted which means that the app cannot hold * them or soft restricted where the app can hold the permission but in a weaker * form. Whether a permission is {@link PermissionInfo#FLAG_HARD_RESTRICTED hard * restricted} or {@link PermissionInfo#FLAG_SOFT_RESTRICTED soft restricted} * depends on the permission declaration. Whitelisting a hard restricted permission * allows the app to hold that permission and whitelisting 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 whitelist * 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 whitelisted but you can change * which ones are whitelisted by calling this method or the corresponding ones * on the {@link PackageManager}. * * @see PackageManager#addWhitelistedRestrictedPermission(String, String, int) * @see PackageManager#removeWhitelistedRestrictedPermission(String, String, int) */ public void setWhitelistedRestrictedPermissions(@Nullable Set permissions) { if (permissions == RESTRICTED_PERMISSIONS_ALL) { installFlags |= PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS; whitelistedRestrictedPermissions = null; } else { installFlags &= ~PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS; whitelistedRestrictedPermissions = (permissions != null) ? new ArrayList<>(permissions) : null; } } /** * 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. * * @param enable set to {@code true} to enable, {@code false} to disable * @hide */ @SystemApi @TestApi public void setEnableRollback(boolean enable) { if (enable) { installFlags |= PackageManager.INSTALL_ENABLE_ROLLBACK; } else { installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK; } } /** * @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; } /** {@hide} */ @SystemApi public void setDontKillApp(boolean dontKillApp) { if (dontKillApp) { installFlags |= PackageManager.INSTALL_DONT_KILL_APP; } else { installFlags &= ~PackageManager.INSTALL_DONT_KILL_APP; } } /** {@hide} */ @SystemApi public void setInstallAsInstantApp(boolean isInstantApp) { if (isInstantApp) { installFlags |= PackageManager.INSTALL_INSTANT_APP; installFlags &= ~PackageManager.INSTALL_FULL_APP; } else { installFlags &= ~PackageManager.INSTALL_INSTANT_APP; installFlags |= PackageManager.INSTALL_FULL_APP; } } /** * Sets the install as a virtual preload. Will only have effect when called * by the verifier. * {@hide} */ @SystemApi public void setInstallAsVirtualPreload() { installFlags |= PackageManager.INSTALL_VIRTUAL_PRELOAD; } /** * Set the reason for installing this package. *

* The install reason should be a pre-defined integer. The behavior is * undefined if other values are used. * * @see PackageManager#INSTALL_REASON_UNKNOWN * @see PackageManager#INSTALL_REASON_POLICY * @see PackageManager#INSTALL_REASON_DEVICE_RESTORE * @see PackageManager#INSTALL_REASON_DEVICE_SETUP * @see PackageManager#INSTALL_REASON_USER */ public void setInstallReason(@InstallReason int installReason) { this.installReason = installReason; } /** {@hide} */ @SystemApi @RequiresPermission(android.Manifest.permission.ALLOCATE_AGGRESSIVE) public void setAllocateAggressive(boolean allocateAggressive) { if (allocateAggressive) { installFlags |= PackageManager.INSTALL_ALLOCATE_AGGRESSIVE; } else { installFlags &= ~PackageManager.INSTALL_ALLOCATE_AGGRESSIVE; } } /** * Set the installer package for the app. * * By default this is the app that created the {@link PackageInstaller} object. * * @param installerPackageName name of the installer package * {@hide} */ public void setInstallerPackageName(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 @TestApi @RequiresPermission(Manifest.permission.INSTALL_PACKAGES) public void setStaged() { this.isStaged = true; } /** * Set this session to be installing an APEX package. * * {@hide} */ @SystemApi @TestApi @RequiresPermission(Manifest.permission.INSTALL_PACKAGES) public void setInstallAsApex() { installFlags |= PackageManager.INSTALL_APEX; } /** @hide */ public boolean getEnableRollback() { return (installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0; } /** {@hide} */ public void dump(IndentingPrintWriter pw) { pw.printPair("mode", mode); pw.printHexPair("installFlags", installFlags); pw.printPair("installLocation", installLocation); pw.printPair("sizeBytes", sizeBytes); pw.printPair("appPackageName", appPackageName); pw.printPair("appIcon", (appIcon != null)); pw.printPair("appLabel", appLabel); pw.printPair("originatingUri", originatingUri); pw.printPair("originatingUid", originatingUid); pw.printPair("referrerUri", referrerUri); pw.printPair("abiOverride", abiOverride); pw.printPair("volumeUuid", volumeUuid); pw.printPair("grantedRuntimePermissions", grantedRuntimePermissions); pw.printPair("whitelistedRestrictedPermissions", whitelistedRestrictedPermissions); pw.printPair("installerPackageName", installerPackageName); pw.printPair("isMultiPackage", isMultiPackage); pw.printPair("isStaged", isStaged); pw.printPair("requiredInstalledVersionCode", requiredInstalledVersionCode); pw.println(); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mode); dest.writeInt(installFlags); dest.writeInt(installLocation); dest.writeInt(installReason); dest.writeLong(sizeBytes); dest.writeString(appPackageName); dest.writeParcelable(appIcon, flags); dest.writeString(appLabel); dest.writeParcelable(originatingUri, flags); dest.writeInt(originatingUid); dest.writeParcelable(referrerUri, flags); dest.writeString(abiOverride); dest.writeString(volumeUuid); dest.writeStringArray(grantedRuntimePermissions); dest.writeStringList(whitelistedRestrictedPermissions); dest.writeString(installerPackageName); dest.writeBoolean(isMultiPackage); dest.writeBoolean(isStaged); dest.writeLong(requiredInstalledVersionCode); } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public SessionParams createFromParcel(Parcel p) { return new SessionParams(p); } @Override public SessionParams[] newArray(int size) { return new SessionParams[size]; } }; } /** * Details for an active install session. */ public static class SessionInfo implements Parcelable { /** * A session ID that does not exist or is invalid. */ public static final int INVALID_ID = -1; /** {@hide} */ private static final int[] NO_SESSIONS = {}; /** @hide */ @IntDef(prefix = { "STAGED_SESSION_" }, value = { STAGED_SESSION_NO_ERROR, STAGED_SESSION_VERIFICATION_FAILED, STAGED_SESSION_ACTIVATION_FAILED, STAGED_SESSION_UNKNOWN}) @Retention(RetentionPolicy.SOURCE) public @interface StagedSessionErrorCode{} /** * Constant indicating that no error occurred during the preparation or the activation of * this staged session. */ public static final int STAGED_SESSION_NO_ERROR = 0; /** * Constant indicating that an error occurred during the verification phase (pre-reboot) of * this staged session. */ public static final int STAGED_SESSION_VERIFICATION_FAILED = 1; /** * Constant indicating that an error occurred during the activation phase (post-reboot) of * this staged session. */ public static final int STAGED_SESSION_ACTIVATION_FAILED = 2; /** * Constant indicating that an unknown error occurred while processing this staged session. */ public static final int STAGED_SESSION_UNKNOWN = 3; /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public int sessionId; /** {@hide} */ public int userId; /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public String installerPackageName; /** {@hide} */ @UnsupportedAppUsage public String resolvedBaseCodePath; /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public float progress; /** {@hide} */ @UnsupportedAppUsage public boolean sealed; /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public boolean active; /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public int mode; /** {@hide} */ public @InstallReason int installReason; /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public long sizeBytes; /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public String appPackageName; /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public Bitmap appIcon; /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public CharSequence appLabel; /** {@hide} */ public int installLocation; /** {@hide} */ public Uri originatingUri; /** {@hide} */ public int originatingUid; /** {@hide} */ public Uri referrerUri; /** {@hide} */ public String[] grantedRuntimePermissions; /** {@hide}*/ public List whitelistedRestrictedPermissions; /** {@hide} */ public int installFlags; /** {@hide} */ public boolean isMultiPackage; /** {@hide} */ public boolean isStaged; /** {@hide} */ public int parentSessionId = INVALID_ID; /** {@hide} */ public int[] childSessionIds = NO_SESSIONS; /** {@hide} */ public boolean isStagedSessionApplied; /** {@hide} */ public boolean isStagedSessionReady; /** {@hide} */ public boolean isStagedSessionFailed; private int mStagedSessionErrorCode; private String mStagedSessionErrorMessage; /** {@hide} */ public boolean isCommitted; /** {@hide} */ public long updatedMillis; /** {@hide} */ @UnsupportedAppUsage public SessionInfo() { } /** {@hide} */ public SessionInfo(Parcel source) { sessionId = source.readInt(); userId = source.readInt(); installerPackageName = source.readString(); resolvedBaseCodePath = source.readString(); progress = source.readFloat(); sealed = source.readInt() != 0; active = source.readInt() != 0; mode = source.readInt(); installReason = source.readInt(); sizeBytes = source.readLong(); appPackageName = source.readString(); appIcon = source.readParcelable(null); appLabel = source.readString(); installLocation = source.readInt(); originatingUri = source.readParcelable(null); originatingUid = source.readInt(); referrerUri = source.readParcelable(null); grantedRuntimePermissions = source.readStringArray(); whitelistedRestrictedPermissions = source.createStringArrayList(); installFlags = source.readInt(); isMultiPackage = source.readBoolean(); isStaged = source.readBoolean(); parentSessionId = source.readInt(); childSessionIds = source.createIntArray(); if (childSessionIds == null) { childSessionIds = NO_SESSIONS; } isStagedSessionApplied = source.readBoolean(); isStagedSessionReady = source.readBoolean(); isStagedSessionFailed = source.readBoolean(); mStagedSessionErrorCode = source.readInt(); mStagedSessionErrorMessage = source.readString(); isCommitted = source.readBoolean(); } /** * Return the ID for this session. */ public int getSessionId() { return sessionId; } /** * Return the user associated with this session. */ public @NonNull UserHandle getUser() { return new UserHandle(userId); } /** * Return the package name of the app that owns this session. */ public @Nullable String getInstallerPackageName() { return installerPackageName; } /** * Return current overall progress of this session, between 0 and 1. *

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

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

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

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

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

The value is a hint and does not have to match the actual size. */ public long getSize() { return sizeBytes; } /** * Get the value set in {@link SessionParams#setOriginatingUri(Uri)}. */ 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)} */ public @Nullable Uri getReferrerUri() { return referrerUri; } /** * 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 whitelisted this method returns {@link * SessionParams#RESTRICTED_PERMISSIONS_ALL}. * * @hide */ @TestApi @SystemApi public @NonNull Set getWhitelistedRestrictedPermissions() { if ((installFlags & PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS) != 0) { return SessionParams.RESTRICTED_PERMISSIONS_ALL; } if (whitelistedRestrictedPermissions != null) { return new ArraySet<>(whitelistedRestrictedPermissions); } return Collections.emptySet(); } /** * Get the value set in {@link SessionParams#setAllowDowngrade(boolean)}. * * @deprecated use {@link #getRequestDowngrade()}. * @hide */ @SystemApi @Deprecated public boolean getAllowDowngrade() { return getRequestDowngrade(); } /** * Get the value set in {@link SessionParams#setRequestDowngrade(boolean)}. * * @hide */ @SystemApi public boolean getRequestDowngrade() { return (installFlags & PackageManager.INSTALL_REQUEST_DOWNGRADE) != 0; } /** * Get the value set in {@link SessionParams#setDontKillApp(boolean)}. * * @hide */ @SystemApi public boolean getDontKillApp() { return (installFlags & PackageManager.INSTALL_DONT_KILL_APP) != 0; } /** * If {@link SessionParams#setInstallAsInstantApp(boolean)} was called with {@code true}, * return true. If it was called with {@code false} or if it was not called return false. * * @hide * * @see #getInstallAsFullApp */ @SystemApi public boolean getInstallAsInstantApp(boolean isInstantApp) { return (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0; } /** * If {@link SessionParams#setInstallAsInstantApp(boolean)} was called with {@code false}, * return true. If it was called with {@code true} or if it was not called return false. * * @hide * * @see #getInstallAsInstantApp */ @SystemApi public boolean getInstallAsFullApp(boolean isInstantApp) { return (installFlags & PackageManager.INSTALL_FULL_APP) != 0; } /** * Get if {@link SessionParams#setInstallAsVirtualPreload()} was called. * * @hide */ @SystemApi public boolean getInstallAsVirtualPreload() { return (installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0; } /** * Return whether rollback is enabled or disabled for the given upgrade. * * @hide */ @SystemApi public boolean getEnableRollback() { return (installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0; } /** * Get the value set in {@link SessionParams#setAllocateAggressive(boolean)}. * * @hide */ @SystemApi public boolean getAllocateAggressive() { return (installFlags & PackageManager.INSTALL_ALLOCATE_AGGRESSIVE) != 0; } /** {@hide} */ @Deprecated public @Nullable Intent getDetailsIntent() { return createDetailsIntent(); } /** * Returns true if this session is a multi-package session containing references to other * sessions. */ public boolean isMultiPackage() { return isMultiPackage; } /** * Returns true if this session is a staged session which will be applied at next reboot. */ public boolean isStaged() { return isStaged; } /** * Returns the parent multi-package session ID if this session belongs to one, * {@link #INVALID_ID} otherwise. */ public int getParentSessionId() { return parentSessionId; } /** * Returns the set of session IDs that will be committed when this session is commited if * this session is a multi-package session. */ @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 isStagedSessionApplied; } /** * Whether the staged session is ready to be applied at next reboot. Only meaningful if * {@code isStaged} is true. */ public boolean isStagedSessionReady() { checkSessionIsStaged(); return isStagedSessionReady; } /** * 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 isStagedSessionFailed; } /** * 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 @StagedSessionErrorCode int getStagedSessionErrorCode() { checkSessionIsStaged(); return mStagedSessionErrorCode; } /** * Text description of the error code returned by {@code getStagedSessionErrorCode}, or * empty string if no error was encountered. */ public @NonNull String getStagedSessionErrorMessage() { checkSessionIsStaged(); return mStagedSessionErrorMessage; } /** {@hide} */ public void setStagedSessionErrorCode(@StagedSessionErrorCode int errorCode, String errorMessage) { mStagedSessionErrorCode = errorCode; mStagedSessionErrorMessage = errorMessage; } /** * Returns {@code true} if {@link Session#commit(IntentSender)}} was called for this * session. */ public boolean isCommitted() { return isCommitted; } /** * 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; } @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(resolvedBaseCodePath); dest.writeFloat(progress); dest.writeInt(sealed ? 1 : 0); dest.writeInt(active ? 1 : 0); dest.writeInt(mode); dest.writeInt(installReason); 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(installFlags); dest.writeBoolean(isMultiPackage); dest.writeBoolean(isStaged); dest.writeInt(parentSessionId); dest.writeIntArray(childSessionIds); dest.writeBoolean(isStagedSessionApplied); dest.writeBoolean(isStagedSessionReady); dest.writeBoolean(isStagedSessionFailed); dest.writeInt(mStagedSessionErrorCode); dest.writeString(mStagedSessionErrorMessage); dest.writeBoolean(isCommitted); } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public SessionInfo createFromParcel(Parcel p) { return new SessionInfo(p); } @Override public SessionInfo[] newArray(int size) { return new SessionInfo[size]; } }; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy