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

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

DataLoader version of installation API.

Test: atest PackageManagerShellCommandTest
Bug: b/136132412 b/146080380

Change-Id: Ifd899c7d43d20fb1851118676fbc892c4e40a3ff
parent 960a8cf9
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -39,6 +39,9 @@ interface IPackageInstallerSession {
    void transfer(in String packageName, in IntentSender statusReceiver);
    void abandon();

    void addFile(String name, long lengthBytes, in byte[] metadata);
    void removeFile(String name);

    boolean isMultiPackage();
    int[] getChildSessionIds();
    void addChildSessionId(in int sessionId);
@@ -46,5 +49,4 @@ interface IPackageInstallerSession {
    int getParentSessionId();

    boolean isStaged();
    void addFile(in String name, long size, in byte[] metadata);
}
+75 −23
Original line number Diff line number Diff line
@@ -230,6 +230,15 @@ public class PackageInstaller {
    /** {@hide} */
    public static final String EXTRA_CALLBACK = "android.content.pm.extra.CALLBACK";

    /**
     * Streaming installation pending.
     * Caller should make sure DataLoader is able to prepare image and reinitiate the operation.
     *
     * @see #EXTRA_SESSION_ID
     * {@hide}
     */
    public static final int STATUS_PENDING_STREAMING = -2;

    /**
     * User action is currently required to proceed. You can launch the intent
     * activity described by {@link Intent#EXTRA_INTENT} to involve the user and
@@ -1059,13 +1068,56 @@ public class PackageInstaller {
            }
        }


        /**
         * Adds a file to session. On commit this file will be pulled from dataLoader.
         *
         * @param name arbitrary, unique name of your choosing to identify the
         *            APK being written. You can open a file again for
         *            additional writes (such as after a reboot) by using the
         *            same name. This name is only meaningful within the context
         *            of a single install session.
         * @param lengthBytes total size of the file being written.
         *            The system may clear various caches as needed to allocate
         *            this space.
         * @param metadata additional info use by dataLoader to pull data for the file.
         * @throws SecurityException if called after the session has been
         *             sealed or abandoned
         * @throws IllegalStateException if called for non-callback session
         * {@hide}
         */
        public void addFile(@NonNull String name, long lengthBytes, @NonNull byte[] metadata) {
            try {
                mSession.addFile(name, lengthBytes, metadata);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }

        /**
         * Removes a file.
         *
         * @param name name of a file, e.g. split.
         * @throws SecurityException if called after the session has been
         *             sealed or abandoned
         * @throws IllegalStateException if called for non-callback session
         * {@hide}
         */
        public void removeFile(@NonNull String name) {
            try {
                mSession.removeFile(name);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }

        /**
         * Attempt to commit everything staged in this session. This may require
         * user intervention, and so it may not happen immediately. The final
         * result of the commit will be reported through the given callback.
         * <p>
         * Once this method is called, the session is sealed and no additional
         * mutations may be performed on the session. If the device reboots
         * Once this method is called, the session is sealed and no additional mutations may be
         * performed on the session. In case of device reboot or data loader transient failure
         * before the session has been finalized, you may commit the session again.
         * <p>
         * If the installer is the device owner or the affiliated profile owner, there will be no
@@ -1219,27 +1271,6 @@ public class PackageInstaller {
            }
        }

        /**
         * Configure files for an installation session.
         *
         * Currently only for Incremental installation session. Once this method is called,
         * the files and their paths, as specified in the parameters, will be created and properly
         * configured in the Incremental File System.
         *
         * TODO(b/136132412): update this and InstallationFile class with latest API design.
         *
         * @throws IllegalStateException if {@link SessionParams#incrementalParams} is null.
         *
         * @hide
         */
        public void addFile(@NonNull String name, long size, @NonNull byte[] metadata) {
            try {
                mSession.addFile(name, size, metadata);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }

        /**
         * Release this session object. You can open the session again if it
         * hasn't been finalized.
@@ -1429,6 +1460,9 @@ public class PackageInstaller {
        public long requiredInstalledVersionCode = PackageManager.VERSION_CODE_HIGHEST;
        /** {@hide} */
        public IncrementalDataLoaderParams incrementalParams;
        /** TODO(b/146080380): add a class name to make it fully compatible with ComponentName.
         * {@hide} */
        public String dataLoaderPackageName;

        /**
         * Construct parameters for a new package install session.
@@ -1468,6 +1502,7 @@ public class PackageInstaller {
                incrementalParams = new IncrementalDataLoaderParams(
                        dataLoaderParamsParcel);
            }
            dataLoaderPackageName = source.readString();
        }

        /** {@hide} */
@@ -1492,6 +1527,7 @@ public class PackageInstaller {
            ret.isStaged = isStaged;
            ret.requiredInstalledVersionCode = requiredInstalledVersionCode;
            ret.incrementalParams = incrementalParams;
            ret.dataLoaderPackageName = dataLoaderPackageName;
            return ret;
        }

@@ -1831,6 +1867,20 @@ public class PackageInstaller {
            this.incrementalParams = incrementalParams;
        }

        /**
         * Set the data provider params for the session.
         * This also switches installation into callback mode and disallow direct writes into
         * staging folder.
         * TODO(b/146080380): unify dataprovider params with Incremental.
         *
         * @param dataLoaderPackageName name of the dataLoader package
         * {@hide}
         */
        @RequiresPermission(Manifest.permission.INSTALL_PACKAGES)
        public void setDataLoaderPackageName(String dataLoaderPackageName) {
            this.dataLoaderPackageName = dataLoaderPackageName;
        }

        /** {@hide} */
        public void dump(IndentingPrintWriter pw) {
            pw.printPair("mode", mode);
@@ -1851,6 +1901,7 @@ public class PackageInstaller {
            pw.printPair("isMultiPackage", isMultiPackage);
            pw.printPair("isStaged", isStaged);
            pw.printPair("requiredInstalledVersionCode", requiredInstalledVersionCode);
            pw.printPair("dataLoaderPackageName", dataLoaderPackageName);
            pw.println();
        }

@@ -1885,6 +1936,7 @@ public class PackageInstaller {
            } else {
                dest.writeParcelable(null, flags);
            }
            dest.writeString(dataLoaderPackageName);
        }

        public static final Parcelable.Creator<SessionParams>
+19 −3
Original line number Diff line number Diff line
@@ -637,7 +637,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
        session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this,
                mInstallThread.getLooper(), mStagingManager, sessionId, userId, callingUid,
                installSource, params, createdMillis,
                stageDir, stageCid, false, false, false, null, SessionInfo.INVALID_ID,
                stageDir, stageCid, null, false, false, false, null, SessionInfo.INVALID_ID,
                false, false, false, SessionInfo.STAGED_SESSION_NO_ERROR, "");

        synchronized (mSessions) {
@@ -1014,12 +1014,28 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
        }
    }

    static void sendPendingStreaming(Context context, IntentSender target, int sessionId,
            Throwable cause) {
        final Intent intent = new Intent();
        intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
        intent.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_PENDING_STREAMING);
        if (cause != null && !TextUtils.isEmpty(cause.getMessage())) {
            intent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE,
                    "Staging Image Not Ready [" + cause.getMessage() + "]");
        } else {
            intent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, "Staging Image Not Ready");
        }
        try {
            target.sendIntent(context, 0, intent, null, null);
        } catch (SendIntentException ignored) {
        }
    }

    static void sendOnUserActionRequired(Context context, IntentSender target, int sessionId,
            Intent intent) {
        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_STATUS, PackageInstaller.STATUS_PENDING_USER_ACTION);
        fillIn.putExtra(Intent.EXTRA_INTENT, intent);
        try {
            target.sendIntent(context, 0, fillIn, null, null);
+309 −47

File changed.

Preview size limit exceeded, changes collapsed.

+58 −10
Original line number Diff line number Diff line
@@ -114,6 +114,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -135,6 +136,8 @@ class PackageManagerShellCommand extends ShellCommand {
    private final static String ART_PROFILE_SNAPSHOT_DEBUG_LOCATION = "/data/misc/profman/";
    private static final int DEFAULT_WAIT_MS = 60 * 1000;

    private static final String PM_SHELL_DATALOADER = "com.android.pm.dataloader";

    final IPackageManager mInterface;
    final IPermissionManager mPermissionManager;
    final private WeakHashMap<String, Resources> mResourceCache =
@@ -175,6 +178,8 @@ class PackageManagerShellCommand extends ShellCommand {
                    return runQueryIntentReceivers();
                case "install":
                    return runInstall();
                case "install-streaming":
                    return runStreamingInstall();
                case "install-abandon":
                case "install-destroy":
                    return runInstallAbandon();
@@ -1152,9 +1157,21 @@ class PackageManagerShellCommand extends ShellCommand {
        return 0;
    }

    private int runStreamingInstall() throws RemoteException {
        final InstallParams params = makeInstallParams();
        if (TextUtils.isEmpty(params.sessionParams.dataLoaderPackageName)) {
            params.sessionParams.setDataLoaderPackageName(PM_SHELL_DATALOADER);
        }
        return doRunInstall(params);
    }

    private int runInstall() throws RemoteException {
        return doRunInstall(makeInstallParams());
    }

    private int doRunInstall(final InstallParams params) throws RemoteException {
        final PrintWriter pw = getOutPrintWriter();
        final InstallParams params = makeInstallParams();
        final boolean streaming = !TextUtils.isEmpty(params.sessionParams.dataLoaderPackageName);

        ArrayList<String> inPaths = getRemainingArgs();
        if (inPaths.isEmpty()) {
@@ -1181,19 +1198,32 @@ class PackageManagerShellCommand extends ShellCommand {
            return 1;
        }

        if (!streaming) {
            setParamsSize(params, inPaths);
        }

        final int sessionId = doCreateSession(params.sessionParams,
                params.installerPackageName, params.userId);
        boolean abandonSession = true;
        try {
            for (String inPath : inPaths) {
                String splitName = hasSplits ? (new File(inPath)).getName()
                if (streaming) {
                    String name = new File(inPath).getName();
                    byte[] metadata = inPath.getBytes(StandardCharsets.UTF_8);
                    if (doAddFile(sessionId, name, params.sessionParams.sizeBytes, metadata,
                            false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
                        return 1;
                    }
                } else {
                    String splitName = hasSplits ? new File(inPath).getName()
                            : "base." + (isApex ? "apex" : "apk");

                    if (doWriteSplit(sessionId, inPath, params.sessionParams.sizeBytes, splitName,
                            false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
                        return 1;
                    }
                }
            }
            if (doCommitSession(sessionId, false /*logSuccess*/)
                    != PackageInstaller.STATUS_SUCCESS) {
                return 1;
@@ -2927,11 +2957,32 @@ class PackageManagerShellCommand extends ShellCommand {
        return sessionId;
    }

    private int doAddFile(int sessionId, String name, long sizeBytes, byte[] metadata,
            boolean logSuccess) throws RemoteException {
        PackageInstaller.Session session = new PackageInstaller.Session(
                mInterface.getPackageInstaller().openSession(sessionId));
        try {
            session.addFile(name, sizeBytes, metadata);

            if (logSuccess) {
                getOutPrintWriter().println("Success");
            }

            return 0;
        } finally {
            IoUtils.closeQuietly(session);
        }
    }

    private int doWriteSplit(int sessionId, String inPath, long sizeBytes, String splitName,
            boolean logSuccess) throws RemoteException {
        PackageInstaller.Session session = null;
        try {
            session = new PackageInstaller.Session(
                    mInterface.getPackageInstaller().openSession(sessionId));

            final PrintWriter pw = getOutPrintWriter();

            final ParcelFileDescriptor fd;
            if (STDIN_PATH.equals(inPath)) {
                fd = ParcelFileDescriptor.dup(getInFileDescriptor());
@@ -2953,8 +3004,6 @@ class PackageManagerShellCommand extends ShellCommand {
                return 1;
            }

            session = new PackageInstaller.Session(
                    mInterface.getPackageInstaller().openSession(sessionId));
            session.write(splitName, 0, sizeBytes, fd);

            if (logSuccess) {
@@ -3000,7 +3049,6 @@ class PackageManagerShellCommand extends ShellCommand {
        try {
            session = new PackageInstaller.Session(
                    mInterface.getPackageInstaller().openSession(sessionId));

            for (String splitName : splitNames) {
                session.removeSplit(splitName);
            }
Loading