diff options
13 files changed, 337 insertions, 3 deletions
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 2b81c86e1b0d..17529a6dacf0 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -794,12 +794,25 @@ public class ApplicationPackageManager extends PackageManager { @Override public List<ModuleInfo> getInstalledModules(int flags) { - return null; + try { + return mPM.getInstalledModules(flags); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } @Override public ModuleInfo getModuleInfo(String packageName, int flags) throws NameNotFoundException { - return null; + try { + ModuleInfo mi = mPM.getModuleInfo(packageName, flags); + if (mi != null) { + return mi; + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + throw new NameNotFoundException("No module info for package: " + packageName); } @SuppressWarnings("unchecked") @@ -3002,7 +3015,6 @@ public class ApplicationPackageManager extends PackageManager { } } - @Override public void sendDeviceCustomizationReadyBroadcast() { try { mPM.sendDeviceCustomizationReadyBroadcast(); diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index eea2b8873fe7..a4ea513a055f 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -36,6 +36,7 @@ import android.content.pm.IOnPermissionsChangeListener; import android.content.pm.IntentFilterVerificationInfo; import android.content.pm.InstrumentationInfo; import android.content.pm.KeySet; +import android.content.pm.ModuleInfo; import android.content.pm.PackageInfo; import android.content.pm.ParceledListSlice; import android.content.pm.ProviderInfo; @@ -680,4 +681,8 @@ interface IPackageManager { boolean isPackageStateProtected(String packageName, int userId); void sendDeviceCustomizationReadyBroadcast(); + + List<ModuleInfo> getInstalledModules(int flags); + + ModuleInfo getModuleInfo(String packageName, int flags); } diff --git a/core/java/android/content/pm/ModuleInfo.aidl b/core/java/android/content/pm/ModuleInfo.aidl new file mode 100644 index 000000000000..cc13bf1044bc --- /dev/null +++ b/core/java/android/content/pm/ModuleInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 2018, 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 ModuleInfo; diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 38caa3655fb0..9277daec7027 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3640,4 +3640,6 @@ (android.view.InputEventCompatProcessor). --> <string name="config_inputEventCompatProcessorOverrideClassName" translatable="false"></string> + <!-- Component name for the default module metadata provider on this device --> + <string name="config_defaultModuleMetadataProvider">com.android.modulemetadata</string> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index f959b4400192..d68681d8c51d 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3519,4 +3519,6 @@ <java-symbol type="dimen" name="rounded_corner_radius" /> <java-symbol type="dimen" name="rounded_corner_radius_top" /> <java-symbol type="dimen" name="rounded_corner_radius_bottom" /> + + <java-symbol type="string" name="config_defaultModuleMetadataProvider" /> </resources> diff --git a/services/core/java/com/android/server/pm/ModuleInfoProvider.java b/services/core/java/com/android/server/pm/ModuleInfoProvider.java new file mode 100644 index 000000000000..886cfb25eadd --- /dev/null +++ b/services/core/java/com/android/server/pm/ModuleInfoProvider.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2018 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 android.content.Context; +import android.content.pm.IPackageManager; +import android.content.pm.ModuleInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources; +import android.content.res.XmlResourceParser; +import android.os.Process; +import android.os.RemoteException; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.Slog; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Provides data to back {@code ModuleInfo} related APIs in the package manager. The data is stored + * as an XML resource in a configurable "module metadata" package. + */ +@VisibleForTesting +public class ModuleInfoProvider { + private static final String TAG = "PackageManager.ModuleInfoProvider"; + + /** + * The key in the package's application level metadata bundle that provides a resource reference + * to the module metadata. + */ + private static final String MODULE_METADATA_KEY = "android.content.pm.MODULE_METADATA"; + + + private final Context mContext; + private final IPackageManager mPackageManager; + private final Map<String, ModuleInfo> mModuleInfo; + + // TODO: Move this to an earlier boot phase if anybody requires it then. + private volatile boolean mMetadataLoaded; + + ModuleInfoProvider(Context context, IPackageManager packageManager) { + mContext = context; + mPackageManager = packageManager; + mModuleInfo = new ArrayMap<>(); + } + + @VisibleForTesting + public ModuleInfoProvider(XmlResourceParser metadata, Resources resources) { + mContext = null; + mPackageManager = null; + mModuleInfo = new ArrayMap<>(); + loadModuleMetadata(metadata, resources); + } + + /** Called by the {@code PackageManager} when it has completed its boot sequence */ + public void systemReady() { + final String packageName = mContext.getResources().getString( + R.string.config_defaultModuleMetadataProvider); + if (TextUtils.isEmpty(packageName)) { + Slog.w(TAG, "No configured module metadata provider."); + return; + } + + final Resources packageResources; + final PackageInfo pi; + try { + pi = mPackageManager.getPackageInfo(packageName, + PackageManager.GET_META_DATA, Process.SYSTEM_UID); + + Context packageContext = mContext.createPackageContext(packageName, 0); + packageResources = packageContext.getResources(); + } catch (RemoteException | NameNotFoundException e) { + Slog.w(TAG, "Unable to discover metadata package: " + packageName, e); + return; + } + + XmlResourceParser parser = packageResources.getXml( + pi.applicationInfo.metaData.getInt(MODULE_METADATA_KEY)); + loadModuleMetadata(parser, packageResources); + } + + private void loadModuleMetadata(XmlResourceParser parser, Resources packageResources) { + try { + // The format for the module metadata is straightforward : + // + // The following attributes on <module> are currently defined : + // -- name : A resource reference to a User visible package name, maps to + // ModuleInfo#getName + // -- packageName : The package name of the module, see ModuleInfo#getPackageName + // -- isHidden : Whether the module is hidden, see ModuleInfo#isHidden + // + // <module-metadata> + // <module name="@string/resource" packageName="package_name" isHidden="false|true" /> + // <module .... /> + // </module-metadata> + + XmlUtils.beginDocument(parser, "module-metadata"); + while (true) { + XmlUtils.nextElement(parser); + if (parser.getEventType() == XmlPullParser.END_DOCUMENT) { + break; + } + + if (!"module".equals(parser.getName())) { + Slog.w(TAG, "Unexpected metadata element: " + parser.getName()); + mModuleInfo.clear(); + break; + } + + // TODO: The module name here is fetched using the resource configuration applied + // at the time of parsing this information. This is probably not the best approach + // to dealing with this as we'll now have to listen to all config changes and + // regenerate the data if required. Also, is this the right way to parse a resource + // reference out of an XML file ? + final String moduleName = packageResources.getString( + Integer.parseInt(parser.getAttributeValue(null, "name").substring(1))); + final String modulePackageName = XmlUtils.readStringAttribute(parser, + "packageName"); + final boolean isHidden = XmlUtils.readBooleanAttribute(parser, "isHidden"); + + ModuleInfo mi = new ModuleInfo(); + mi.setHidden(isHidden); + mi.setPackageName(modulePackageName); + mi.setName(moduleName); + + mModuleInfo.put(modulePackageName, mi); + } + } catch (XmlPullParserException | IOException e) { + Slog.w(TAG, "Error parsing module metadata", e); + mModuleInfo.clear(); + } finally { + parser.close(); + mMetadataLoaded = true; + } + } + + List<ModuleInfo> getInstalledModules(int flags) { + if (!mMetadataLoaded) { + throw new IllegalStateException("Call to getInstalledModules before metadata loaded"); + } + + return new ArrayList<>(mModuleInfo.values()); + } + + ModuleInfo getModuleInfo(String packageName, int flags) { + if (!mMetadataLoaded) { + throw new IllegalStateException("Call to getModuleInfo before metadata loaded"); + } + + return mModuleInfo.get(packageName); + } +} diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 2826e7b8fe2b..28fb01d6d958 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -162,12 +162,14 @@ import android.content.pm.InstantAppRequest; import android.content.pm.InstrumentationInfo; import android.content.pm.IntentFilterVerificationInfo; import android.content.pm.KeySet; +import android.content.pm.ModuleInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageInfoLite; import android.content.pm.PackageInstaller; import android.content.pm.PackageList; import android.content.pm.PackageManager; import android.content.pm.PackageManager.LegacyPackageDeleteObserver; +import android.content.pm.PackageManager.ModuleInfoFlags; import android.content.pm.PackageManagerInternal; import android.content.pm.PackageManagerInternal.CheckPermissionDelegate; import android.content.pm.PackageManagerInternal.PackageListObserver; @@ -718,6 +720,8 @@ public class PackageManagerService extends IPackageManager.Stub private PackageManager mPackageManager; + private final ModuleInfoProvider mModuleInfoProvider; + class PackageParserCallback implements PackageParser.Callback { @Override public final boolean hasFeature(String feature) { return PackageManagerService.this.hasSystemFeature(feature, 0); @@ -3030,6 +3034,8 @@ public class PackageManagerService extends IPackageManager.Stub } // synchronized (mPackages) } // synchronized (mInstallLock) + mModuleInfoProvider = new ModuleInfoProvider(mContext, this); + // Now after opening every single application zip, make sure they // are all flushed. Not really needed, but keeps things nice and // tidy. @@ -4969,6 +4975,16 @@ public class PackageManagerService extends IPackageManager.Stub } @Override + public ModuleInfo getModuleInfo(String packageName, @ModuleInfoFlags int flags) { + return mModuleInfoProvider.getModuleInfo(packageName, flags); + } + + @Override + public List<ModuleInfo> getInstalledModules(int flags) { + return mModuleInfoProvider.getInstalledModules(flags); + } + + @Override public String[] getSystemSharedLibraryNames() { // allow instant applications synchronized (mPackages) { @@ -20242,6 +20258,8 @@ public class PackageManagerService extends IPackageManager.Stub } }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED)); } + + mModuleInfoProvider.systemReady(); } public void waitForAppDataPrepared() { diff --git a/services/tests/servicestests/res/values/strings.xml b/services/tests/servicestests/res/values/strings.xml index 57da0af42a88..50ccd1f769f5 100644 --- a/services/tests/servicestests/res/values/strings.xml +++ b/services/tests/servicestests/res/values/strings.xml @@ -32,4 +32,6 @@ <string name="config_batterySaverDeviceSpecificConfig_1"></string> <string name="config_batterySaverDeviceSpecificConfig_2">cpufreq-n=1:123/2:456</string> <string name="config_batterySaverDeviceSpecificConfig_3">cpufreq-n=2:222,cpufreq-i=3:333/4:444</string> + <string name="module_1_name" translatable="false">module_1_name</string> + <string name="module_2_name" translatable="false">module_2_name</string> </resources> diff --git a/services/tests/servicestests/res/xml/unparseable_metadata1.xml b/services/tests/servicestests/res/xml/unparseable_metadata1.xml new file mode 100644 index 000000000000..73967f113184 --- /dev/null +++ b/services/tests/servicestests/res/xml/unparseable_metadata1.xml @@ -0,0 +1,4 @@ +<not-module-metadata> + <module name="@string/module_1_name" packageName="com.android.module1" isHidden="false"/> + <module name="@string/module_2_name" packageName="com.android.module2" isHidden="true"/> +</not-module-metadata> diff --git a/services/tests/servicestests/res/xml/unparseable_metadata2.xml b/services/tests/servicestests/res/xml/unparseable_metadata2.xml new file mode 100644 index 000000000000..bb5a1b267012 --- /dev/null +++ b/services/tests/servicestests/res/xml/unparseable_metadata2.xml @@ -0,0 +1,4 @@ +<module-metadata> + <module name="@string/module_1_name" packageName="com.android.module1" isHidden="false"/> + <not-module name="@string/module_2_name" packageName="com.android.module2" isHidden="true"/> +</module-metadata> diff --git a/services/tests/servicestests/res/xml/well_formed_metadata.xml b/services/tests/servicestests/res/xml/well_formed_metadata.xml new file mode 100644 index 000000000000..17cc36945207 --- /dev/null +++ b/services/tests/servicestests/res/xml/well_formed_metadata.xml @@ -0,0 +1,4 @@ +<module-metadata> + <module name="@string/module_1_name" packageName="com.android.module1" isHidden="false"/> + <module name="@string/module_2_name" packageName="com.android.module2" isHidden="true"/> +</module-metadata> diff --git a/services/tests/servicestests/res/xml/well_formed_metadata2.xml b/services/tests/servicestests/res/xml/well_formed_metadata2.xml new file mode 100644 index 000000000000..47279e657bc4 --- /dev/null +++ b/services/tests/servicestests/res/xml/well_formed_metadata2.xml @@ -0,0 +1,5 @@ +<module-metadata> + <module name="@string/module_1_name" packageName="com.android.module1" isHidden="false" + attribute1="attribute1" attribute2="attribute2" /> + <module name="@string/module_2_name" packageName="com.android.module2" isHidden="true"/> +</module-metadata> diff --git a/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java b/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java new file mode 100644 index 000000000000..bd3d9ab2220d --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2018 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 android.content.Context; +import android.content.pm.ModuleInfo; +import android.test.InstrumentationTestCase; + +import com.android.frameworks.servicestests.R; + +import java.util.Collections; +import java.util.List; + +public class ModuleInfoProviderTest extends InstrumentationTestCase { + public void testSuccessfulParse() { + ModuleInfoProvider provider = getProvider(R.xml.well_formed_metadata); + + List<ModuleInfo> mi = provider.getInstalledModules(0); + assertEquals(2, mi.size()); + + Collections.sort(mi, (ModuleInfo m1, ModuleInfo m2) -> + m1.getPackageName().compareTo(m1.getPackageName())); + assertEquals("com.android.module1", mi.get(0).getPackageName()); + assertEquals("com.android.module2", mi.get(1).getPackageName()); + + ModuleInfo mi1 = provider.getModuleInfo("com.android.module1", 0); + assertEquals("com.android.module1", mi1.getPackageName()); + assertEquals("module_1_name", mi1.getName()); + assertEquals(false, mi1.isHidden()); + + ModuleInfo mi2 = provider.getModuleInfo("com.android.module2", 0); + assertEquals("com.android.module2", mi2.getPackageName()); + assertEquals("module_2_name", mi2.getName()); + assertEquals(true, mi2.isHidden()); + } + + public void testParseFailure_incorrectTopLevelElement() { + ModuleInfoProvider provider = getProvider(R.xml.unparseable_metadata1); + assertEquals(0, provider.getInstalledModules(0).size()); + } + + public void testParseFailure_incorrectModuleElement() { + ModuleInfoProvider provider = getProvider(R.xml.unparseable_metadata2); + assertEquals(0, provider.getInstalledModules(0).size()); + } + + public void testParse_unknownAttributesIgnored() { + ModuleInfoProvider provider = getProvider(R.xml.well_formed_metadata); + + List<ModuleInfo> mi = provider.getInstalledModules(0); + assertEquals(2, mi.size()); + + ModuleInfo mi1 = provider.getModuleInfo("com.android.module1", 0); + assertEquals("com.android.module1", mi1.getPackageName()); + assertEquals("module_1_name", mi1.getName()); + assertEquals(false, mi1.isHidden()); + } + + /** + * Constructs an {@code ModuleInfoProvider} using the test package resources. + */ + private ModuleInfoProvider getProvider(int resourceId) { + final Context ctx = getInstrumentation().getContext(); + return new ModuleInfoProvider(ctx.getResources().getXml(resourceId), ctx.getResources()); + } +} |