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; 26 import android.content.pm.ApplicationInfo; 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; 33 import android.os.SystemProperties; 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 62 private static final String LOGTAG = "PluginManager"; 63 64 private static final String PLUGIN_SYSTEM_LIB = "/system/lib/plugins/"; 65 66 private static final String PLUGIN_TYPE = "type"; 67 private static final String TYPE_NATIVE = "native"; 68 69 private static PluginManager mInstance = null; 70 71 private final Context mContext; 72 73 private ArrayList<PackageInfo> mPackageInfoCache; 74 75 // Only plugin matches one of the signatures in the list can be loaded 76 // inside the WebView process 77 private static final String SIGNATURE_1 = "308204c5308203ada003020102020900d7cb412f75f4887e300d06092a864886f70d010105050030819d310b3009060355040613025553311330110603550408130a43616c69666f726e69613111300f0603550407130853616e204a6f736531233021060355040a131a41646f62652053797374656d7320496e636f72706f7261746564311c301a060355040b1313496e666f726d6174696f6e2053797374656d73312330210603550403131a41646f62652053797374656d7320496e636f72706f7261746564301e170d3039313030313030323331345a170d3337303231363030323331345a30819d310b3009060355040613025553311330110603550408130a43616c69666f726e69613111300f0603550407130853616e204a6f736531233021060355040a131a41646f62652053797374656d7320496e636f72706f7261746564311c301a060355040b1313496e666f726d6174696f6e2053797374656d73312330210603550403131a41646f62652053797374656d7320496e636f72706f726174656430820120300d06092a864886f70d01010105000382010d0030820108028201010099724f3e05bbd78843794f357776e04b340e13cb1c9ccb3044865180d7d8fec8166c5bbd876da8b80aa71eb6ba3d4d3455c9a8de162d24a25c4c1cd04c9523affd06a279fc8f0d018f242486bdbb2dbfbf6fcb21ed567879091928b876f7ccebc7bccef157366ebe74e33ae1d7e9373091adab8327482154afc0693a549522f8c796dd84d16e24bb221f5dbb809ca56dd2b6e799c5fa06b6d9c5c09ada54ea4c5db1523a9794ed22a3889e5e05b29f8ee0a8d61efe07ae28f65dece2ff7edc5b1416d7c7aad7f0d35e8f4a4b964dbf50ae9aa6d620157770d974131b3e7e3abd6d163d65758e2f0822db9c88598b9db6263d963d13942c91fc5efe34fc1e06e3020103a382010630820102301d0603551d0e041604145af418e419a639e1657db960996364a37ef20d403081d20603551d230481ca3081c780145af418e419a639e1657db960996364a37ef20d40a181a3a481a030819d310b3009060355040613025553311330110603550408130a43616c69666f726e69613111300f0603550407130853616e204a6f736531233021060355040a131a41646f62652053797374656d7320496e636f72706f7261746564311c301a060355040b1313496e666f726d6174696f6e2053797374656d73312330210603550403131a41646f62652053797374656d7320496e636f72706f7261746564820900d7cb412f75f4887e300c0603551d13040530030101ff300d06092a864886f70d0101050500038201010076c2a11fe303359689c2ebc7b2c398eff8c3f9ad545cdbac75df63bf7b5395b6988d1842d6aa1556d595b5692e08224d667a4c9c438f05e74906c53dd8016dde7004068866f01846365efd146e9bfaa48c9ecf657f87b97c757da11f225c4a24177bf2d7188e6cce2a70a1e8a841a14471eb51457398b8a0addd8b6c8c1538ca8f1e40b4d8b960009ea22c188d28924813d2c0b4a4d334b7cf05507e1fcf0a06fe946c7ffc435e173af6fc3e3400643710acc806f830a14788291d46f2feed9fb5c70423ca747ed1572d752894ac1f19f93989766308579393fabb43649aa8806a313b1ab9a50922a44c2467b9062037f2da0d484d9ffd8fe628eeea629ba637"; 78 79 private static final Signature[] SIGNATURES = new Signature[] { 80 new Signature(SIGNATURE_1) 81 }; 82 PluginManager(Context context)83 private PluginManager(Context context) { 84 mContext = context; 85 mPackageInfoCache = new ArrayList<PackageInfo>(); 86 } 87 getInstance(Context context)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 } 94 mInstance = new PluginManager(context.getApplicationContext()); 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 */ refreshPlugins(boolean reloadOpenPages)106 public void refreshPlugins(boolean reloadOpenPages) { 107 BrowserFrame.sJavaBridge.obtainMessage( 108 JWebCoreJavaBridge.REFRESH_PLUGINS, reloadOpenPages) 109 .sendToTarget(); 110 } 111 getPluginDirectories()112 String[] getPluginDirectories() { 113 114 ArrayList<String> directories = new ArrayList<String>(); 115 PackageManager pm = mContext.getPackageManager(); 116 List<ResolveInfo> plugins = pm.queryIntentServices(new Intent(PLUGIN_ACTION), 117 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA); 118 119 synchronized(mPackageInfoCache) { 120 121 // clear the list of existing packageInfo objects 122 mPackageInfoCache.clear(); 123 124 for (ResolveInfo info : plugins) { 125 126 // retrieve the plugin's service information 127 ServiceInfo serviceInfo = info.serviceInfo; 128 if (serviceInfo == null) { 129 Log.w(LOGTAG, "Ignore bad plugin"); 130 continue; 131 } 132 133 // retrieve information from the plugin's manifest 134 PackageInfo pkgInfo; 135 try { 136 pkgInfo = pm.getPackageInfo(serviceInfo.packageName, 137 PackageManager.GET_PERMISSIONS 138 | PackageManager.GET_SIGNATURES); 139 } catch (NameNotFoundException e) { 140 Log.w(LOGTAG, "Can't find plugin: " + serviceInfo.packageName); 141 continue; 142 } 143 if (pkgInfo == null) { 144 continue; 145 } 146 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 */ 153 String directory = pkgInfo.applicationInfo.dataDir + "/lib"; 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; 160 } 161 162 // check if the plugin has the required permissions and 163 // signatures 164 if (!containsPluginPermissionAndSignatures(pkgInfo)) { 165 continue; 166 } 167 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); 175 if (!TYPE_NATIVE.equals(pluginType)) { 176 Log.e(LOGTAG, "Unrecognized plugin type: " + pluginType); 177 continue; 178 } 179 180 try { 181 Class<?> cls = getPluginClass(serviceInfo.packageName, serviceInfo.name); 182 183 //TODO implement any requirements of the plugin class here! 184 boolean classFound = true; 185 186 if (!classFound) { 187 Log.e(LOGTAG, "The plugin's class' " + serviceInfo.name + "' does not extend the appropriate class."); 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 200 mPackageInfoCache.add(pkgInfo); 201 directories.add(directory); 202 } 203 } 204 205 return directories.toArray(new String[directories.size()]); 206 } 207 208 /* package */ containsPluginPermissionAndSignatures(String pluginAPKName)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 containsPluginPermissionAndSignatures(PackageInfo pkgInfo)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 */ getPluginsAPKName(String pluginLib)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 275 synchronized(mPackageInfoCache) { 276 for (PackageInfo pkgInfo : mPackageInfoCache) { 277 if (pluginLib.contains(pkgInfo.packageName)) { 278 return pkgInfo.packageName; 279 } 280 } 281 } 282 283 // if no apk was found then return null 284 return null; 285 } 286 getPluginSharedDataDirectory()287 String getPluginSharedDataDirectory() { 288 return mContext.getDir("plugins", 0).getPath(); 289 } 290 291 /* package */ getPluginClass(String packageName, String className)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 } 300 } 301