Grace Kloba | 658ab7d | 2009-05-14 14:45:26 -0700 | [diff] [blame] | 1 | /* |
| 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 | |
| 17 | package android.webkit; |
| 18 | |
| 19 | import java.util.ArrayList; |
| 20 | import java.util.List; |
| 21 | |
| 22 | import android.annotation.SdkConstant; |
| 23 | import android.annotation.SdkConstant.SdkConstantType; |
| 24 | import android.content.Context; |
| 25 | import android.content.Intent; |
Derek Sollenberger | de2c49e | 2010-08-26 14:48:12 -0400 | [diff] [blame] | 26 | import android.content.pm.ApplicationInfo; |
Grace Kloba | 658ab7d | 2009-05-14 14:45:26 -0700 | [diff] [blame] | 27 | import android.content.pm.PackageInfo; |
| 28 | import android.content.pm.PackageManager; |
| 29 | import android.content.pm.ResolveInfo; |
| 30 | import android.content.pm.ServiceInfo; |
| 31 | import android.content.pm.Signature; |
| 32 | import android.content.pm.PackageManager.NameNotFoundException; |
Grace Kloba | 2919f49 | 2009-09-30 21:48:19 -0700 | [diff] [blame] | 33 | import android.os.SystemProperties; |
Grace Kloba | 658ab7d | 2009-05-14 14:45:26 -0700 | [diff] [blame] | 34 | import 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 | */ |
| 43 | public 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 Sollenberger | de2c49e | 2010-08-26 14:48:12 -0400 | [diff] [blame] | 62 | private static final String LOGTAG = "PluginManager"; |
| 63 | |
| 64 | private static final String PLUGIN_SYSTEM_LIB = "/system/lib/plugins/"; |
Grace Kloba | 658ab7d | 2009-05-14 14:45:26 -0700 | [diff] [blame] | 65 | |
Derek Sollenberger | cb37e71 | 2009-11-24 14:58:09 -0500 | [diff] [blame] | 66 | private static final String PLUGIN_TYPE = "type"; |
| 67 | private static final String TYPE_NATIVE = "native"; |
| 68 | |
Grace Kloba | 658ab7d | 2009-05-14 14:45:26 -0700 | [diff] [blame] | 69 | private static PluginManager mInstance = null; |
| 70 | |
| 71 | private final Context mContext; |
| 72 | |
Derek Sollenberger | 7c5bf46 | 2010-01-11 12:50:51 -0500 | [diff] [blame] | 73 | private ArrayList<PackageInfo> mPackageInfoCache; |
Derek Sollenberger | c0b8a96 | 2009-09-22 14:08:09 -0400 | [diff] [blame] | 74 | |
Grace Kloba | 73477e7 | 2009-09-30 17:45:46 -0700 | [diff] [blame] | 75 | // Only plugin matches one of the signatures in the list can be loaded |
| 76 | // inside the WebView process |
Grace Kloba | 2919f49 | 2009-09-30 21:48:19 -0700 | [diff] [blame] | 77 | private static final String SIGNATURE_1 = "308204c5308203ada003020102020900d7cb412f75f4887e300d06092a864886f70d010105050030819d310b3009060355040613025553311330110603550408130a43616c69666f726e69613111300f0603550407130853616e204a6f736531233021060355040a131a41646f62652053797374656d7320496e636f72706f7261746564311c301a060355040b1313496e666f726d6174696f6e2053797374656d73312330210603550403131a41646f62652053797374656d7320496e636f72706f7261746564301e170d3039313030313030323331345a170d3337303231363030323331345a30819d310b3009060355040613025553311330110603550408130a43616c69666f726e69613111300f0603550407130853616e204a6f736531233021060355040a131a41646f62652053797374656d7320496e636f72706f7261746564311c301a060355040b1313496e666f726d6174696f6e2053797374656d73312330210603550403131a41646f62652053797374656d7320496e636f72706f726174656430820120300d06092a864886f70d01010105000382010d0030820108028201010099724f3e05bbd78843794f357776e04b340e13cb1c9ccb3044865180d7d8fec8166c5bbd876da8b80aa71eb6ba3d4d3455c9a8de162d24a25c4c1cd04c9523affd06a279fc8f0d018f242486bdbb2dbfbf6fcb21ed567879091928b876f7ccebc7bccef157366ebe74e33ae1d7e9373091adab8327482154afc0693a549522f8c796dd84d16e24bb221f5dbb809ca56dd2b6e799c5fa06b6d9c5c09ada54ea4c5db1523a9794ed22a3889e5e05b29f8ee0a8d61efe07ae28f65dece2ff7edc5b1416d7c7aad7f0d35e8f4a4b964dbf50ae9aa6d620157770d974131b3e7e3abd6d163d65758e2f0822db9c88598b9db6263d963d13942c91fc5efe34fc1e06e3020103a382010630820102301d0603551d0e041604145af418e419a639e1657db960996364a37ef20d403081d20603551d230481ca3081c780145af418e419a639e1657db960996364a37ef20d40a181a3a481a030819d310b3009060355040613025553311330110603550408130a43616c69666f726e69613111300f0603550407130853616e204a6f736531233021060355040a131a41646f62652053797374656d7320496e636f72706f7261746564311c301a060355040b1313496e666f726d6174696f6e2053797374656d73312330210603550403131a41646f62652053797374656d7320496e636f72706f7261746564820900d7cb412f75f4887e300c0603551d13040530030101ff300d06092a864886f70d0101050500038201010076c2a11fe303359689c2ebc7b2c398eff8c3f9ad545cdbac75df63bf7b5395b6988d1842d6aa1556d595b5692e08224d667a4c9c438f05e74906c53dd8016dde7004068866f01846365efd146e9bfaa48c9ecf657f87b97c757da11f225c4a24177bf2d7188e6cce2a70a1e8a841a14471eb51457398b8a0addd8b6c8c1538ca8f1e40b4d8b960009ea22c188d28924813d2c0b4a4d334b7cf05507e1fcf0a06fe946c7ffc435e173af6fc3e3400643710acc806f830a14788291d46f2feed9fb5c70423ca747ed1572d752894ac1f19f93989766308579393fabb43649aa8806a313b1ab9a50922a44c2467b9062037f2da0d484d9ffd8fe628eeea629ba637"; |
Grace Kloba | 73477e7 | 2009-09-30 17:45:46 -0700 | [diff] [blame] | 78 | |
| 79 | private static final Signature[] SIGNATURES = new Signature[] { |
Grace Kloba | 2919f49 | 2009-09-30 21:48:19 -0700 | [diff] [blame] | 80 | new Signature(SIGNATURE_1) |
Grace Kloba | 73477e7 | 2009-09-30 17:45:46 -0700 | [diff] [blame] | 81 | }; |
| 82 | |
Grace Kloba | 658ab7d | 2009-05-14 14:45:26 -0700 | [diff] [blame] | 83 | private PluginManager(Context context) { |
| 84 | mContext = context; |
Derek Sollenberger | 7c5bf46 | 2010-01-11 12:50:51 -0500 | [diff] [blame] | 85 | mPackageInfoCache = new ArrayList<PackageInfo>(); |
Grace Kloba | 658ab7d | 2009-05-14 14:45:26 -0700 | [diff] [blame] | 86 | } |
| 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 Guy | 01d0fbf | 2009-12-01 14:52:19 -0800 | [diff] [blame] | 94 | mInstance = new PluginManager(context.getApplicationContext()); |
Grace Kloba | 658ab7d | 2009-05-14 14:45:26 -0700 | [diff] [blame] | 95 | } |
| 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 Sollenberger | 37eb3c35 | 2009-08-18 13:59:48 -0400 | [diff] [blame] | 112 | String[] getPluginDirectories() { |
Derek Sollenberger | c0b8a96 | 2009-09-22 14:08:09 -0400 | [diff] [blame] | 113 | |
Grace Kloba | 658ab7d | 2009-05-14 14:45:26 -0700 | [diff] [blame] | 114 | ArrayList<String> directories = new ArrayList<String>(); |
| 115 | PackageManager pm = mContext.getPackageManager(); |
Derek Sollenberger | de2c49e | 2010-08-26 14:48:12 -0400 | [diff] [blame] | 116 | List<ResolveInfo> plugins = pm.queryIntentServices(new Intent(PLUGIN_ACTION), |
| 117 | PackageManager.GET_SERVICES | PackageManager.GET_META_DATA); |
Derek Sollenberger | c0b8a96 | 2009-09-22 14:08:09 -0400 | [diff] [blame] | 118 | |
Derek Sollenberger | 7c5bf46 | 2010-01-11 12:50:51 -0500 | [diff] [blame] | 119 | synchronized(mPackageInfoCache) { |
Derek Sollenberger | c0b8a96 | 2009-09-22 14:08:09 -0400 | [diff] [blame] | 120 | |
| 121 | // clear the list of existing packageInfo objects |
Derek Sollenberger | 7c5bf46 | 2010-01-11 12:50:51 -0500 | [diff] [blame] | 122 | mPackageInfoCache.clear(); |
Derek Sollenberger | c0b8a96 | 2009-09-22 14:08:09 -0400 | [diff] [blame] | 123 | |
| 124 | for (ResolveInfo info : plugins) { |
Derek Sollenberger | cb37e71 | 2009-11-24 14:58:09 -0500 | [diff] [blame] | 125 | |
| 126 | // retrieve the plugin's service information |
Derek Sollenberger | c0b8a96 | 2009-09-22 14:08:09 -0400 | [diff] [blame] | 127 | ServiceInfo serviceInfo = info.serviceInfo; |
| 128 | if (serviceInfo == null) { |
| 129 | Log.w(LOGTAG, "Ignore bad plugin"); |
| 130 | continue; |
| 131 | } |
Derek Sollenberger | cb37e71 | 2009-11-24 14:58:09 -0500 | [diff] [blame] | 132 | |
| 133 | // retrieve information from the plugin's manifest |
Derek Sollenberger | c0b8a96 | 2009-09-22 14:08:09 -0400 | [diff] [blame] | 134 | PackageInfo pkgInfo; |
| 135 | try { |
| 136 | pkgInfo = pm.getPackageInfo(serviceInfo.packageName, |
| 137 | PackageManager.GET_PERMISSIONS |
| 138 | | PackageManager.GET_SIGNATURES); |
| 139 | } catch (NameNotFoundException e) { |
Derek Sollenberger | cb37e71 | 2009-11-24 14:58:09 -0500 | [diff] [blame] | 140 | Log.w(LOGTAG, "Can't find plugin: " + serviceInfo.packageName); |
Derek Sollenberger | c0b8a96 | 2009-09-22 14:08:09 -0400 | [diff] [blame] | 141 | continue; |
| 142 | } |
| 143 | if (pkgInfo == null) { |
| 144 | continue; |
| 145 | } |
Derek Sollenberger | cb37e71 | 2009-11-24 14:58:09 -0500 | [diff] [blame] | 146 | |
Derek Sollenberger | de2c49e | 2010-08-26 14:48:12 -0400 | [diff] [blame] | 147 | /* |
| 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 Sollenberger | c0b8a96 | 2009-09-22 14:08:09 -0400 | [diff] [blame] | 153 | String directory = pkgInfo.applicationInfo.dataDir + "/lib"; |
Derek Sollenberger | de2c49e | 2010-08-26 14:48:12 -0400 | [diff] [blame] | 154 | 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 Sollenberger | c0b8a96 | 2009-09-22 14:08:09 -0400 | [diff] [blame] | 160 | } |
Derek Sollenberger | cb37e71 | 2009-11-24 14:58:09 -0500 | [diff] [blame] | 161 | |
Derek Sollenberger | 41d5e93 | 2010-08-23 14:51:41 -0400 | [diff] [blame] | 162 | // check if the plugin has the required permissions and |
| 163 | // signatures |
| 164 | if (!containsPluginPermissionAndSignatures(pkgInfo)) { |
Derek Sollenberger | c0b8a96 | 2009-09-22 14:08:09 -0400 | [diff] [blame] | 165 | continue; |
| 166 | } |
Grace Kloba | 26f1faa | 2010-03-25 10:25:59 -0700 | [diff] [blame] | 167 | |
Derek Sollenberger | cb37e71 | 2009-11-24 14:58:09 -0500 | [diff] [blame] | 168 | // 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 Sollenberger | 7c5bf46 | 2010-01-11 12:50:51 -0500 | [diff] [blame] | 175 | if (!TYPE_NATIVE.equals(pluginType)) { |
Derek Sollenberger | cb37e71 | 2009-11-24 14:58:09 -0500 | [diff] [blame] | 176 | Log.e(LOGTAG, "Unrecognized plugin type: " + pluginType); |
| 177 | continue; |
| 178 | } |
| 179 | |
| 180 | try { |
| 181 | Class<?> cls = getPluginClass(serviceInfo.packageName, serviceInfo.name); |
| 182 | |
Derek Sollenberger | 7c5bf46 | 2010-01-11 12:50:51 -0500 | [diff] [blame] | 183 | //TODO implement any requirements of the plugin class here! |
| 184 | boolean classFound = true; |
Derek Sollenberger | cb37e71 | 2009-11-24 14:58:09 -0500 | [diff] [blame] | 185 | |
| 186 | if (!classFound) { |
Derek Sollenberger | 7c5bf46 | 2010-01-11 12:50:51 -0500 | [diff] [blame] | 187 | Log.e(LOGTAG, "The plugin's class' " + serviceInfo.name + "' does not extend the appropriate class."); |
Derek Sollenberger | cb37e71 | 2009-11-24 14:58:09 -0500 | [diff] [blame] | 188 | 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 Sollenberger | 7c5bf46 | 2010-01-11 12:50:51 -0500 | [diff] [blame] | 200 | mPackageInfoCache.add(pkgInfo); |
Derek Sollenberger | c0b8a96 | 2009-09-22 14:08:09 -0400 | [diff] [blame] | 201 | directories.add(directory); |
Grace Kloba | 658ab7d | 2009-05-14 14:45:26 -0700 | [diff] [blame] | 202 | } |
Grace Kloba | 658ab7d | 2009-05-14 14:45:26 -0700 | [diff] [blame] | 203 | } |
Derek Sollenberger | 37eb3c35 | 2009-08-18 13:59:48 -0400 | [diff] [blame] | 204 | |
Grace Kloba | 658ab7d | 2009-05-14 14:45:26 -0700 | [diff] [blame] | 205 | return directories.toArray(new String[directories.size()]); |
| 206 | } |
Grace Kloba | fd055b2 | 2009-08-04 17:52:05 -0700 | [diff] [blame] | 207 | |
Derek Sollenberger | cb37e71 | 2009-11-24 14:58:09 -0500 | [diff] [blame] | 208 | /* package */ |
Derek Sollenberger | 41d5e93 | 2010-08-23 14:51:41 -0400 | [diff] [blame] | 209 | 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 Sollenberger | c0b8a96 | 2009-09-22 14:08:09 -0400 | [diff] [blame] | 267 | 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 Sollenberger | 7c5bf46 | 2010-01-11 12:50:51 -0500 | [diff] [blame] | 275 | synchronized(mPackageInfoCache) { |
| 276 | for (PackageInfo pkgInfo : mPackageInfoCache) { |
Derek Sollenberger | de2c49e | 2010-08-26 14:48:12 -0400 | [diff] [blame] | 277 | if (pluginLib.contains(pkgInfo.packageName)) { |
Derek Sollenberger | c0b8a96 | 2009-09-22 14:08:09 -0400 | [diff] [blame] | 278 | return pkgInfo.packageName; |
| 279 | } |
| 280 | } |
| 281 | } |
| 282 | |
| 283 | // if no apk was found then return null |
| 284 | return null; |
| 285 | } |
| 286 | |
Grace Kloba | fd055b2 | 2009-08-04 17:52:05 -0700 | [diff] [blame] | 287 | String getPluginSharedDataDirectory() { |
| 288 | return mContext.getDir("plugins", 0).getPath(); |
| 289 | } |
Derek Sollenberger | cb37e71 | 2009-11-24 14:58:09 -0500 | [diff] [blame] | 290 | |
| 291 | /* package */ |
Derek Sollenberger | cb37e71 | 2009-11-24 14:58:09 -0500 | [diff] [blame] | 292 | 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 Kloba | 658ab7d | 2009-05-14 14:45:26 -0700 | [diff] [blame] | 300 | } |