From ea4fe39ad3233d265fd7487593b96593310945bf Mon Sep 17 00:00:00 2001 From: Alex Buynytskyy Date: Fri, 22 Nov 2019 16:48:43 -0800 Subject: [PATCH] Adding a non-blocking version of transfer. Along with blocking utility method. Test: atest PackageManagerShellCommandTest Bug: b/136132412 Change-Id: I4ff8504c54c989e4d6a18d53d8b015d9481b4781 --- api/current.txt | 4 + .../content/pm/IPackageInstallerSession.aidl | 2 +- .../android/content/pm/PackageInstaller.java | 107 ++++++++++++++- .../server/pm/PackageInstallerSession.java | 127 +++++++++++++++--- 4 files changed, 212 insertions(+), 28 deletions(-) diff --git a/api/current.txt b/api/current.txt index b4be9e19da103..c1d3220471597 100644 --- a/api/current.txt +++ b/api/current.txt @@ -11556,8 +11556,11 @@ package android.content.pm { field public static final int STATUS_FAILURE_ABORTED = 3; // 0x3 field public static final int STATUS_FAILURE_BLOCKED = 2; // 0x2 field public static final int STATUS_FAILURE_CONFLICT = 5; // 0x5 + field public static final int STATUS_FAILURE_ILLEGAL_STATE = 9; // 0x9 field public static final int STATUS_FAILURE_INCOMPATIBLE = 7; // 0x7 field public static final int STATUS_FAILURE_INVALID = 4; // 0x4 + field public static final int STATUS_FAILURE_NAME_NOT_FOUND = 8; // 0x8 + field public static final int STATUS_FAILURE_SECURITY = 10; // 0xa field public static final int STATUS_FAILURE_STORAGE = 6; // 0x6 field public static final int STATUS_PENDING_USER_ACTION = -1; // 0xffffffff field public static final int STATUS_SUCCESS = 0; // 0x0 @@ -11579,6 +11582,7 @@ package android.content.pm { method public void removeChildSessionId(int); method public void removeSplit(@NonNull String) throws java.io.IOException; method public void setStagingProgress(float); + method public void transfer(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException; method public void transfer(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; } diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl index 04e15c7957bd0..b74061793f9bb 100644 --- a/core/java/android/content/pm/IPackageInstallerSession.aidl +++ b/core/java/android/content/pm/IPackageInstallerSession.aidl @@ -36,7 +36,7 @@ interface IPackageInstallerSession { void close(); void commit(in IntentSender statusReceiver, boolean forTransferred); - void transfer(in String packageName); + void transfer(in String packageName, in IntentSender statusReceiver); void abandon(); boolean isMultiPackage(); diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 0c529798622e8..4ab6f3cd92b4b 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -29,6 +29,8 @@ import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.app.ActivityManager; import android.app.AppGlobals; +import android.content.IIntentReceiver; +import android.content.IIntentSender; import android.content.Intent; import android.content.IntentSender; import android.content.pm.PackageManager.DeleteFlags; @@ -36,9 +38,11 @@ import android.content.pm.PackageManager.InstallReason; import android.graphics.Bitmap; import android.net.Uri; import android.os.Build; +import android.os.Bundle; import android.os.FileBridge; import android.os.Handler; import android.os.HandlerExecutor; +import android.os.IBinder; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; @@ -67,6 +71,8 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.stream.Collectors; @@ -176,7 +182,7 @@ public class PackageInstaller { * {@link #STATUS_PENDING_USER_ACTION}, {@link #STATUS_SUCCESS}, * {@link #STATUS_FAILURE}, {@link #STATUS_FAILURE_ABORTED}, * {@link #STATUS_FAILURE_BLOCKED}, {@link #STATUS_FAILURE_CONFLICT}, - * {@link #STATUS_FAILURE_INCOMPATIBLE}, {@link #STATUS_FAILURE_INVALID}, or + * {@link #STATUS_FAILURE_INCOMPATIBLE}, {@link #STATUS_FAILURE_INVALID}, * {@link #STATUS_FAILURE_STORAGE}. *

* More information about a status may be available through additional @@ -316,6 +322,34 @@ public class PackageInstaller { */ public static final int STATUS_FAILURE_INCOMPATIBLE = 7; + /** + * The transfer failed because a target package can't be found. For example + * transferring a session to a non-existing package. + *

+ * The result may also contain {@link #EXTRA_OTHER_PACKAGE_NAME} with the + * missing package. + * + * @see #EXTRA_STATUS_MESSAGE + * @see #EXTRA_OTHER_PACKAGE_NAME + */ + public static final int STATUS_FAILURE_NAME_NOT_FOUND = 8; + + /** + * The transfer failed because a session is in invalid state. For example + * transferring an already committed session. + * + * @see #EXTRA_STATUS_MESSAGE + */ + public static final int STATUS_FAILURE_ILLEGAL_STATE = 9; + + /** + * The transfer failed for security reasons. For example transferring + * to a package which does not have INSTALL_PACKAGES permission. + * + * @see #EXTRA_STATUS_MESSAGE + */ + public static final int STATUS_FAILURE_SECURITY = 10; + private final IPackageInstaller mInstaller; private final int mUserId; private final String mInstallerPackageName; @@ -1052,7 +1086,8 @@ public class PackageInstaller { } /** - * Attempt to commit a session that has been {@link #transfer(String) transferred}. + * Attempt to commit a session that has been {@link #transfer(String, IntentSender) + * transferred}. * *

If the device reboots before the session has been finalized, you may commit the * session again. @@ -1093,6 +1128,38 @@ public class PackageInstaller { * * @param packageName The package of the new owner. Needs to hold the INSTALL_PACKAGES * permission. + * @param statusReceiver Called when the state of the session changes. Intents sent to this + * receiver contain {@link #EXTRA_STATUS}. Refer to the individual + * transfer status codes on how to handle them. + * + * @throws PackageManager.NameNotFoundException if the new owner could not be found. + * @throws SecurityException if called after the session has been committed or abandoned. + * @throws SecurityException if the session does not update the original installer + * @throws SecurityException if streams opened through + * {@link #openWrite(String, long, long) are still open. + */ + public void transfer(@NonNull String packageName, @NonNull IntentSender statusReceiver) + throws PackageManager.NameNotFoundException { + Preconditions.checkNotNull(statusReceiver); + Preconditions.checkNotNull(packageName); + + try { + mSession.transfer(packageName, statusReceiver); + } catch (ParcelableException e) { + e.maybeRethrow(PackageManager.NameNotFoundException.class); + throw new RuntimeException(e); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Transfer the session to a new owner. + * This is a convenience blocking wrapper around {@link #transfer(String, IntentSender)}. + * Converts all statuses into exceptions. + * + * @param packageName The package of the new owner. Needs to hold the INSTALL_PACKAGES + * permission. * * @throws PackageManager.NameNotFoundException if the new owner could not be found. * @throws SecurityException if called after the session has been committed or abandoned. @@ -1104,13 +1171,43 @@ public class PackageInstaller { throws PackageManager.NameNotFoundException { Preconditions.checkNotNull(packageName); + CompletableFuture intentFuture = new CompletableFuture(); try { - mSession.transfer(packageName); + IIntentSender localSender = new IIntentSender.Stub() { + @Override + public void send(int code, Intent intent, String resolvedType, + IBinder whitelistToken, + IIntentReceiver finishedReceiver, String requiredPermission, + Bundle options) { + intentFuture.complete(intent); + } + }; + transfer(packageName, new IntentSender(localSender)); } catch (ParcelableException e) { e.maybeRethrow(PackageManager.NameNotFoundException.class); throw new RuntimeException(e); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + } + + try { + Intent intent = intentFuture.get(); + final int status = intent.getIntExtra(EXTRA_STATUS, Integer.MIN_VALUE); + final String statusMessage = intent.getStringExtra(EXTRA_STATUS_MESSAGE); + switch (status) { + case STATUS_SUCCESS: + break; + case STATUS_FAILURE_NAME_NOT_FOUND: + throw new PackageManager.NameNotFoundException(statusMessage); + case STATUS_FAILURE_ILLEGAL_STATE: + throw new IllegalStateException(statusMessage); + case STATUS_FAILURE_SECURITY: + throw new SecurityException(statusMessage); + default: + throw new RuntimeException(statusMessage); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + throw new RuntimeException(e); } } diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index d63e7041ea528..4852947619f95 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -133,6 +133,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private static final int MSG_COMMIT = 1; private static final int MSG_ON_PACKAGE_INSTALLED = 2; private static final int MSG_SEAL = 3; + private static final int MSG_TRANSFER = 4; /** XML constants used for persisting a session */ static final String TAG_SESSION = "session"; @@ -332,6 +333,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private final Handler.Callback mHandlerCallback = new Handler.Callback() { @Override public boolean handleMessage(Message msg) { + SomeArgs args; + String packageName; + IntentSender statusReceiver; switch (msg.what) { case MSG_SEAL: handleSeal((IntentSender) msg.obj); @@ -339,12 +343,20 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { case MSG_COMMIT: handleCommit(); break; + case MSG_TRANSFER: + args = (SomeArgs) msg.obj; + packageName = (String) args.arg1; + statusReceiver = (IntentSender) args.arg2; + args.recycle(); + + handleTransfer(statusReceiver, packageName); + break; case MSG_ON_PACKAGE_INSTALLED: - final SomeArgs args = (SomeArgs) msg.obj; - final String packageName = (String) args.arg1; + args = (SomeArgs) msg.obj; + packageName = (String) args.arg1; final String message = (String) args.arg2; final Bundle extras = (Bundle) args.arg3; - final IntentSender statusReceiver = (IntentSender) args.arg4; + statusReceiver = (IntentSender) args.arg4; final int returnCode = args.argi1; args.recycle(); @@ -378,7 +390,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { * Checks if the permissions still need to be confirmed. * *

This is dependant on the identity of the installer, hence this cannot be cached if the - * installer might still {@link #transfer(String) change}. + * installer might still {@link #transfer(String, IntentSender) change}. * * @return {@code true} iff we need to ask to confirm the permissions? */ @@ -1183,13 +1195,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } - @Override - public void transfer(String packageName) { - Preconditions.checkNotNull(packageName); - + private int assertCanBeTransferredAndReturnNewOwner(String packageName) + throws PackageManager.NameNotFoundException { ApplicationInfo newOwnerAppInfo = mPm.getApplicationInfo(packageName, 0, userId); if (newOwnerAppInfo == null) { - throw new ParcelableException(new PackageManager.NameNotFoundException(packageName)); + throw new PackageManager.NameNotFoundException(packageName); } if (PackageManager.PERMISSION_GRANTED != mPm.checkUidPermission( @@ -1204,31 +1214,104 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { throw new SecurityException("Can only transfer sessions that use public options"); } - List childSessions = getChildSessions(); + return newOwnerAppInfo.uid; + } + + @Override + public void transfer(String packageName, IntentSender statusReceiver) { + Preconditions.checkNotNull(statusReceiver); + Preconditions.checkNotNull(packageName); + + try { + assertCanBeTransferredAndReturnNewOwner(packageName); + } catch (PackageManager.NameNotFoundException e) { + throw new ParcelableException(e); + } synchronized (mLock) { assertCallerIsOwnerOrRootLocked(); assertPreparedAndNotSealedLocked("transfer"); + } - try { - sealAndValidateLocked(childSessions); - } catch (PackageManagerException e) { - throw new IllegalArgumentException("Package is not valid", e); + final SomeArgs args = SomeArgs.obtain(); + args.arg1 = packageName; + args.arg2 = statusReceiver; + + mHandler.obtainMessage(MSG_TRANSFER, args).sendToTarget(); + } + + private void handleTransfer(IntentSender statusReceiver, String packageName) { + List childSessions = getChildSessions(); + + try { + final int uid = assertCanBeTransferredAndReturnNewOwner(packageName); + + synchronized (mLock) { + assertPreparedAndNotSealedLocked("transfer"); + + try { + sealAndValidateLocked(childSessions); + } catch (PackageManagerException e) { + throw new IllegalArgumentException("Package is not valid", e); + } + + if (!mPackageName.equals(mInstallSource.installerPackageName)) { + throw new SecurityException( + "Can only transfer sessions that update the original installer"); + } + + mInstallerUid = uid; + mInstallSource = InstallSource.create(packageName, null, packageName, false); } - - if (!mPackageName.equals(mInstallSource.installerPackageName)) { - throw new SecurityException("Can only transfer sessions that update the original " - + "installer"); - } - - mInstallerUid = newOwnerAppInfo.uid; - mInstallSource = InstallSource.create(packageName, null, packageName, false); + } catch (PackageManager.NameNotFoundException e) { + onSessionTransferStatus(statusReceiver, packageName, + PackageInstaller.STATUS_FAILURE_NAME_NOT_FOUND, e.getMessage()); + return; + } catch (IllegalStateException e) { + onSessionTransferStatus(statusReceiver, packageName, + PackageInstaller.STATUS_FAILURE_ILLEGAL_STATE, e.getMessage()); + return; + } catch (SecurityException e) { + onSessionTransferStatus(statusReceiver, packageName, + PackageInstaller.STATUS_FAILURE_SECURITY, e.getMessage()); + return; + } catch (Throwable e) { + onSessionTransferStatus(statusReceiver, packageName, PackageInstaller.STATUS_FAILURE, + e.getMessage()); + return; } // Persist the fact that we've sealed ourselves to prevent // mutations of any hard links we create. We do this without holding // the session lock, since otherwise it's a lock inversion. mCallback.onSessionSealedBlocking(this); + + // Report success. + onSessionTransferStatus(statusReceiver, packageName, PackageInstaller.STATUS_SUCCESS, null); + } + + private void onSessionTransferStatus(IntentSender statusReceiver, String otherPackageName, + int status, String statusMessage) { + final String packageName; + synchronized (mLock) { + packageName = mPackageName; + } + + final Intent fillIn = new Intent(); + fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName); + fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId); + fillIn.putExtra(PackageInstaller.EXTRA_OTHER_PACKAGE_NAME, otherPackageName); + + fillIn.putExtra(PackageInstaller.EXTRA_STATUS, status); + if (!TextUtils.isEmpty(statusMessage)) { + fillIn.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, statusMessage); + } + + try { + statusReceiver.sendIntent(mContext, 0, fillIn, null, null); + } catch (IntentSender.SendIntentException ignored) { + } + } private void handleCommit() {