blob: ab3b6d5fef384f02ed56de60eb8f8f3b4f4ca183 [file] [log] [blame]
Grace Kloba658ab7d2009-05-14 14:45:26 -07001/*
2 * Copyright (C) 2009 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
17package android.webkit;
18
19import java.util.ArrayList;
20import java.util.List;
21
22import android.annotation.SdkConstant;
23import android.annotation.SdkConstant.SdkConstantType;
24import android.content.Context;
25import android.content.Intent;
Derek Sollenbergerde2c49e2010-08-26 14:48:12 -040026import android.content.pm.ApplicationInfo;
Grace Kloba658ab7d2009-05-14 14:45:26 -070027import android.content.pm.PackageInfo;
28import android.content.pm.PackageManager;
29import android.content.pm.ResolveInfo;
30import android.content.pm.ServiceInfo;
31import android.content.pm.Signature;
32import android.content.pm.PackageManager.NameNotFoundException;
Grace Kloba2919f492009-09-30 21:48:19 -070033import android.os.SystemProperties;
Grace Kloba658ab7d2009-05-14 14:45:26 -070034import android.util.Log;
35
36/**
37 * Class for managing the relationship between the {@link WebView} and installed
38 * plugins in the system. You can find this class through
39 * {@link PluginManager#getInstance}.
40 *
41 * @hide pending API solidification
42 */
43public class PluginManager {
44
45 /**
46 * Service Action: A plugin wishes to be loaded in the WebView must provide
47 * {@link android.content.IntentFilter IntentFilter} that accepts this
48 * action in their AndroidManifest.xml.
49 * <p>
50 * TODO: we may change this to a new PLUGIN_ACTION if this is going to be
51 * public.
52 */
53 @SdkConstant(SdkConstantType.SERVICE_ACTION)
54 public static final String PLUGIN_ACTION = "android.webkit.PLUGIN";
55
56 /**
57 * A plugin wishes to be loaded in the WebView must provide this permission
58 * in their AndroidManifest.xml.
59 */
60 public static final String PLUGIN_PERMISSION = "android.webkit.permission.PLUGIN";
61
Derek Sollenbergerde2c49e2010-08-26 14:48:12 -040062 private static final String LOGTAG = "PluginManager";
63
64 private static final String PLUGIN_SYSTEM_LIB = "/system/lib/plugins/";
Grace Kloba658ab7d2009-05-14 14:45:26 -070065
Derek Sollenbergercb37e712009-11-24 14:58:09 -050066 private static final String PLUGIN_TYPE = "type";
67 private static final String TYPE_NATIVE = "native";
68
Grace Kloba658ab7d2009-05-14 14:45:26 -070069 private static PluginManager mInstance = null;
70
71 private final Context mContext;
72
Derek Sollenberger7c5bf462010-01-11 12:50:51 -050073 private ArrayList<PackageInfo> mPackageInfoCache;
Derek Sollenbergerc0b8a962009-09-22 14:08:09 -040074
Grace Kloba73477e72009-09-30 17:45:46 -070075 // Only plugin matches one of the signatures in the list can be loaded
76 // inside the WebView process
Grace Kloba2919f492009-09-30 21:48:19 -070077 private static final String SIGNATURE_1 = "308204c5308203ada003020102020900d7cb412f75f4887e300d06092a864886f70d010105050030819d310b3009060355040613025553311330110603550408130a43616c69666f726e69613111300f0603550407130853616e204a6f736531233021060355040a131a41646f62652053797374656d7320496e636f72706f7261746564311c301a060355040b1313496e666f726d6174696f6e2053797374656d73312330210603550403131a41646f62652053797374656d7320496e636f72706f7261746564301e170d3039313030313030323331345a170d3337303231363030323331345a30819d310b3009060355040613025553311330110603550408130a43616c69666f726e69613111300f0603550407130853616e204a6f736531233021060355040a131a41646f62652053797374656d7320496e636f72706f7261746564311c301a060355040b1313496e666f726d6174696f6e2053797374656d73312330210603550403131a41646f62652053797374656d7320496e636f72706f726174656430820120300d06092a864886f70d01010105000382010d0030820108028201010099724f3e05bbd78843794f357776e04b340e13cb1c9ccb3044865180d7d8fec8166c5bbd876da8b80aa71eb6ba3d4d3455c9a8de162d24a25c4c1cd04c9523affd06a279fc8f0d018f242486bdbb2dbfbf6fcb21ed567879091928b876f7ccebc7bccef157366ebe74e33ae1d7e9373091adab8327482154afc0693a549522f8c796dd84d16e24bb221f5dbb809ca56dd2b6e799c5fa06b6d9c5c09ada54ea4c5db1523a9794ed22a3889e5e05b29f8ee0a8d61efe07ae28f65dece2ff7edc5b1416d7c7aad7f0d35e8f4a4b964dbf50ae9aa6d620157770d974131b3e7e3abd6d163d65758e2f0822db9c88598b9db6263d963d13942c91fc5efe34fc1e06e3020103a382010630820102301d0603551d0e041604145af418e419a639e1657db960996364a37ef20d403081d20603551d230481ca3081c780145af418e419a639e1657db960996364a37ef20d40a181a3a481a030819d310b3009060355040613025553311330110603550408130a43616c69666f726e69613111300f0603550407130853616e204a6f736531233021060355040a131a41646f62652053797374656d7320496e636f72706f7261746564311c301a060355040b1313496e666f726d6174696f6e2053797374656d73312330210603550403131a41646f62652053797374656d7320496e636f72706f7261746564820900d7cb412f75f4887e300c0603551d13040530030101ff300d06092a864886f70d0101050500038201010076c2a11fe303359689c2ebc7b2c398eff8c3f9ad545cdbac75df63bf7b5395b6988d1842d6aa1556d595b5692e08224d667a4c9c438f05e74906c53dd8016dde7004068866f01846365efd146e9bfaa48c9ecf657f87b97c757da11f225c4a24177bf2d7188e6cce2a70a1e8a841a14471eb51457398b8a0addd8b6c8c1538ca8f1e40b4d8b960009ea22c188d28924813d2c0b4a4d334b7cf05507e1fcf0a06fe946c7ffc435e173af6fc3e3400643710acc806f830a14788291d46f2feed9fb5c70423ca747ed1572d752894ac1f19f93989766308579393fabb43649aa8806a313b1ab9a50922a44c2467b9062037f2da0d484d9ffd8fe628eeea629ba637";
Grace Kloba73477e72009-09-30 17:45:46 -070078
79 private static final Signature[] SIGNATURES = new Signature[] {
Grace Kloba2919f492009-09-30 21:48:19 -070080 new Signature(SIGNATURE_1)
Grace Kloba73477e72009-09-30 17:45:46 -070081 };
82
Grace Kloba658ab7d2009-05-14 14:45:26 -070083 private PluginManager(Context context) {
84 mContext = context;
Derek Sollenberger7c5bf462010-01-11 12:50:51 -050085 mPackageInfoCache = new ArrayList<PackageInfo>();
Grace Kloba658ab7d2009-05-14 14:45:26 -070086 }
87
88 public static synchronized PluginManager getInstance(Context context) {
89 if (mInstance == null) {
90 if (context == null) {
91 throw new IllegalStateException(
92 "First call to PluginManager need a valid context.");
93 }
Romain Guy01d0fbf2009-12-01 14:52:19 -080094 mInstance = new PluginManager(context.getApplicationContext());
Grace Kloba658ab7d2009-05-14 14:45:26 -070095 }
96 return mInstance;
97 }
98
99 /**
100 * Signal the WebCore thread to refresh its list of plugins. Use this if the
101 * directory contents of one of the plugin directories has been modified and
102 * needs its changes reflecting. May cause plugin load and/or unload.
103 *
104 * @param reloadOpenPages Set to true to reload all open pages.
105 */
106 public void refreshPlugins(boolean reloadOpenPages) {
107 BrowserFrame.sJavaBridge.obtainMessage(
108 JWebCoreJavaBridge.REFRESH_PLUGINS, reloadOpenPages)
109 .sendToTarget();
110 }
111
Derek Sollenberger37eb3c352009-08-18 13:59:48 -0400112 String[] getPluginDirectories() {
Derek Sollenbergerc0b8a962009-09-22 14:08:09 -0400113
Grace Kloba658ab7d2009-05-14 14:45:26 -0700114 ArrayList<String> directories = new ArrayList<String>();
115 PackageManager pm = mContext.getPackageManager();
Derek Sollenbergerde2c49e2010-08-26 14:48:12 -0400116 List<ResolveInfo> plugins = pm.queryIntentServices(new Intent(PLUGIN_ACTION),
117 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
Derek Sollenbergerc0b8a962009-09-22 14:08:09 -0400118
Derek Sollenberger7c5bf462010-01-11 12:50:51 -0500119 synchronized(mPackageInfoCache) {
Derek Sollenbergerc0b8a962009-09-22 14:08:09 -0400120
121 // clear the list of existing packageInfo objects
Derek Sollenberger7c5bf462010-01-11 12:50:51 -0500122 mPackageInfoCache.clear();
Derek Sollenbergerc0b8a962009-09-22 14:08:09 -0400123
124 for (ResolveInfo info : plugins) {
Derek Sollenbergercb37e712009-11-24 14:58:09 -0500125
126 // retrieve the plugin's service information
Derek Sollenbergerc0b8a962009-09-22 14:08:09 -0400127 ServiceInfo serviceInfo = info.serviceInfo;
128 if (serviceInfo == null) {
129 Log.w(LOGTAG, "Ignore bad plugin");
130 continue;
131 }
Derek Sollenbergercb37e712009-11-24 14:58:09 -0500132
133 // retrieve information from the plugin's manifest
Derek Sollenbergerc0b8a962009-09-22 14:08:09 -0400134 PackageInfo pkgInfo;
135 try {
136 pkgInfo = pm.getPackageInfo(serviceInfo.packageName,
137 PackageManager.GET_PERMISSIONS
138 | PackageManager.GET_SIGNATURES);
139 } catch (NameNotFoundException e) {
Derek Sollenbergercb37e712009-11-24 14:58:09 -0500140 Log.w(LOGTAG, "Can't find plugin: " + serviceInfo.packageName);
Derek Sollenbergerc0b8a962009-09-22 14:08:09 -0400141 continue;
142 }
143 if (pkgInfo == null) {
144 continue;
145 }
Derek Sollenbergercb37e712009-11-24 14:58:09 -0500146
Derek Sollenbergerde2c49e2010-08-26 14:48:12 -0400147 /*
148 * find the location of the plugin's shared library. The default
149 * is to assume the app is either a user installed app or an
150 * updated system app. In both of these cases the library is
151 * stored in the app's data directory.
152 */
Derek Sollenbergerc0b8a962009-09-22 14:08:09 -0400153 String directory = pkgInfo.applicationInfo.dataDir + "/lib";
Derek Sollenbergerde2c49e2010-08-26 14:48:12 -0400154 final int appFlags = pkgInfo.applicationInfo.flags;
155 final int updatedSystemFlags = ApplicationInfo.FLAG_SYSTEM |
156 ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
157 // preloaded system app with no user updates
158 if ((appFlags & updatedSystemFlags) == ApplicationInfo.FLAG_SYSTEM) {
159 directory = PLUGIN_SYSTEM_LIB + pkgInfo.packageName;
Derek Sollenbergerc0b8a962009-09-22 14:08:09 -0400160 }
Derek Sollenbergercb37e712009-11-24 14:58:09 -0500161
Derek Sollenberger41d5e932010-08-23 14:51:41 -0400162 // check if the plugin has the required permissions and
163 // signatures
164 if (!containsPluginPermissionAndSignatures(pkgInfo)) {
Derek Sollenbergerc0b8a962009-09-22 14:08:09 -0400165 continue;
166 }
Grace Kloba26f1faa2010-03-25 10:25:59 -0700167
Derek Sollenbergercb37e712009-11-24 14:58:09 -0500168 // determine the type of plugin from the manifest
169 if (serviceInfo.metaData == null) {
170 Log.e(LOGTAG, "The plugin '" + serviceInfo.name + "' has no type defined");
171 continue;
172 }
173
174 String pluginType = serviceInfo.metaData.getString(PLUGIN_TYPE);
Derek Sollenberger7c5bf462010-01-11 12:50:51 -0500175 if (!TYPE_NATIVE.equals(pluginType)) {
Derek Sollenbergercb37e712009-11-24 14:58:09 -0500176 Log.e(LOGTAG, "Unrecognized plugin type: " + pluginType);
177 continue;
178 }
179
180 try {
181 Class<?> cls = getPluginClass(serviceInfo.packageName, serviceInfo.name);
182
Derek Sollenberger7c5bf462010-01-11 12:50:51 -0500183 //TODO implement any requirements of the plugin class here!
184 boolean classFound = true;
Derek Sollenbergercb37e712009-11-24 14:58:09 -0500185
186 if (!classFound) {
Derek Sollenberger7c5bf462010-01-11 12:50:51 -0500187 Log.e(LOGTAG, "The plugin's class' " + serviceInfo.name + "' does not extend the appropriate class.");
Derek Sollenbergercb37e712009-11-24 14:58:09 -0500188 continue;
189 }
190
191 } catch (NameNotFoundException e) {
192 Log.e(LOGTAG, "Can't find plugin: " + serviceInfo.packageName);
193 continue;
194 } catch (ClassNotFoundException e) {
195 Log.e(LOGTAG, "Can't find plugin's class: " + serviceInfo.name);
196 continue;
197 }
198
199 // if all checks have passed then make the plugin available
Derek Sollenberger7c5bf462010-01-11 12:50:51 -0500200 mPackageInfoCache.add(pkgInfo);
Derek Sollenbergerc0b8a962009-09-22 14:08:09 -0400201 directories.add(directory);
Grace Kloba658ab7d2009-05-14 14:45:26 -0700202 }
Grace Kloba658ab7d2009-05-14 14:45:26 -0700203 }
Derek Sollenberger37eb3c352009-08-18 13:59:48 -0400204
Grace Kloba658ab7d2009-05-14 14:45:26 -0700205 return directories.toArray(new String[directories.size()]);
206 }
Grace Klobafd055b22009-08-04 17:52:05 -0700207
Derek Sollenbergercb37e712009-11-24 14:58:09 -0500208 /* package */
Derek Sollenberger41d5e932010-08-23 14:51:41 -0400209 boolean containsPluginPermissionAndSignatures(String pluginAPKName) {
210 PackageManager pm = mContext.getPackageManager();
211
212 // retrieve information from the plugin's manifest
213 try {
214 PackageInfo pkgInfo = pm.getPackageInfo(pluginAPKName, PackageManager.GET_PERMISSIONS
215 | PackageManager.GET_SIGNATURES);
216 if (pkgInfo != null) {
217 return containsPluginPermissionAndSignatures(pkgInfo);
218 }
219 } catch (NameNotFoundException e) {
220 Log.w(LOGTAG, "Can't find plugin: " + pluginAPKName);
221 }
222 return false;
223 }
224
225 private static boolean containsPluginPermissionAndSignatures(PackageInfo pkgInfo) {
226
227 // check if the plugin has the required permissions
228 String permissions[] = pkgInfo.requestedPermissions;
229 if (permissions == null) {
230 return false;
231 }
232 boolean permissionOk = false;
233 for (String permit : permissions) {
234 if (PLUGIN_PERMISSION.equals(permit)) {
235 permissionOk = true;
236 break;
237 }
238 }
239 if (!permissionOk) {
240 return false;
241 }
242
243 // check to ensure the plugin is properly signed
244 Signature signatures[] = pkgInfo.signatures;
245 if (signatures == null) {
246 return false;
247 }
248 if (SystemProperties.getBoolean("ro.secure", false)) {
249 boolean signatureMatch = false;
250 for (Signature signature : signatures) {
251 for (int i = 0; i < SIGNATURES.length; i++) {
252 if (SIGNATURES[i].equals(signature)) {
253 signatureMatch = true;
254 break;
255 }
256 }
257 }
258 if (!signatureMatch) {
259 return false;
260 }
261 }
262
263 return true;
264 }
265
266 /* package */
Derek Sollenbergerc0b8a962009-09-22 14:08:09 -0400267 String getPluginsAPKName(String pluginLib) {
268
269 // basic error checking on input params
270 if (pluginLib == null || pluginLib.length() == 0) {
271 return null;
272 }
273
274 // must be synchronized to ensure the consistency of the cache
Derek Sollenberger7c5bf462010-01-11 12:50:51 -0500275 synchronized(mPackageInfoCache) {
276 for (PackageInfo pkgInfo : mPackageInfoCache) {
Derek Sollenbergerde2c49e2010-08-26 14:48:12 -0400277 if (pluginLib.contains(pkgInfo.packageName)) {
Derek Sollenbergerc0b8a962009-09-22 14:08:09 -0400278 return pkgInfo.packageName;
279 }
280 }
281 }
282
283 // if no apk was found then return null
284 return null;
285 }
286
Grace Klobafd055b22009-08-04 17:52:05 -0700287 String getPluginSharedDataDirectory() {
288 return mContext.getDir("plugins", 0).getPath();
289 }
Derek Sollenbergercb37e712009-11-24 14:58:09 -0500290
291 /* package */
Derek Sollenbergercb37e712009-11-24 14:58:09 -0500292 Class<?> getPluginClass(String packageName, String className)
293 throws NameNotFoundException, ClassNotFoundException {
294 Context pluginContext = mContext.createPackageContext(packageName,
295 Context.CONTEXT_INCLUDE_CODE |
296 Context.CONTEXT_IGNORE_SECURITY);
297 ClassLoader pluginCL = pluginContext.getClassLoader();
298 return pluginCL.loadClass(className);
299 }
Grace Kloba658ab7d2009-05-14 14:45:26 -0700300}