From 569435e0eb42bcd9ae31efb22244ed186ed82d45 Mon Sep 17 00:00:00 2001 From: Todd Kennedy Date: Wed, 18 Nov 2020 09:27:18 -0800 Subject: [PATCH] Implment get/query APIs for properties Bug: 169258655 Test: atest CtsContentTestCases:PackageManagerGetPropertyTest Test: atest CtsContentTestCases:PackageManagerQueryPropertyTest Change-Id: I91d58cd42e7f1f10b0e494ff9f318a9e3fa073db --- core/api/current.txt | 7 + .../app/ApplicationPackageManager.java | 110 +++++++ .../android/content/pm/IPackageManager.aidl | 4 + .../android/content/pm/PackageManager.aidl | 20 ++ .../android/content/pm/PackageManager.java | 121 +++++++- .../server/pm/PackageManagerService.java | 60 ++-- .../android/server/pm/PackageProperty.java | 287 ++++++++++++++++++ 7 files changed, 590 insertions(+), 19 deletions(-) create mode 100644 core/java/android/content/pm/PackageManager.aidl create mode 100644 services/core/java/com/android/server/pm/PackageProperty.java diff --git a/core/api/current.txt b/core/api/current.txt index 98cf24a9b8396..9f7f3917a11f4 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -12105,6 +12105,8 @@ package android.content.pm { method public abstract android.content.pm.PermissionInfo getPermissionInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException; method @Deprecated public abstract int getPreferredActivities(@NonNull java.util.List, @NonNull java.util.List, @Nullable String); method @Deprecated @NonNull public abstract java.util.List getPreferredPackages(int); + method @NonNull public android.content.pm.PackageManager.Property getProperty(@NonNull String, @NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; + method @NonNull public android.content.pm.PackageManager.Property getProperty(@NonNull String, @NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull public abstract android.content.pm.ProviderInfo getProviderInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull public abstract android.content.pm.ActivityInfo getReceiverInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull public abstract android.content.res.Resources getResourcesForActivity(@NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException; @@ -12137,6 +12139,8 @@ package android.content.pm { method public boolean isPackageSuspended(); method @CheckResult public abstract boolean isPermissionRevokedByPolicy(@NonNull String, @NonNull String); method public abstract boolean isSafeMode(); + method @NonNull public java.util.List queryActivityProperty(@NonNull String); + method @NonNull public java.util.List queryApplicationProperty(@NonNull String); method @NonNull public abstract java.util.List queryBroadcastReceivers(@NonNull android.content.Intent, int); method @NonNull public abstract java.util.List queryContentProviders(@Nullable String, int, int); method @NonNull public abstract java.util.List queryInstrumentation(@NonNull String, int); @@ -12145,6 +12149,9 @@ package android.content.pm { method @NonNull public abstract java.util.List queryIntentContentProviders(@NonNull android.content.Intent, int); method @NonNull public abstract java.util.List queryIntentServices(@NonNull android.content.Intent, int); method @NonNull public abstract java.util.List queryPermissionsByGroup(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException; + method @NonNull public java.util.List queryProviderProperty(@NonNull String); + method @NonNull public java.util.List queryReceiverProperty(@NonNull String); + method @NonNull public java.util.List queryServiceProperty(@NonNull String); method @Deprecated public abstract void removePackageFromPreferred(@NonNull String); method public abstract void removePermission(@NonNull String); method @RequiresPermission(value="android.permission.WHITELIST_RESTRICTED_PERMISSIONS", conditional=true) public boolean removeWhitelistedRestrictedPermission(@NonNull String, @NonNull String, int); diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 7cef93fe75474..34437afb614ab 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -58,6 +58,8 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.PackageManager.Property; import android.content.pm.ParceledListSlice; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; @@ -3551,4 +3553,112 @@ public class ApplicationPackageManager extends PackageManager { throw e.rethrowAsRuntimeException(); } } + + @Override + public Property getProperty(String propertyName, String packageName) + throws NameNotFoundException { + Objects.requireNonNull(packageName); + Objects.requireNonNull(propertyName); + try { + final Property property = mPM.getProperty(propertyName, packageName, null); + if (property == null) { + throw new NameNotFoundException(); + } + return property; + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + @Override + public Property getProperty(String propertyName, ComponentName component) + throws NameNotFoundException { + Objects.requireNonNull(component); + Objects.requireNonNull(propertyName); + try { + final Property property = mPM.getProperty( + propertyName, component.getPackageName(), component.getClassName()); + if (property == null) { + throw new NameNotFoundException(); + } + return property; + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + @Override + public List queryApplicationProperty(String propertyName) { + Objects.requireNonNull(propertyName); + try { + final ParceledListSlice parceledList = + mPM.queryProperty(propertyName, TYPE_APPLICATION); + if (parceledList == null) { + return Collections.emptyList(); + } + return parceledList.getList(); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + @Override + public List queryActivityProperty(String propertyName) { + Objects.requireNonNull(propertyName); + try { + final ParceledListSlice parceledList = + mPM.queryProperty(propertyName, TYPE_ACTIVITY); + if (parceledList == null) { + return Collections.emptyList(); + } + return parceledList.getList(); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + @Override + public List queryProviderProperty(String propertyName) { + Objects.requireNonNull(propertyName); + try { + final ParceledListSlice parceledList = + mPM.queryProperty(propertyName, TYPE_PROVIDER); + if (parceledList == null) { + return Collections.emptyList(); + } + return parceledList.getList(); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + @Override + public List queryReceiverProperty(String propertyName) { + Objects.requireNonNull(propertyName); + try { + final ParceledListSlice parceledList = + mPM.queryProperty(propertyName, TYPE_RECEIVER); + if (parceledList == null) { + return Collections.emptyList(); + } + return parceledList.getList(); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + @Override + public List queryServiceProperty(String propertyName) { + Objects.requireNonNull(propertyName); + try { + final ParceledListSlice parceledList = + mPM.queryProperty(propertyName, TYPE_SERVICE); + if (parceledList == null) { + return Collections.emptyList(); + } + return parceledList.getList(); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } } diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index d66a42a6232a8..f634b8a54a0f0 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -38,6 +38,7 @@ import android.content.pm.InstrumentationInfo; import android.content.pm.KeySet; import android.content.pm.ModuleInfo; import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.pm.ProviderInfo; import android.content.pm.PermissionGroupInfo; @@ -797,4 +798,7 @@ interface IPackageManager { IBinder getHoldLockToken(); void holdLock(in IBinder token, in int durationMs); + + PackageManager.Property getProperty(String propertyName, String packageName, String className); + ParceledListSlice queryProperty(String propertyName, int componentType); } diff --git a/core/java/android/content/pm/PackageManager.aidl b/core/java/android/content/pm/PackageManager.aidl new file mode 100644 index 0000000000000..31365a19f2860 --- /dev/null +++ b/core/java/android/content/pm/PackageManager.aidl @@ -0,0 +1,20 @@ +/* +** +** Copyright 2020, 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; + +parcelable PackageManager.Property; diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 53dfcbf3a3b41..044b3b2e8284a 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -311,6 +311,8 @@ public abstract class PackageManager { public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeString(mName); dest.writeInt(mType); + dest.writeString(mPackageName); + dest.writeString(mClassName); if (mType == TYPE_BOOLEAN) { dest.writeBoolean(mBooleanValue); } else if (mType == TYPE_FLOAT) { @@ -322,8 +324,6 @@ public abstract class PackageManager { } else if (mType == TYPE_STRING) { dest.writeString(mStringValue); } - dest.writeString(mPackageName); - dest.writeString(mClassName); } @NonNull @@ -370,6 +370,41 @@ public abstract class PackageManager { public void onPermissionsChanged(int uid); } + /** @hide */ + public static final int TYPE_UNKNOWN = 0; + /** @hide */ + public static final int TYPE_ACTIVITY = 1; + /** @hide */ + public static final int TYPE_RECEIVER = 2; + /** @hide */ + public static final int TYPE_SERVICE = 3; + /** @hide */ + public static final int TYPE_PROVIDER = 4; + /** @hide */ + public static final int TYPE_APPLICATION = 5; + /** @hide */ + @IntDef(prefix = { "TYPE_" }, value = { + TYPE_UNKNOWN, + TYPE_ACTIVITY, + TYPE_RECEIVER, + TYPE_SERVICE, + TYPE_PROVIDER, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ComponentType {} + + /** @hide */ + @IntDef(prefix = { "TYPE_" }, value = { + TYPE_UNKNOWN, + TYPE_ACTIVITY, + TYPE_RECEIVER, + TYPE_SERVICE, + TYPE_PROVIDER, + TYPE_APPLICATION, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface PropertyLocation {} + /** * As a guiding principle: *

@@ -8601,6 +8636,88 @@ public abstract class PackageManager { throw new UnsupportedOperationException( "getMimeGroup not implemented in subclass"); } + + /** + * Returns the property defined in the given package's <appliction> tag. + * + * @throws NameNotFoundException if either the given package is not installed or if the + * given property is not defined within the <application> tag. + */ + @NonNull + public Property getProperty(@NonNull String propertyName, @NonNull String packageName) + throws NameNotFoundException { + throw new UnsupportedOperationException( + "getProperty not implemented in subclass"); + } + + /** + * Returns the property defined in the given component declaration. + * + * @throws NameNotFoundException if either the given component does not exist or if the + * given property is not defined within the component declaration. + */ + @NonNull + public Property getProperty(@NonNull String propertyName, @NonNull ComponentName component) + throws NameNotFoundException { + throw new UnsupportedOperationException( + "getProperty not implemented in subclass"); + } + + /** + * Returns the property definition for all <application> tags. + *

If the property is not defined with any <application> tag, + * returns and empty list. + */ + @NonNull + public List queryApplicationProperty(@NonNull String propertyName) { + throw new UnsupportedOperationException( + "qeuryApplicationProperty not implemented in subclass"); + } + + /** + * Returns the property definition for all <activity> and <activity-alias> tags. + *

If the property is not defined with any <activity> and <activity-alias> tag, + * returns and empty list. + */ + @NonNull + public List queryActivityProperty(@NonNull String propertyName) { + throw new UnsupportedOperationException( + "qeuryActivityProperty not implemented in subclass"); + } + + /** + * Returns the property definition for all <provider> tags. + *

If the property is not defined with any <provider> tag, + * returns and empty list. + */ + @NonNull + public List queryProviderProperty(@NonNull String propertyName) { + throw new UnsupportedOperationException( + "qeuryProviderProperty not implemented in subclass"); + } + + /** + * Returns the property definition for all <receiver> tags. + *

If the property is not defined with any <receiver> tag, + * returns and empty list. + */ + @NonNull + public List queryReceiverProperty(@NonNull String propertyName) { + throw new UnsupportedOperationException( + "qeuryReceiverProperty not implemented in subclass"); + } + + /** + * Returns the property definition for all <service> tags. + *

If the property is not defined with any <service> tag, + * returns and empty list. + */ + @NonNull + public List queryServiceProperty(@NonNull String propertyName) { + throw new UnsupportedOperationException( + "qeuryServiceProperty not implemented in subclass"); + } + /** * Grants implicit visibility of the package that provides an authority to a querying UID. * diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 8bc524cc5f2f6..38807112576a4 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -94,6 +94,11 @@ import static android.content.pm.PackageManager.MOVE_FAILED_OPERATION_PENDING; import static android.content.pm.PackageManager.MOVE_FAILED_SYSTEM_PACKAGE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.content.pm.PackageManager.RESTRICTION_NONE; +import static android.content.pm.PackageManager.TYPE_ACTIVITY; +import static android.content.pm.PackageManager.TYPE_PROVIDER; +import static android.content.pm.PackageManager.TYPE_RECEIVER; +import static android.content.pm.PackageManager.TYPE_SERVICE; +import static android.content.pm.PackageManager.TYPE_UNKNOWN; import static android.content.pm.PackageManager.UNINSTALL_REASON_UNKNOWN; import static android.content.pm.PackageManagerInternal.LAST_KNOWN_PACKAGE; import static android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4; @@ -195,8 +200,11 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageInfoLite; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.ComponentType; import android.content.pm.PackageManager.LegacyPackageDeleteObserver; import android.content.pm.PackageManager.ModuleInfoFlags; +import android.content.pm.PackageManager.Property; +import android.content.pm.PackageManager.PropertyLocation; import android.content.pm.PackageManagerInternal; import android.content.pm.PackageManagerInternal.PackageListObserver; import android.content.pm.PackageManagerInternal.PrivateResolveFlags; @@ -339,7 +347,6 @@ import com.android.internal.telephony.CarrierAppUtils; import com.android.internal.util.ArrayUtils; import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.DumpUtils; -import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; @@ -394,7 +401,6 @@ import libcore.util.HexEncoding; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlSerializer; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; @@ -572,21 +578,6 @@ public class PackageManagerService extends IPackageManager.Stub private static final int[] EMPTY_INT_ARRAY = new int[0]; - private static final int TYPE_UNKNOWN = 0; - private static final int TYPE_ACTIVITY = 1; - private static final int TYPE_RECEIVER = 2; - private static final int TYPE_SERVICE = 3; - private static final int TYPE_PROVIDER = 4; - @IntDef(prefix = { "TYPE_" }, value = { - TYPE_UNKNOWN, - TYPE_ACTIVITY, - TYPE_RECEIVER, - TYPE_SERVICE, - TYPE_PROVIDER, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ComponentType {} - /** * Timeout (in milliseconds) after which the watchdog should declare that * our handler thread is wedged. The usual default for such things is one @@ -1317,6 +1308,8 @@ public class PackageManagerService extends IPackageManager.Stub private final IncrementalManager mIncrementalManager; + private final PackageProperty mPackageProperty = new PackageProperty(); + private static class IFVerificationParams { String packageName; boolean hasDomainUrls; @@ -12708,6 +12701,37 @@ public class PackageManagerService extends IPackageManager.Stub return true; } + @Override + public Property getProperty(String propertyName, String packageName, String className) { + Objects.requireNonNull(propertyName); + Objects.requireNonNull(packageName); + synchronized (mLock) { + final PackageSetting ps = getPackageSetting(packageName); + if (shouldFilterApplicationLocked(ps, Binder.getCallingUid(), + UserHandle.getCallingUserId())) { + return null; + } + return mPackageProperty.getProperty(propertyName, packageName, className); + } + } + + @Override + public ParceledListSlice queryProperty( + String propertyName, @PropertyLocation int componentType) { + Objects.requireNonNull(propertyName); + final int callingUid = Binder.getCallingUid(); + final int callingUserId = UserHandle.getCallingUserId(); + final List result = + mPackageProperty.queryProperty(propertyName, componentType, packageName -> { + final PackageSetting ps = getPackageSetting(packageName); + return shouldFilterApplicationLocked(ps, callingUid, callingUserId); + }); + if (result == null) { + return ParceledListSlice.emptyList(); + } + return new ParceledListSlice<>(result); + } + /** * Adds a scanned package to the system. When this method is finished, the package will * be available for query, resolution, etc... @@ -12834,6 +12858,7 @@ public class PackageManagerService extends IPackageManager.Stub final boolean isReplace = reconciledPkg.prepareResult != null && reconciledPkg.prepareResult.replace; mAppsFilter.addPackage(pkgSetting, isReplace); + mPackageProperty.addAllProperties(pkg); int collectionSize = ArrayUtils.size(pkg.getInstrumentations()); StringBuilder r = null; @@ -12978,6 +13003,7 @@ public class PackageManagerService extends IPackageManager.Stub private void cleanPackageDataStructuresLILPw(AndroidPackage pkg, boolean chatty) { mComponentResolver.removeAllComponents(pkg, chatty); mPermissionManager.onPackageRemoved(pkg); + mPackageProperty.removeAllProperties(pkg); final int instrumentationSize = ArrayUtils.size(pkg.getInstrumentations()); StringBuilder r = null; diff --git a/services/core/java/com/android/server/pm/PackageProperty.java b/services/core/java/com/android/server/pm/PackageProperty.java new file mode 100644 index 0000000000000..d18a02d9f3157 --- /dev/null +++ b/services/core/java/com/android/server/pm/PackageProperty.java @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2020 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 com.android.server.pm; + +import static android.content.pm.PackageManager.TYPE_ACTIVITY; +import static android.content.pm.PackageManager.TYPE_APPLICATION; +import static android.content.pm.PackageManager.TYPE_PROVIDER; +import static android.content.pm.PackageManager.TYPE_RECEIVER; +import static android.content.pm.PackageManager.TYPE_SERVICE; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.Property; +import android.content.pm.PackageManager.PropertyLocation; +import android.content.pm.parsing.component.ParsedComponent; +import android.os.Binder; +import android.os.UserHandle; +import android.util.ArrayMap; + +import com.android.server.pm.parsing.pkg.AndroidPackage; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Predicate; + +/** + * Manages properties defined within a package using the <property> tag. + */ +public class PackageProperty { + /** + * Mapping of property name to all defined defined properties. + *

This is a mapping of property name --> package map. The package + * map is a mapping of package name -> list of properties. + */ + private ArrayMap>> mApplicationProperties; + private ArrayMap>> mActivityProperties; + private ArrayMap>> mProviderProperties; + private ArrayMap>> mReceiverProperties; + private ArrayMap>> mServiceProperties; + + /** + * If the provided component is {@code null}, returns the property defined on the + * application. Otherwise, returns the property defined on the component. + */ + public Property getProperty(@NonNull String propertyName, @NonNull String packageName, + @Nullable String className) { + if (className == null) { + return getApplicationProperty(propertyName, packageName); + } + return getComponentProperty(propertyName, packageName, className); + } + + /** + * Returns all properties defined at the given location. + *

Valid locations are {@link PackageManager#TYPE_APPLICATION}, + * {@link PackageManager#TYPE_ACTIVITY}, {@link PackageManager#TYPE_PROVIDER}, + * {@link PackageManager#TYPE_RECEIVER}, or {@link PackageManager#TYPE_SERVICE}. + */ + public List queryProperty(@NonNull String propertyName, + @PropertyLocation int componentType, Predicate filter) { + final ArrayMap>> propertyMap; + if (componentType == TYPE_APPLICATION) { + propertyMap = mApplicationProperties; + } else if (componentType == TYPE_ACTIVITY) { + propertyMap = mActivityProperties; + } else if (componentType == TYPE_PROVIDER) { + propertyMap = mProviderProperties; + } else if (componentType == TYPE_RECEIVER) { + propertyMap = mReceiverProperties; + } else if (componentType == TYPE_SERVICE) { + propertyMap = mServiceProperties; + } else { + propertyMap = null; + } + if (propertyMap == null) { + return null; + } + final ArrayMap> packagePropertyMap = + propertyMap.get(propertyName); + if (packagePropertyMap == null) { + return null; + } + final int callingUid = Binder.getCallingUid(); + final int callingUserId = UserHandle.getCallingUserId(); + final int mapSize = packagePropertyMap.size(); + final List result = new ArrayList<>(mapSize); + for (int i = 0; i < mapSize; i++) { + final String packageName = packagePropertyMap.keyAt(i); + if (filter.test(packageName)) { + continue; + } + result.addAll(packagePropertyMap.valueAt(i)); + } + return result; + } + + /** Adds all properties defined for the given package */ + void addAllProperties(AndroidPackage pkg) { + mApplicationProperties = addProperties(pkg.getProperties(), mApplicationProperties); + mActivityProperties = addComponentProperties(pkg.getActivities(), mActivityProperties); + mProviderProperties = addComponentProperties(pkg.getProviders(), mProviderProperties); + mReceiverProperties = addComponentProperties(pkg.getReceivers(), mReceiverProperties); + mServiceProperties = addComponentProperties(pkg.getServices(), mServiceProperties); + } + + /** Adds all properties defined for the given package */ + void removeAllProperties(AndroidPackage pkg) { + mApplicationProperties = removeProperties(pkg.getProperties(), mApplicationProperties); + mActivityProperties = removeComponentProperties(pkg.getActivities(), mActivityProperties); + mProviderProperties = removeComponentProperties(pkg.getProviders(), mProviderProperties); + mReceiverProperties = removeComponentProperties(pkg.getReceivers(), mReceiverProperties); + mServiceProperties = removeComponentProperties(pkg.getServices(), mServiceProperties); + } + + /** Add the properties defined on the given components to the property collection */ + private static + ArrayMap>> addComponentProperties( + @NonNull List components, + @Nullable ArrayMap>> propertyCollection) { + ArrayMap>> returnCollection = + propertyCollection; + final int componentsSize = components.size(); + for (int i = 0; i < componentsSize; i++) { + final Map properties = components.get(i).getProperties(); + if (properties.size() == 0) { + continue; + } + returnCollection = addProperties(properties, returnCollection); + } + return returnCollection; + } + + /** Add the given properties to the property collection */ + private static ArrayMap>> addProperties( + @NonNull Map properties, + @Nullable ArrayMap>> propertyCollection) { + if (properties.size() == 0) { + return propertyCollection; + } + final ArrayMap>> returnCollection = + propertyCollection == null ? new ArrayMap<>(10) : propertyCollection; + final Iterator iter = properties.values().iterator(); + while (iter.hasNext()) { + final Property property = iter.next(); + final String propertyName = property.getName(); + final String packageName = property.getPackageName(); + ArrayMap> propertyMap = returnCollection.get(propertyName); + if (propertyMap == null) { + propertyMap = new ArrayMap<>(); + returnCollection.put(propertyName, propertyMap); + } + ArrayList packageProperties = propertyMap.get(packageName); + if (packageProperties == null) { + packageProperties = new ArrayList<>(properties.size()); + propertyMap.put(packageName, packageProperties); + } + packageProperties.add(property); + } + return returnCollection; + } + + /** Removes the properties defined on the given components from the property collection */ + private static + ArrayMap>> removeComponentProperties( + @NonNull List components, + @Nullable ArrayMap>> propertyCollection) { + ArrayMap>> returnCollection = + propertyCollection; + final int componentsSize = components.size(); + for (int i = 0; returnCollection != null && i < componentsSize; i++) { + final Map properties = components.get(i).getProperties(); + if (properties.size() == 0) { + continue; + } + returnCollection = removeProperties(properties, returnCollection); + } + return returnCollection; + } + + /** Removes the given properties from the property collection */ + private static ArrayMap>> removeProperties( + @NonNull Map properties, + @Nullable ArrayMap>> propertyCollection) { + if (propertyCollection == null) { + return null; + } + final Iterator iter = properties.values().iterator(); + while (iter.hasNext()) { + final Property property = iter.next(); + final String propertyName = property.getName(); + final String packageName = property.getPackageName(); + ArrayMap> propertyMap = + propertyCollection.get(propertyName); + if (propertyMap == null) { + // error + continue; + } + ArrayList packageProperties = propertyMap.get(packageName); + if (packageProperties == null) { + //error + continue; + } + packageProperties.remove(property); + + // clean up empty structures + if (packageProperties.size() == 0) { + propertyMap.remove(packageName); + } + if (propertyMap.size() == 0) { + propertyCollection.remove(propertyName); + } + } + if (propertyCollection.size() == 0) { + return null; + } + return propertyCollection; + } + + private static Property getProperty(String propertyName, String packageName, String className, + ArrayMap>> propertyMap) { + final ArrayMap> packagePropertyMap = + propertyMap.get(propertyName); + if (packagePropertyMap == null) { + return null; + } + final List propertyList = packagePropertyMap.get(packageName); + if (propertyList == null) { + return null; + } + for (int i = propertyList.size() - 1; i >= 0; i--) { + final Property property = propertyList.get(i); + if (Objects.equals(className, property.getClassName())) { + return property; + } + } + return null; + } + + private Property getComponentProperty( + String propertyName, String packageName, String className) { + Property property = null; + if (property == null && mActivityProperties != null) { + property = getProperty(propertyName, packageName, className, mActivityProperties); + } + if (property == null && mProviderProperties != null) { + property = getProperty(propertyName, packageName, className, mProviderProperties); + } + if (property == null && mReceiverProperties != null) { + property = getProperty(propertyName, packageName, className, mReceiverProperties); + } + if (property == null && mServiceProperties != null) { + property = getProperty(propertyName, packageName, className, mServiceProperties); + } + return property; + } + + private Property getApplicationProperty(String propertyName, String packageName) { + final ArrayMap> packagePropertyMap = + mApplicationProperties.get(propertyName); + if (packagePropertyMap == null) { + return null; + } + final List propertyList = packagePropertyMap.get(packageName); + if (propertyList == null) { + return null; + } + return propertyList.get(0); + } +}