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

Commit 6bce3f64 authored by Alex Buynytskyy's avatar Alex Buynytskyy
Browse files

Async DataLoader calls.

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

Change-Id: I2e2c44096c8775d057ba10393e92f04f546d940d
parent 769f815c
Loading
Loading
Loading
Loading
+144 −133
Original line number Diff line number Diff line
@@ -154,6 +154,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_STREAM_AND_VALIDATE = 4;

    /** XML constants used for persisting a session */
    static final String TAG_SESSION = "session";
@@ -369,6 +370,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
    @GuardedBy("mLock")
    private boolean mVerityFound;

    private boolean mDataLoaderFinished = false;

    // TODO(b/146080380): merge file list with Callback installation.
    private IncrementalFileStorages mIncrementalFileStorages;

@@ -402,6 +405,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
                case MSG_SEAL:
                    handleSeal((IntentSender) msg.obj);
                    break;
                case MSG_STREAM_AND_VALIDATE:
                    handleStreamAndValidate();
                    break;
                case MSG_COMMIT:
                    handleCommit();
                    break;
@@ -1009,11 +1015,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
    }

    private void handleSeal(@NonNull IntentSender statusReceiver) {
        // TODO(b/136132412): update with new APIs
        if (mIncrementalFileStorages != null) {
            mIncrementalFileStorages.startLoading();
        }
        if (!markAsCommitted(statusReceiver)) {
        if (!markAsSealed(statusReceiver)) {
            return;
        }
        if (isMultiPackage()) {
@@ -1021,20 +1023,51 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            final IntentSender childIntentSender =
                    new ChildStatusIntentReceiver(remainingSessions, statusReceiver)
                            .getIntentSender();
            boolean commitFailed = false;
            boolean sealFailed = false;
            for (int i = mChildSessionIds.size() - 1; i >= 0; --i) {
                final int childSessionId = mChildSessionIds.keyAt(i);
                // seal all children, regardless if any of them fail; we'll throw/return
                // as appropriate once all children have been processed
                if (!mSessionProvider.getSession(childSessionId)
                        .markAsSealed(childIntentSender)) {
                    sealFailed = true;
                }
            }
            if (sealFailed) {
                return;
            }
        }

        dispatchStreamAndValidate();
    }

    private void dispatchStreamAndValidate() {
        mHandler.obtainMessage(MSG_STREAM_AND_VALIDATE).sendToTarget();
    }

    private void handleStreamAndValidate() {
        // TODO(b/136132412): update with new APIs
        if (mIncrementalFileStorages != null) {
            mIncrementalFileStorages.startLoading();
        }

        boolean commitFailed = !markAsCommitted();

        if (isMultiPackage()) {
            for (int i = mChildSessionIds.size() - 1; i >= 0; --i) {
                final int childSessionId = mChildSessionIds.keyAt(i);
                // commit all children, regardless if any of them fail; we'll throw/return
                // as appropriate once all children have been processed
                if (!mSessionProvider.getSession(childSessionId)
                        .markAsCommitted(childIntentSender)) {
                        .markAsCommitted()) {
                    commitFailed = true;
                }
            }
        }

        if (commitFailed) {
            return;
        }
        }

        mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
    }
@@ -1160,44 +1193,48 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
    }

    /**
     * Do everything but actually commit the session. If this was not already called, the session
     * will be sealed and marked as committed. The caller of this method is responsible for
     * subsequently submitting this session for processing.
     * If this was not already called, the session will be sealed.
     *
     * This method may be called multiple times to update the status receiver validate caller
     * permissions.
     */
    private boolean markAsCommitted(@NonNull IntentSender statusReceiver) {
    private boolean markAsSealed(@NonNull IntentSender statusReceiver) {
        Objects.requireNonNull(statusReceiver);

        List<PackageInstallerSession> childSessions = getChildSessions();

        final boolean wasSealed;
        synchronized (mLock) {
            mRemoteStatusReceiver = statusReceiver;

            // After validations and updating the observer, we can skip re-sealing, etc. because we
            // have already marked ourselves as committed.
            if (mCommitted) {
            // After updating the observer, we can skip re-sealing.
            if (mSealed) {
                return true;
            }

            wasSealed = mSealed;
            try {
                if (!mSealed) {
                sealLocked(childSessions);
            } catch (PackageManagerException e) {
                return false;
            }
        }

                try {
                    streamAndValidateLocked();
                } catch (StreamingException e) {
                    // In case of streaming failure we don't want to fail or commit the session.
                    // Just return from this method and allow caller to commit again.
                    PackageInstallerService.sendPendingStreaming(mContext, mRemoteStatusReceiver,
                            sessionId, e);
                    return false;
        // 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);

        return true;
    }
            } catch (PackageManagerException e) {

    private boolean markAsCommitted() {
        synchronized (mLock) {
            Objects.requireNonNull(mRemoteStatusReceiver);

            if (mCommitted) {
                return true;
            }

            if (!streamAndValidateLocked()) {
                return false;
            }

@@ -1212,12 +1249,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            mCommitted = true;
        }

        if (!wasSealed) {
            // 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);
        }
        return true;
    }

@@ -1284,17 +1315,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        }
    }

    /**
     * Convenience wrapper, see {@link #sealLocked(List<PackageInstallerSession>) seal} and
     * {@link #streamAndValidateLocked()}.
     */
    @GuardedBy("mLock")
    private void sealAndValidateLocked(List<PackageInstallerSession> childSessions)
            throws PackageManagerException, StreamingException {
        sealLocked(childSessions);
        streamAndValidateLocked();
    }

    /**
     * Seal the session to prevent further modification.
     *
@@ -1328,23 +1348,23 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
     * Prepare DataLoader and stream content for DataLoader sessions.
     * Validate the contents of all session.
     *
     * @throws StreamingException if streaming failed.
     * @throws PackageManagerException if validation failed.
     * @return false if validation failed.
     */
    @GuardedBy("mLock")
    private void streamAndValidateLocked()
            throws PackageManagerException, StreamingException {
    private boolean streamAndValidateLocked() {
        try {
            // Read transfers from the original owner stay open, but as the session's data cannot
            // be modified anymore, there is no leak of information. For staged sessions, further
            // validation is performed by the staging manager.
            if (!params.isMultiPackage) {
                if (!prepareDataLoaderLocked()) {
                    return false;
                }

                final PackageInfo pkgInfo = mPm.getPackageInfo(
                        params.appPackageName, PackageManager.GET_SIGNATURES
                                | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);

                prepareDataLoader();

                if (isApexInstallation()) {
                    validateApexInstallLocked();
                } else {
@@ -1355,15 +1375,16 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            if (params.isStaged) {
                mStagingManager.checkNonOverlappingWithStagedSessions(this);
            }

            return true;
        } catch (PackageManagerException e) {
            throw onSessionVerificationFailure(e);
        } catch (StreamingException e) {
            throw e;
            onSessionVerificationFailure(e);
        } catch (Throwable e) {
            // Convert all exceptions into package manager exceptions as only those are handled
            // in the code above.
            throw onSessionVerificationFailure(new PackageManagerException(e));
            onSessionVerificationFailure(new PackageManagerException(e));
        }
        return false;
    }

    private PackageManagerException onSessionVerificationFailure(PackageManagerException e) {
@@ -2377,6 +2398,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        synchronized (mLock) {
            assertCallerIsOwnerOrRootLocked();
            assertPreparedAndNotSealedLocked("addFile");

            mFiles.add(FileInfo.added(location, name, lengthBytes, metadata, signature));
        }
    }
@@ -2399,48 +2421,30 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        }
    }

    static class Notificator {
        private int mValue = 0;

        void setValue(int value) {
            synchronized (this) {
                mValue = value;
                this.notify();
            }
        }
        int waitForValue() {
            synchronized (this) {
                while (mValue == 0) {
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        // Happens if someone interrupts your thread.
                    }
                }
                return mValue;
            }
        }
    }

    /**
     * Makes sure files are present in staging location.
     */
    private void prepareDataLoader()
            throws PackageManagerException, StreamingException {
    @GuardedBy("mLock")
    private boolean prepareDataLoaderLocked()
            throws PackageManagerException {
        if (!isDataLoaderInstallation()) {
            return;
            return true;
        }
        if (mDataLoaderFinished) {
            return true;
        }

        List<InstallationFile> addedFiles = mFiles.stream().filter(
        final List<InstallationFile> addedFiles = mFiles.stream().filter(
                file -> sAddedFilter.accept(new File(file.name))).map(
                    file -> new InstallationFile(
                        file.name, file.lengthBytes, file.metadata)).collect(
                Collectors.toList());
        List<String> removedFiles = mFiles.stream().filter(
        final List<String> removedFiles = mFiles.stream().filter(
                file -> sRemovedFilter.accept(new File(file.name))).map(
                    file -> file.name.substring(
                        0, file.name.length() - REMOVE_MARKER_EXTENSION.length())).collect(
                Collectors.toList());

        if (mIncrementalFileStorages != null) {
            for (InstallationFile file : addedFiles) {
                try {
@@ -2451,46 +2455,73 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
                            "Failed to add and configure Incremental File: " + file.getName(), ex);
                }
            }
            return;
            return true;
        }

        final FileSystemConnector connector = new FileSystemConnector(addedFiles);

        DataLoaderManager dataLoaderManager = mContext.getSystemService(DataLoaderManager.class);
        final DataLoaderManager dataLoaderManager = mContext.getSystemService(
                DataLoaderManager.class);
        if (dataLoaderManager == null) {
            throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE,
                    "Failed to find data loader manager service");
        }

        // TODO(b/146080380): make this code async.
        final Notificator created = new Notificator();
        final Notificator started = new Notificator();
        final Notificator imageReady = new Notificator();

        IDataLoaderStatusListener listener = new IDataLoaderStatusListener.Stub() {
            @Override
            public void onStatusChanged(int dataLoaderId, int status) {
                try {
                    if (status == IDataLoaderStatusListener.DATA_LOADER_DESTROYED) {
                        return;
                    }

                    IDataLoader dataLoader = dataLoaderManager.getDataLoader(dataLoaderId);
                    if (dataLoader == null) {
                        mDataLoaderFinished = true;
                        onSessionVerificationFailure(
                                new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE,
                                        "Failure to obtain data loader"));
                        return;
                    }

                    switch (status) {
                        case IDataLoaderStatusListener.DATA_LOADER_CREATED: {
                        created.setValue(1);
                            dataLoader.start();
                            break;
                        }
                        case IDataLoaderStatusListener.DATA_LOADER_STARTED: {
                        started.setValue(1);
                            dataLoader.prepareImage(addedFiles, removedFiles);
                            break;
                        }
                        case IDataLoaderStatusListener.DATA_LOADER_IMAGE_READY: {
                        imageReady.setValue(1);
                            mDataLoaderFinished = true;
                            if (hasParentSessionId()) {
                                mSessionProvider.getSession(
                                        mParentSessionId).dispatchStreamAndValidate();
                            } else {
                                dispatchStreamAndValidate();
                            }
                            dataLoader.destroy();
                            break;
                        }
                        case IDataLoaderStatusListener.DATA_LOADER_IMAGE_NOT_READY: {
                        imageReady.setValue(2);
                            mDataLoaderFinished = true;
                            onSessionVerificationFailure(
                                    new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE,
                                            "Failed to prepare image."));
                            dataLoader.destroy();
                            break;
                        }
                    }
                } catch (RemoteException e) {
                    // In case of streaming failure we don't want to fail or commit the session.
                    // Just return from this method and allow caller to commit again.
                    PackageInstallerService.sendPendingStreaming(mContext,
                            mRemoteStatusReceiver,
                            sessionId, new StreamingException(e));
                }
            }
        };

        final FileSystemConnector connector = new FileSystemConnector(addedFiles);
        final FileSystemControlParcel control = new FileSystemControlParcel();
        control.callback = connector;

@@ -2505,28 +2536,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
            throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE,
                    "Failed to initialize data loader");
        }
        created.waitForValue();

        IDataLoader dataLoader = dataLoaderManager.getDataLoader(sessionId);
        if (dataLoader == null) {
            throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE,
                    "Failure to obtain data loader");
        }

        try {
            dataLoader.start();
            started.waitForValue();

            dataLoader.prepareImage(addedFiles, removedFiles);
            if (imageReady.waitForValue() == 2) {
                throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE,
                        "Failed to prepare image.");
            }

            dataLoader.destroy();
        } catch (RemoteException e) {
            throw new StreamingException(e);
        }
        return false;
    }

    @Override
+1 −0
Original line number Diff line number Diff line
@@ -152,6 +152,7 @@ public class PackageManagerShellCommandDataLoader extends DataLoaderService {
                }
                return true;
            } catch (IOException e) {
                Slog.e(TAG, "Exception while streaming files", e);
                return false;
            }
        }