/*
|
* 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 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;
|
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() {
|
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);
|
|
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.ModuleInfoFlags 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 packageName, int flags) {
|
if (!mMetadataLoaded) {
|
throw new IllegalStateException("Call to getModuleInfo before metadata loaded");
|
}
|
|
return mModuleInfo.get(packageName);
|
}
|
|
String getPackageName() {
|
if (!mMetadataLoaded) {
|
throw new IllegalStateException("Call to getVersion before metadata loaded");
|
}
|
return mPackageName;
|
}
|
}
|