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