summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/content/pm/PackageParser.java51
-rw-r--r--core/java/android/content/pm/PackageParserCacheHelper.java157
-rw-r--r--core/java/android/os/BaseBundle.java134
-rw-r--r--core/java/android/os/Bundle.java59
-rw-r--r--core/java/android/os/Parcel.java75
-rw-r--r--core/tests/coretests/apks/install_complete_package_info/AndroidManifest.xml22
-rw-r--r--core/tests/coretests/src/android/content/pm/PackageParserCacheHelperTest.java69
-rw-r--r--core/tests/coretests/src/android/content/pm/PackageParserTest.java62
-rw-r--r--core/tests/coretests/src/android/os/BundleTest.java140
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java15
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);