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

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

Improve performance of file copy in PackageInstaller.

Bug: 275658500
Fixes: 275658500
Test: manually install an APK
Test: atest InstallInfoTest
Change-Id: If9a816aab7adb58dec7561eeaf46031c0101daf3
Merged-In: If9a816aab7adb58dec7561eeaf46031c0101daf3
parent c5951997
Loading
Loading
Loading
Loading
+34 −0
Original line number Diff line number Diff line
@@ -2072,6 +2072,25 @@ public class PackageInstaller {
        return new InstallInfo(result);
    }

    /**
     * Parse a single APK file passed as an FD to get install relevant information about
     * the package wrapped in {@link InstallInfo}.
     * @throws PackageParsingException if the package source file(s) provided is(are) not valid,
     * or the parser isn't able to parse the supplied source(s).
     * @hide
     */
    @NonNull
    public InstallInfo readInstallInfo(@NonNull ParcelFileDescriptor pfd,
            @Nullable String debugPathName, int flags) throws PackageParsingException {
        final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
        final ParseResult<PackageLite> result = ApkLiteParseUtils.parseMonolithicPackageLite(input,
                pfd.getFileDescriptor(), debugPathName, flags);
        if (result.isError()) {
            throw new PackageParsingException(result.getErrorCode(), result.getErrorMessage());
        }
        return new InstallInfo(result);
    }

    // (b/239722738) This class serves as a bridge between the PackageLite class, which
    // is a hidden class, and the consumers of this class. (e.g. InstallInstalling.java)
    // This is a part of an effort to remove dependency on hidden APIs and use SystemAPIs or
@@ -2125,6 +2144,21 @@ public class PackageInstaller {
        public long calculateInstalledSize(@NonNull SessionParams params) throws IOException {
            return InstallLocationUtils.calculateInstalledSize(mPkg, params.abiOverride);
        }

        /**
         * @param params {@link SessionParams} of the installation
         * @param pfd of an APK opened for read
         * @return Total disk space occupied by an application after installation.
         * Includes the size of the raw APKs, possibly unpacked resources, raw dex metadata files,
         * and all relevant native code.
         * @throws IOException when size of native binaries cannot be calculated.
         * @hide
         */
        public long calculateInstalledSize(@NonNull SessionParams params,
                @NonNull ParcelFileDescriptor pfd) throws IOException {
            return InstallLocationUtils.calculateInstalledSize(mPkg, params.abiOverride,
                    pfd.getFileDescriptor());
        }
    }

    /**
+28 −1
Original line number Diff line number Diff line
@@ -109,7 +109,7 @@ public class ApkLiteParseUtils {
    }

    /**
     * Parse lightweight details about a single APK files.
     * Parse lightweight details about a single APK file.
     */
    public static ParseResult<PackageLite> parseMonolithicPackageLite(ParseInput input,
            File packageFile, int flags) {
@@ -134,6 +134,33 @@ public class ApkLiteParseUtils {
        }
    }

    /**
     * Parse lightweight details about a single APK file passed as an FD.
     */
    public static ParseResult<PackageLite> parseMonolithicPackageLite(ParseInput input,
            FileDescriptor packageFd, String debugPathName, int flags) {
        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
        try {
            final ParseResult<ApkLite> result = parseApkLite(input, packageFd, debugPathName,
                    flags);
            if (result.isError()) {
                return input.error(result);
            }

            final ApkLite baseApk = result.getResult();
            final String packagePath = debugPathName;
            return input.success(
                    new PackageLite(packagePath, baseApk.getPath(), baseApk, null /* splitNames */,
                            null /* isFeatureSplits */, null /* usesSplitNames */,
                            null /* configForSplit */, null /* splitApkPaths */,
                            null /* splitRevisionCodes */, baseApk.getTargetSdkVersion(),
                            null /* requiredSplitTypes */, null, /* splitTypes */
                            baseApk.isAllowUpdateOwnership()));
        } finally {
            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
        }
    }

    /**
     * Parse lightweight details about a directory of APKs.
     *
+9 −4
Original line number Diff line number Diff line
@@ -16,14 +16,14 @@

package com.android.packageinstaller;

import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_STAGED_SESSION_ID;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;

import androidx.annotation.Nullable;

import java.io.File;

/**
 * Trampoline activity. Calls PackageInstallerActivity and deletes staged install file onResult.
 */
@@ -52,8 +52,13 @@ public class DeleteStagedFileOnResult extends Activity {
        super.onDestroy();

        if (isFinishing()) {
            File sourceFile = new File(getIntent().getData().getPath());
            new Thread(sourceFile::delete).start();
            // While we expect PIA/InstallStaging to abandon/commit the session, still there
            // might be cases when the session becomes orphan.
            int sessionId = getIntent().getIntExtra(EXTRA_STAGED_SESSION_ID, 0);
            try {
                getPackageManager().getPackageInstaller().abandonSession(sessionId);
            } catch (SecurityException ignored) {
            }
        }
    }
}
+14 −93
Original line number Diff line number Diff line
@@ -16,17 +16,17 @@

package com.android.packageinstaller;

import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_STAGED_SESSION_ID;

import android.app.PendingIntent;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.InstallInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Process;
import android.util.Log;
import android.view.View;
import android.widget.Button;
@@ -34,10 +34,7 @@ import android.widget.Button;
import androidx.annotation.Nullable;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * Send package to the package manager and handle results from package manager. Once the
@@ -77,7 +74,7 @@ public class InstallInstalling extends AlertActivity {
                .getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
        mPackageURI = getIntent().getData();

        if ("package".equals(mPackageURI.getScheme())) {
        if (PackageInstallerActivity.SCHEME_PACKAGE.equals(mPackageURI.getScheme())) {
            try {
                getPackageManager().installExistingPackage(appInfo.packageName);
                launchSuccess();
@@ -86,6 +83,8 @@ public class InstallInstalling extends AlertActivity {
                        PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
            }
        } else {
            // ContentResolver.SCHEME_FILE
            // STAGED_SESSION_ID extra contains an ID of a previously staged install session.
            final File sourceFile = new File(mPackageURI.getPath());
            PackageUtil.AppSnippet as = PackageUtil.getAppSnippet(this, appInfo, sourceFile);

@@ -122,41 +121,6 @@ public class InstallInstalling extends AlertActivity {
                    // Does not happen
                }
            } else {
                PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
                        PackageInstaller.SessionParams.MODE_FULL_INSTALL);
                final Uri referrerUri = getIntent().getParcelableExtra(Intent.EXTRA_REFERRER);
                params.setPackageSource(
                        referrerUri != null ? PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE
                                : PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE);
                params.setInstallAsInstantApp(false);
                params.setReferrerUri(referrerUri);
                params.setOriginatingUri(getIntent()
                        .getParcelableExtra(Intent.EXTRA_ORIGINATING_URI));
                params.setOriginatingUid(getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,
                        Process.INVALID_UID));
                params.setInstallerPackageName(getIntent().getStringExtra(
                        Intent.EXTRA_INSTALLER_PACKAGE_NAME));
                params.setInstallReason(PackageManager.INSTALL_REASON_USER);

                File file = new File(mPackageURI.getPath());
                try {
                    final InstallInfo result = getPackageManager().getPackageInstaller()
                            .readInstallInfo(file, 0);
                    params.setAppPackageName(result.getPackageName());
                    params.setInstallLocation(result.getInstallLocation());
                    try {
                        params.setSize(result.calculateInstalledSize(params));
                    } catch (IOException e) {
                        e.printStackTrace();
                        params.setSize(file.length());
                    }
                } catch (PackageInstaller.PackageParsingException e) {

                    Log.e(LOG_TAG, "Cannot parse package " + file + ". Assuming defaults.", e);
                    Log.e(LOG_TAG,
                            "Cannot calculate installed size " + file + ". Try only apk size.");
                    params.setSize(file.length());
                }
                try {
                    mInstallId = InstallEventReceiver
                            .addObserver(this, EventResultPersister.GENERATE_NEW_ID,
@@ -166,9 +130,14 @@ public class InstallInstalling extends AlertActivity {
                            PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
                }

                try {
                    mSessionId = getPackageManager().getPackageInstaller().createSession(params);
                } catch (IOException e) {
                mSessionId = getIntent().getIntExtra(EXTRA_STAGED_SESSION_ID, 0);
                // Try to open session previously staged in InstallStaging.
                try (PackageInstaller.Session ignored =
                             getPackageManager().getPackageInstaller().openSession(
                        mSessionId)) {
                    Log.d(LOG_TAG, "Staged session is valid, proceeding with the install");
                } catch (IOException | SecurityException e) {
                    Log.e(LOG_TAG, "Invalid session id passed", e);
                    launchFailure(PackageInstaller.STATUS_FAILURE,
                            PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
                }
@@ -293,57 +262,9 @@ public class InstallInstalling extends AlertActivity {

        @Override
        protected PackageInstaller.Session doInBackground(Void... params) {
            PackageInstaller.Session session;
            try {
                session = getPackageManager().getPackageInstaller().openSession(mSessionId);
                return getPackageManager().getPackageInstaller().openSession(mSessionId);
            } catch (IOException e) {
                synchronized (this) {
                    isDone = true;
                    notifyAll();
                }
                return null;
            }

            session.setStagingProgress(0);

            try {
                File file = new File(mPackageURI.getPath());

                try (InputStream in = new FileInputStream(file)) {
                    long sizeBytes = file.length();
                    long totalRead = 0;
                    try (OutputStream out = session
                            .openWrite("PackageInstaller", 0, sizeBytes)) {
                        byte[] buffer = new byte[1024 * 1024];
                        while (true) {
                            int numRead = in.read(buffer);

                            if (numRead == -1) {
                                session.fsync(out);
                                break;
                            }

                            if (isCancelled()) {
                                session.close();
                                break;
                            }

                            out.write(buffer, 0, numRead);
                            if (sizeBytes > 0) {
                                totalRead += numRead;
                                float fraction = ((float) totalRead / (float) sizeBytes);
                                session.setStagingProgress(fraction);
                            }
                        }
                    }
                }

                return session;
            } catch (IOException | SecurityException e) {
                Log.e(LOG_TAG, "Could not write package", e);

                session.close();

                return null;
            } finally {
                synchronized (this) {
+193 −51

File changed.

Preview size limit exceeded, changes collapsed.

Loading