From fb2afbf7fa63e972e3de5a9ecf1e246019246792 Mon Sep 17 00:00:00 2001 From: Narayan Kamath Date: Thu, 24 Nov 2016 13:22:40 +0000 Subject: [PATCH] PackageParser: Add serialization mechanism for parse results. Also adds unit tests that assert that the cached value is equivalent to the parsed value. bug: 30792387 Test: PackageParserTest Change-Id: Ibf6dfd1225243b436e3d7473170c2ccc31cfbbd7 --- core/java/android/content/IntentFilter.java | 3 +- .../android/content/pm/PackageParser.java | 637 +++++++++++++++++- .../android/server/pm/PackageParserTest.java | 424 ++++++++++++ 3 files changed, 1044 insertions(+), 20 deletions(-) diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java index 7036f87b98f98..e6cae69324025 100644 --- a/core/java/android/content/IntentFilter.java +++ b/core/java/android/content/IntentFilter.java @@ -1883,7 +1883,8 @@ public class IntentFilter implements Parcelable { */ } - private IntentFilter(Parcel source) { + /** @hide */ + public IntentFilter(Parcel source) { mActions = new ArrayList(); source.readStringList(mActions); if (source.readInt() != 0) { diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 2236291fd248f..4bfbfd819d76c 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -16,6 +16,8 @@ package android.content.pm; +import android.os.Parcel; +import android.os.Parcelable; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; @@ -65,6 +67,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; +import java.lang.reflect.Constructor; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; @@ -80,6 +83,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import java.util.zip.ZipEntry; @@ -307,14 +311,16 @@ public class PackageParser { } } - static class ParseComponentArgs extends ParsePackageItemArgs { + /** @hide */ + @VisibleForTesting + public static class ParseComponentArgs extends ParsePackageItemArgs { final String[] sepProcesses; final int processRes; final int descriptionRes; final int enabledRes; int flags; - ParseComponentArgs(Package _owner, String[] _outError, + public ParseComponentArgs(Package _owner, String[] _outError, int _nameRes, int _labelRes, int _iconRes, int _roundIconRes, int _logoRes, int _bannerRes, String[] _sepProcesses, int _processRes, @@ -874,12 +880,24 @@ public class PackageParser { @VisibleForTesting protected Package fromCacheEntry(byte[] bytes) throws IOException { - return null; + Parcel p = Parcel.obtain(); + p.unmarshall(bytes, 0, bytes.length); + p.setDataPosition(0); + + PackageParser.Package pkg = new PackageParser.Package(p); + p.recycle(); + + return pkg; } @VisibleForTesting protected byte[] toCacheEntry(Package pkg) throws IOException { - return null; + Parcel p = Parcel.obtain(); + pkg.writeToParcel(p, 0 /* flags */); + byte[] serialized = p.marshall(); + p.recycle(); + + return serialized; } /** @@ -1516,7 +1534,7 @@ public class PackageParser { final Certificate[][] certificates; if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) { // TODO: factor signature related items out of Package object - final Package tempPkg = new Package(null); + final Package tempPkg = new Package((String) null); Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates"); try { collectCertificates(tempPkg, apkFile, 0 /*parseFlags*/); @@ -3623,6 +3641,13 @@ public class PackageParser { private static boolean parsePackageItemInfo(Package owner, PackageItemInfo outInfo, String[] outError, String tag, TypedArray sa, boolean nameRequired, int nameRes, int labelRes, int iconRes, int roundIconRes, int logoRes, int bannerRes) { + // This case can only happen in unit tests where we sometimes need to create fakes + // of various package parser data structures. + if (sa == null) { + outError[0] = tag + " does not contain any attributes"; + return false; + } + String name = sa.getNonConfigurationString(nameRes, 0); if (name == null) { if (nameRequired) { @@ -5113,7 +5138,7 @@ public class PackageParser { * Representation of a full package parsed from APK files on disk. A package * consists of a single base APK, and zero or more split APKs. */ - public final static class Package { + public final static class Package implements Parcelable { public String packageName; @@ -5154,7 +5179,7 @@ public class PackageParser { public boolean baseHardwareAccelerated; // For now we only support one application per package. - public final ApplicationInfo applicationInfo = new ApplicationInfo(); + public ApplicationInfo applicationInfo = new ApplicationInfo(); public final ArrayList permissions = new ArrayList(0); public final ArrayList permissionGroups = new ArrayList(0); @@ -5578,13 +5603,296 @@ public class PackageParser { + Integer.toHexString(System.identityHashCode(this)) + " " + packageName + "}"; } + + @Override + public int describeContents() { + return 0; + } + + public Package(Parcel dest) { + // We use the boot classloader for all classes that we load. + final ClassLoader boot = Object.class.getClassLoader(); + + packageName = dest.readString(); + splitNames = dest.readStringArray(); + volumeUuid = dest.readString(); + codePath = dest.readString(); + baseCodePath = dest.readString(); + splitCodePaths = dest.readStringArray(); + baseRevisionCode = dest.readInt(); + splitRevisionCodes = dest.createIntArray(); + splitFlags = dest.createIntArray(); + splitPrivateFlags = dest.createIntArray(); + baseHardwareAccelerated = (dest.readInt() == 1); + applicationInfo = dest.readParcelable(boot); + + // We don't serialize the "owner" package and the application info object for each of + // these components, in order to save space and to avoid circular dependencies while + // serialization. We need to fix them all up here. + dest.readParcelableList(permissions, boot); + fixupOwner(permissions); + dest.readParcelableList(permissionGroups, boot); + fixupOwner(permissionGroups); + dest.readParcelableList(activities, boot); + fixupOwner(activities); + dest.readParcelableList(receivers, boot); + fixupOwner(receivers); + dest.readParcelableList(providers, boot); + fixupOwner(providers); + dest.readParcelableList(services, boot); + fixupOwner(services); + dest.readParcelableList(instrumentation, boot); + fixupOwner(instrumentation); + + dest.readStringList(requestedPermissions); + protectedBroadcasts = dest.createStringArrayList(); + parentPackage = dest.readParcelable(boot); + + childPackages = new ArrayList<>(); + dest.readParcelableList(childPackages, boot); + if (childPackages.size() == 0) { + childPackages = null; + } + + libraryNames = dest.createStringArrayList(); + usesLibraries = dest.createStringArrayList(); + usesOptionalLibraries = dest.createStringArrayList(); + usesLibraryFiles = dest.readStringArray(); + + preferredActivityFilters = new ArrayList<>(); + dest.readParcelableList(preferredActivityFilters, boot); + if (preferredActivityFilters.size() == 0) { + preferredActivityFilters = null; + } + + mOriginalPackages = dest.createStringArrayList(); + mRealPackage = dest.readString(); + mAdoptPermissions = dest.createStringArrayList(); + mAppMetaData = dest.readBundle(); + mVersionCode = dest.readInt(); + mVersionName = dest.readString(); + mSharedUserId = dest.readString(); + mSharedUserLabel = dest.readInt(); + + mSignatures = (Signature[]) dest.readParcelableArray(boot, Signature.class); + mCertificates = (Certificate[][]) dest.readSerializable(); + + mPreferredOrder = dest.readInt(); + + // long[] packageUsageTimeMillis is not persisted because it isn't information that + // is parsed from the APK. + + // Object mExtras is not persisted because it is not information that is read from + // the APK, rather, it is supplied by callers. + + + configPreferences = new ArrayList<>(); + dest.readParcelableList(configPreferences, boot); + if (configPreferences.size() == 0) { + configPreferences = null; + } + + reqFeatures = new ArrayList<>(); + dest.readParcelableList(reqFeatures, boot); + if (reqFeatures.size() == 0) { + reqFeatures = null; + } + + featureGroups = new ArrayList<>(); + dest.readParcelableList(featureGroups, boot); + if (featureGroups.size() == 0) { + featureGroups = null; + } + + installLocation = dest.readInt(); + coreApp = (dest.readInt() == 1); + mRequiredForAllUsers = (dest.readInt() == 1); + mRestrictedAccountType = dest.readString(); + mRequiredAccountType = dest.readString(); + mOverlayTarget = dest.readString(); + mOverlayPriority = dest.readInt(); + mTrustedOverlay = (dest.readInt() == 1); + mSigningKeys = (ArraySet) dest.readArraySet(boot); + mUpgradeKeySets = (ArraySet) dest.readArraySet(boot); + + mKeySetMapping = readKeySetMapping(dest); + + cpuAbiOverride = dest.readString(); + use32bitAbi = (dest.readInt() == 1); + restrictUpdateHash = dest.createByteArray(); + } + + /** + * Sets the package owner and the the {@code applicationInfo} for every component + * owner by this package. + */ + private void fixupOwner(List> list) { + if (list != null) { + for (Component c : list) { + c.owner = this; + if (c instanceof Activity) { + ((Activity) c).info.applicationInfo = this.applicationInfo; + } else if (c instanceof Service) { + ((Service) c).info.applicationInfo = this.applicationInfo; + } else if (c instanceof Provider) { + ((Provider) c).info.applicationInfo = this.applicationInfo; + } + } + } + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(packageName); + dest.writeStringArray(splitNames); + dest.writeString(volumeUuid); + dest.writeString(codePath); + dest.writeString(baseCodePath); + dest.writeStringArray(splitCodePaths); + dest.writeInt(baseRevisionCode); + dest.writeIntArray(splitRevisionCodes); + dest.writeIntArray(splitFlags); + dest.writeIntArray(splitPrivateFlags); + dest.writeInt(baseHardwareAccelerated ? 1 : 0); + dest.writeParcelable(applicationInfo, flags); + + dest.writeParcelableList(permissions, flags); + dest.writeParcelableList(permissionGroups, flags); + dest.writeParcelableList(activities, flags); + dest.writeParcelableList(receivers, flags); + dest.writeParcelableList(providers, flags); + dest.writeParcelableList(services, flags); + dest.writeParcelableList(instrumentation, flags); + + dest.writeStringList(requestedPermissions); + dest.writeStringList(protectedBroadcasts); + dest.writeParcelable(parentPackage, flags); + dest.writeParcelableList(childPackages, flags); + dest.writeStringList(libraryNames); + dest.writeStringList(usesLibraries); + dest.writeStringList(usesOptionalLibraries); + dest.writeStringArray(usesLibraryFiles); + + dest.writeParcelableList(preferredActivityFilters, flags); + + dest.writeStringList(mOriginalPackages); + dest.writeString(mRealPackage); + dest.writeStringList(mAdoptPermissions); + dest.writeBundle(mAppMetaData); + dest.writeInt(mVersionCode); + dest.writeString(mVersionName); + dest.writeString(mSharedUserId); + dest.writeInt(mSharedUserLabel); + + dest.writeParcelableArray(mSignatures, flags); + dest.writeSerializable(mCertificates); + + dest.writeInt(mPreferredOrder); + + // long[] packageUsageTimeMillis is not persisted because it isn't information that + // is parsed from the APK. + + // Object mExtras is not persisted because it is not information that is read from + // the APK, rather, it is supplied by callers. + + dest.writeParcelableList(configPreferences, flags); + dest.writeParcelableList(reqFeatures, flags); + dest.writeParcelableList(featureGroups, flags); + + dest.writeInt(installLocation); + dest.writeInt(coreApp ? 1 : 0); + dest.writeInt(mRequiredForAllUsers ? 1 : 0); + dest.writeString(mRestrictedAccountType); + dest.writeString(mRequiredAccountType); + dest.writeString(mOverlayTarget); + dest.writeInt(mOverlayPriority); + dest.writeInt(mTrustedOverlay ? 1 : 0); + dest.writeArraySet(mSigningKeys); + dest.writeArraySet(mUpgradeKeySets); + writeKeySetMapping(dest, mKeySetMapping); + dest.writeString(cpuAbiOverride); + dest.writeInt(use32bitAbi ? 1 : 0); + dest.writeByteArray(restrictUpdateHash); + } + + + /** + * Writes the keyset mapping to the provided package. {@code null} mappings are permitted. + */ + private static void writeKeySetMapping( + Parcel dest, ArrayMap> keySetMapping) { + if (keySetMapping == null) { + dest.writeInt(-1); + return; + } + + final int N = keySetMapping.size(); + dest.writeInt(N); + + for (int i = 0; i < N; i++) { + dest.writeString(keySetMapping.keyAt(i)); + ArraySet keys = keySetMapping.valueAt(i); + if (keys == null) { + dest.writeInt(-1); + continue; + } + + final int M = keys.size(); + dest.writeInt(M); + for (int j = 0; j < M; j++) { + dest.writeSerializable(keys.valueAt(j)); + } + } + } + + /** + * Reads a keyset mapping from the given parcel at the given data position. May return + * {@code null} if the serialized mapping was {@code null}. + */ + private static ArrayMap> readKeySetMapping(Parcel in) { + final int N = in.readInt(); + if (N == -1) { + return null; + } + + ArrayMap> keySetMapping = new ArrayMap<>(); + for (int i = 0; i < N; ++i) { + String key = in.readString(); + final int M = in.readInt(); + if (M == -1) { + keySetMapping.put(key, null); + continue; + } + + ArraySet keys = new ArraySet<>(M); + for (int j = 0; j < M; ++j) { + PublicKey pk = (PublicKey) in.readSerializable(); + keys.add(pk); + } + + keySetMapping.put(key, keys); + } + + return keySetMapping; + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public Package createFromParcel(Parcel in) { + return new Package(in); + } + + public Package[] newArray(int size) { + return new Package[size]; + } + }; } - public static class Component { - public final Package owner; + public static abstract class Component { public final ArrayList intents; public final String className; + public Bundle metaData; + public Package owner; ComponentName componentName; String componentShortName; @@ -5655,6 +5963,83 @@ public class PackageParser { return componentName; } + protected Component(Parcel in) { + className = in.readString(); + metaData = in.readBundle(); + intents = createIntentsList(in); + + owner = null; + } + + protected void writeToParcel(Parcel dest, int flags) { + dest.writeString(className); + dest.writeBundle(metaData); + + writeIntentsList(intents, dest, flags); + } + + /** + *

+ * Implementation note: The serialized form for the intent list also contains the name + * of the concrete class that's stored in the list, and assumes that every element of the + * list is of the same type. This is very similar to the original parcelable mechanism. + * We cannot use that directly because IntentInfo extends IntentFilter, which is parcelable + * and is public API. It also declares Parcelable related methods as final which means + * we can't extend them. The approach of using composition instead of inheritance leads to + * a large set of cascading changes in the PackageManagerService, which seem undesirable. + * + *

+ * WARNING: The list of objects returned by this function might need to be fixed up + * to make sure their owner fields are consistent. See {@code fixupOwner}. + */ + private static void writeIntentsList(ArrayList list, Parcel out, + int flags) { + if (list == null) { + out.writeInt(-1); + return; + } + + final int N = list.size(); + out.writeInt(N); + + // Don't bother writing the component name if the list is empty. + if (N > 0) { + IntentInfo info = list.get(0); + out.writeString(info.getClass().getName()); + + for (int i = 0; i < N;i++) { + list.get(i).writeIntentInfoToParcel(out, flags); + } + } + } + + private static ArrayList createIntentsList(Parcel in) { + int N = in.readInt(); + if (N == -1) { + return null; + } + + if (N == 0) { + return new ArrayList<>(0); + } + + String componentName = in.readString(); + final ArrayList intentsList; + try { + final Class cls = (Class) Class.forName(componentName); + final Constructor cons = cls.getConstructor(Parcel.class); + + intentsList = new ArrayList<>(N); + for (int i = 0; i < N; ++i) { + intentsList.add(cons.newInstance(in)); + } + } catch (ReflectiveOperationException ree) { + throw new AssertionError("Unable to construct intent list for: " + componentName); + } + + return intentsList; + } + public void appendComponentShortName(StringBuilder sb) { ComponentName.appendShortString(sb, owner.applicationInfo.packageName, className); } @@ -5669,7 +6054,7 @@ public class PackageParser { } } - public final static class Permission extends Component { + public final static class Permission extends Component implements Parcelable { public final PermissionInfo info; public boolean tree; public PermissionGroup group; @@ -5694,9 +6079,40 @@ public class PackageParser { + Integer.toHexString(System.identityHashCode(this)) + " " + info.name + "}"; } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeParcelable(info, flags); + dest.writeInt(tree ? 1 : 0); + dest.writeParcelable(group, flags); + } + + private Permission(Parcel in) { + super(in); + final ClassLoader boot = Object.class.getClassLoader(); + info = in.readParcelable(boot); + tree = (in.readInt() == 1); + group = in.readParcelable(boot); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public Permission createFromParcel(Parcel in) { + return new Permission(in); + } + + public Permission[] newArray(int size) { + return new Permission[size]; + } + }; } - public final static class PermissionGroup extends Component { + public final static class PermissionGroup extends Component implements Parcelable { public final PermissionGroupInfo info; public PermissionGroup(Package _owner) { @@ -5719,6 +6135,32 @@ public class PackageParser { + Integer.toHexString(System.identityHashCode(this)) + " " + info.name + "}"; } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeParcelable(info, flags); + } + + private PermissionGroup(Parcel in) { + super(in); + info = in.readParcelable(Object.class.getClassLoader()); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public PermissionGroup createFromParcel(Parcel in) { + return new PermissionGroup(in); + } + + public PermissionGroup[] newArray(int size) { + return new PermissionGroup[size]; + } + }; } private static boolean copyNeeded(int flags, Package p, @@ -5871,7 +6313,7 @@ public class PackageParser { return pgi; } - public final static class Activity extends Component { + public final static class Activity extends Component implements Parcelable { public final ActivityInfo info; public Activity(final ParseComponentArgs args, final ActivityInfo _info) { @@ -5894,6 +6336,36 @@ public class PackageParser { sb.append('}'); return sb.toString(); } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeParcelable(info, flags | Parcelable.PARCELABLE_ELIDE_DUPLICATES); + } + + private Activity(Parcel in) { + super(in); + info = in.readParcelable(Object.class.getClassLoader()); + + for (ActivityIntentInfo aii : intents) { + aii.activity = this; + } + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public Activity createFromParcel(Parcel in) { + return new Activity(in); + } + + public Activity[] newArray(int size) { + return new Activity[size]; + } + }; } public static final ActivityInfo generateActivityInfo(Activity a, int flags, @@ -5925,7 +6397,7 @@ public class PackageParser { return ai; } - public final static class Service extends Component { + public final static class Service extends Component implements Parcelable { public final ServiceInfo info; public Service(final ParseComponentArgs args, final ServiceInfo _info) { @@ -5948,6 +6420,36 @@ public class PackageParser { sb.append('}'); return sb.toString(); } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeParcelable(info, flags | Parcelable.PARCELABLE_ELIDE_DUPLICATES); + } + + private Service(Parcel in) { + super(in); + info = in.readParcelable(Object.class.getClassLoader()); + + for (ServiceIntentInfo aii : intents) { + aii.service = this; + } + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public Service createFromParcel(Parcel in) { + return new Service(in); + } + + public Service[] newArray(int size) { + return new Service[size]; + } + }; } public static final ServiceInfo generateServiceInfo(Service s, int flags, @@ -5966,7 +6468,7 @@ public class PackageParser { return si; } - public final static class Provider extends Component { + public final static class Provider extends Component implements Parcelable { public final ProviderInfo info; public boolean syncable; @@ -5997,6 +6499,38 @@ public class PackageParser { sb.append('}'); return sb.toString(); } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeParcelable(info, flags | Parcelable.PARCELABLE_ELIDE_DUPLICATES); + dest.writeInt((syncable) ? 1 : 0); + } + + private Provider(Parcel in) { + super(in); + info = in.readParcelable(Object.class.getClassLoader()); + syncable = (in.readInt() == 1); + + for (ProviderIntentInfo aii : intents) { + aii.provider = this; + } + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public Provider createFromParcel(Parcel in) { + return new Provider(in); + } + + public Provider[] newArray(int size) { + return new Provider[size]; + } + }; } public static final ProviderInfo generateProviderInfo(Provider p, int flags, @@ -6020,7 +6554,8 @@ public class PackageParser { return pi; } - public final static class Instrumentation extends Component { + public final static class Instrumentation extends Component implements + Parcelable { public final InstrumentationInfo info; public Instrumentation(final ParsePackageItemArgs args, final InstrumentationInfo _info) { @@ -6042,6 +6577,32 @@ public class PackageParser { sb.append('}'); return sb.toString(); } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeParcelable(info, flags); + } + + private Instrumentation(Parcel in) { + super(in); + info = in.readParcelable(Object.class.getClassLoader()); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public Instrumentation createFromParcel(Parcel in) { + return new Instrumentation(in); + } + + public Instrumentation[] newArray(int size) { + return new Instrumentation[size]; + } + }; } public static final InstrumentationInfo generateInstrumentationInfo( @@ -6055,7 +6616,7 @@ public class PackageParser { return ii; } - public static class IntentInfo extends IntentFilter { + public static abstract class IntentInfo extends IntentFilter { public boolean hasDefault; public int labelRes; public CharSequence nonLocalizedLabel; @@ -6063,10 +6624,36 @@ public class PackageParser { public int logo; public int banner; public int preferred; + + protected IntentInfo() { + } + + protected IntentInfo(Parcel dest) { + super(dest); + hasDefault = (dest.readInt() == 1); + labelRes = dest.readInt(); + nonLocalizedLabel = dest.readCharSequence(); + icon = dest.readInt(); + logo = dest.readInt(); + banner = dest.readInt(); + preferred = dest.readInt(); + } + + + public void writeIntentInfoToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(hasDefault ? 1 : 0); + dest.writeInt(labelRes); + dest.writeCharSequence(nonLocalizedLabel); + dest.writeInt(icon); + dest.writeInt(logo); + dest.writeInt(banner); + dest.writeInt(preferred); + } } public final static class ActivityIntentInfo extends IntentInfo { - public final Activity activity; + public Activity activity; public ActivityIntentInfo(Activity _activity) { activity = _activity; @@ -6081,10 +6668,14 @@ public class PackageParser { sb.append('}'); return sb.toString(); } + + public ActivityIntentInfo(Parcel in) { + super(in); + } } public final static class ServiceIntentInfo extends IntentInfo { - public final Service service; + public Service service; public ServiceIntentInfo(Service _service) { service = _service; @@ -6099,10 +6690,14 @@ public class PackageParser { sb.append('}'); return sb.toString(); } + + public ServiceIntentInfo(Parcel in) { + super(in); + } } public static final class ProviderIntentInfo extends IntentInfo { - public final Provider provider; + public Provider provider; public ProviderIntentInfo(Provider provider) { this.provider = provider; @@ -6117,6 +6712,10 @@ public class PackageParser { sb.append('}'); return sb.toString(); } + + public ProviderIntentInfo(Parcel in) { + super(in); + } } /** diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java index 84bb4e889793c..e30bd5d966727 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java @@ -15,14 +15,37 @@ */ package com.android.server.pm; +import android.annotation.TestApi; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.ConfigurationInfo; +import android.content.pm.FeatureGroupInfo; +import android.content.pm.FeatureInfo; +import android.content.pm.InstrumentationInfo; import android.content.pm.PackageParser; +import android.content.pm.ProviderInfo; +import android.content.pm.ServiceInfo; +import android.content.pm.Signature; +import android.os.Bundle; +import android.os.Parcel; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.MediumTest; import java.io.File; +import java.lang.reflect.Array; +import java.lang.reflect.Field; import java.nio.charset.StandardCharsets; +import java.security.cert.Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import static org.junit.Assert.*; + +import android.util.ArrayMap; +import android.util.ArraySet; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -87,6 +110,36 @@ public class PackageParserTest { assertEquals("android", pkg.packageName); } + @Test + public void test_serializePackage() throws Exception { + PackageParser pp = new PackageParser(); + pp.setCacheDir(mTmpDir); + + PackageParser.Package pkg = pp.parsePackage(FRAMEWORK, 0 /* parseFlags */, + true /* useCaches */); + + Parcel p = Parcel.obtain(); + pkg.writeToParcel(p, 0 /* flags */); + + p.setDataPosition(0); + PackageParser.Package deserialized = new PackageParser.Package(p); + + assertPackagesEqual(pkg, deserialized); + } + + @Test + public void test_roundTripKnownFields() throws Exception { + PackageParser.Package pkg = new PackageParser.Package("foo"); + setKnownFields(pkg); + + Parcel p = Parcel.obtain(); + pkg.writeToParcel(p, 0 /* flags */); + + p.setDataPosition(0); + PackageParser.Package deserialized = new PackageParser.Package(p); + assertAllFieldsExist(deserialized); + } + /** * A trivial subclass of package parser that only caches the package name, and throws away * all other information. @@ -102,4 +155,375 @@ public class PackageParserTest { return new Package(new String(cacheEntry, StandardCharsets.UTF_8)); } } + + // NOTE: The equality assertions below are based on code autogenerated by IntelliJ. + + public static void assertPackagesEqual(PackageParser.Package a, PackageParser.Package b) { + assertEquals(a.baseRevisionCode, b.baseRevisionCode); + assertEquals(a.baseHardwareAccelerated, b.baseHardwareAccelerated); + assertEquals(a.mVersionCode, b.mVersionCode); + assertEquals(a.mSharedUserLabel, b.mSharedUserLabel); + assertEquals(a.mPreferredOrder, b.mPreferredOrder); + assertEquals(a.installLocation, b.installLocation); + assertEquals(a.coreApp, b.coreApp); + assertEquals(a.mRequiredForAllUsers, b.mRequiredForAllUsers); + assertEquals(a.mOverlayPriority, b.mOverlayPriority); + assertEquals(a.mTrustedOverlay, b.mTrustedOverlay); + assertEquals(a.use32bitAbi, b.use32bitAbi); + assertEquals(a.packageName, b.packageName); + assertTrue(Arrays.equals(a.splitNames, b.splitNames)); + assertEquals(a.volumeUuid, b.volumeUuid); + assertEquals(a.codePath, b.codePath); + assertEquals(a.baseCodePath, b.baseCodePath); + assertTrue(Arrays.equals(a.splitCodePaths, b.splitCodePaths)); + assertTrue(Arrays.equals(a.splitRevisionCodes, b.splitRevisionCodes)); + assertTrue(Arrays.equals(a.splitFlags, b.splitFlags)); + assertTrue(Arrays.equals(a.splitPrivateFlags, b.splitPrivateFlags)); + assertApplicationInfoEqual(a.applicationInfo, b.applicationInfo); + + assertEquals(a.permissions.size(), b.permissions.size()); + for (int i = 0; i < a.permissions.size(); ++i) { + assertPermissionsEqual(a.permissions.get(i), b.permissions.get(i)); + assertSame(a.permissions.get(i).owner, a); + assertSame(b.permissions.get(i).owner, b); + } + + assertEquals(a.permissionGroups.size(), b.permissionGroups.size()); + for (int i = 0; i < a.permissionGroups.size(); ++i) { + assertPermissionGroupsEqual(a.permissionGroups.get(i), b.permissionGroups.get(i)); + } + + assertEquals(a.activities.size(), b.activities.size()); + for (int i = 0; i < a.activities.size(); ++i) { + assertActivitiesEqual(a.activities.get(i), b.activities.get(i)); + } + + assertEquals(a.receivers.size(), b.receivers.size()); + for (int i = 0; i < a.receivers.size(); ++i) { + assertActivitiesEqual(a.receivers.get(i), b.receivers.get(i)); + } + + assertEquals(a.providers.size(), b.providers.size()); + for (int i = 0; i < a.providers.size(); ++i) { + assertProvidersEqual(a.providers.get(i), b.providers.get(i)); + } + + assertEquals(a.services.size(), b.services.size()); + for (int i = 0; i < a.services.size(); ++i) { + assertServicesEqual(a.services.get(i), b.services.get(i)); + } + + assertEquals(a.instrumentation.size(), b.instrumentation.size()); + for (int i = 0; i < a.instrumentation.size(); ++i) { + assertInstrumentationEqual(a.instrumentation.get(i), b.instrumentation.get(i)); + } + + assertEquals(a.requestedPermissions, b.requestedPermissions); + assertEquals(a.protectedBroadcasts, b.protectedBroadcasts); + assertEquals(a.parentPackage, b.parentPackage); + assertEquals(a.childPackages, b.childPackages); + assertEquals(a.libraryNames, b.libraryNames); + assertEquals(a.usesLibraries, b.usesLibraries); + assertEquals(a.usesOptionalLibraries, b.usesOptionalLibraries); + assertTrue(Arrays.equals(a.usesLibraryFiles, b.usesLibraryFiles)); + assertEquals(a.mOriginalPackages, b.mOriginalPackages); + assertEquals(a.mRealPackage, b.mRealPackage); + assertEquals(a.mAdoptPermissions, b.mAdoptPermissions); + assertBundleApproximateEquals(a.mAppMetaData, b.mAppMetaData); + assertEquals(a.mVersionName, b.mVersionName); + assertEquals(a.mSharedUserId, b.mSharedUserId); + assertTrue(Arrays.equals(a.mSignatures, b.mSignatures)); + assertTrue(Arrays.equals(a.mCertificates, b.mCertificates)); + assertTrue(Arrays.equals(a.mLastPackageUsageTimeInMills, b.mLastPackageUsageTimeInMills)); + assertEquals(a.mExtras, b.mExtras); + assertEquals(a.mRestrictedAccountType, b.mRestrictedAccountType); + assertEquals(a.mRequiredAccountType, b.mRequiredAccountType); + assertEquals(a.mOverlayTarget, b.mOverlayTarget); + assertEquals(a.mSigningKeys, b.mSigningKeys); + assertEquals(a.mUpgradeKeySets, b.mUpgradeKeySets); + assertEquals(a.mKeySetMapping, b.mKeySetMapping); + assertEquals(a.cpuAbiOverride, b.cpuAbiOverride); + assertTrue(Arrays.equals(a.restrictUpdateHash, b.restrictUpdateHash)); + } + + private static void assertBundleApproximateEquals(Bundle a, Bundle b) { + if (a == b) { + return; + } + + // Force the bundles to be unparceled. + a.getBoolean("foo"); + b.getBoolean("foo"); + + assertEquals(a.toString(), b.toString()); + } + + private static void assertComponentsEqual(PackageParser.Component a, + PackageParser.Component b) { + assertEquals(a.className, b.className); + assertBundleApproximateEquals(a.metaData, b.metaData); + assertEquals(a.getComponentName(), b.getComponentName()); + + if (a.intents != null && b.intents != null) { + assertEquals(a.intents.size(), b.intents.size()); + } else if (a.intents == null || b.intents == null) { + return; + } + + for (int i = 0; i < a.intents.size(); ++i) { + PackageParser.IntentInfo aIntent = a.intents.get(i); + PackageParser.IntentInfo bIntent = b.intents.get(i); + + assertEquals(aIntent.hasDefault, bIntent.hasDefault); + assertEquals(aIntent.labelRes, bIntent.labelRes); + assertEquals(aIntent.nonLocalizedLabel, bIntent.nonLocalizedLabel); + assertEquals(aIntent.icon, bIntent.icon); + assertEquals(aIntent.logo, bIntent.logo); + assertEquals(aIntent.banner, bIntent.banner); + assertEquals(aIntent.preferred, bIntent.preferred); + } + } + + private static void assertPermissionsEqual(PackageParser.Permission a, + PackageParser.Permission b) { + assertComponentsEqual(a, b); + assertEquals(a.tree, b.tree); + + // Verify basic flags in PermissionInfo to make sure they're consistent. We don't perform + // a full structural equality here because the code that serializes them isn't parser + // specific and is tested elsewhere. + assertEquals(a.info.protectionLevel, b.info.protectionLevel); + assertEquals(a.info.group, b.info.group); + assertEquals(a.info.flags, b.info.flags); + + if (a.group != null && b.group != null) { + assertPermissionGroupsEqual(a.group, b.group); + } else if (a.group != null || b.group != null) { + throw new AssertionError(); + } + } + + private static void assertInstrumentationEqual(PackageParser.Instrumentation a, + PackageParser.Instrumentation b) { + assertComponentsEqual(a, b); + + // Sanity check for InstrumentationInfo. + assertEquals(a.info.targetPackage, b.info.targetPackage); + assertEquals(a.info.sourceDir, b.info.sourceDir); + assertEquals(a.info.publicSourceDir, b.info.publicSourceDir); + } + + private static void assertServicesEqual(PackageParser.Service a, PackageParser.Service b) { + assertComponentsEqual(a, b); + + // Sanity check for ServiceInfo. + assertApplicationInfoEqual(a.info.applicationInfo, b.info.applicationInfo); + assertEquals(a.info.name, b.info.name); + } + + private static void assertProvidersEqual(PackageParser.Provider a, PackageParser.Provider b) { + assertComponentsEqual(a, b); + + // Sanity check for ProviderInfo + assertApplicationInfoEqual(a.info.applicationInfo, b.info.applicationInfo); + assertEquals(a.info.name, b.info.name); + } + + private static void assertActivitiesEqual(PackageParser.Activity a, PackageParser.Activity b) { + assertComponentsEqual(a, b); + + // Sanity check for ActivityInfo. + assertApplicationInfoEqual(a.info.applicationInfo, b.info.applicationInfo); + assertEquals(a.info.name, b.info.name); + } + + private static void assertPermissionGroupsEqual(PackageParser.PermissionGroup a, + PackageParser.PermissionGroup b) { + assertComponentsEqual(a, b); + + // Sanity check for PermissionGroupInfo. + assertEquals(a.info.name, b.info.name); + assertEquals(a.info.descriptionRes, b.info.descriptionRes); + } + + private static void assertApplicationInfoEqual(ApplicationInfo a, ApplicationInfo that) { + assertEquals(a.descriptionRes, that.descriptionRes); + assertEquals(a.theme, that.theme); + assertEquals(a.fullBackupContent, that.fullBackupContent); + assertEquals(a.uiOptions, that.uiOptions); + assertEquals(a.flags, that.flags); + assertEquals(a.privateFlags, that.privateFlags); + assertEquals(a.requiresSmallestWidthDp, that.requiresSmallestWidthDp); + assertEquals(a.compatibleWidthLimitDp, that.compatibleWidthLimitDp); + assertEquals(a.largestWidthLimitDp, that.largestWidthLimitDp); + assertEquals(a.nativeLibraryRootRequiresIsa, that.nativeLibraryRootRequiresIsa); + assertEquals(a.uid, that.uid); + assertEquals(a.minSdkVersion, that.minSdkVersion); + assertEquals(a.targetSdkVersion, that.targetSdkVersion); + assertEquals(a.versionCode, that.versionCode); + assertEquals(a.enabled, that.enabled); + assertEquals(a.enabledSetting, that.enabledSetting); + assertEquals(a.installLocation, that.installLocation); + assertEquals(a.networkSecurityConfigRes, that.networkSecurityConfigRes); + assertEquals(a.taskAffinity, that.taskAffinity); + assertEquals(a.permission, that.permission); + assertEquals(a.processName, that.processName); + assertEquals(a.className, that.className); + assertEquals(a.manageSpaceActivityName, that.manageSpaceActivityName); + assertEquals(a.backupAgentName, that.backupAgentName); + assertEquals(a.volumeUuid, that.volumeUuid); + assertEquals(a.scanSourceDir, that.scanSourceDir); + assertEquals(a.scanPublicSourceDir, that.scanPublicSourceDir); + assertEquals(a.sourceDir, that.sourceDir); + assertEquals(a.publicSourceDir, that.publicSourceDir); + assertTrue(Arrays.equals(a.splitSourceDirs, that.splitSourceDirs)); + assertTrue(Arrays.equals(a.splitPublicSourceDirs, that.splitPublicSourceDirs)); + assertTrue(Arrays.equals(a.resourceDirs, that.resourceDirs)); + assertEquals(a.seinfo, that.seinfo); + assertTrue(Arrays.equals(a.sharedLibraryFiles, that.sharedLibraryFiles)); + assertEquals(a.dataDir, that.dataDir); + assertEquals(a.deviceProtectedDataDir, that.deviceProtectedDataDir); + assertEquals(a.deviceEncryptedDataDir, that.deviceEncryptedDataDir); + assertEquals(a.credentialProtectedDataDir, that.credentialProtectedDataDir); + assertEquals(a.credentialEncryptedDataDir, that.credentialEncryptedDataDir); + assertEquals(a.nativeLibraryDir, that.nativeLibraryDir); + assertEquals(a.secondaryNativeLibraryDir, that.secondaryNativeLibraryDir); + assertEquals(a.nativeLibraryRootDir, that.nativeLibraryRootDir); + assertEquals(a.primaryCpuAbi, that.primaryCpuAbi); + assertEquals(a.secondaryCpuAbi, that.secondaryCpuAbi); + } + + public static void setKnownFields(PackageParser.Package pkg) { + pkg.baseRevisionCode = 100; + pkg.baseHardwareAccelerated = true; + pkg.mVersionCode = 100; + pkg.mSharedUserLabel = 100; + pkg.mPreferredOrder = 100; + pkg.installLocation = 100; + pkg.coreApp = true; + pkg.mRequiredForAllUsers = true; + pkg.mOverlayPriority = 100; + pkg.mTrustedOverlay = true; + pkg.use32bitAbi = true; + pkg.packageName = "foo"; + pkg.splitNames = new String[] { "foo" }; + pkg.volumeUuid = "foo"; + pkg.codePath = "foo"; + pkg.baseCodePath = "foo"; + pkg.splitCodePaths = new String[] { "foo" }; + pkg.splitRevisionCodes = new int[] { 100 }; + pkg.splitFlags = new int[] { 100 }; + pkg.splitPrivateFlags = new int[] { 100 }; + pkg.applicationInfo = new ApplicationInfo(); + + pkg.permissions.add(new PackageParser.Permission(pkg)); + pkg.permissionGroups.add(new PackageParser.PermissionGroup(pkg)); + + final PackageParser.ParseComponentArgs dummy = new PackageParser.ParseComponentArgs( + pkg, new String[1], 0, 0, 0, 0, 0, 0, null, 0, 0, 0); + + pkg.activities.add(new PackageParser.Activity(dummy, new ActivityInfo())); + pkg.receivers.add(new PackageParser.Activity(dummy, new ActivityInfo())); + pkg.providers.add(new PackageParser.Provider(dummy, new ProviderInfo())); + pkg.services.add(new PackageParser.Service(dummy, new ServiceInfo())); + pkg.instrumentation.add(new PackageParser.Instrumentation(dummy, new InstrumentationInfo())); + pkg.requestedPermissions.add("foo"); + + pkg.protectedBroadcasts = new ArrayList<>(); + pkg.protectedBroadcasts.add("foo"); + + pkg.parentPackage = new PackageParser.Package("foo"); + + pkg.childPackages = new ArrayList<>(); + pkg.childPackages.add(new PackageParser.Package("bar")); + + pkg.libraryNames = new ArrayList<>(); + pkg.libraryNames.add("foo"); + + pkg.usesLibraries = new ArrayList<>(); + pkg.usesLibraries.add("foo"); + + pkg.usesOptionalLibraries = new ArrayList<>(); + pkg.usesOptionalLibraries.add("foo"); + + pkg.usesLibraryFiles = new String[] { "foo "}; + + pkg.mOriginalPackages = new ArrayList<>(); + pkg.mOriginalPackages.add("foo"); + + pkg.mRealPackage = "foo"; + + pkg.mAdoptPermissions = new ArrayList<>(); + pkg.mAdoptPermissions.add("foo"); + + pkg.mAppMetaData = new Bundle(); + pkg.mVersionName = "foo"; + pkg.mSharedUserId = "foo"; + pkg.mSignatures = new Signature[] { new Signature(new byte[16]) }; + pkg.mCertificates = new Certificate[][] { new Certificate[] { null }}; + pkg.mExtras = new Bundle(); + pkg.mRestrictedAccountType = "foo"; + pkg.mRequiredAccountType = "foo"; + pkg.mOverlayTarget = "foo"; + pkg.mSigningKeys = new ArraySet<>(); + pkg.mUpgradeKeySets = new ArraySet<>(); + pkg.mKeySetMapping = new ArrayMap<>(); + pkg.cpuAbiOverride = "foo"; + pkg.restrictUpdateHash = new byte[16]; + + pkg.preferredActivityFilters = new ArrayList<>(); + pkg.preferredActivityFilters.add(new PackageParser.ActivityIntentInfo( + new PackageParser.Activity(dummy, new ActivityInfo()))); + + pkg.configPreferences = new ArrayList<>(); + pkg.configPreferences.add(new ConfigurationInfo()); + + pkg.reqFeatures = new ArrayList<>(); + pkg.reqFeatures.add(new FeatureInfo()); + + pkg.featureGroups = new ArrayList<>(); + pkg.featureGroups.add(new FeatureGroupInfo()); + } + + private static void assertAllFieldsExist(PackageParser.Package pkg) throws Exception { + Field[] fields = PackageParser.Package.class.getDeclaredFields(); + + Set nonSerializedFields = new HashSet<>(); + nonSerializedFields.add("mExtras"); + nonSerializedFields.add("packageUsageTimeMillis"); + + for (Field f : fields) { + final Class fieldType = f.getType(); + + if (nonSerializedFields.contains(f.getName())) { + continue; + } + + if (List.class.isAssignableFrom(fieldType)) { + // Sanity check for list fields: Assume they're non-null and contain precisely + // one element. + List list = (List) f.get(pkg); + assertNotNull(list); + assertEquals(1, list.size()); + } else if (fieldType.getComponentType() != null) { + // Sanity check for array fields: Assume they're non-null and contain precisely + // one element. + Object array = f.get(pkg); + assertNotNull(Array.get(array, 0)); + } else if (fieldType == String.class) { + // String fields: Check that they're set to "foo". + String value = (String) f.get(pkg); + assertEquals("foo", value); + } else if (fieldType == int.class) { + // int fields: Check that they're set to 100. + int value = (int) f.get(pkg); + assertEquals(100, value); + } else { + // All other fields: Check that they're set. + Object o = f.get(pkg); + assertNotNull("Field was null: " + f.getName(), o); + } + } + } } +