PluginManager.java revision 26f1faa122a4557c26042c5a5ab59ef659fe2974
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
17package android.webkit;
18
19import java.util.ArrayList;
20import java.util.List;
21
22import android.annotation.SdkConstant;
23import android.annotation.SdkConstant.SdkConstantType;
24import android.app.Service;
25import android.content.Context;
26import android.content.Intent;
27import 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;
33import android.os.SystemProperties;
34import 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
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