diff options
10 files changed, 697 insertions, 87 deletions
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index e8eca8ca7aa2..4ae1aaf71349 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -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; @@ -235,6 +238,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 { @@ -1035,21 +1043,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(); @@ -1146,13 +1178,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; @@ -6421,8 +6447,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); diff --git a/core/java/android/content/pm/PackageParserCacheHelper.java b/core/java/android/content/pm/PackageParserCacheHelper.java new file mode 100644 index 000000000000..44def3321c34 --- /dev/null +++ b/core/java/android/content/pm/PackageParserCacheHelper.java @@ -0,0 +1,157 @@ +/* + * 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"); + } + } + } +} diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java index a5763efb034d..5312dcaeb508 100644 --- a/core/java/android/os/BaseBundle.java +++ b/core/java/android/os/BaseBundle.java @@ -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,59 +221,72 @@ public class BaseBundle { */ /* package */ void unparcel() { synchronized (this) { - final Parcel parcelledData = mParcelledData; - if (parcelledData == null) { - if (DEBUG) Log.d(TAG, "unparcel " - + Integer.toHexString(System.identityHashCode(this)) - + ": no parcelled data"); - return; + 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"); + } } + } + } - 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()); - } + 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 { - mMap.erase(); - } - mParcelledData = null; - return; } - - int N = parcelledData.readInt(); - if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this)) - + ": reading " + N + " maps"); - if (N < 0) { - return; - } - ArrayMap<String, Object> map = mMap; - if (map == null) { - map = new ArrayMap<>(N); + if (mMap == null) { + mMap = new ArrayMap<>(1); } else { + mMap.erase(); + } + mParcelledData = null; + return; + } + + 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<>(count); + } else { + map.erase(); + map.ensureCapacity(count); + } + try { + parcelledData.readArrayMapInternal(map, count, mClassLoader); + } catch (BadParcelableException e) { + if (sShouldDefuse) { + Log.w(TAG, "Failed to parse Bundle, but defusing quietly", e); map.erase(); - map.ensureCapacity(N); + } else { + throw e; } - try { - parcelledData.readArrayMapInternal(map, N, mClassLoader); - } catch (BadParcelableException e) { - if (sShouldDefuse) { - Log.w(TAG, "Failed to parse Bundle, but defusing quietly", e); - map.erase(); - } else { - throw e; - } - } finally { - mMap = map; - parcelledData.recycle(); - mParcelledData = null; + } finally { + mMap = map; + if (recycleParcel) { + recycleParcel(parcelledData); } - if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this)) + mParcelledData = null; + } + 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)); diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java index 51f96eed822e..c58153aa34d1 100644 --- a/core/java/android/os/Bundle.java +++ b/core/java/android/os/Bundle.java @@ -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; - if (mParcelledData.hasFileDescriptors()) { - mFlags |= FLAG_HAS_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 | 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 diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 2efb0f5e419d..fae9d5310f8e 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -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); @@ -339,6 +339,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. */ public static Parcel obtain() { @@ -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 diff --git a/core/tests/coretests/apks/install_complete_package_info/AndroidManifest.xml b/core/tests/coretests/apks/install_complete_package_info/AndroidManifest.xml index 4b01736d67c4..2c430e08c16a 100644 --- a/core/tests/coretests/apks/install_complete_package_info/AndroidManifest.xml +++ b/core/tests/coretests/apks/install_complete_package_info/AndroidManifest.xml @@ -40,15 +40,31 @@ <application android:hasCode="true"> + <meta-data android:name="key1" android:value="value1" /> + <meta-data android:name="key2" android:value="this_is_app" /> + <activity android:name="com.android.frameworks.coretests.TestActivity"> + <meta-data android:name="key1" android:value="value1" /> + <meta-data android:name="key2" android:value="this_is_activity" /> </activity> <provider android:name="com.android.frameworks.coretests.TestProvider" - android:authorities="com.android.frameworks.coretests.testprovider" /> + android:authorities="com.android.frameworks.coretests.testprovider" > + <meta-data android:name="key1" android:value="value1" /> + <meta-data android:name="key2" android:value="this_is_provider" /> + </provider> + <receiver - android:name="com.android.frameworks.coretests.TestReceiver" /> + android:name="com.android.frameworks.coretests.TestReceiver" > + <meta-data android:name="key1" android:value="value1" /> + <meta-data android:name="key2" android:value="this_is_receiver" /> + </receiver> + <service - android:name="com.android.frameworks.coretests.TestService" /> + android:name="com.android.frameworks.coretests.TestService" > + <meta-data android:name="key1" android:value="value1" /> + <meta-data android:name="key2" android:value="this_is_service" /> + </service> </application> </manifest> diff --git a/core/tests/coretests/src/android/content/pm/PackageParserCacheHelperTest.java b/core/tests/coretests/src/android/content/pm/PackageParserCacheHelperTest.java new file mode 100644 index 000000000000..00be82219e4f --- /dev/null +++ b/core/tests/coretests/src/android/content/pm/PackageParserCacheHelperTest.java @@ -0,0 +1,69 @@ +/* + * 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 static org.junit.Assert.assertEquals; + +import android.content.pm.PackageParserCacheHelper.ReadHelper; +import android.content.pm.PackageParserCacheHelper.WriteHelper; +import android.os.Bundle; +import android.os.Parcel; +import android.support.test.runner.AndroidJUnit4; +import android.test.suitebuilder.annotation.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class PackageParserCacheHelperTest { + @Test + public void testParcelUnparcel() throws Exception { + final Bundle source = new Bundle(); + source.putInt("i1", 123); + source.putString("s1", "abcdef"); + source.putString("s2", "xyz"); + source.putString("s3", null); + + final Bundle nest = new Bundle(); + nest.putString("s1", "xyz"); + source.putBundle("b1", nest); + + final Parcel p = Parcel.obtain(); + final WriteHelper writeHelper = new WriteHelper(p); + + source.writeToParcel(p, 0); + writeHelper.finishAndUninstall(); + + p.setDataPosition(0); + + final ReadHelper readHelper = new ReadHelper(p); + readHelper.startAndInstall(); + + final Bundle dest = new Bundle(); + dest.readFromParcel(p); + + dest.size(); // Unparcel so that toString() returns the content. + + assertEquals(source.get("i1"), dest.get("i1")); + assertEquals(source.get("s1"), dest.get("s1")); + assertEquals(source.get("s2"), dest.get("s2")); + assertEquals(source.get("s3"), dest.get("s3")); + assertEquals(source.getBundle("b1").get("s1"), dest.getBundle("b1").get("s1")); + assertEquals(source.keySet().size(), dest.keySet().size()); + } +} diff --git a/core/tests/coretests/src/android/content/pm/PackageParserTest.java b/core/tests/coretests/src/android/content/pm/PackageParserTest.java index 73c153f2754a..fda0f1e74543 100644 --- a/core/tests/coretests/src/android/content/pm/PackageParserTest.java +++ b/core/tests/coretests/src/android/content/pm/PackageParserTest.java @@ -26,6 +26,7 @@ import android.content.pm.PackageParser.Component; import android.content.pm.PackageParser.Package; import android.content.pm.PackageParser.Permission; import android.os.Build; +import android.os.Bundle; import android.os.FileUtils; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; @@ -39,6 +40,7 @@ import org.junit.runner.RunWith; import java.io.File; import java.io.InputStream; import java.util.Arrays; +import java.util.function.Function; @SmallTest @RunWith(AndroidJUnit4.class) @@ -271,22 +273,27 @@ public class PackageParserTest { assertEquals(0x0083, finalConfigChanges); // Should be 10000011. } + Package parsePackage(String apkFileName, int apkResourceId) throws Exception { + return parsePackage(apkFileName, apkResourceId, p -> p); + } + /** * Attempts to parse a package. * * APKs are put into coretests/apks/packageparser_*. * - * @param apkName temporary file name to store apk extracted from resources + * @param apkFileName temporary file name to store apk extracted from resources * @param apkResourceId identifier of the apk as a resource */ - Package parsePackage(String apkFileName, int apkResourceId) throws Exception { + Package parsePackage(String apkFileName, int apkResourceId, + Function<Package, Package> converter) throws Exception { // Copy the resource to a file. Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); File outFile = new File(context.getFilesDir(), apkFileName); try { InputStream is = context.getResources().openRawResource(apkResourceId); assertTrue(FileUtils.copyToFile(is, outFile)); - return new PackageParser().parsePackage(outFile, 0 /* flags */); + return converter.apply(new PackageParser().parsePackage(outFile, 0 /* flags */)); } finally { outFile.delete(); } @@ -331,10 +338,39 @@ public class PackageParserTest { assertEquals(protectionLevel, permission.info.protectionLevel); } + private void assertMetadata(Bundle b, String... keysAndValues) { + assertTrue("Odd number of elements in keysAndValues", (keysAndValues.length % 2) == 0); + + assertNotNull(b); + assertEquals(keysAndValues.length / 2, b.size()); + + for (int i = 0; i < keysAndValues.length; i += 2) { + final String key = keysAndValues[i]; + final String value = keysAndValues[i + 1]; + + assertEquals(value, b.getString(key)); + } + } + + // TODO Add a "_cached" test for testMultiPackageComponents() too, after fixing b/64295061. + // Package.writeToParcel can't handle circular package references. + + @Test + public void testPackageWithComponents_no_cache() throws Exception { + checkPackageWithComponents(p -> p); + } + @Test - public void testPackageWithComponents() throws Exception { + public void testPackageWithComponents_cached() throws Exception { + checkPackageWithComponents(p -> + PackageParser.fromCacheEntryStatic(PackageParser.toCacheEntryStatic(p))); + } + + private void checkPackageWithComponents( + Function<Package, Package> converter) throws Exception { Package p = parsePackage( - "install_complete_package_info.apk", R.raw.install_complete_package_info); + "install_complete_package_info.apk", R.raw.install_complete_package_info, + converter); String packageName = "com.android.frameworks.coretests.install_complete_package_info"; assertEquals(packageName, p.packageName); @@ -344,6 +380,22 @@ public class PackageParserTest { packageName, PermissionInfo.PROTECTION_NORMAL, p.permissions.get(0)); assertOneComponentOfEachType("com.android.frameworks.coretests.Test%s", p); + + assertMetadata(p.mAppMetaData, + "key1", "value1", + "key2", "this_is_app"); + assertMetadata(p.activities.get(0).metaData, + "key1", "value1", + "key2", "this_is_activity"); + assertMetadata(p.services.get(0).metaData, + "key1", "value1", + "key2", "this_is_service"); + assertMetadata(p.receivers.get(0).metaData, + "key1", "value1", + "key2", "this_is_receiver"); + assertMetadata(p.providers.get(0).metaData, + "key1", "value1", + "key2", "this_is_provider"); } @Test diff --git a/core/tests/coretests/src/android/os/BundleTest.java b/core/tests/coretests/src/android/os/BundleTest.java new file mode 100644 index 000000000000..9fcf96d6f3ae --- /dev/null +++ b/core/tests/coretests/src/android/os/BundleTest.java @@ -0,0 +1,140 @@ +/* + * 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.os; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.support.test.runner.AndroidJUnit4; +import android.test.suitebuilder.annotation.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Unit tests for bundle that requires accessing hidden APS. Tests that can be written only with + * public APIs should go in the CTS counterpart. + * + * Run with: + * bit FrameworksCoreTests:android.os.BundleTest + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class BundleTest { + /** + * Create a test bundle, parcel it and return the parcel. + */ + private Parcel createBundleParcel(boolean withFd) throws Exception { + final Bundle source = new Bundle(); + source.putString("string", "abc"); + source.putInt("int", 1); + if (withFd) { + ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); + pipe[1].close(); + source.putParcelable("fd", pipe[0]); + } + final Parcel p = Parcel.obtain(); + // Don't use p.writeParcelabe(), which would write the creator, which we don't need. + source.writeToParcel(p, 0); + p.setDataPosition(0); + + return p; + } + + /** + * Verify a bundle generated by {@link #createBundleParcel(boolean)}. + */ + private void checkBundle(Bundle b, boolean withFd) { + // First, do the checks without actually unparceling the bundle. + // (Note looking into the contents will unparcel a bundle, so we'll do it later.) + assertTrue("mParcelledData shouldn't be null here.", b.isParcelled()); + + // Make sure FLAG_HAS_FDS and FLAG_HAS_FDS_KNOWN are set/cleared properly. + if (withFd) { + // FLAG_HAS_FDS and FLAG_HAS_FDS_KNOWN should both be set. + assertEquals(Bundle.FLAG_HAS_FDS | Bundle.FLAG_HAS_FDS_KNOWN, + b.mFlags & (Bundle.FLAG_HAS_FDS | Bundle.FLAG_HAS_FDS_KNOWN)); + } else { + // FLAG_HAS_FDS_KNOWN should be set, bot not FLAG_HAS_FDS. + assertEquals(Bundle.FLAG_HAS_FDS_KNOWN, + b.mFlags & (Bundle.FLAG_HAS_FDS | Bundle.FLAG_HAS_FDS_KNOWN)); + } + + // Then, check the contents. + assertEquals("abc", b.getString("string")); + assertEquals(1, b.getInt("int")); + + // Make sure FLAG_HAS_FDS and FLAG_HAS_FDS_KNOWN are set/cleared properly. + if (withFd) { + assertEquals(ParcelFileDescriptor.class, b.getParcelable("fd").getClass()); + assertEquals(3, b.keySet().size()); + } else { + assertEquals(2, b.keySet().size()); + } + assertFalse(b.isParcelled()); + } + + @Test + public void testCreateFromParcel() throws Exception { + boolean withFd; + Parcel p; + Bundle b; + int length; + + withFd = false; + + // new Bundle with p + p = createBundleParcel(withFd); + checkBundle(new Bundle(p), withFd); + p.recycle(); + + // new Bundle with p and length + p = createBundleParcel(withFd); + length = p.readInt(); + checkBundle(new Bundle(p, length), withFd); + p.recycle(); + + // readFromParcel() + p = createBundleParcel(withFd); + b = new Bundle(); + b.readFromParcel(p); + checkBundle(b, withFd); + p.recycle(); + + // Same test with FDs. + withFd = true; + + // new Bundle with p + p = createBundleParcel(withFd); + checkBundle(new Bundle(p), withFd); + p.recycle(); + + // new Bundle with p and length + p = createBundleParcel(withFd); + length = p.readInt(); + checkBundle(new Bundle(p, length), withFd); + p.recycle(); + + // readFromParcel() + p = createBundleParcel(withFd); + b = new Bundle(); + b.readFromParcel(p); + checkBundle(b, withFd); + p.recycle(); + } +} diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index aead98b90046..b68630e16f8e 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -2722,14 +2722,17 @@ public class PackageManagerService extends IPackageManager.Stub //delete tmp files deleteTempPackageFiles(); + final int cachedSystemApps = PackageParser.sCachedPackageReadCount.get(); + // Remove any shared userIDs that have no associated packages mSettings.pruneSharedUsersLPw(); final long systemScanTime = SystemClock.uptimeMillis() - startTime; final int systemPackagesCount = mPackages.size(); Slog.i(TAG, "Finished scanning system apps. Time: " + systemScanTime + " ms, packageCount: " + systemPackagesCount - + " ms, timePerPackage: " - + (systemPackagesCount == 0 ? 0 : systemScanTime / systemPackagesCount)); + + " , timePerPackage: " + + (systemPackagesCount == 0 ? 0 : systemScanTime / systemPackagesCount) + + " , cached: " + cachedSystemApps); if (mIsUpgrade && systemPackagesCount > 0) { MetricsLogger.histogram(null, "ota_package_manager_system_app_avg_scan_time", ((int) systemScanTime) / systemPackagesCount); @@ -2820,12 +2823,16 @@ public class PackageManagerService extends IPackageManager.Stub // This must be done last to ensure all stubs are replaced or disabled. decompressSystemApplications(stubSystemApps, scanFlags); + final int cachedNonSystemApps = PackageParser.sCachedPackageReadCount.get() + - cachedSystemApps; + final long dataScanTime = SystemClock.uptimeMillis() - systemScanTime - startTime; final int dataPackagesCount = mPackages.size() - systemPackagesCount; Slog.i(TAG, "Finished scanning non-system apps. Time: " + dataScanTime + " ms, packageCount: " + dataPackagesCount - + " ms, timePerPackage: " - + (dataPackagesCount == 0 ? 0 : dataScanTime / dataPackagesCount)); + + " , timePerPackage: " + + (dataPackagesCount == 0 ? 0 : dataScanTime / dataPackagesCount) + + " , cached: " + cachedNonSystemApps); if (mIsUpgrade && dataPackagesCount > 0) { MetricsLogger.histogram(null, "ota_package_manager_data_app_avg_scan_time", ((int) dataScanTime) / dataPackagesCount); |