WebViewFactory.java revision 810c052d9b117217152c2a609ccec056a2a61d1e
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.app.ActivityManagerInternal;
20import android.app.AppGlobals;
21import android.content.Context;
22import android.content.pm.PackageManager;
23import android.os.Build;
24import android.os.Process;
25import android.os.RemoteException;
26import android.os.ServiceManager;
27import android.os.StrictMode;
28import android.util.AndroidRuntimeException;
29import android.util.Log;
30import com.android.server.LocalServices;
31import dalvik.system.VMRuntime;
32
33import java.io.File;
34
35import com.android.internal.os.Zygote;
36
37/**
38 * Top level factory, used creating all the main WebView implementation classes.
39 *
40 * @hide
41 */
42public final class WebViewFactory {
43
44    private static final String CHROMIUM_WEBVIEW_FACTORY =
45            "com.android.webview.chromium.WebViewChromiumFactoryProvider";
46
47    private static final String NULL_WEBVIEW_FACTORY =
48            "com.android.webview.nullwebview.NullWebViewFactoryProvider";
49
50    // TODO(torne): we need to use a system property instead of hardcoding the library paths to
51    // enable it to be changed when a webview update apk is installed.
52    private static final String CHROMIUM_WEBVIEW_NATIVE_LIB_32 =
53            "/system/lib/libwebviewchromium.so";
54    private static final String CHROMIUM_WEBVIEW_NATIVE_LIB_64 =
55            "/system/lib64/libwebviewchromium.so";
56    private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_32 =
57            "/data/misc/shared_relro/libwebviewchromium32.relro";
58    private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_64 =
59            "/data/misc/shared_relro/libwebviewchromium64.relro";
60
61    private static final String LOGTAG = "WebViewFactory";
62
63    private static final boolean DEBUG = false;
64
65    // Cache the factory both for efficiency, and ensure any one process gets all webviews from the
66    // same provider.
67    private static WebViewFactoryProvider sProviderInstance;
68    private static final Object sProviderLock = new Object();
69    private static boolean sAddressSpaceReserved = false;
70
71    public static String getWebViewPackageName() {
72        // TODO: Make this dynamic based on resource configuration.
73        return "com.android.webview";
74    }
75
76    static WebViewFactoryProvider getProvider() {
77        synchronized (sProviderLock) {
78            // For now the main purpose of this function (and the factory abstraction) is to keep
79            // us honest and minimize usage of WebView internals when binding the proxy.
80            if (sProviderInstance != null) return sProviderInstance;
81
82            loadNativeLibrary();
83
84            Class<WebViewFactoryProvider> providerClass;
85            try {
86                providerClass = getFactoryClass();
87            } catch (ClassNotFoundException e) {
88                Log.e(LOGTAG, "error loading provider", e);
89                throw new AndroidRuntimeException(e);
90            }
91
92            StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
93            try {
94                sProviderInstance = providerClass.newInstance();
95                if (DEBUG) Log.v(LOGTAG, "Loaded provider: " + sProviderInstance);
96                return sProviderInstance;
97            } catch (Exception e) {
98                Log.e(LOGTAG, "error instantiating provider", e);
99                throw new AndroidRuntimeException(e);
100            } finally {
101                StrictMode.setThreadPolicy(oldPolicy);
102            }
103        }
104    }
105
106    private static Class<WebViewFactoryProvider> getFactoryClass() throws ClassNotFoundException {
107        try {
108            Context webViewContext = AppGlobals.getInitialApplication().createPackageContext(
109                    getWebViewPackageName(),
110                    Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
111            ClassLoader clazzLoader = webViewContext.getClassLoader();
112            return (Class<WebViewFactoryProvider>) Class.forName(CHROMIUM_WEBVIEW_FACTORY, true,
113                                                                 clazzLoader);
114        } catch (PackageManager.NameNotFoundException e) {
115            Log.e(LOGTAG, "Chromium WebView package does not exist");
116            return (Class<WebViewFactoryProvider>) Class.forName(NULL_WEBVIEW_FACTORY);
117        }
118    }
119
120    /**
121     * Perform any WebView loading preparations that must happen in the zygote.
122     * Currently, this means allocating address space to load the real JNI library later.
123     */
124    public static void prepareWebViewInZygote() {
125        try {
126            System.loadLibrary("webviewchromium_loader");
127            sAddressSpaceReserved = nativeReserveAddressSpace(CHROMIUM_WEBVIEW_NATIVE_LIB_32,
128                                                              CHROMIUM_WEBVIEW_NATIVE_LIB_64);
129            if (sAddressSpaceReserved) {
130                if (DEBUG) Log.v(LOGTAG, "address space reserved");
131            } else {
132                Log.e(LOGTAG, "reserving address space failed");
133            }
134        } catch (Throwable t) {
135            // Log and discard errors at this stage as we must not crash the zygote.
136            Log.e(LOGTAG, "error preparing native loader", t);
137        }
138    }
139
140    /**
141     * Perform any WebView loading preparations that must happen at boot from the system server,
142     * after the package manager has started.
143     * This must be called in the system server.
144     * Currently, this means spawning the child processes which will create the relro files.
145     */
146    public static void prepareWebViewInSystemServer() {
147        if (DEBUG) Log.v(LOGTAG, "creating relro files");
148        if (new File(CHROMIUM_WEBVIEW_NATIVE_LIB_64).exists()) {
149            createRelroFile(true /* is64Bit */);
150        }
151        if (new File(CHROMIUM_WEBVIEW_NATIVE_LIB_32).exists()) {
152            createRelroFile(false /* is64Bit */);
153        }
154    }
155
156    private static void createRelroFile(final boolean is64Bit) {
157        String abi = is64Bit ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0];
158
159        // crashHandler is invoked by the ActivityManagerService when the isolated process crashes.
160        Runnable crashHandler = new Runnable() {
161            @Override
162            public void run() {
163                try {
164                    getUpdateService().notifyRelroCreationCompleted(is64Bit, false);
165                } catch (RemoteException e) {
166                    Log.e(LOGTAG, "Cannot reach WebViewUpdateService. " + e.getMessage());
167                }
168            }
169        };
170
171        try {
172            String[] args = null;  // TODO: plumb native library paths via args.
173            LocalServices.getService(ActivityManagerInternal.class).startIsolatedProcess(
174                    RelroFileCreator.class.getName(), args, "WebViewLoader-" + abi, abi,
175                    Process.SHARED_RELRO_UID, crashHandler);
176        } catch (Throwable t) {
177            // Log and discard errors as we must not crash the system server.
178            Log.e(LOGTAG, "error starting relro file creator for abi " + abi, t);
179            crashHandler.run();
180        }
181    }
182
183    private static class RelroFileCreator {
184        // Called in an unprivileged child process to create the relro file.
185        public static void main(String[] args) {
186            if (!sAddressSpaceReserved) {
187                Log.e(LOGTAG, "can't create relro file; address space not reserved");
188                return;
189            }
190            boolean result = nativeCreateRelroFile(CHROMIUM_WEBVIEW_NATIVE_LIB_32,
191                                                   CHROMIUM_WEBVIEW_NATIVE_LIB_64,
192                                                   CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
193                                                   CHROMIUM_WEBVIEW_NATIVE_RELRO_64);
194            if (!result) {
195                Log.e(LOGTAG, "failed to create relro file");
196            } else if (DEBUG) {
197                Log.v(LOGTAG, "created relro file");
198            }
199            try {
200                getUpdateService().notifyRelroCreationCompleted(VMRuntime.getRuntime().is64Bit(),
201                                                                result);
202            } catch (RemoteException e) {
203                Log.e(LOGTAG, "error notifying update service", e);
204            }
205
206            // Must explicitly exit or else this process will just sit around after we return.
207            System.exit(0);
208        }
209    }
210
211    private static void loadNativeLibrary() {
212        if (!sAddressSpaceReserved) {
213            Log.e(LOGTAG, "can't load with relro file; address space not reserved");
214            return;
215        }
216
217        try {
218            getUpdateService().waitForRelroCreationCompleted(VMRuntime.getRuntime().is64Bit());
219        } catch (RemoteException e) {
220            Log.e(LOGTAG, "error waiting for relro creation, proceeding without", e);
221            return;
222        }
223
224        boolean result = nativeLoadWithRelroFile(CHROMIUM_WEBVIEW_NATIVE_LIB_32,
225                                                 CHROMIUM_WEBVIEW_NATIVE_LIB_64,
226                                                 CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
227                                                 CHROMIUM_WEBVIEW_NATIVE_RELRO_64);
228        if (!result) {
229            Log.w(LOGTAG, "failed to load with relro file, proceeding without");
230        } else if (DEBUG) {
231            Log.v(LOGTAG, "loaded with relro file");
232        }
233    }
234
235    private static IWebViewUpdateService getUpdateService() {
236        return IWebViewUpdateService.Stub.asInterface(ServiceManager.getService("webviewupdate"));
237    }
238
239    private static native boolean nativeReserveAddressSpace(String lib32, String lib64);
240    private static native boolean nativeCreateRelroFile(String lib32, String lib64,
241                                                        String relro32, String relro64);
242    private static native boolean nativeLoadWithRelroFile(String lib32, String lib64,
243                                                          String relro32, String relro64);
244}
245