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

Commit 02bd7849 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Reduce PackageInstaller I/O pressure.

When performing a restore during initial device setup, we could be
installing hundreds of packages.  Currently, we're writing all
metadata (including heavy icons) for every session mutation!  Because
we're holding the mSessions lock while writing all this heavy data,
we end up causing ANRs when apps call other PackageInstaller APIs.

This patch mitigates by moving the heavy icon data into separate
per-session PNG files, which we only persist when changed.

Bug: 17881962, 17567794
Change-Id: I4dee15d4a65a8eb65c381e6bb7477728b6cc30d2
parent 71c4578d
Loading
Loading
Loading
Loading
+2 −0
Original line number Original line Diff line number Diff line
@@ -880,6 +880,8 @@ public class PackageInstaller {
        /** {@hide} */
        /** {@hide} */
        public String appLabel;
        public String appLabel;
        /** {@hide} */
        /** {@hide} */
        public long appIconLastModified = -1;
        /** {@hide} */
        public Uri originatingUri;
        public Uri originatingUri;
        /** {@hide} */
        /** {@hide} */
        public Uri referrerUri;
        public Uri referrerUri;
+1 −0
Original line number Original line Diff line number Diff line
@@ -1503,6 +1503,7 @@ public class XmlUtils {
        }
        }
    }
    }


    @Deprecated
    public static void writeBitmapAttribute(XmlSerializer out, String name, Bitmap value)
    public static void writeBitmapAttribute(XmlSerializer out, String name, Bitmap value)
            throws IOException {
            throws IOException {
        if (value != null) {
        if (value != null) {
+76 −8
Original line number Original line Diff line number Diff line
@@ -22,7 +22,6 @@ import static com.android.internal.util.XmlUtils.readIntAttribute;
import static com.android.internal.util.XmlUtils.readLongAttribute;
import static com.android.internal.util.XmlUtils.readLongAttribute;
import static com.android.internal.util.XmlUtils.readStringAttribute;
import static com.android.internal.util.XmlUtils.readStringAttribute;
import static com.android.internal.util.XmlUtils.readUriAttribute;
import static com.android.internal.util.XmlUtils.readUriAttribute;
import static com.android.internal.util.XmlUtils.writeBitmapAttribute;
import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
import static com.android.internal.util.XmlUtils.writeIntAttribute;
import static com.android.internal.util.XmlUtils.writeIntAttribute;
import static com.android.internal.util.XmlUtils.writeLongAttribute;
import static com.android.internal.util.XmlUtils.writeLongAttribute;
@@ -47,6 +46,8 @@ import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.net.Uri;
import android.os.Binder;
import android.os.Binder;
import android.os.Bundle;
import android.os.Bundle;
@@ -69,7 +70,6 @@ import android.text.format.DateUtils;
import android.util.ArraySet;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.AtomicFile;
import android.util.ExceptionUtils;
import android.util.ExceptionUtils;
import android.util.Log;
import android.util.Slog;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseBooleanArray;
@@ -125,6 +125,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
    private static final String ATTR_INSTALL_LOCATION = "installLocation";
    private static final String ATTR_INSTALL_LOCATION = "installLocation";
    private static final String ATTR_SIZE_BYTES = "sizeBytes";
    private static final String ATTR_SIZE_BYTES = "sizeBytes";
    private static final String ATTR_APP_PACKAGE_NAME = "appPackageName";
    private static final String ATTR_APP_PACKAGE_NAME = "appPackageName";
    @Deprecated
    private static final String ATTR_APP_ICON = "appIcon";
    private static final String ATTR_APP_ICON = "appIcon";
    private static final String ATTR_APP_LABEL = "appLabel";
    private static final String ATTR_APP_LABEL = "appLabel";
    private static final String ATTR_ORIGINATING_URI = "originatingUri";
    private static final String ATTR_ORIGINATING_URI = "originatingUri";
@@ -149,10 +150,16 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
    private final Callbacks mCallbacks;
    private final Callbacks mCallbacks;


    /**
    /**
     * File storing persisted {@link #mSessions}.
     * File storing persisted {@link #mSessions} metadata.
     */
     */
    private final AtomicFile mSessionsFile;
    private final AtomicFile mSessionsFile;


    /**
     * Directory storing persisted {@link #mSessions} metadata which is too
     * heavy to store directly in {@link #mSessionsFile}.
     */
    private final File mSessionsDir;

    private final InternalCallback mInternalCallback = new InternalCallback();
    private final InternalCallback mInternalCallback = new InternalCallback();


    /**
    /**
@@ -195,26 +202,38 @@ public class PackageInstallerService extends IPackageInstaller.Stub {


        mSessionsFile = new AtomicFile(
        mSessionsFile = new AtomicFile(
                new File(Environment.getSystemSecureDirectory(), "install_sessions.xml"));
                new File(Environment.getSystemSecureDirectory(), "install_sessions.xml"));
        mSessionsDir = new File(Environment.getSystemSecureDirectory(), "install_sessions");
        mSessionsDir.mkdirs();


        synchronized (mSessions) {
        synchronized (mSessions) {
            readSessionsLocked();
            readSessionsLocked();


            final ArraySet<File> unclaimed = Sets.newArraySet(mStagingDir.listFiles(sStageFilter));
            final ArraySet<File> unclaimedStages = Sets.newArraySet(
                    mStagingDir.listFiles(sStageFilter));
            final ArraySet<File> unclaimedIcons = Sets.newArraySet(
                    mSessionsDir.listFiles());


            // Ignore stages claimed by active sessions
            // Ignore stages and icons claimed by active sessions
            for (int i = 0; i < mSessions.size(); i++) {
            for (int i = 0; i < mSessions.size(); i++) {
                final PackageInstallerSession session = mSessions.valueAt(i);
                final PackageInstallerSession session = mSessions.valueAt(i);
                unclaimed.remove(session.stageDir);
                unclaimedStages.remove(session.stageDir);
                unclaimedIcons.remove(buildAppIconFile(session.sessionId));
            }
            }


            // Clean up orphaned staging directories
            // Clean up orphaned staging directories
            for (File stage : unclaimed) {
            for (File stage : unclaimedStages) {
                Slog.w(TAG, "Deleting orphan stage " + stage);
                Slog.w(TAG, "Deleting orphan stage " + stage);
                if (stage.isDirectory()) {
                if (stage.isDirectory()) {
                    FileUtils.deleteContents(stage);
                    FileUtils.deleteContents(stage);
                }
                }
                stage.delete();
                stage.delete();
            }
            }

            // Clean up orphaned icons
            for (File icon : unclaimedIcons) {
                Slog.w(TAG, "Deleting orphan icon " + icon);
                icon.delete();
            }
        }
        }
    }
    }


@@ -359,6 +378,12 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
        params.referrerUri = readUriAttribute(in, ATTR_REFERRER_URI);
        params.referrerUri = readUriAttribute(in, ATTR_REFERRER_URI);
        params.abiOverride = readStringAttribute(in, ATTR_ABI_OVERRIDE);
        params.abiOverride = readStringAttribute(in, ATTR_ABI_OVERRIDE);


        final File appIconFile = buildAppIconFile(sessionId);
        if (appIconFile.exists()) {
            params.appIcon = BitmapFactory.decodeFile(appIconFile.getAbsolutePath());
            params.appIconLastModified = appIconFile.lastModified();
        }

        return new PackageInstallerSession(mInternalCallback, mContext, mPm,
        return new PackageInstallerSession(mInternalCallback, mContext, mPm,
                mInstallThread.getLooper(), sessionId, userId, installerPackageName, installerUid,
                mInstallThread.getLooper(), sessionId, userId, installerPackageName, installerUid,
                params, createdMillis, stageDir, stageCid, prepared, sealed);
                params, createdMillis, stageDir, stageCid, prepared, sealed);
@@ -418,15 +443,38 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
        writeIntAttribute(out, ATTR_INSTALL_LOCATION, params.installLocation);
        writeIntAttribute(out, ATTR_INSTALL_LOCATION, params.installLocation);
        writeLongAttribute(out, ATTR_SIZE_BYTES, params.sizeBytes);
        writeLongAttribute(out, ATTR_SIZE_BYTES, params.sizeBytes);
        writeStringAttribute(out, ATTR_APP_PACKAGE_NAME, params.appPackageName);
        writeStringAttribute(out, ATTR_APP_PACKAGE_NAME, params.appPackageName);
        writeBitmapAttribute(out, ATTR_APP_ICON, params.appIcon);
        writeStringAttribute(out, ATTR_APP_LABEL, params.appLabel);
        writeStringAttribute(out, ATTR_APP_LABEL, params.appLabel);
        writeUriAttribute(out, ATTR_ORIGINATING_URI, params.originatingUri);
        writeUriAttribute(out, ATTR_ORIGINATING_URI, params.originatingUri);
        writeUriAttribute(out, ATTR_REFERRER_URI, params.referrerUri);
        writeUriAttribute(out, ATTR_REFERRER_URI, params.referrerUri);
        writeStringAttribute(out, ATTR_ABI_OVERRIDE, params.abiOverride);
        writeStringAttribute(out, ATTR_ABI_OVERRIDE, params.abiOverride);


        // Persist app icon if changed since last written
        final File appIconFile = buildAppIconFile(session.sessionId);
        if (params.appIcon == null && appIconFile.exists()) {
            appIconFile.delete();
        } else if (params.appIcon != null
                && appIconFile.lastModified() != params.appIconLastModified) {
            if (LOGD) Slog.w(TAG, "Writing changed icon " + appIconFile);
            FileOutputStream os = null;
            try {
                os = new FileOutputStream(appIconFile);
                params.appIcon.compress(CompressFormat.PNG, 90, os);
            } catch (IOException e) {
                Slog.w(TAG, "Failed to write icon " + appIconFile + ": " + e.getMessage());
            } finally {
                IoUtils.closeQuietly(os);
            }

            params.appIconLastModified = appIconFile.lastModified();
        }

        out.endTag(null, TAG_SESSION);
        out.endTag(null, TAG_SESSION);
    }
    }


    private File buildAppIconFile(int sessionId) {
        return new File(mSessionsDir, "app_icon." + sessionId + ".png");
    }

    private void writeSessionsAsync() {
    private void writeSessionsAsync() {
        IoThread.getHandler().post(new Runnable() {
        IoThread.getHandler().post(new Runnable() {
            @Override
            @Override
@@ -548,7 +596,21 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
            if (session == null || !isCallingUidOwner(session)) {
            if (session == null || !isCallingUidOwner(session)) {
                throw new SecurityException("Caller has no access to session " + sessionId);
                throw new SecurityException("Caller has no access to session " + sessionId);
            }
            }

            // Defensively resize giant app icons
            if (appIcon != null) {
                final ActivityManager am = (ActivityManager) mContext.getSystemService(
                        Context.ACTIVITY_SERVICE);
                final int iconSize = am.getLauncherLargeIconSize();
                if ((appIcon.getWidth() > iconSize * 2)
                        || (appIcon.getHeight() > iconSize * 2)) {
                    appIcon = Bitmap.createScaledBitmap(appIcon, iconSize, iconSize, true);
                }
            }

            session.params.appIcon = appIcon;
            session.params.appIcon = appIcon;
            session.params.appIconLastModified = -1;

            mInternalCallback.onSessionBadgingChanged(session);
            mInternalCallback.onSessionBadgingChanged(session);
        }
        }
    }
    }
@@ -973,6 +1035,12 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
                    synchronized (mSessions) {
                    synchronized (mSessions) {
                        mSessions.remove(session.sessionId);
                        mSessions.remove(session.sessionId);
                        mHistoricalSessions.put(session.sessionId, session);
                        mHistoricalSessions.put(session.sessionId, session);

                        final File appIconFile = buildAppIconFile(session.sessionId);
                        if (appIconFile.exists()) {
                            appIconFile.delete();
                        }

                        writeSessionsLocked();
                        writeSessionsLocked();
                    }
                    }
                }
                }