Pre-commit install approval (2/n)
Introduce a new mechanism to allow developers requesting the approval from users for an install before committing the session and without any of the APK file. Bug: 242677131 Test: atest CtsPackageInstallTestCases Change-Id: I38029d9dbf28f03b81e4988f1f864e552129c957
This commit is contained in:
@@ -11595,6 +11595,7 @@ package android.content.pm {
|
||||
field public static final String ACTION_SESSION_UPDATED = "android.content.pm.action.SESSION_UPDATED";
|
||||
field public static final String EXTRA_OTHER_PACKAGE_NAME = "android.content.pm.extra.OTHER_PACKAGE_NAME";
|
||||
field public static final String EXTRA_PACKAGE_NAME = "android.content.pm.extra.PACKAGE_NAME";
|
||||
field public static final String EXTRA_PRE_APPROVAL = "android.content.pm.extra.PRE_APPROVAL";
|
||||
field public static final String EXTRA_SESSION = "android.content.pm.extra.SESSION";
|
||||
field public static final String EXTRA_SESSION_ID = "android.content.pm.extra.SESSION_ID";
|
||||
field public static final String EXTRA_STATUS = "android.content.pm.extra.STATUS";
|
||||
@@ -11651,6 +11652,7 @@ package android.content.pm {
|
||||
method public void removeChildSessionId(int);
|
||||
method public void removeSplit(@NonNull String) throws java.io.IOException;
|
||||
method public void requestChecksums(@NonNull String, int, @NonNull java.util.List<java.security.cert.Certificate>, @NonNull java.util.concurrent.Executor, @NonNull android.content.pm.PackageManager.OnChecksumsReadyListener) throws java.security.cert.CertificateEncodingException, java.io.FileNotFoundException;
|
||||
method public void requestUserPreapproval(@NonNull android.content.pm.PackageInstaller.PreapprovalDetails, @NonNull android.content.IntentSender);
|
||||
method @Deprecated public void setChecksums(@NonNull String, @NonNull java.util.List<android.content.pm.Checksum>, @Nullable byte[]) throws java.io.IOException;
|
||||
method public void setStagingProgress(float);
|
||||
method public void transfer(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.content.pm.Checksum;
|
||||
import android.content.pm.DataLoaderParamsParcel;
|
||||
import android.content.pm.IOnChecksumsReadyListener;
|
||||
import android.content.pm.IPackageInstallObserver2;
|
||||
import android.content.pm.PackageInstaller;
|
||||
import android.content.IntentSender;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
@@ -58,4 +59,6 @@ interface IPackageInstallerSession {
|
||||
|
||||
boolean isStaged();
|
||||
int getInstallFlags();
|
||||
|
||||
void requestUserPreapproval(in PackageInstaller.PreapprovalDetails details, in IntentSender statusReceiver);
|
||||
}
|
||||
|
||||
@@ -170,6 +170,10 @@ public class PackageInstaller {
|
||||
/** {@hide} */
|
||||
public static final String ACTION_CONFIRM_INSTALL = "android.content.pm.action.CONFIRM_INSTALL";
|
||||
|
||||
/** @hide */
|
||||
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.
|
||||
*
|
||||
@@ -206,6 +210,17 @@ public class PackageInstaller {
|
||||
*/
|
||||
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.
|
||||
@@ -1667,6 +1682,41 @@ public class PackageInstaller {
|
||||
e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to request the approval before committing this session.
|
||||
*
|
||||
* For installers that have been granted the
|
||||
* {@link android.Manifest.permission#REQUEST_INSTALL_PACKAGES REQUEST_INSTALL_PACKAGES}
|
||||
* permission, they can request the approval from users before
|
||||
* {@link Session#commit(IntentSender)} is called. This may require user intervention as
|
||||
* well. The result of the request will be reported through the given callback.
|
||||
*
|
||||
* @param details the adequate context to this session for requesting the approval from
|
||||
* users prior to commit.
|
||||
* @param statusReceiver called when the state of the session changes.
|
||||
* Intents sent to this receiver contain
|
||||
* {@link #EXTRA_STATUS}. Refer to the individual
|
||||
* status codes on how to handle them.
|
||||
*
|
||||
* @throws IllegalArgumentException when {@link PreapprovalDetails} is {@code null}.
|
||||
* @throws IllegalArgumentException if {@link IntentSender} is {@code null}.
|
||||
* @throws IllegalStateException if called on a multi-package session (no matter
|
||||
* the parent session or any of the children sessions).
|
||||
* @throws IllegalStateException if called again after this method has been called on
|
||||
* this session.
|
||||
* @throws SecurityException when the caller does not own this session.
|
||||
*/
|
||||
public void requestUserPreapproval(@NonNull PreapprovalDetails details,
|
||||
@NonNull IntentSender statusReceiver) {
|
||||
Preconditions.checkArgument(details != null, "preapprovalDetails cannot be null.");
|
||||
Preconditions.checkArgument(statusReceiver != null, "statusReceiver cannot be null.");
|
||||
try {
|
||||
mSession.requestUserPreapproval(details, statusReceiver);
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2631,6 +2681,9 @@ public class PackageInstaller {
|
||||
/** {@hide} */
|
||||
public int installerUid;
|
||||
|
||||
/** @hide */
|
||||
public boolean isPreapprovalRequested;
|
||||
|
||||
/** {@hide} */
|
||||
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||||
public SessionInfo() {
|
||||
@@ -2678,6 +2731,7 @@ public class PackageInstaller {
|
||||
mSessionErrorCode = source.readInt();
|
||||
mSessionErrorMessage = source.readString();
|
||||
isCommitted = source.readBoolean();
|
||||
isPreapprovalRequested = source.readBoolean();
|
||||
rollbackDataPolicy = source.readInt();
|
||||
createdMillis = source.readLong();
|
||||
requireUserAction = source.readInt();
|
||||
@@ -3257,6 +3311,7 @@ public class PackageInstaller {
|
||||
dest.writeInt(mSessionErrorCode);
|
||||
dest.writeString(mSessionErrorMessage);
|
||||
dest.writeBoolean(isCommitted);
|
||||
dest.writeBoolean(isPreapprovalRequested);
|
||||
dest.writeInt(rollbackDataPolicy);
|
||||
dest.writeLong(createdMillis);
|
||||
dest.writeInt(requireUserAction);
|
||||
|
||||
@@ -81,9 +81,11 @@ import android.content.pm.InstallationFile;
|
||||
import android.content.pm.InstallationFileParcel;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageInstaller;
|
||||
import android.content.pm.PackageInstaller.PreapprovalDetails;
|
||||
import android.content.pm.PackageInstaller.SessionInfo;
|
||||
import android.content.pm.PackageInstaller.SessionParams;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.PackageInfoFlags;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.content.pm.SigningDetails;
|
||||
import android.content.pm.dex.DexMetadataHelper;
|
||||
@@ -92,8 +94,13 @@ 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.res.ApkAssets;
|
||||
import android.content.res.AssetManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.icu.util.ULocale;
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
@@ -193,6 +200,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
private static final int MSG_INSTALL = 3;
|
||||
private static final int MSG_ON_PACKAGE_INSTALLED = 4;
|
||||
private static final int MSG_SESSION_VALIDATION_FAILURE = 5;
|
||||
private static final int MSG_PRE_APPROVAL_REQUEST = 6;
|
||||
|
||||
/** XML constants used for persisting a session */
|
||||
static final String TAG_SESSION = "session";
|
||||
@@ -360,6 +368,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
@GuardedBy("mLock")
|
||||
private boolean mShouldBeSealed = false;
|
||||
|
||||
private final AtomicBoolean mPreapprovalRequested = new AtomicBoolean(false);
|
||||
private final AtomicBoolean mCommitted = new AtomicBoolean(false);
|
||||
|
||||
/**
|
||||
@@ -387,6 +396,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
@GuardedBy("mLock")
|
||||
private IntentSender mRemoteStatusReceiver;
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private PreapprovalDetails mPreapprovalDetails;
|
||||
|
||||
/** Fields derived from commit parsing */
|
||||
@GuardedBy("mLock")
|
||||
private String mPackageName;
|
||||
@@ -740,11 +752,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
final Bundle extras = (Bundle) args.arg3;
|
||||
final IntentSender statusReceiver = (IntentSender) args.arg4;
|
||||
final int returnCode = args.argi1;
|
||||
final boolean isPreapproval = args.argi2 == 1;
|
||||
args.recycle();
|
||||
|
||||
sendOnPackageInstalled(mContext, statusReceiver, sessionId,
|
||||
isInstallerDeviceOwnerOrAffiliatedProfileOwner(), userId,
|
||||
packageName, returnCode, message, extras);
|
||||
packageName, returnCode, isPreapproval, message, extras);
|
||||
|
||||
break;
|
||||
case MSG_SESSION_VALIDATION_FAILURE:
|
||||
@@ -752,6 +765,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
final String detailMessage = (String) msg.obj;
|
||||
onSessionValidationFailure(error, detailMessage);
|
||||
break;
|
||||
case MSG_PRE_APPROVAL_REQUEST:
|
||||
handlePreapprovalRequest();
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -779,10 +795,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
*/
|
||||
private boolean isInstallerDeviceOwnerOrAffiliatedProfileOwner() {
|
||||
assertNotLocked("isInstallerDeviceOwnerOrAffiliatedProfileOwner");
|
||||
// It is safe to access mInstallerUid and mInstallSource without lock
|
||||
// because they are immutable after sealing.
|
||||
assertSealed("isInstallerDeviceOwnerOrAffiliatedProfileOwner");
|
||||
if (userId != UserHandle.getUserId(mInstallerUid)) {
|
||||
if (userId != UserHandle.getUserId(getInstallerUid())) {
|
||||
return false;
|
||||
}
|
||||
DevicePolicyManagerInternal dpmi =
|
||||
@@ -1032,17 +1045,21 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
info.progress = progress;
|
||||
info.sealed = mSealed;
|
||||
info.isCommitted = mCommitted.get();
|
||||
info.isPreapprovalRequested = isPreapprovalRequested();
|
||||
info.active = mActiveCount.get() > 0;
|
||||
|
||||
info.mode = params.mode;
|
||||
info.installReason = params.installReason;
|
||||
info.installScenario = params.installScenario;
|
||||
info.sizeBytes = params.sizeBytes;
|
||||
info.appPackageName = mPackageName != null ? mPackageName : params.appPackageName;
|
||||
info.appPackageName = mPreapprovalDetails != null ? mPreapprovalDetails.getPackageName()
|
||||
: mPackageName != null ? mPackageName : params.appPackageName;
|
||||
if (includeIcon) {
|
||||
info.appIcon = params.appIcon;
|
||||
info.appIcon = mPreapprovalDetails != null && mPreapprovalDetails.getIcon() != null
|
||||
? mPreapprovalDetails.getIcon() : params.appIcon;
|
||||
}
|
||||
info.appLabel = params.appLabel;
|
||||
info.appLabel =
|
||||
mPreapprovalDetails != null ? mPreapprovalDetails.getLabel() : params.appLabel;
|
||||
|
||||
info.installLocation = params.installLocation;
|
||||
if (!scrubData) {
|
||||
@@ -1086,6 +1103,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
boolean isPreapprovalRequested() {
|
||||
return mPreapprovalRequested.get();
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
boolean isCommitted() {
|
||||
return mCommitted.get();
|
||||
@@ -1121,6 +1143,14 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void assertPreparedAndNotPreapprovalRequestedLocked(String cookie) {
|
||||
assertPreparedAndNotSealedLocked(cookie);
|
||||
if (isPreapprovalRequested()) {
|
||||
throw new IllegalStateException(cookie + " not allowed after requesting");
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void assertPreparedAndNotSealedLocked(String cookie) {
|
||||
assertPreparedAndNotCommittedOrDestroyedLocked(cookie);
|
||||
@@ -1708,6 +1738,20 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private void handlePreapprovalRequest() {
|
||||
/**
|
||||
* Stops the process if the session needs user action. When the user answers the yes,
|
||||
* {@link #setPermissionsResult(boolean)} is called and then
|
||||
* {@link #MSG_PRE_APPROVAL_REQUEST} is handled to come back here to check again.
|
||||
*/
|
||||
if (sendPendingUserActionIntentIfNeeded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatchSessionPreappoved();
|
||||
}
|
||||
|
||||
private final class FileSystemConnector extends
|
||||
IPackageInstallerSessionFileSystemConnector.Stub {
|
||||
final Set<String> mAddedFiles = new ArraySet<>();
|
||||
@@ -2115,7 +2159,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
*/
|
||||
@WorkerThread
|
||||
private boolean sendPendingUserActionIntentIfNeeded() {
|
||||
assertNotChild("PackageInstallerSession#sendPendingUserActionIntentIfNeeded");
|
||||
// To support pre-approval request of atomic install, we allow child session to handle
|
||||
// the result by itself since it has the status receiver.
|
||||
if (isCommitted()) {
|
||||
assertNotChild("PackageInstallerSession#sendPendingUserActionIntentIfNeeded");
|
||||
}
|
||||
|
||||
final IntentSender statusReceiver = getRemoteStatusReceiver();
|
||||
return sessionContains(s -> checkUserActionRequirement(s, statusReceiver));
|
||||
@@ -2420,7 +2468,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
// User needs to confirm installation;
|
||||
// give installer an intent they can use to involve
|
||||
// user.
|
||||
final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_INSTALL);
|
||||
final boolean isPreapproval = isPreapprovalRequested() && !isCommitted();
|
||||
final Intent intent = new Intent(
|
||||
isPreapproval ? PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL
|
||||
: PackageInstaller.ACTION_CONFIRM_INSTALL);
|
||||
intent.setPackage(mPm.getPackageInstallerPackageName());
|
||||
intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
|
||||
|
||||
@@ -3009,6 +3060,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assertPreapprovalDetailsConsistentIfNeededLocked(packageLite, pkgInfo);
|
||||
|
||||
if (packageLite.isUseEmbeddedDex()) {
|
||||
for (File file : mResolvedStagedFiles) {
|
||||
if (file.getName().endsWith(".apk")
|
||||
@@ -3253,6 +3307,78 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void assertPreapprovalDetailsConsistentIfNeededLocked(@NonNull PackageLite packageLite,
|
||||
@Nullable PackageInfo info) throws PackageManagerException {
|
||||
if (mPreapprovalDetails == null || !isPreapprovalRequested()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TextUtils.equals(mPackageName, mPreapprovalDetails.getPackageName())) {
|
||||
throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
|
||||
mPreapprovalDetails + " inconsistent with " + mPackageName);
|
||||
}
|
||||
|
||||
// In case the app label in PreapprovalDetails from different locale in split APK,
|
||||
// we check all APK files to find the app label.
|
||||
final PackageInfo packageInfo =
|
||||
info != null ? info : mContext.getPackageManager().getPackageArchiveInfo(
|
||||
packageLite.getPath(), PackageInfoFlags.of(0));
|
||||
if (packageInfo == null) {
|
||||
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
|
||||
"Failure to obtain package info.");
|
||||
}
|
||||
final List<String> filePaths = packageLite.getAllApkPaths();
|
||||
final String appLabel = mPreapprovalDetails.getLabel();
|
||||
final ULocale appLocale = mPreapprovalDetails.getLocale();
|
||||
final ApplicationInfo appInfo = packageInfo.applicationInfo;
|
||||
boolean appLabelMatched = false;
|
||||
for (int i = filePaths.size() - 1; i >= 0 && !appLabelMatched; i--) {
|
||||
appLabelMatched |= TextUtils.equals(getAppLabel(filePaths.get(i), appLocale, appInfo),
|
||||
appLabel);
|
||||
}
|
||||
if (!appLabelMatched) {
|
||||
throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
|
||||
mPreapprovalDetails + " inconsistent with app label");
|
||||
}
|
||||
}
|
||||
|
||||
private CharSequence getAppLabel(String path, ULocale locale, ApplicationInfo appInfo)
|
||||
throws PackageManagerException {
|
||||
final Resources pRes = mContext.getResources();
|
||||
final AssetManager assetManager = new AssetManager();
|
||||
final Configuration config = new Configuration(pRes.getConfiguration());
|
||||
final ApkAssets apkAssets;
|
||||
try {
|
||||
apkAssets = ApkAssets.loadFromPath(path);
|
||||
} catch (IOException e) {
|
||||
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
|
||||
"Failure to get resources from package archive " + path);
|
||||
}
|
||||
assetManager.setApkAssets(new ApkAssets[]{apkAssets}, false /* invalidateCaches */);
|
||||
config.setLocale(locale.toLocale());
|
||||
final Resources res = new Resources(assetManager, pRes.getDisplayMetrics(), config);
|
||||
return tryLoadingAppLabel(res, appInfo);
|
||||
}
|
||||
|
||||
private CharSequence tryLoadingAppLabel(@NonNull Resources res, @NonNull ApplicationInfo info) {
|
||||
CharSequence label = null;
|
||||
// Try to load the label from the package's resources. If an app has not explicitly
|
||||
// specified any label, just use the package name.
|
||||
if (info.labelRes != 0) {
|
||||
try {
|
||||
label = res.getText(info.labelRes);
|
||||
} catch (Resources.NotFoundException ignore) {
|
||||
}
|
||||
}
|
||||
if (label == null) {
|
||||
label = (info.nonLocalizedLabel != null)
|
||||
? info.nonLocalizedLabel : info.packageName;
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
private SigningDetails unsafeGetCertsWithoutVerification(String path)
|
||||
throws PackageManagerException {
|
||||
final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
|
||||
@@ -3469,11 +3595,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
}
|
||||
|
||||
void setPermissionsResult(boolean accepted) {
|
||||
if (!isSealed()) {
|
||||
if (!isSealed() && !isPreapprovalRequested()) {
|
||||
throw new SecurityException("Must be sealed to accept permissions");
|
||||
}
|
||||
|
||||
PackageInstallerSession root = hasParentSessionId()
|
||||
// To support pre-approval request of atomic install, we allow child session to handle
|
||||
// the result by itself since it has the status receiver.
|
||||
final PackageInstallerSession root = hasParentSessionId() && isCommitted()
|
||||
? mSessionProvider.getSession(getParentSessionId()) : this;
|
||||
|
||||
if (accepted) {
|
||||
@@ -3481,7 +3609,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
synchronized (mLock) {
|
||||
mPermissionsManuallyAccepted = true;
|
||||
}
|
||||
root.mHandler.obtainMessage(MSG_INSTALL).sendToTarget();
|
||||
root.mHandler.obtainMessage(
|
||||
isCommitted() ? MSG_INSTALL : MSG_PRE_APPROVAL_REQUEST).sendToTarget();
|
||||
} else {
|
||||
root.destroy();
|
||||
root.dispatchSessionFinished(INSTALL_FAILED_ABORTED, "User rejected permissions", null);
|
||||
@@ -4096,10 +4225,68 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
args.arg3 = extras;
|
||||
args.arg4 = statusReceiver;
|
||||
args.argi1 = returnCode;
|
||||
args.argi2 = isPreapprovalRequested() && !isCommitted() ? 1 : 0;
|
||||
mHandler.obtainMessage(MSG_ON_PACKAGE_INSTALLED, args).sendToTarget();
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchSessionPreappoved() {
|
||||
final IntentSender target = getRemoteStatusReceiver();
|
||||
final Intent intent = new Intent();
|
||||
intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
|
||||
intent.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_SUCCESS);
|
||||
intent.putExtra(PackageInstaller.EXTRA_PRE_APPROVAL, true);
|
||||
try {
|
||||
target.sendIntent(mContext, 0 /* code */, intent, null /* onFinished */,
|
||||
null /* handler */);
|
||||
} catch (IntentSender.SendIntentException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestUserPreapproval(@NonNull PreapprovalDetails details,
|
||||
@NonNull IntentSender statusReceiver) {
|
||||
validatePreapprovalRequest(details, statusReceiver);
|
||||
dispatchPreapprovalRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates whether the necessary information (e.g., PreapprovalDetails) are provided.
|
||||
*/
|
||||
private void validatePreapprovalRequest(@NonNull PreapprovalDetails details,
|
||||
@NonNull IntentSender statusReceiver) {
|
||||
assertCallerIsOwnerOrRoot();
|
||||
if (isMultiPackage()) {
|
||||
throw new IllegalStateException(
|
||||
"Session " + sessionId + " is a parent of multi-package session and "
|
||||
+ "requestUserPreapproval on the parent session isn't supported.");
|
||||
}
|
||||
|
||||
synchronized (mLock) {
|
||||
assertPreparedAndNotSealedLocked("request of session " + sessionId);
|
||||
mPreapprovalDetails = details;
|
||||
setRemoteStatusReceiver(statusReceiver);
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchPreapprovalRequest() {
|
||||
synchronized (mLock) {
|
||||
assertPreparedAndNotPreapprovalRequestedLocked("dispatchPreapprovalRequest");
|
||||
}
|
||||
|
||||
// Mark this session are pre-approval requested, and ready to progress to the next phase.
|
||||
markAsPreapprovalRequested();
|
||||
|
||||
mHandler.obtainMessage(MSG_PRE_APPROVAL_REQUEST).sendToTarget();
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks this session as pre-approval requested, and prevents further related modification.
|
||||
*/
|
||||
private void markAsPreapprovalRequested() {
|
||||
mPreapprovalRequested.set(true);
|
||||
}
|
||||
|
||||
void setSessionReady() {
|
||||
synchronized (mLock) {
|
||||
// Do not allow destroyed/failed session to change state
|
||||
@@ -4265,6 +4452,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
pw.printPair("mClientProgress", clientProgress);
|
||||
pw.printPair("mProgress", progress);
|
||||
pw.printPair("mCommitted", mCommitted);
|
||||
pw.printPair("mPreapprovalRequested", mPreapprovalRequested);
|
||||
pw.printPair("mSealed", mSealed);
|
||||
pw.printPair("mPermissionsManuallyAccepted", mPermissionsManuallyAccepted);
|
||||
pw.printPair("mStageDirInUse", mStageDirInUse);
|
||||
@@ -4282,6 +4470,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
pw.printPair("mSessionReady", mSessionReady);
|
||||
pw.printPair("mSessionErrorCode", mSessionErrorCode);
|
||||
pw.printPair("mSessionErrorMessage", mSessionErrorMessage);
|
||||
pw.printPair("mPreapprovalDetails", mPreapprovalDetails);
|
||||
pw.println();
|
||||
|
||||
pw.decreaseIndent();
|
||||
@@ -4295,6 +4484,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
final Intent fillIn = new Intent();
|
||||
fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
|
||||
fillIn.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_PENDING_USER_ACTION);
|
||||
fillIn.putExtra(PackageInstaller.EXTRA_PRE_APPROVAL,
|
||||
PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL.equals(intent.getAction()));
|
||||
fillIn.putExtra(Intent.EXTRA_INTENT, intent);
|
||||
try {
|
||||
target.sendIntent(context, 0, fillIn, null, null);
|
||||
@@ -4307,7 +4498,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
*/
|
||||
private static void sendOnPackageInstalled(Context context, IntentSender target, int sessionId,
|
||||
boolean showNotification, int userId, String basePackageName, int returnCode,
|
||||
String msg, Bundle extras) {
|
||||
boolean isPreapproval, String msg, Bundle extras) {
|
||||
if (INSTALL_SUCCEEDED == returnCode && showNotification) {
|
||||
boolean update = (extras != null) && extras.getBoolean(Intent.EXTRA_REPLACING);
|
||||
Notification notification = PackageInstallerService.buildSuccessNotification(context,
|
||||
@@ -4330,6 +4521,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
fillIn.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE,
|
||||
PackageManager.installStatusToString(returnCode, msg));
|
||||
fillIn.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, returnCode);
|
||||
fillIn.putExtra(PackageInstaller.EXTRA_PRE_APPROVAL, isPreapproval);
|
||||
if (extras != null) {
|
||||
final String existing = extras.getString(
|
||||
PackageManager.EXTRA_FAILURE_EXISTING_PACKAGE);
|
||||
|
||||
@@ -325,6 +325,7 @@ public class PackageInstallerSessionTest {
|
||||
actual.getSessionErrorMessage());
|
||||
assertEquals(expected.isPrepared(), actual.isPrepared());
|
||||
assertEquals(expected.isCommitted(), actual.isCommitted());
|
||||
assertEquals(expected.isPreapprovalRequested(), actual.isPreapprovalRequested());
|
||||
assertEquals(expected.createdMillis, actual.createdMillis);
|
||||
assertEquals(expected.isSealed(), actual.isSealed());
|
||||
assertEquals(expected.isMultiPackage(), actual.isMultiPackage());
|
||||
|
||||
Reference in New Issue
Block a user