Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ea4fe39a authored by Alex Buynytskyy's avatar Alex Buynytskyy
Browse files

Adding a non-blocking version of transfer.

Along with blocking utility method.

Test: atest PackageManagerShellCommandTest
Bug: b/136132412

Change-Id: I4ff8504c54c989e4d6a18d53d8b015d9481b4781
parent 426e4c1b
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -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;
  }
+1 −1
Original line number Diff line number Diff line
@@ -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();
+101 −4
Original line number Diff line number Diff line
@@ -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}.
     * <p>
     * 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.
     * <p>
     * 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}.
         *
         * <p>If the device reboots before the session has been finalized, you may commit the
         * session again.
@@ -1093,6 +1128,9 @@ 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.
@@ -1100,12 +1138,13 @@ public class PackageInstaller {
         * @throws SecurityException if streams opened through
         *                           {@link #openWrite(String, long, long) are still open.
         */
        public void transfer(@NonNull String packageName)
        public void transfer(@NonNull String packageName, @NonNull IntentSender statusReceiver)
                throws PackageManager.NameNotFoundException {
            Preconditions.checkNotNull(statusReceiver);
            Preconditions.checkNotNull(packageName);

            try {
                mSession.transfer(packageName);
                mSession.transfer(packageName, statusReceiver);
            } catch (ParcelableException e) {
                e.maybeRethrow(PackageManager.NameNotFoundException.class);
                throw new RuntimeException(e);
@@ -1114,6 +1153,64 @@ public class PackageInstaller {
            }
        }

        /**
         * 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.
         * @throws SecurityException if the session does not update the original installer
         * @throws SecurityException if streams opened through
         *                           {@link #openWrite(String, long, long) are still open.
         */
        public void transfer(@NonNull String packageName)
                throws PackageManager.NameNotFoundException {
            Preconditions.checkNotNull(packageName);

            CompletableFuture<Intent> intentFuture = new CompletableFuture<Intent>();
            try {
                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);
            }

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

        /**
         * Release this session object. You can open the session again if it
         * hasn't been finalized.
+104 −21
Original line number Diff line number Diff line
@@ -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.
     *
     * <p>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,11 +1214,40 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            throw new SecurityException("Can only transfer sessions that use public options");
        }

        List<PackageInstallerSession> 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");
        }

        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<PackageInstallerSession> childSessions = getChildSessions();

        try {
            final int uid = assertCanBeTransferredAndReturnNewOwner(packageName);

            synchronized (mLock) {
                assertPreparedAndNotSealedLocked("transfer");

                try {
                    sealAndValidateLocked(childSessions);
@@ -1217,18 +1256,62 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
                }

                if (!mPackageName.equals(mInstallSource.installerPackageName)) {
                throw new SecurityException("Can only transfer sessions that update the original "
                        + "installer");
                    throw new SecurityException(
                            "Can only transfer sessions that update the original installer");
                }

            mInstallerUid = newOwnerAppInfo.uid;
                mInstallerUid = 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() {