1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.pm; 18 19 import android.content.Context; 20 import android.content.pm.IPackageManager; 21 import android.content.pm.ModuleInfo; 22 import android.content.pm.PackageInfo; 23 import android.content.pm.PackageManager; 24 import android.content.pm.PackageManager.NameNotFoundException; 25 import android.content.res.Resources; 26 import android.content.res.XmlResourceParser; 27 import android.os.RemoteException; 28 import android.os.UserHandle; 29 import android.text.TextUtils; 30 import android.util.ArrayMap; 31 import android.util.Slog; 32 33 import com.android.internal.R; 34 import com.android.internal.annotations.VisibleForTesting; 35 import com.android.internal.util.XmlUtils; 36 37 import org.xmlpull.v1.XmlPullParser; 38 import org.xmlpull.v1.XmlPullParserException; 39 40 import java.io.IOException; 41 import java.util.ArrayList; 42 import java.util.Collections; 43 import java.util.List; 44 import java.util.Map; 45 46 /** 47 * Provides data to back {@code ModuleInfo} related APIs in the package manager. The data is stored 48 * as an XML resource in a configurable "module metadata" package. 49 */ 50 @VisibleForTesting 51 public class ModuleInfoProvider { 52 private static final String TAG = "PackageManager.ModuleInfoProvider"; 53 54 /** 55 * The key in the package's application level metadata bundle that provides a resource reference 56 * to the module metadata. 57 */ 58 private static final String MODULE_METADATA_KEY = "android.content.pm.MODULE_METADATA"; 59 60 61 private final Context mContext; 62 private final IPackageManager mPackageManager; 63 private final Map<String, ModuleInfo> mModuleInfo; 64 65 // TODO: Move this to an earlier boot phase if anybody requires it then. 66 private volatile boolean mMetadataLoaded; 67 private volatile String mPackageName; 68 ModuleInfoProvider(Context context, IPackageManager packageManager)69 ModuleInfoProvider(Context context, IPackageManager packageManager) { 70 mContext = context; 71 mPackageManager = packageManager; 72 mModuleInfo = new ArrayMap<>(); 73 } 74 75 @VisibleForTesting ModuleInfoProvider(XmlResourceParser metadata, Resources resources)76 public ModuleInfoProvider(XmlResourceParser metadata, Resources resources) { 77 mContext = null; 78 mPackageManager = null; 79 mModuleInfo = new ArrayMap<>(); 80 loadModuleMetadata(metadata, resources); 81 } 82 83 /** Called by the {@code PackageManager} when it has completed its boot sequence */ systemReady()84 public void systemReady() { 85 mPackageName = mContext.getResources().getString( 86 R.string.config_defaultModuleMetadataProvider); 87 if (TextUtils.isEmpty(mPackageName)) { 88 Slog.w(TAG, "No configured module metadata provider."); 89 return; 90 } 91 92 final Resources packageResources; 93 final PackageInfo pi; 94 try { 95 pi = mPackageManager.getPackageInfo(mPackageName, 96 PackageManager.GET_META_DATA, UserHandle.USER_SYSTEM); 97 98 Context packageContext = mContext.createPackageContext(mPackageName, 0); 99 packageResources = packageContext.getResources(); 100 } catch (RemoteException | NameNotFoundException e) { 101 Slog.w(TAG, "Unable to discover metadata package: " + mPackageName, e); 102 return; 103 } 104 105 XmlResourceParser parser = packageResources.getXml( 106 pi.applicationInfo.metaData.getInt(MODULE_METADATA_KEY)); 107 loadModuleMetadata(parser, packageResources); 108 } 109 loadModuleMetadata(XmlResourceParser parser, Resources packageResources)110 private void loadModuleMetadata(XmlResourceParser parser, Resources packageResources) { 111 try { 112 // The format for the module metadata is straightforward : 113 // 114 // The following attributes on <module> are currently defined : 115 // -- name : A resource reference to a User visible package name, maps to 116 // ModuleInfo#getName 117 // -- packageName : The package name of the module, see ModuleInfo#getPackageName 118 // -- isHidden : Whether the module is hidden, see ModuleInfo#isHidden 119 // 120 // <module-metadata> 121 // <module name="@string/resource" packageName="package_name" isHidden="false|true" /> 122 // <module .... /> 123 // </module-metadata> 124 125 XmlUtils.beginDocument(parser, "module-metadata"); 126 while (true) { 127 XmlUtils.nextElement(parser); 128 if (parser.getEventType() == XmlPullParser.END_DOCUMENT) { 129 break; 130 } 131 132 if (!"module".equals(parser.getName())) { 133 Slog.w(TAG, "Unexpected metadata element: " + parser.getName()); 134 mModuleInfo.clear(); 135 break; 136 } 137 138 // TODO: The module name here is fetched using the resource configuration applied 139 // at the time of parsing this information. This is probably not the best approach 140 // to dealing with this as we'll now have to listen to all config changes and 141 // regenerate the data if required. Also, is this the right way to parse a resource 142 // reference out of an XML file ? 143 final CharSequence moduleName = packageResources.getText( 144 Integer.parseInt(parser.getAttributeValue(null, "name").substring(1))); 145 final String modulePackageName = XmlUtils.readStringAttribute(parser, 146 "packageName"); 147 final boolean isHidden = XmlUtils.readBooleanAttribute(parser, "isHidden"); 148 149 ModuleInfo mi = new ModuleInfo(); 150 mi.setHidden(isHidden); 151 mi.setPackageName(modulePackageName); 152 mi.setName(moduleName); 153 154 mModuleInfo.put(modulePackageName, mi); 155 } 156 } catch (XmlPullParserException | IOException e) { 157 Slog.w(TAG, "Error parsing module metadata", e); 158 mModuleInfo.clear(); 159 } finally { 160 parser.close(); 161 mMetadataLoaded = true; 162 } 163 } 164 165 /** 166 * By default, returns installed module info, including installed apex modules. 167 * 168 * @param flags Use {@link PackageManager#MATCH_ALL} flag to get all modules. 169 */ getInstalledModules(@ackageManager.ModuleInfoFlags int flags)170 List<ModuleInfo> getInstalledModules(@PackageManager.ModuleInfoFlags int flags) { 171 if (!mMetadataLoaded) { 172 throw new IllegalStateException("Call to getInstalledModules before metadata loaded"); 173 } 174 175 if ((flags & PackageManager.MATCH_ALL) != 0) { 176 return new ArrayList<>(mModuleInfo.values()); 177 } 178 179 List<PackageInfo> allPackages; 180 try { 181 allPackages = mPackageManager.getInstalledPackages( 182 flags | PackageManager.MATCH_APEX, UserHandle.USER_SYSTEM).getList(); 183 } catch (RemoteException e) { 184 Slog.w(TAG, "Unable to retrieve all package names", e); 185 return Collections.emptyList(); 186 } 187 188 ArrayList<ModuleInfo> installedModules = new ArrayList<>(allPackages.size()); 189 for (PackageInfo p : allPackages) { 190 ModuleInfo m = mModuleInfo.get(p.packageName); 191 if (m != null) { 192 installedModules.add(m); 193 } 194 } 195 return installedModules; 196 } 197 getModuleInfo(String packageName, int flags)198 ModuleInfo getModuleInfo(String packageName, int flags) { 199 if (!mMetadataLoaded) { 200 throw new IllegalStateException("Call to getModuleInfo before metadata loaded"); 201 } 202 203 return mModuleInfo.get(packageName); 204 } 205 getPackageName()206 String getPackageName() { 207 if (!mMetadataLoaded) { 208 throw new IllegalStateException("Call to getVersion before metadata loaded"); 209 } 210 return mPackageName; 211 } 212 } 213