Merge changes from topic "pre-commit-install"
* changes: Refactoring PackageInstallerSession (4/n) Handle pre-commit install session in PackageInstaller (3/n) Pre-commit install approval (2/n)
This commit is contained in:
@@ -11596,6 +11596,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";
|
||||
@@ -11652,6 +11653,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();
|
||||
@@ -3099,7 +3153,7 @@ public class PackageInstaller {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of session IDs that will be committed when this session is commited if
|
||||
* Returns the set of session IDs that will be committed when this session is committed if
|
||||
* this session is a multi-package session.
|
||||
*/
|
||||
@NonNull
|
||||
@@ -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);
|
||||
|
||||
@@ -59,6 +59,10 @@
|
||||
<action android:name="android.content.pm.action.CONFIRM_INSTALL" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<intent-filter android:priority="1">
|
||||
<action android:name="android.content.pm.action.CONFIRM_PRE_APPROVAL" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".InstallStaging"
|
||||
|
||||
@@ -59,7 +59,8 @@ public class InstallStart extends Activity {
|
||||
String callingAttributionTag = null;
|
||||
|
||||
final boolean isSessionInstall =
|
||||
PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction());
|
||||
PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL.equals(intent.getAction())
|
||||
|| PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction());
|
||||
|
||||
// If the activity was started via a PackageInstaller session, we retrieve the calling
|
||||
// package from that session
|
||||
|
||||
@@ -39,9 +39,11 @@ import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.IPackageManager;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageInstaller;
|
||||
import android.content.pm.PackageInstaller.SessionInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Process;
|
||||
@@ -151,7 +153,7 @@ public class PackageInstallerActivity extends AlertActivity {
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace any dialog shown by the dialog with the one for the given {@link #createDialog id}.
|
||||
* Replace any dialog shown by the dialog with the one for the given {@link #createDialog(int)}.
|
||||
*
|
||||
* @param id The dialog type to add
|
||||
*/
|
||||
@@ -296,6 +298,14 @@ public class PackageInstallerActivity extends AlertActivity {
|
||||
? RESULT_OK : RESULT_FIRST_USER, result);
|
||||
}
|
||||
|
||||
private static PackageInfo generateStubPackageInfo(String packageName) {
|
||||
final PackageInfo info = new PackageInfo();
|
||||
final ApplicationInfo aInfo = new ApplicationInfo();
|
||||
info.applicationInfo = aInfo;
|
||||
info.packageName = info.applicationInfo.packageName = packageName;
|
||||
return info;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle icicle) {
|
||||
if (mLocalLOGV) Log.i(TAG, "creating for user " + getUserId());
|
||||
@@ -315,6 +325,7 @@ public class PackageInstallerActivity extends AlertActivity {
|
||||
mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
|
||||
|
||||
final Intent intent = getIntent();
|
||||
final String action = intent.getAction();
|
||||
|
||||
mCallingPackage = intent.getStringExtra(EXTRA_CALLING_PACKAGE);
|
||||
mCallingAttributionTag = intent.getStringExtra(EXTRA_CALLING_ATTRIBUTION_TAG);
|
||||
@@ -324,11 +335,11 @@ public class PackageInstallerActivity extends AlertActivity {
|
||||
mOriginatingPackage = (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN)
|
||||
? getPackageNameForUid(mOriginatingUid) : null;
|
||||
|
||||
final Uri packageUri;
|
||||
|
||||
if (PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction())) {
|
||||
final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
|
||||
final PackageInstaller.SessionInfo info = mInstaller.getSessionInfo(sessionId);
|
||||
final Object packageSource;
|
||||
if (PackageInstaller.ACTION_CONFIRM_INSTALL.equals(action)) {
|
||||
final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID,
|
||||
-1 /* defaultValue */);
|
||||
final SessionInfo info = mInstaller.getSessionInfo(sessionId);
|
||||
if (info == null || !info.sealed || info.resolvedBaseCodePath == null) {
|
||||
Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
|
||||
finish();
|
||||
@@ -336,18 +347,32 @@ public class PackageInstallerActivity extends AlertActivity {
|
||||
}
|
||||
|
||||
mSessionId = sessionId;
|
||||
packageUri = Uri.fromFile(new File(info.resolvedBaseCodePath));
|
||||
packageSource = Uri.fromFile(new File(info.resolvedBaseCodePath));
|
||||
mOriginatingURI = null;
|
||||
mReferrerURI = null;
|
||||
} else if (PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL.equals(action)) {
|
||||
final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID,
|
||||
-1 /* defaultValue */);
|
||||
final SessionInfo info = mInstaller.getSessionInfo(sessionId);
|
||||
if (info == null || !info.isPreapprovalRequested) {
|
||||
Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
mSessionId = sessionId;
|
||||
packageSource = info;
|
||||
mOriginatingURI = null;
|
||||
mReferrerURI = null;
|
||||
} else {
|
||||
mSessionId = -1;
|
||||
packageUri = intent.getData();
|
||||
packageSource = intent.getData();
|
||||
mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
|
||||
mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
|
||||
}
|
||||
|
||||
// if there's nothing to do, quietly slip into the ether
|
||||
if (packageUri == null) {
|
||||
if (packageSource == null) {
|
||||
Log.w(TAG, "Unspecified source");
|
||||
setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
|
||||
finish();
|
||||
@@ -359,7 +384,7 @@ public class PackageInstallerActivity extends AlertActivity {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean wasSetUp = processPackageUri(packageUri);
|
||||
final boolean wasSetUp = processAppSnippet(packageSource);
|
||||
if (mLocalLOGV) Log.i(TAG, "wasSetUp: " + wasSetUp);
|
||||
|
||||
if (!wasSetUp) {
|
||||
@@ -619,6 +644,39 @@ public class PackageInstallerActivity extends AlertActivity {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the SessionInfo and set up the installer for pre-commit install session.
|
||||
*
|
||||
* @param info The SessionInfo to compose
|
||||
*
|
||||
* @return {@code true} iff the installer could be set up
|
||||
*/
|
||||
private boolean processSessionInfo(@NonNull SessionInfo info) {
|
||||
mPkgInfo = generateStubPackageInfo(info.appPackageName);
|
||||
mAppSnippet = new PackageUtil.AppSnippet(info.appLabel,
|
||||
info.appIcon != null ? new BitmapDrawable(getResources(), info.appIcon)
|
||||
: getPackageManager().getDefaultActivityIcon());
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the Uri (post-commit install session) or use the SessionInfo (pre-commit install
|
||||
* session) to set up the installer for this install.
|
||||
*
|
||||
* @param source The source of package URI or SessionInfo
|
||||
*
|
||||
* @return {@code true} iff the installer could be set up
|
||||
*/
|
||||
private boolean processAppSnippet(@NonNull Object source) {
|
||||
if (source instanceof Uri) {
|
||||
return processPackageUri((Uri) source);
|
||||
} else if (source instanceof SessionInfo) {
|
||||
return processSessionInfo((SessionInfo) source);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (mSessionId != -1) {
|
||||
|
||||
@@ -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";
|
||||
@@ -275,7 +283,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
* #mValidatedTargetSdk} is compared with {@link Build.VERSION_CODES#R} before getting the
|
||||
* target sdk version from a validated apk in {@link #validateApkInstallLocked()}, the compared
|
||||
* result will not trigger any user action in
|
||||
* {@link #checkUserActionRequirement(PackageInstallerSession)}.
|
||||
* {@link #checkUserActionRequirement(PackageInstallerSession, IntentSender)}.
|
||||
*/
|
||||
private static final int INVALID_TARGET_SDK_VERSION = Integer.MAX_VALUE;
|
||||
|
||||
@@ -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 =
|
||||
@@ -874,7 +887,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
|
||||
if (snapshot.isInstallDisabledForPackage(getInstallerPackageName(), mInstallerUid,
|
||||
userId)) {
|
||||
// show the installer to account for device poslicy or unknown sources use cases
|
||||
// show the installer to account for device policy or unknown sources use cases
|
||||
return USER_ACTION_REQUIRED;
|
||||
}
|
||||
|
||||
@@ -1031,18 +1044,22 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
mResolvedBaseFile.getAbsolutePath() : null;
|
||||
info.progress = progress;
|
||||
info.sealed = mSealed;
|
||||
info.isCommitted = mCommitted.get();
|
||||
info.isCommitted = isCommitted();
|
||||
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);
|
||||
@@ -1132,7 +1162,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
@GuardedBy("mLock")
|
||||
private void assertPreparedAndNotCommittedOrDestroyedLocked(String cookie) {
|
||||
assertPreparedAndNotDestroyedLocked(cookie);
|
||||
if (mCommitted.get()) {
|
||||
if (isCommitted()) {
|
||||
throw new SecurityException(cookie + " not allowed after commit");
|
||||
}
|
||||
}
|
||||
@@ -1173,7 +1203,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
|
||||
@GuardedBy("mProgressLock")
|
||||
private void computeProgressLocked(boolean forcePublish) {
|
||||
if (!isIncrementalInstallation() || !mCommitted.get()) {
|
||||
if (!isIncrementalInstallation() || !isCommitted()) {
|
||||
mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f)
|
||||
+ MathUtils.constrain(mInternalProgress * 0.2f, 0f, 0.2f);
|
||||
} else {
|
||||
@@ -1200,7 +1230,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
assertCallerIsOwnerRootOrVerifier();
|
||||
synchronized (mLock) {
|
||||
assertPreparedAndNotDestroyedLocked("getNames");
|
||||
if (!mCommitted.get()) {
|
||||
if (!isCommitted()) {
|
||||
return getNamesLocked();
|
||||
} else {
|
||||
return getStageDirContentsLocked();
|
||||
@@ -1640,11 +1670,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
|
||||
@Override
|
||||
public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {
|
||||
if (hasParentSessionId()) {
|
||||
throw new IllegalStateException(
|
||||
"Session " + sessionId + " is a child of multi-package session "
|
||||
+ getParentSessionId() + " and may not be committed directly.");
|
||||
}
|
||||
assertNotChild("commit");
|
||||
|
||||
if (!markAsSealed(statusReceiver, forTransfer)) {
|
||||
return;
|
||||
@@ -1708,6 +1734,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<>();
|
||||
@@ -1835,7 +1875,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
// single / child sessions.
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
if (mCommitted.get()) {
|
||||
if (isCommitted()) {
|
||||
return true;
|
||||
}
|
||||
// Read transfers from the original owner stay open, but as the session's data
|
||||
@@ -2115,7 +2155,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 +2464,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 +3056,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assertPreapprovalDetailsConsistentIfNeededLocked(packageLite, pkgInfo);
|
||||
|
||||
if (packageLite.isUseEmbeddedDex()) {
|
||||
for (File file : mResolvedStagedFiles) {
|
||||
if (file.getName().endsWith(".apk")
|
||||
@@ -3253,6 +3303,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 +3591,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 +3605,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);
|
||||
@@ -3593,7 +3718,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
mDestroyed = true;
|
||||
r = () -> {
|
||||
assertNotLocked("abandonStaged");
|
||||
if (isStaged() && mCommitted.get()) {
|
||||
if (isStaged() && isCommitted()) {
|
||||
mStagingManager.abortCommittedSession(mStagedSession);
|
||||
}
|
||||
destroy();
|
||||
@@ -3937,7 +4062,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
private boolean canBeAddedAsChild(int parentCandidate) {
|
||||
synchronized (mLock) {
|
||||
return (!hasParentSessionId() || mParentSessionId == parentCandidate)
|
||||
&& !mCommitted.get()
|
||||
&& !isCommitted()
|
||||
&& !mDestroyed;
|
||||
}
|
||||
}
|
||||
@@ -4096,10 +4221,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 +4448,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 +4466,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 +4480,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 +4494,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 +4517,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);
|
||||
@@ -4448,7 +4636,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
writeStringAttribute(out, ATTR_SESSION_STAGE_CID, stageCid);
|
||||
}
|
||||
writeBooleanAttribute(out, ATTR_PREPARED, mPrepared);
|
||||
writeBooleanAttribute(out, ATTR_COMMITTED, mCommitted.get());
|
||||
writeBooleanAttribute(out, ATTR_COMMITTED, isCommitted());
|
||||
writeBooleanAttribute(out, ATTR_DESTROYED, mDestroyed);
|
||||
writeBooleanAttribute(out, ATTR_SEALED, mSealed);
|
||||
|
||||
|
||||
@@ -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