summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/ApplicationPackageManager.java18
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl5
-rw-r--r--core/java/android/content/pm/ModuleInfo.aidl19
-rw-r--r--core/res/res/values/config.xml2
-rw-r--r--core/res/res/values/symbols.xml2
-rw-r--r--services/core/java/com/android/server/pm/ModuleInfoProvider.java178
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java18
-rw-r--r--services/tests/servicestests/res/values/strings.xml2
-rw-r--r--services/tests/servicestests/res/xml/unparseable_metadata1.xml4
-rw-r--r--services/tests/servicestests/res/xml/unparseable_metadata2.xml4
-rw-r--r--services/tests/servicestests/res/xml/well_formed_metadata.xml4
-rw-r--r--services/tests/servicestests/res/xml/well_formed_metadata2.xml5
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java79
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());
+ }
+}