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

Commit c7421bae authored by Makoto Onuki's avatar Makoto Onuki Committed by android-build-merger
Browse files

Merge "Reduce duplicate strings due to the package cache." into oc-mr1-dev am: ce3e863f

am: a0518fb7

Change-Id: I02cfeba937fac4815c55ea158dfdc9723a2a2031
parents f4fb876a a0518fb7
Loading
Loading
Loading
Loading
+40 −11
Original line number Diff line number Diff line
@@ -50,6 +50,8 @@ import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageParserCacheHelper.ReadHelper;
import android.content.pm.PackageParserCacheHelper.WriteHelper;
import android.content.pm.split.DefaultSplitAssetLoader;
import android.content.pm.split.SplitAssetDependencyLoader;
import android.content.pm.split.SplitAssetLoader;
@@ -121,6 +123,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.ZipEntry;

@@ -234,6 +237,11 @@ public class PackageParser {

    private static final boolean LOG_UNSAFE_BROADCASTS = false;

    /**
     * Total number of packages that were read from the cache.  We use it only for logging.
     */
    public static final AtomicInteger sCachedPackageReadCount = new AtomicInteger();

    // Set of broadcast actions that are safe for manifest receivers
    private static final Set<String> SAFE_BROADCASTS = new ArraySet<>();
    static {
@@ -1034,21 +1042,45 @@ public class PackageParser {
    }

    @VisibleForTesting
    protected Package fromCacheEntry(byte[] bytes) throws IOException {
        Parcel p = Parcel.obtain();
    protected Package fromCacheEntry(byte[] bytes) {
        return fromCacheEntryStatic(bytes);
    }

    /** static version of {@link #fromCacheEntry} for unit tests. */
    @VisibleForTesting
    public static Package fromCacheEntryStatic(byte[] bytes) {
        final Parcel p = Parcel.obtain();
        p.unmarshall(bytes, 0, bytes.length);
        p.setDataPosition(0);

        final ReadHelper helper = new ReadHelper(p);
        helper.startAndInstall();

        PackageParser.Package pkg = new PackageParser.Package(p);

        p.recycle();

        sCachedPackageReadCount.incrementAndGet();

        return pkg;
    }

    @VisibleForTesting
    protected byte[] toCacheEntry(Package pkg) throws IOException {
        Parcel p = Parcel.obtain();
    protected byte[] toCacheEntry(Package pkg) {
        return toCacheEntryStatic(pkg);

    }

    /** static version of {@link #toCacheEntry} for unit tests. */
    @VisibleForTesting
    public static byte[] toCacheEntryStatic(Package pkg) {
        final Parcel p = Parcel.obtain();
        final WriteHelper helper = new WriteHelper(p);

        pkg.writeToParcel(p, 0 /* flags */);

        helper.finishAndUninstall();

        byte[] serialized = p.marshall();
        p.recycle();

@@ -1145,13 +1177,7 @@ public class PackageParser {
            }
        }

        final byte[] cacheEntry;
        try {
            cacheEntry = toCacheEntry(parsed);
        } catch (IOException ioe) {
            Slog.e(TAG, "Unable to serialize parsed package for: " + packageFile);
            return;
        }
        final byte[] cacheEntry = toCacheEntry(parsed);

        if (cacheEntry == null) {
            return;
@@ -6426,8 +6452,11 @@ public class PackageParser {

            dest.writeStringList(requestedPermissions);
            dest.writeStringList(protectedBroadcasts);

            // TODO: This doesn't work: b/64295061
            dest.writeParcelable(parentPackage, flags);
            dest.writeParcelableList(childPackages, flags);

            dest.writeString(staticSharedLibName);
            dest.writeInt(staticSharedLibVersion);
            dest.writeStringList(libraryNames);
+157 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.content.pm;

import android.os.Parcel;
import android.util.Log;

import java.util.ArrayList;
import java.util.HashMap;

/**
 * Helper classes to read from and write to Parcel with pooled strings.
 *
 * @hide
 */
public class PackageParserCacheHelper {
    private PackageParserCacheHelper() {
    }

    private static final String TAG = "PackageParserCacheHelper";
    private static final boolean DEBUG = false;

    /**
     * Parcel read helper with a string pool.
     */
    public static class ReadHelper extends Parcel.ReadWriteHelper {
        private final ArrayList<String> mStrings = new ArrayList<>();

        private final Parcel mParcel;

        public ReadHelper(Parcel p) {
            mParcel = p;
        }

        /**
         * Prepare to read from a parcel, and install itself as a read-write helper.
         *
         * (We don't do it in the constructor to avoid calling methods before the constructor
         * finishes.)
         */
        public void startAndInstall() {
            mStrings.clear();

            final int poolPosition = mParcel.readInt();
            final int startPosition = mParcel.dataPosition();

            // The pool is at the end of the parcel.
            mParcel.setDataPosition(poolPosition);
            mParcel.readStringList(mStrings);

            // Then move back.
            mParcel.setDataPosition(startPosition);

            if (DEBUG) {
                Log.i(TAG, "Read " + mStrings.size() + " strings");
                for (int i = 0; i < mStrings.size(); i++) {
                    Log.i(TAG, "  " + i + ": \"" + mStrings.get(i) + "\"");
                }
            }

            mParcel.setReadWriteHelper(this);
        }

        /**
         * Read an string index from a parcel, and returns the corresponding string from the pool.
         */
        @Override
        public String readString(Parcel p) {
            return mStrings.get(p.readInt());
        }
    }

    /**
     * Parcel write helper with a string pool.
     */
    public static class WriteHelper extends Parcel.ReadWriteHelper {
        private final ArrayList<String> mStrings = new ArrayList<>();

        private final HashMap<String, Integer> mIndexes = new HashMap<>();

        private final Parcel mParcel;
        private final int mStartPos;

        /**
         * Constructor.  Prepare a parcel, and install it self as a read-write helper.
         */
        public WriteHelper(Parcel p) {
            mParcel = p;
            mStartPos = p.dataPosition();
            mParcel.writeInt(0); // We come back later here and write the pool position.

            mParcel.setReadWriteHelper(this);
        }

        /**
         * Instead of writing a string directly to a parcel, this method adds it to the pool,
         * and write the index in the pool to the parcel.
         */
        @Override
        public void writeString(Parcel p, String s) {
            final Integer cur = mIndexes.get(s);
            if (cur != null) {
                // String already in the pool. Just write the index.
                p.writeInt(cur); // Already in the pool.
                if (DEBUG) {
                    Log.i(TAG, "Duplicate '" + s + "' at " + cur);
                }
            } else {
                // Not in the pool. Add to the pool, and write the index.
                final int index = mStrings.size();
                mIndexes.put(s, index);
                mStrings.add(s);

                if (DEBUG) {
                    Log.i(TAG, "New '" + s + "' at " + index);
                }

                p.writeInt(index);
            }
        }

        /**
         * Closes a parcel by appending the string pool at the end and updating the pool offset,
         * which it assumes is at the first byte.  It also uninstalls itself as a read-write helper.
         */
        public void finishAndUninstall() {
            // Uninstall first, so that writeStringList() uses the native writeString.
            mParcel.setReadWriteHelper(null);

            final int poolPosition = mParcel.dataPosition();
            mParcel.writeStringList(mStrings);

            mParcel.setDataPosition(mStartPos);
            mParcel.writeInt(poolPosition);

            // Move back to the end.
            mParcel.setDataPosition(mParcel.dataSize());
            if (DEBUG) {
                Log.i(TAG, "Wrote " + mStrings.size() + " strings");
            }
        }
    }
}
+88 −46
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.os;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.ArrayMap;
import android.util.Log;
@@ -23,6 +24,7 @@ import android.util.MathUtils;
import android.util.Slog;
import android.util.SparseArray;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;

import java.io.Serializable;
@@ -94,7 +96,8 @@ public class BaseBundle {
    private ClassLoader mClassLoader;

    /** {@hide} */
    int mFlags;
    @VisibleForTesting
    public int mFlags;

    /**
     * Constructs a new, empty Bundle that uses a specific ClassLoader for
@@ -218,22 +221,30 @@ public class BaseBundle {
     */
    /* package */ void unparcel() {
        synchronized (this) {
            final Parcel parcelledData = mParcelledData;
            if (parcelledData == null) {
                if (DEBUG) Log.d(TAG, "unparcel "
            final Parcel source = mParcelledData;
            if (source != null) {
                initializeFromParcelLocked(source, /*recycleParcel=*/ true);
            } else {
                if (DEBUG) {
                    Log.d(TAG, "unparcel "
                            + Integer.toHexString(System.identityHashCode(this))
                            + ": no parcelled data");
                return;
                }
            }
        }
    }

    private void initializeFromParcelLocked(@NonNull Parcel parcelledData, boolean recycleParcel) {
        if (LOG_DEFUSABLE && sShouldDefuse && (mFlags & FLAG_DEFUSABLE) == 0) {
            Slog.wtf(TAG, "Attempting to unparcel a Bundle while in transit; this may "
                    + "clobber all data inside!", new Throwable());
        }

            if (isEmptyParcel()) {
                if (DEBUG) Log.d(TAG, "unparcel "
        if (isEmptyParcel(parcelledData)) {
            if (DEBUG) {
                Log.d(TAG, "unparcel "
                        + Integer.toHexString(System.identityHashCode(this)) + ": empty");
            }
            if (mMap == null) {
                mMap = new ArrayMap<>(1);
            } else {
@@ -243,21 +254,23 @@ public class BaseBundle {
            return;
        }

            int N = parcelledData.readInt();
            if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
                    + ": reading " + N + " maps");
            if (N < 0) {
        final int count = parcelledData.readInt();
        if (DEBUG) {
            Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
                    + ": reading " + count + " maps");
        }
        if (count < 0) {
            return;
        }
        ArrayMap<String, Object> map = mMap;
        if (map == null) {
                map = new ArrayMap<>(N);
            map = new ArrayMap<>(count);
        } else {
            map.erase();
                map.ensureCapacity(N);
            map.ensureCapacity(count);
        }
        try {
                parcelledData.readArrayMapInternal(map, N, mClassLoader);
            parcelledData.readArrayMapInternal(map, count, mClassLoader);
        } catch (BadParcelableException e) {
            if (sShouldDefuse) {
                Log.w(TAG, "Failed to parse Bundle, but defusing quietly", e);
@@ -267,10 +280,13 @@ public class BaseBundle {
            }
        } finally {
            mMap = map;
                parcelledData.recycle();
            if (recycleParcel) {
                recycleParcel(parcelledData);
            }
            mParcelledData = null;
        }
            if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
        if (DEBUG) {
            Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
                    + " final map: " + mMap);
        }
    }
@@ -286,7 +302,20 @@ public class BaseBundle {
     * @hide
     */
    public boolean isEmptyParcel() {
        return mParcelledData == NoImagePreloadHolder.EMPTY_PARCEL;
        return isEmptyParcel(mParcelledData);
    }

    /**
     * @hide
     */
    private static boolean isEmptyParcel(Parcel p) {
        return p == NoImagePreloadHolder.EMPTY_PARCEL;
    }

    private static void recycleParcel(Parcel p) {
        if (p != null && !isEmptyParcel(p)) {
            p.recycle();
        }
    }

    /** @hide */
@@ -1476,6 +1505,10 @@ public class BaseBundle {
     * @param parcel The parcel to copy this bundle to.
     */
    void writeToParcelInner(Parcel parcel, int flags) {
        // If the parcel has a read-write helper, we can't just copy the blob, so unparcel it first.
        if (parcel.hasReadWriteHelper()) {
            unparcel();
        }
        // Keep implementation in sync with writeToParcel() in
        // frameworks/native/libs/binder/PersistableBundle.cpp.
        final ArrayMap<String, Object> map;
@@ -1544,6 +1577,15 @@ public class BaseBundle {
                    + Integer.toHexString(magic));
        }

        if (parcel.hasReadWriteHelper()) {
            // If the parcel has a read-write helper, then we can't lazily-unparcel it, so just
            // unparcel right away.
            synchronized (this) {
                initializeFromParcelLocked(parcel, /*recycleParcel=*/ false);
            }
            return;
        }

        // Advance within this Parcel
        int offset = parcel.dataPosition();
        parcel.setDataPosition(MathUtils.addOrThrow(offset, length));
+43 −16
Original line number Diff line number Diff line
@@ -22,6 +22,8 @@ import android.util.Size;
import android.util.SizeF;
import android.util.SparseArray;

import com.android.internal.annotations.VisibleForTesting;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@@ -32,9 +34,14 @@ import java.util.List;
 * @see PersistableBundle
 */
public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
    private static final int FLAG_HAS_FDS = 1 << 8;
    private static final int FLAG_HAS_FDS_KNOWN = 1 << 9;
    private static final int FLAG_ALLOW_FDS = 1 << 10;
    @VisibleForTesting
    static final int FLAG_HAS_FDS = 1 << 8;

    @VisibleForTesting
    static final int FLAG_HAS_FDS_KNOWN = 1 << 9;

    @VisibleForTesting
    static final int FLAG_ALLOW_FDS = 1 << 10;

    public static final Bundle EMPTY;

@@ -65,20 +72,42 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
     * will be unparcelled on first contact, using the assigned ClassLoader.
     *
     * @param parcelledData a Parcel containing a Bundle
     *
     * @hide
     */
    Bundle(Parcel parcelledData) {
    @VisibleForTesting
    public Bundle(Parcel parcelledData) {
        super(parcelledData);
        mFlags = FLAG_HAS_FDS_KNOWN | FLAG_ALLOW_FDS;
        if (mParcelledData.hasFileDescriptors()) {
            mFlags |= FLAG_HAS_FDS;
        }
        mFlags = FLAG_ALLOW_FDS;
        maybePrefillHasFds();
    }

    /* package */ Bundle(Parcel parcelledData, int length) {
    /**
     * Constructor from a parcel for when the length is known *and is not stored in the parcel.*
     * The other constructor that takes a parcel assumes the length is in the parcel.
     *
     * @hide
     */
    @VisibleForTesting
    public Bundle(Parcel parcelledData, int length) {
        super(parcelledData, length);
        mFlags = FLAG_HAS_FDS_KNOWN | FLAG_ALLOW_FDS;
        mFlags = FLAG_ALLOW_FDS;
        maybePrefillHasFds();
    }

    /**
     * If {@link #mParcelledData} is not null, copy the HAS FDS bit from it because it's fast.
     * Otherwise (if {@link #mParcelledData} is already null), leave {@link #FLAG_HAS_FDS_KNOWN}
     * unset, because scanning a map is slower.  We'll do it lazily in
     * {@link #hasFileDescriptors()}.
     */
    private void maybePrefillHasFds() {
        if (mParcelledData != null) {
            if (mParcelledData.hasFileDescriptors()) {
            mFlags |= FLAG_HAS_FDS;
                mFlags |= FLAG_HAS_FDS | FLAG_HAS_FDS_KNOWN;
            } else {
                mFlags |= FLAG_HAS_FDS_KNOWN;
            }
        }
    }

@@ -1213,10 +1242,8 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
     */
    public void readFromParcel(Parcel parcel) {
        super.readFromParcelInner(parcel);
        mFlags = FLAG_HAS_FDS_KNOWN | FLAG_ALLOW_FDS;
        if (mParcelledData.hasFileDescriptors()) {
            mFlags |= FLAG_HAS_FDS;
        }
        mFlags = FLAG_ALLOW_FDS;
        maybePrefillHasFds();
    }

    @Override
+73 −2
Original line number Diff line number Diff line
@@ -291,7 +291,7 @@ public final class Parcel {
    private static native void nativeWriteFloat(long nativePtr, float val);
    @FastNative
    private static native void nativeWriteDouble(long nativePtr, double val);
    private static native void nativeWriteString(long nativePtr, String val);
    static native void nativeWriteString(long nativePtr, String val);
    private static native void nativeWriteStrongBinder(long nativePtr, IBinder val);
    private static native long nativeWriteFileDescriptor(long nativePtr, FileDescriptor val);

@@ -306,7 +306,7 @@ public final class Parcel {
    private static native float nativeReadFloat(long nativePtr);
    @CriticalNative
    private static native double nativeReadDouble(long nativePtr);
    private static native String nativeReadString(long nativePtr);
    static native String nativeReadString(long nativePtr);
    private static native IBinder nativeReadStrongBinder(long nativePtr);
    private static native FileDescriptor nativeReadFileDescriptor(long nativePtr);

@@ -338,6 +338,33 @@ public final class Parcel {
        }
    };

    /**
     * @hide
     */
    public static class ReadWriteHelper {
        public static final ReadWriteHelper DEFAULT = new ReadWriteHelper();

        /**
         * Called when writing a string to a parcel. Subclasses wanting to write a string
         * must use {@link #writeStringNoHelper(String)} to avoid
         * infinity recursive calls.
         */
        public void writeString(Parcel p, String s) {
            nativeWriteString(p.mNativePtr, s);
        }

        /**
         * Called when reading a string to a parcel. Subclasses wanting to read a string
         * must use {@link #readStringNoHelper()} to avoid
         * infinity recursive calls.
         */
        public String readString(Parcel p) {
            return nativeReadString(p.mNativePtr);
        }
    }

    private ReadWriteHelper mReadWriteHelper = ReadWriteHelper.DEFAULT;

    /**
     * Retrieve a new Parcel object from the pool.
     */
@@ -352,6 +379,7 @@ public final class Parcel {
                    if (DEBUG_RECYCLE) {
                        p.mStack = new RuntimeException();
                    }
                    p.mReadWriteHelper = ReadWriteHelper.DEFAULT;
                    return p;
                }
            }
@@ -385,6 +413,25 @@ public final class Parcel {
        }
    }

    /**
     * Set a {@link ReadWriteHelper}, which can be used to avoid having duplicate strings, for
     * example.
     *
     * @hide
     */
    public void setReadWriteHelper(ReadWriteHelper helper) {
        mReadWriteHelper = helper != null ? helper : ReadWriteHelper.DEFAULT;
    }

    /**
     * @return whether this parcel has a {@link ReadWriteHelper}.
     *
     * @hide
     */
    public boolean hasReadWriteHelper() {
        return (mReadWriteHelper != null) && (mReadWriteHelper != ReadWriteHelper.DEFAULT);
    }

    /** @hide */
    public static native long getGlobalAllocSize();

@@ -625,6 +672,17 @@ public final class Parcel {
     * growing dataCapacity() if needed.
     */
    public final void writeString(String val) {
        mReadWriteHelper.writeString(this, val);
    }

    /**
     * Write a string without going though a {@link ReadWriteHelper}.  Subclasses of
     * {@link ReadWriteHelper} must use this method instead of {@link #writeString} to avoid
     * infinity recursive calls.
     *
     * @hide
     */
    public void writeStringNoHelper(String val) {
        nativeWriteString(mNativePtr, val);
    }

@@ -1997,6 +2055,17 @@ public final class Parcel {
     * Read a string value from the parcel at the current dataPosition().
     */
    public final String readString() {
        return mReadWriteHelper.readString(this);
    }

    /**
     * Read a string without going though a {@link ReadWriteHelper}.  Subclasses of
     * {@link ReadWriteHelper} must use this method instead of {@link #readString} to avoid
     * infinity recursive calls.
     *
     * @hide
     */
    public String readStringNoHelper() {
        return nativeReadString(mNativePtr);
    }

@@ -2997,6 +3066,7 @@ public final class Parcel {
        if (mOwnsNativeParcelObject) {
            updateNativeSize(nativeFreeBuffer(mNativePtr));
        }
        mReadWriteHelper = ReadWriteHelper.DEFAULT;
    }

    private void destroy() {
@@ -3007,6 +3077,7 @@ public final class Parcel {
            }
            mNativePtr = 0;
        }
        mReadWriteHelper = null;
    }

    @Override
Loading