Home | History | Annotate | Download | only in webkit
      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.app.Service;
     25 import android.content.Context;
     26 import android.content.Intent;
     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 = "webkit";
     63 
     64     private static final String PLUGIN_TYPE = "type";
     65     private static final String TYPE_NATIVE = "native";
     66 
     67     private static PluginManager mInstance = null;
     68 
     69     private final Context mContext;
     70 
     71     private ArrayList<PackageInfo> mPackageInfoCache;
     72 
     73     // Only plugin matches one of the signatures in the list can be loaded
     74     // inside the WebView process
     75     private static final String SIGNATURE_1 = "308204c5308203ada003020102020900d7cb412f75f4887e300d06092a864886f70d010105050030819d310b3009060355040613025553311330110603550408130a43616c69666f726e69613111300f0603550407130853616e204a6f736531233021060355040a131a41646f62652053797374656d7320496e636f72706f7261746564311c301a060355040b1313496e666f726d6174696f6e2053797374656d73312330210603550403131a41646f62652053797374656d7320496e636f72706f7261746564301e170d3039313030313030323331345a170d3337303231363030323331345a30819d310b3009060355040613025553311330110603550408130a43616c69666f726e69613111300f0603550407130853616e204a6f736531233021060355040a131a41646f62652053797374656d7320496e636f72706f7261746564311c301a060355040b1313496e666f726d6174696f6e2053797374656d73312330210603550403131a41646f62652053797374656d7320496e636f72706f726174656430820120300d06092a864886f70d01010105000382010d0030820108028201010099724f3e05bbd78843794f357776e04b340e13cb1c9ccb3044865180d7d8fec8166c5bbd876da8b80aa71eb6ba3d4d3455c9a8de162d24a25c4c1cd04c9523affd06a279fc8f0d018f242486bdbb2dbfbf6fcb21ed567879091928b876f7ccebc7bccef157366ebe74e33ae1d7e9373091adab8327482154afc0693a549522f8c796dd84d16e24bb221f5dbb809ca56dd2b6e799c5fa06b6d9c5c09ada54ea4c5db1523a9794ed22a3889e5e05b29f8ee0a8d61efe07ae28f65dece2ff7edc5b1416d7c7aad7f0d35e8f4a4b964dbf50ae9aa6d620157770d974131b3e7e3abd6d163d65758e2f0822db9c88598b9db6263d963d13942c91fc5efe34fc1e06e3020103a382010630820102301d0603551d0e041604145af418e419a639e1657db960996364a37ef20d403081d20603551d230481ca3081c780145af418e419a639e1657db960996364a37ef20d40a181a3a481a030819d310b3009060355040613025553311330110603550408130a43616c69666f726e69613111300f0603550407130853616e204a6f736531233021060355040a131a41646f62652053797374656d7320496e636f72706f7261746564311c301a060355040b1313496e666f726d6174696f6e2053797374656d73312330210603550403131a41646f62652053797374656d7320496e636f72706f7261746564820900d7cb412f75f4887e300c0603551d13040530030101ff300d06092a864886f70d0101050500038201010076c2a11fe303359689c2ebc7b2c398eff8c3f9ad545cdbac75df63bf7b5395b6988d1842d6aa1556d595b5692e08224d667a4c9c438f05e74906c53dd8016dde7004068866f01846365efd146e9bfaa48c9ecf657f87b97c757da11f225c4a24177bf2d7188e6cce2a70a1e8a841a14471eb51457398b8a0addd8b6c8c1538ca8f1e40b4d8b960009ea22c188d28924813d2c0b4a4d334b7cf05507e1fcf0a06fe946c7ffc435e173af6fc3e3400643710acc806f830a14788291d46f2feed9fb5c70423ca747ed1572d752894ac1f19f93989766308579393fabb43649aa8806a313b1ab9a50922a44c2467b9062037f2da0d484d9ffd8fe628eeea629ba637";
     76 
     77     private static final Signature[] SIGNATURES = new Signature[] {
     78         new Signature(SIGNATURE_1)
     79     };
     80 
     81     private PluginManager(Context context) {
     82         mContext = context;
     83         mPackageInfoCache = new ArrayList<PackageInfo>();
     84     }
     85 
     86     public static synchronized PluginManager getInstance(Context context) {
     87         if (mInstance == null) {
     88             if (context == null) {
     89                 throw new IllegalStateException(
     90                         "First call to PluginManager need a valid context.");
     91             }
     92             mInstance = new PluginManager(context.getApplicationContext());
     93         }
     94         return mInstance;
     95     }
     96 
     97     /**
     98      * Signal the WebCore thread to refresh its list of plugins. Use this if the
     99      * directory contents of one of the plugin directories has been modified and
    100      * needs its changes reflecting. May cause plugin load and/or unload.
    101      *
    102      * @param reloadOpenPages Set to true to reload all open pages.
    103      */
    104     public void refreshPlugins(boolean reloadOpenPages) {
    105         BrowserFrame.sJavaBridge.obtainMessage(
    106                 JWebCoreJavaBridge.REFRESH_PLUGINS, reloadOpenPages)
    107                 .sendToTarget();
    108     }
    109 
    110     String[] getPluginDirectories() {
    111 
    112         ArrayList<String> directories = new ArrayList<String>();
    113         PackageManager pm = mContext.getPackageManager();
    114         List<ResolveInfo> plugins = pm.queryIntentServices(new Intent(
    115                 PLUGIN_ACTION), PackageManager.GET_SERVICES
    116                 | PackageManager.GET_META_DATA);
    117 
    118         synchronized(mPackageInfoCache) {
    119 
    120             // clear the list of existing packageInfo objects
    121             mPackageInfoCache.clear();
    122 
    123             for (ResolveInfo info : plugins) {
    124 
    125                 // retrieve the plugin's service information
    126                 ServiceInfo serviceInfo = info.serviceInfo;
    127                 if (serviceInfo == null) {
    128                     Log.w(LOGTAG, "Ignore bad plugin");
    129                     continue;
    130                 }
    131 
    132                 // retrieve information from the plugin's manifest
    133                 PackageInfo pkgInfo;
    134                 try {
    135                     pkgInfo = pm.getPackageInfo(serviceInfo.packageName,
    136                                     PackageManager.GET_PERMISSIONS
    137                                     | PackageManager.GET_SIGNATURES);
    138                 } catch (NameNotFoundException e) {
    139                     Log.w(LOGTAG, "Can't find plugin: " + serviceInfo.packageName);
    140                     continue;
    141                 }
    142                 if (pkgInfo == null) {
    143                     continue;
    144                 }
    145 
    146                 // check if their is a conflict in the lib directory names
    147                 String directory = pkgInfo.applicationInfo.dataDir + "/lib";
    148                 if (directories.contains(directory)) {
    149                     continue;
    150                 }
    151 
    152                 // check if the plugin has the required permissions
    153                 String permissions[] = pkgInfo.requestedPermissions;
    154                 if (permissions == null) {
    155                     continue;
    156                 }
    157                 boolean permissionOk = false;
    158                 for (String permit : permissions) {
    159                     if (PLUGIN_PERMISSION.equals(permit)) {
    160                         permissionOk = true;
    161                         break;
    162                     }
    163                 }
    164                 if (!permissionOk) {
    165                     continue;
    166                 }
    167 
    168                 // check to ensure the plugin is properly signed
    169                 Signature signatures[] = pkgInfo.signatures;
    170                 if (signatures == null) {
    171                     continue;
    172                 }
    173                 if (SystemProperties.getBoolean("ro.secure", false)) {
    174                     boolean signatureMatch = false;
    175                     for (Signature signature : signatures) {
    176                         for (int i = 0; i < SIGNATURES.length; i++) {
    177                             if (SIGNATURES[i].equals(signature)) {
    178                                 signatureMatch = true;
    179                                 break;
    180                             }
    181                         }
    182                     }
    183                     if (!signatureMatch) {
    184                         continue;
    185                     }
    186                 }
    187 
    188                 // determine the type of plugin from the manifest
    189                 if (serviceInfo.metaData == null) {
    190                     Log.e(LOGTAG, "The plugin '" + serviceInfo.name + "' has no type defined");
    191                     continue;
    192                 }
    193 
    194                 String pluginType = serviceInfo.metaData.getString(PLUGIN_TYPE);
    195                 if (!TYPE_NATIVE.equals(pluginType)) {
    196                     Log.e(LOGTAG, "Unrecognized plugin type: " + pluginType);
    197                     continue;
    198                 }
    199 
    200                 try {
    201                     Class<?> cls = getPluginClass(serviceInfo.packageName, serviceInfo.name);
    202 
    203                     //TODO implement any requirements of the plugin class here!
    204                     boolean classFound = true;
    205 
    206                     if (!classFound) {
    207                         Log.e(LOGTAG, "The plugin's class' " + serviceInfo.name + "' does not extend the appropriate class.");
    208                         continue;
    209                     }
    210 
    211                 } catch (NameNotFoundException e) {
    212                     Log.e(LOGTAG, "Can't find plugin: " + serviceInfo.packageName);
    213                     continue;
    214                 } catch (ClassNotFoundException e) {
    215                     Log.e(LOGTAG, "Can't find plugin's class: " + serviceInfo.name);
    216                     continue;
    217                 }
    218 
    219                 // if all checks have passed then make the plugin available
    220                 mPackageInfoCache.add(pkgInfo);
    221                 directories.add(directory);
    222             }
    223         }
    224 
    225         return directories.toArray(new String[directories.size()]);
    226     }
    227 
    228     /* package */
    229     String getPluginsAPKName(String pluginLib) {
    230 
    231         // basic error checking on input params
    232         if (pluginLib == null || pluginLib.length() == 0) {
    233             return null;
    234         }
    235 
    236         // must be synchronized to ensure the consistency of the cache
    237         synchronized(mPackageInfoCache) {
    238             for (PackageInfo pkgInfo : mPackageInfoCache) {
    239                 if (pluginLib.startsWith(pkgInfo.applicationInfo.dataDir)) {
    240                     return pkgInfo.packageName;
    241                 }
    242             }
    243         }
    244 
    245         // if no apk was found then return null
    246         return null;
    247     }
    248 
    249     String getPluginSharedDataDirectory() {
    250         return mContext.getDir("plugins", 0).getPath();
    251     }
    252 
    253     /* package */
    254     Class<?> getPluginClass(String packageName, String className)
    255             throws NameNotFoundException, ClassNotFoundException {
    256         Context pluginContext = mContext.createPackageContext(packageName,
    257                 Context.CONTEXT_INCLUDE_CODE |
    258                 Context.CONTEXT_IGNORE_SECURITY);
    259         ClassLoader pluginCL = pluginContext.getClassLoader();
    260         return pluginCL.loadClass(className);
    261     }
    262 }
    263