WebViewFactory.java revision 85edb6c6bff62e163e4490951ec49a958c63fad0
1/*
2 * Copyright (C) 2012 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 android.annotation.SystemApi;
20import android.app.ActivityManagerInternal;
21import android.app.AppGlobals;
22import android.app.Application;
23import android.content.Context;
24import android.content.pm.ApplicationInfo;
25import android.content.pm.PackageInfo;
26import android.content.pm.PackageManager;
27import android.os.Build;
28import android.os.Process;
29import android.os.RemoteException;
30import android.os.ServiceManager;
31import android.os.StrictMode;
32import android.os.SystemProperties;
33import android.os.Trace;
34import android.text.TextUtils;
35import android.util.AndroidRuntimeException;
36import android.util.Log;
37
38import com.android.server.LocalServices;
39
40import dalvik.system.VMRuntime;
41
42import java.io.File;
43import java.util.Arrays;
44
45/**
46 * Top level factory, used creating all the main WebView implementation classes.
47 *
48 * @hide
49 */
50@SystemApi
51public final class WebViewFactory {
52
53    private static final String CHROMIUM_WEBVIEW_FACTORY =
54            "com.android.webview.chromium.WebViewChromiumFactoryProvider";
55
56    private static final String NULL_WEBVIEW_FACTORY =
57            "com.android.webview.nullwebview.NullWebViewFactoryProvider";
58
59    private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_32 =
60            "/data/misc/shared_relro/libwebviewchromium32.relro";
61    private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_64 =
62            "/data/misc/shared_relro/libwebviewchromium64.relro";
63
64    public static final String CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY =
65            "persist.sys.webview.vmsize";
66    private static final long CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES = 100 * 1024 * 1024;
67
68    private static final String LOGTAG = "WebViewFactory";
69
70    private static final boolean DEBUG = false;
71
72    // Cache the factory both for efficiency, and ensure any one process gets all webviews from the
73    // same provider.
74    private static WebViewFactoryProvider sProviderInstance;
75    private static final Object sProviderLock = new Object();
76    private static boolean sAddressSpaceReserved = false;
77    private static PackageInfo sPackageInfo;
78
79    // Error codes for loadWebViewNativeLibraryFromPackage
80    public static final int LIBLOAD_SUCCESS = 0;
81    public static final int LIBLOAD_WRONG_PACKAGE_NAME = 1;
82    public static final int LIBLOAD_ADDRESS_SPACE_NOT_RESERVED = 2;
83    public static final int LIBLOAD_FAILED_WAITING_FOR_RELRO = 3;
84    public static final int LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES = 4;
85
86    // native relro loading error codes
87    public static final int LIBLOAD_FAILED_TO_OPEN_RELRO_FILE = 5;
88    public static final int LIBLOAD_FAILED_TO_LOAD_LIBRARY = 6;
89    public static final int LIBLOAD_FAILED_JNI_CALL = 7;
90
91    private static class MissingWebViewPackageException extends AndroidRuntimeException {
92        public MissingWebViewPackageException(String message) { super(message); }
93        public MissingWebViewPackageException(Exception e) { super(e); }
94    }
95
96    /** @hide */
97    public static String[] getWebViewPackageNames() {
98        return AppGlobals.getInitialApplication().getResources().getStringArray(
99                com.android.internal.R.array.config_webViewPackageNames);
100    }
101
102    // TODO (gsennton) remove when committing webview xts test change
103    public static String getWebViewPackageName() {
104        String[] webViewPackageNames = getWebViewPackageNames();
105        return webViewPackageNames[webViewPackageNames.length-1];
106    }
107
108    /**
109     * Return the package info of the first package in the webview priority list that contains
110     * webview.
111     *
112     * @hide
113     */
114    public static PackageInfo findPreferredWebViewPackage() {
115        PackageManager pm = AppGlobals.getInitialApplication().getPackageManager();
116
117        for (String packageName : getWebViewPackageNames()) {
118            try {
119                PackageInfo packageInfo = pm.getPackageInfo(packageName,
120                    PackageManager.GET_META_DATA);
121                ApplicationInfo applicationInfo = packageInfo.applicationInfo;
122
123                // If the correct flag is set the package contains webview.
124                if (getWebViewLibrary(applicationInfo) != null) {
125                    return packageInfo;
126                }
127            } catch (PackageManager.NameNotFoundException e) {
128            }
129        }
130        throw new MissingWebViewPackageException("Could not find a loadable WebView package");
131    }
132
133    // throws MissingWebViewPackageException
134    private static ApplicationInfo getWebViewApplicationInfo() {
135        if (sPackageInfo == null)
136            return findPreferredWebViewPackage().applicationInfo;
137        else
138            return sPackageInfo.applicationInfo;
139    }
140
141    private static String getWebViewLibrary(ApplicationInfo ai) {
142        if (ai.metaData != null)
143            return ai.metaData.getString("com.android.webview.WebViewLibrary");
144        return null;
145    }
146
147    public static PackageInfo getLoadedPackageInfo() {
148        return sPackageInfo;
149    }
150
151    /**
152     * Load the native library for the given package name iff that package
153     * name is the same as the one providing the current webview.
154     */
155    public static int loadWebViewNativeLibraryFromPackage(String packageName) {
156        sPackageInfo = findPreferredWebViewPackage();
157        if (packageName != null && packageName.equals(sPackageInfo.packageName)) {
158            return loadNativeLibrary();
159        }
160        return LIBLOAD_WRONG_PACKAGE_NAME;
161    }
162
163    static WebViewFactoryProvider getProvider() {
164        synchronized (sProviderLock) {
165            // For now the main purpose of this function (and the factory abstraction) is to keep
166            // us honest and minimize usage of WebView internals when binding the proxy.
167            if (sProviderInstance != null) return sProviderInstance;
168
169            final int uid = android.os.Process.myUid();
170            if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID) {
171                throw new UnsupportedOperationException(
172                        "For security reasons, WebView is not allowed in privileged processes");
173            }
174
175            Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()");
176            try {
177                Class<WebViewFactoryProvider> providerClass = getProviderClass();
178
179                StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
180                Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "providerClass.newInstance()");
181                try {
182                    sProviderInstance = providerClass.getConstructor(WebViewDelegate.class)
183                            .newInstance(new WebViewDelegate());
184                    if (DEBUG) Log.v(LOGTAG, "Loaded provider: " + sProviderInstance);
185                    return sProviderInstance;
186                } catch (Exception e) {
187                    Log.e(LOGTAG, "error instantiating provider", e);
188                    throw new AndroidRuntimeException(e);
189                } finally {
190                    Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
191                    StrictMode.setThreadPolicy(oldPolicy);
192                }
193            } finally {
194                Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
195            }
196        }
197    }
198
199    private static Class<WebViewFactoryProvider> getProviderClass() {
200        try {
201            // First fetch the package info so we can log the webview package version.
202            sPackageInfo = findPreferredWebViewPackage();
203            Log.i(LOGTAG, "Loading " + sPackageInfo.packageName + " version " +
204                sPackageInfo.versionName + " (code " + sPackageInfo.versionCode + ")");
205
206            Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.loadNativeLibrary()");
207            loadNativeLibrary();
208            Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
209
210            Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getChromiumProviderClass()");
211            try {
212                return getChromiumProviderClass();
213            } catch (ClassNotFoundException e) {
214                Log.e(LOGTAG, "error loading provider", e);
215                throw new AndroidRuntimeException(e);
216            } finally {
217                Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
218            }
219        } catch (MissingWebViewPackageException e) {
220            // If the package doesn't exist, then try loading the null WebView instead.
221            // If that succeeds, then this is a device without WebView support; if it fails then
222            // swallow the failure, complain that the real WebView is missing and rethrow the
223            // original exception.
224            try {
225                return (Class<WebViewFactoryProvider>) Class.forName(NULL_WEBVIEW_FACTORY);
226            } catch (ClassNotFoundException e2) {
227                // Ignore.
228            }
229            Log.e(LOGTAG, "Chromium WebView package does not exist", e);
230            throw new AndroidRuntimeException(e);
231        }
232    }
233
234    // throws MissingWebViewPackageException
235    private static Class<WebViewFactoryProvider> getChromiumProviderClass()
236            throws ClassNotFoundException {
237        Application initialApplication = AppGlobals.getInitialApplication();
238        try {
239            // Construct a package context to load the Java code into the current app.
240            Context webViewContext = initialApplication.createPackageContext(
241                sPackageInfo.packageName,
242                Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
243            initialApplication.getAssets().addAssetPath(
244                    webViewContext.getApplicationInfo().sourceDir);
245            ClassLoader clazzLoader = webViewContext.getClassLoader();
246            Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "Class.forName()");
247            try {
248                return (Class<WebViewFactoryProvider>) Class.forName(CHROMIUM_WEBVIEW_FACTORY, true,
249                                                                     clazzLoader);
250            } finally {
251                Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
252            }
253        } catch (PackageManager.NameNotFoundException e) {
254            throw new MissingWebViewPackageException(e);
255        }
256    }
257
258    /**
259     * Perform any WebView loading preparations that must happen in the zygote.
260     * Currently, this means allocating address space to load the real JNI library later.
261     */
262    public static void prepareWebViewInZygote() {
263        try {
264            System.loadLibrary("webviewchromium_loader");
265            long addressSpaceToReserve =
266                    SystemProperties.getLong(CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY,
267                    CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
268            sAddressSpaceReserved = nativeReserveAddressSpace(addressSpaceToReserve);
269
270            if (sAddressSpaceReserved) {
271                if (DEBUG) {
272                    Log.v(LOGTAG, "address space reserved: " + addressSpaceToReserve + " bytes");
273                }
274            } else {
275                Log.e(LOGTAG, "reserving " + addressSpaceToReserve +
276                        " bytes of address space failed");
277            }
278        } catch (Throwable t) {
279            // Log and discard errors at this stage as we must not crash the zygote.
280            Log.e(LOGTAG, "error preparing native loader", t);
281        }
282    }
283
284    /**
285     * Perform any WebView loading preparations that must happen at boot from the system server,
286     * after the package manager has started or after an update to the webview is installed.
287     * This must be called in the system server.
288     * Currently, this means spawning the child processes which will create the relro files.
289     */
290    public static void prepareWebViewInSystemServer() {
291        String[] nativePaths = null;
292        try {
293            nativePaths = getWebViewNativeLibraryPaths();
294        } catch (Throwable t) {
295            // Log and discard errors at this stage as we must not crash the system server.
296            Log.e(LOGTAG, "error preparing webview native library", t);
297        }
298        prepareWebViewInSystemServer(nativePaths);
299    }
300
301    private static void prepareWebViewInSystemServer(String[] nativeLibraryPaths) {
302        if (DEBUG) Log.v(LOGTAG, "creating relro files");
303
304        // We must always trigger createRelRo regardless of the value of nativeLibraryPaths. Any
305        // unexpected values will be handled there to ensure that we trigger notifying any process
306        // waiting on relreo creation.
307        if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
308            if (DEBUG) Log.v(LOGTAG, "Create 32 bit relro");
309            createRelroFile(false /* is64Bit */, nativeLibraryPaths);
310        }
311
312        if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
313            if (DEBUG) Log.v(LOGTAG, "Create 64 bit relro");
314            createRelroFile(true /* is64Bit */, nativeLibraryPaths);
315        }
316    }
317
318    public static void onWebViewUpdateInstalled() {
319        String[] nativeLibs = null;
320        try {
321            nativeLibs = WebViewFactory.getWebViewNativeLibraryPaths();
322            if (nativeLibs != null) {
323                long newVmSize = 0L;
324
325                for (String path : nativeLibs) {
326                    if (DEBUG) Log.d(LOGTAG, "Checking file size of " + path);
327                    if (path == null) continue;
328                    File f = new File(path);
329                    if (f.exists()) {
330                        long length = f.length();
331                        if (length > newVmSize) {
332                            newVmSize = length;
333                        }
334                    }
335                }
336
337                if (DEBUG) {
338                    Log.v(LOGTAG, "Based on library size, need " + newVmSize +
339                            " bytes of address space.");
340                }
341                // The required memory can be larger than the file on disk (due to .bss), and an
342                // upgraded version of the library will likely be larger, so always attempt to
343                // reserve twice as much as we think to allow for the library to grow during this
344                // boot cycle.
345                newVmSize = Math.max(2 * newVmSize, CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
346                Log.d(LOGTAG, "Setting new address space to " + newVmSize);
347                SystemProperties.set(CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY,
348                        Long.toString(newVmSize));
349            }
350        } catch (Throwable t) {
351            // Log and discard errors at this stage as we must not crash the system server.
352            Log.e(LOGTAG, "error preparing webview native library", t);
353        }
354        prepareWebViewInSystemServer(nativeLibs);
355    }
356
357    // throws MissingWebViewPackageException
358    private static String[] getWebViewNativeLibraryPaths() {
359        ApplicationInfo ai = getWebViewApplicationInfo();
360        final String NATIVE_LIB_FILE_NAME = getWebViewLibrary(ai);
361
362        String path32;
363        String path64;
364        boolean primaryArchIs64bit = VMRuntime.is64BitAbi(ai.primaryCpuAbi);
365        if (!TextUtils.isEmpty(ai.secondaryCpuAbi)) {
366            // Multi-arch case.
367            if (primaryArchIs64bit) {
368                // Primary arch: 64-bit, secondary: 32-bit.
369                path64 = ai.nativeLibraryDir;
370                path32 = ai.secondaryNativeLibraryDir;
371            } else {
372                // Primary arch: 32-bit, secondary: 64-bit.
373                path64 = ai.secondaryNativeLibraryDir;
374                path32 = ai.nativeLibraryDir;
375            }
376        } else if (primaryArchIs64bit) {
377            // Single-arch 64-bit.
378            path64 = ai.nativeLibraryDir;
379            path32 = "";
380        } else {
381            // Single-arch 32-bit.
382            path32 = ai.nativeLibraryDir;
383            path64 = "";
384        }
385        if (!TextUtils.isEmpty(path32)) path32 += "/" + NATIVE_LIB_FILE_NAME;
386        if (!TextUtils.isEmpty(path64)) path64 += "/" + NATIVE_LIB_FILE_NAME;
387        return new String[] { path32, path64 };
388    }
389
390    private static void createRelroFile(final boolean is64Bit, String[] nativeLibraryPaths) {
391        final String abi =
392                is64Bit ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0];
393
394        // crashHandler is invoked by the ActivityManagerService when the isolated process crashes.
395        Runnable crashHandler = new Runnable() {
396            @Override
397            public void run() {
398                try {
399                    Log.e(LOGTAG, "relro file creator for " + abi + " crashed. Proceeding without");
400                    getUpdateService().notifyRelroCreationCompleted(is64Bit, false);
401                } catch (RemoteException e) {
402                    Log.e(LOGTAG, "Cannot reach WebViewUpdateService. " + e.getMessage());
403                }
404            }
405        };
406
407        try {
408            if (nativeLibraryPaths == null
409                    || nativeLibraryPaths[0] == null || nativeLibraryPaths[1] == null) {
410                throw new IllegalArgumentException(
411                        "Native library paths to the WebView RelRo process must not be null!");
412            }
413            int pid = LocalServices.getService(ActivityManagerInternal.class).startIsolatedProcess(
414                    RelroFileCreator.class.getName(), nativeLibraryPaths, "WebViewLoader-" + abi, abi,
415                    Process.SHARED_RELRO_UID, crashHandler);
416            if (pid <= 0) throw new Exception("Failed to start the relro file creator process");
417        } catch (Throwable t) {
418            // Log and discard errors as we must not crash the system server.
419            Log.e(LOGTAG, "error starting relro file creator for abi " + abi, t);
420            crashHandler.run();
421        }
422    }
423
424    private static class RelroFileCreator {
425        // Called in an unprivileged child process to create the relro file.
426        public static void main(String[] args) {
427            boolean result = false;
428            boolean is64Bit = VMRuntime.getRuntime().is64Bit();
429            try{
430                if (args.length != 2 || args[0] == null || args[1] == null) {
431                    Log.e(LOGTAG, "Invalid RelroFileCreator args: " + Arrays.toString(args));
432                    return;
433                }
434                Log.v(LOGTAG, "RelroFileCreator (64bit = " + is64Bit + "), " +
435                        " 32-bit lib: " + args[0] + ", 64-bit lib: " + args[1]);
436                if (!sAddressSpaceReserved) {
437                    Log.e(LOGTAG, "can't create relro file; address space not reserved");
438                    return;
439                }
440                result = nativeCreateRelroFile(args[0] /* path32 */,
441                                               args[1] /* path64 */,
442                                               CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
443                                               CHROMIUM_WEBVIEW_NATIVE_RELRO_64);
444                if (result && DEBUG) Log.v(LOGTAG, "created relro file");
445            } finally {
446                // We must do our best to always notify the update service, even if something fails.
447                try {
448                    getUpdateService().notifyRelroCreationCompleted(is64Bit, result);
449                } catch (RemoteException e) {
450                    Log.e(LOGTAG, "error notifying update service", e);
451                }
452
453                if (!result) Log.e(LOGTAG, "failed to create relro file");
454
455                // Must explicitly exit or else this process will just sit around after we return.
456                System.exit(0);
457            }
458        }
459    }
460
461    private static int loadNativeLibrary() {
462        if (!sAddressSpaceReserved) {
463            Log.e(LOGTAG, "can't load with relro file; address space not reserved");
464            return LIBLOAD_ADDRESS_SPACE_NOT_RESERVED;
465        }
466
467        try {
468            getUpdateService().waitForRelroCreationCompleted(VMRuntime.getRuntime().is64Bit());
469        } catch (RemoteException e) {
470            Log.e(LOGTAG, "error waiting for relro creation, proceeding without", e);
471            return LIBLOAD_FAILED_WAITING_FOR_RELRO;
472        }
473
474        try {
475            String[] args = getWebViewNativeLibraryPaths();
476            int result = nativeLoadWithRelroFile(args[0] /* path32 */,
477                                                     args[1] /* path64 */,
478                                                     CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
479                                                     CHROMIUM_WEBVIEW_NATIVE_RELRO_64);
480            if (result != LIBLOAD_SUCCESS) {
481                Log.w(LOGTAG, "failed to load with relro file, proceeding without");
482            } else if (DEBUG) {
483                Log.v(LOGTAG, "loaded with relro file");
484            }
485            return result;
486        } catch (MissingWebViewPackageException e) {
487            Log.e(LOGTAG, "Failed to list WebView package libraries for loadNativeLibrary", e);
488            return LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES;
489        }
490    }
491
492    private static IWebViewUpdateService getUpdateService() {
493        return IWebViewUpdateService.Stub.asInterface(ServiceManager.getService("webviewupdate"));
494    }
495
496    private static native boolean nativeReserveAddressSpace(long addressSpaceToReserve);
497    private static native boolean nativeCreateRelroFile(String lib32, String lib64,
498                                                        String relro32, String relro64);
499    private static native int nativeLoadWithRelroFile(String lib32, String lib64,
500                                                          String relro32, String relro64);
501}
502