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:
Jackal Guo
2022-10-12 23:57:39 +00:00
committed by Android (Google) Code Review
8 changed files with 353 additions and 41 deletions

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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"

View File

@@ -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

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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());