blob: 06706cd06e11f7aaa56ddab28b495f51b0daad1a [file] [log] [blame]
/*
* 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.RemoteException;
import android.os.UserHandle;
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.Collections;
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 ApexManager mApexManager;
private final Map<String, ModuleInfo> mModuleInfo;
// TODO: Move this to an earlier boot phase if anybody requires it then.
private volatile boolean mMetadataLoaded;
private volatile String mPackageName;
ModuleInfoProvider(Context context, IPackageManager packageManager) {
mContext = context;
mPackageManager = packageManager;
mApexManager = ApexManager.getInstance();
mModuleInfo = new ArrayMap<>();
}
@VisibleForTesting
public ModuleInfoProvider(
XmlResourceParser metadata, Resources resources, ApexManager apexManager) {
mContext = null;
mPackageManager = null;
mApexManager = apexManager;
mModuleInfo = new ArrayMap<>();
loadModuleMetadata(metadata, resources);
}
/** Called by the {@code PackageManager} when it has completed its boot sequence */
public void systemReady() {
mPackageName = mContext.getResources().getString(
R.string.config_defaultModuleMetadataProvider);
if (TextUtils.isEmpty(mPackageName)) {
Slog.w(TAG, "No configured module metadata provider.");
return;
}
final Resources packageResources;
final PackageInfo pi;
try {
pi = mPackageManager.getPackageInfo(mPackageName,
PackageManager.GET_META_DATA, UserHandle.USER_SYSTEM);
Context packageContext = mContext.createPackageContext(mPackageName, 0);
packageResources = packageContext.getResources();
} catch (RemoteException | NameNotFoundException e) {
Slog.w(TAG, "Unable to discover metadata package: " + mPackageName, 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 CharSequence moduleName = packageResources.getText(
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);
mi.setApexModuleName(
mApexManager.getApexModuleNameForPackageName(modulePackageName));
mModuleInfo.put(modulePackageName, mi);
}
} catch (XmlPullParserException | IOException e) {
Slog.w(TAG, "Error parsing module metadata", e);
mModuleInfo.clear();
} finally {
parser.close();
mMetadataLoaded = true;
}
}
/**
* By default, returns installed module info, including installed apex modules.
*
* @param flags Use {@link PackageManager#MATCH_ALL} flag to get all modules.
*/
List<ModuleInfo> getInstalledModules(@PackageManager.InstalledModulesFlags int flags) {
if (!mMetadataLoaded) {
throw new IllegalStateException("Call to getInstalledModules before metadata loaded");
}
if ((flags & PackageManager.MATCH_ALL) != 0) {
return new ArrayList<>(mModuleInfo.values());
}
List<PackageInfo> allPackages;
try {
allPackages = mPackageManager.getInstalledPackages(
flags | PackageManager.MATCH_APEX, UserHandle.USER_SYSTEM).getList();
} catch (RemoteException e) {
Slog.w(TAG, "Unable to retrieve all package names", e);
return Collections.emptyList();
}
ArrayList<ModuleInfo> installedModules = new ArrayList<>(allPackages.size());
for (PackageInfo p : allPackages) {
ModuleInfo m = mModuleInfo.get(p.packageName);
if (m != null) {
installedModules.add(m);
}
}
return installedModules;
}
ModuleInfo getModuleInfo(String name, @PackageManager.ModuleInfoFlags int flags) {
if (!mMetadataLoaded) {
throw new IllegalStateException("Call to getModuleInfo before metadata loaded");
}
if ((flags & PackageManager.MODULE_APEX_NAME) != 0) {
for (ModuleInfo moduleInfo : mModuleInfo.values()) {
if (name.equals(moduleInfo.getApexModuleName())) {
return moduleInfo;
}
}
return null;
}
return mModuleInfo.get(name);
}
String getPackageName() {
if (!mMetadataLoaded) {
throw new IllegalStateException("Call to getVersion before metadata loaded");
}
return mPackageName;
}
}