WebViewFactory.java revision 5ced502fba5a69dc1d2e55b3d7e5fd429280d6ae
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.Application;
21import android.app.AppGlobals;
22import android.content.Context;
23import android.content.pm.ApplicationInfo;
24import android.content.pm.PackageManager;
25import android.os.Build;
26import android.os.Process;
27import android.os.RemoteException;
28import android.os.ServiceManager;
29import android.os.StrictMode;
30import android.os.SystemProperties;
31import android.text.TextUtils;
32import android.util.AndroidRuntimeException;
33import android.util.Log;
34import com.android.server.LocalServices;
35import dalvik.system.VMRuntime;
36
37import java.io.File;
38import java.util.Arrays;
39
40import com.android.internal.os.Zygote;
41
42/**
43 * Top level factory, used creating all the main WebView implementation classes.
44 *
45 * @hide
46 */
47public final class WebViewFactory {
48
49    private static final String CHROMIUM_WEBVIEW_FACTORY =
50            "com.android.webview.chromium.WebViewChromiumFactoryProvider";
51
52    private static final String NULL_WEBVIEW_FACTORY =
53            "com.android.webview.nullwebview.NullWebViewFactoryProvider";
54
55    private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_32 =
56            "/data/misc/shared_relro/libwebviewchromium32.relro";
57    private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_64 =
58            "/data/misc/shared_relro/libwebviewchromium64.relro";
59
60    public static final String CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY =
61            "persist.sys.webview.vmsize";
62    private static final long CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES = 100 * 1024 * 1024;
63
64    private static final String LOGTAG = "WebViewFactory";
65
66    private static final boolean DEBUG = false;
67
68    // Cache the factory both for efficiency, and ensure any one process gets all webviews from the
69    // same provider.
70    private static WebViewFactoryProvider sProviderInstance;
71    private static final Object sProviderLock = new Object();
72    private static boolean sAddressSpaceReserved = false;
73
74    public static String getWebViewPackageName() {
75        // TODO: Make this dynamic based on resource configuration.
76        return "com.android.webview";
77    }
78
79    static WebViewFactoryProvider getProvider() {
80        synchronized (sProviderLock) {
81            // For now the main purpose of this function (and the factory abstraction) is to keep
82            // us honest and minimize usage of WebView internals when binding the proxy.
83            if (sProviderInstance != null) return sProviderInstance;
84
85            loadNativeLibrary();
86
87            Class<WebViewFactoryProvider> providerClass;
88            try {
89                providerClass = getFactoryClass();
90            } catch (ClassNotFoundException e) {
91                Log.e(LOGTAG, "error loading provider", e);
92                throw new AndroidRuntimeException(e);
93            }
94
95            StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
96            try {
97                sProviderInstance = providerClass.newInstance();
98                if (DEBUG) Log.v(LOGTAG, "Loaded provider: " + sProviderInstance);
99                return sProviderInstance;
100            } catch (Exception e) {
101                Log.e(LOGTAG, "error instantiating provider", e);
102                throw new AndroidRuntimeException(e);
103            } finally {
104                StrictMode.setThreadPolicy(oldPolicy);
105            }
106        }
107    }
108
109    private static Class<WebViewFactoryProvider> getFactoryClass() throws ClassNotFoundException {
110        Application initialApplication = AppGlobals.getInitialApplication();
111        try {
112            Context webViewContext = initialApplication.createPackageContext(
113                    getWebViewPackageName(),
114                    Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
115            initialApplication.getAssets().addAssetPath(
116                    webViewContext.getApplicationInfo().sourceDir);
117            ClassLoader clazzLoader = webViewContext.getClassLoader();
118            return (Class<WebViewFactoryProvider>) Class.forName(CHROMIUM_WEBVIEW_FACTORY, true,
119                                                                 clazzLoader);
120        } catch (PackageManager.NameNotFoundException e) {
121            Log.e(LOGTAG, "Chromium WebView package does not exist");
122            return (Class<WebViewFactoryProvider>) Class.forName(NULL_WEBVIEW_FACTORY);
123        }
124    }
125
126    /**
127     * Perform any WebView loading preparations that must happen in the zygote.
128     * Currently, this means allocating address space to load the real JNI library later.
129     */
130    public static void prepareWebViewInZygote() {
131        try {
132            System.loadLibrary("webviewchromium_loader");
133            long addressSpaceToReserve =
134                    SystemProperties.getLong(CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY,
135                    CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
136            sAddressSpaceReserved = nativeReserveAddressSpace(addressSpaceToReserve);
137
138            if (sAddressSpaceReserved) {
139                if (DEBUG) {
140                    Log.v(LOGTAG, "address space reserved: " + addressSpaceToReserve + " bytes");
141                }
142            } else {
143                Log.e(LOGTAG, "reserving " + addressSpaceToReserve +
144                        " bytes of address space failed");
145            }
146        } catch (Throwable t) {
147            // Log and discard errors at this stage as we must not crash the zygote.
148            Log.e(LOGTAG, "error preparing native loader", t);
149        }
150    }
151
152    /**
153     * Perform any WebView loading preparations that must happen at boot from the system server,
154     * after the package manager has started or after an update to the webview is installed.
155     * This must be called in the system server.
156     * Currently, this means spawning the child processes which will create the relro files.
157     */
158    public static void prepareWebViewInSystemServer() {
159        String[] nativePaths = null;
160        try {
161            nativePaths = getWebViewNativeLibraryPaths();
162        } catch (PackageManager.NameNotFoundException e) {
163        }
164        prepareWebViewInSystemServer(nativePaths);
165    }
166
167    private static void prepareWebViewInSystemServer(String[] nativeLibraryPaths) {
168        if (DEBUG) Log.v(LOGTAG, "creating relro files");
169
170        // We must always trigger createRelRo regardless of the value of nativeLibraryPaths. Any
171        // unexpected values will be handled there to ensure that we trigger notifying any process
172        // waiting on relreo creation.
173        if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
174            if (DEBUG) Log.v(LOGTAG, "Create 32 bit relro");
175            createRelroFile(false /* is64Bit */, nativeLibraryPaths);
176        }
177
178        if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
179            if (DEBUG) Log.v(LOGTAG, "Create 64 bit relro");
180            createRelroFile(true /* is64Bit */, nativeLibraryPaths);
181        }
182    }
183
184    public static void onWebViewUpdateInstalled() {
185        String[] nativeLibs = null;
186        try {
187            nativeLibs = WebViewFactory.getWebViewNativeLibraryPaths();
188        } catch (PackageManager.NameNotFoundException e) {
189        }
190
191        if (nativeLibs != null) {
192            long newVmSize = 0L;
193
194            for (String path : nativeLibs) {
195                if (DEBUG) Log.d(LOGTAG, "Checking file size of " + path);
196                if (path == null) continue;
197                File f = new File(path);
198                if (f.exists()) {
199                    long length = f.length();
200                    if (length > newVmSize) {
201                        newVmSize = length;
202                    }
203                }
204            }
205
206            if (DEBUG) {
207                Log.v(LOGTAG, "Based on library size, need " + newVmSize +
208                        " bytes of address space.");
209            }
210            // The required memory can be larger than the file on disk (due to .bss), and an
211            // upgraded version of the library will likely be larger, so always attempt to reserve
212            // twice as much as we think to allow for the library to grow during this boot cycle.
213            newVmSize = Math.max(2 * newVmSize, CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
214            Log.d(LOGTAG, "Setting new address space to " + newVmSize);
215            SystemProperties.set(CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY,
216                    Long.toString(newVmSize));
217        }
218        prepareWebViewInSystemServer(nativeLibs);
219    }
220
221    private static String[] getWebViewNativeLibraryPaths()
222            throws PackageManager.NameNotFoundException {
223        final String NATIVE_LIB_FILE_NAME = "libwebviewchromium.so";
224
225        PackageManager pm = AppGlobals.getInitialApplication().getPackageManager();
226        ApplicationInfo ai = pm.getApplicationInfo(getWebViewPackageName(), 0);
227
228        String path32;
229        String path64;
230        boolean primaryArchIs64bit = VMRuntime.is64BitAbi(ai.primaryCpuAbi);
231        if (!TextUtils.isEmpty(ai.secondaryCpuAbi)) {
232            // Multi-arch case.
233            if (primaryArchIs64bit) {
234                // Primary arch: 64-bit, secondary: 32-bit.
235                path64 = ai.nativeLibraryDir;
236                path32 = ai.secondaryNativeLibraryDir;
237            } else {
238                // Primary arch: 32-bit, secondary: 64-bit.
239                path64 = ai.secondaryNativeLibraryDir;
240                path32 = ai.nativeLibraryDir;
241            }
242        } else if (primaryArchIs64bit) {
243            // Single-arch 64-bit.
244            path64 = ai.nativeLibraryDir;
245            path32 = "";
246        } else {
247            // Single-arch 32-bit.
248            path32 = ai.nativeLibraryDir;
249            path64 = "";
250        }
251        if (!TextUtils.isEmpty(path32)) path32 += "/" + NATIVE_LIB_FILE_NAME;
252        if (!TextUtils.isEmpty(path64)) path64 += "/" + NATIVE_LIB_FILE_NAME;
253        return new String[] { path32, path64 };
254    }
255
256    private static void createRelroFile(final boolean is64Bit, String[] nativeLibraryPaths) {
257        final String abi =
258                is64Bit ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0];
259
260        // crashHandler is invoked by the ActivityManagerService when the isolated process crashes.
261        Runnable crashHandler = new Runnable() {
262            @Override
263            public void run() {
264                try {
265                    Log.e(LOGTAG, "relro file creator for " + abi + " crashed. Proceeding without");
266                    getUpdateService().notifyRelroCreationCompleted(is64Bit, false);
267                } catch (RemoteException e) {
268                    Log.e(LOGTAG, "Cannot reach WebViewUpdateService. " + e.getMessage());
269                }
270            }
271        };
272
273        try {
274            if (nativeLibraryPaths == null
275                    || nativeLibraryPaths[0] == null || nativeLibraryPaths[1] == null) {
276                throw new IllegalArgumentException(
277                        "Native library paths to the WebView RelRo process must not be null!");
278            }
279            int pid = LocalServices.getService(ActivityManagerInternal.class).startIsolatedProcess(
280                    RelroFileCreator.class.getName(), nativeLibraryPaths, "WebViewLoader-" + abi, abi,
281                    Process.SHARED_RELRO_UID, crashHandler);
282            if (pid <= 0) throw new Exception("Failed to start the isolated process");
283        } catch (Throwable t) {
284            // Log and discard errors as we must not crash the system server.
285            Log.e(LOGTAG, "error starting relro file creator for abi " + abi, t);
286            crashHandler.run();
287        }
288    }
289
290    private static class RelroFileCreator {
291        // Called in an unprivileged child process to create the relro file.
292        public static void main(String[] args) {
293            boolean result = false;
294            boolean is64Bit = VMRuntime.getRuntime().is64Bit();
295            try{
296                if (args.length != 2 || args[0] == null || args[1] == null) {
297                    Log.e(LOGTAG, "Invalid RelroFileCreator args: " + Arrays.toString(args));
298                    return;
299                }
300                Log.v(LOGTAG, "RelroFileCreator (64bit = " + is64Bit + "), " +
301                        " 32-bit lib: " + args[0] + ", 64-bit lib: " + args[1]);
302                if (!sAddressSpaceReserved) {
303                    Log.e(LOGTAG, "can't create relro file; address space not reserved");
304                    return;
305                }
306                result = nativeCreateRelroFile(args[0] /* path32 */,
307                                               args[1] /* path64 */,
308                                               CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
309                                               CHROMIUM_WEBVIEW_NATIVE_RELRO_64);
310                if (DEBUG) Log.v(LOGTAG, "created relro file");
311            } finally {
312                // We must do our best to always notify the update service, even if something fails.
313                try {
314                    getUpdateService().notifyRelroCreationCompleted(is64Bit, result);
315                } catch (RemoteException e) {
316                    Log.e(LOGTAG, "error notifying update service", e);
317                }
318
319                if (!result) Log.e(LOGTAG, "failed to create relro file");
320
321                // Must explicitly exit or else this process will just sit around after we return.
322                System.exit(0);
323            }
324        }
325    }
326
327    private static void loadNativeLibrary() {
328        if (!sAddressSpaceReserved) {
329            Log.e(LOGTAG, "can't load with relro file; address space not reserved");
330            return;
331        }
332
333        try {
334            getUpdateService().waitForRelroCreationCompleted(VMRuntime.getRuntime().is64Bit());
335        } catch (RemoteException e) {
336            Log.e(LOGTAG, "error waiting for relro creation, proceeding without", e);
337            return;
338        }
339
340        try {
341            String[] args = getWebViewNativeLibraryPaths();
342            boolean result = nativeLoadWithRelroFile(args[0] /* path32 */,
343                                                     args[1] /* path64 */,
344                                                     CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
345                                                     CHROMIUM_WEBVIEW_NATIVE_RELRO_64);
346            if (!result) {
347                Log.w(LOGTAG, "failed to load with relro file, proceeding without");
348            } else if (DEBUG) {
349                Log.v(LOGTAG, "loaded with relro file");
350            }
351        } catch (PackageManager.NameNotFoundException e) {
352            Log.e(LOGTAG, "Failed to list WebView package libraries for loadNativeLibrary", e);
353        }
354    }
355
356    private static IWebViewUpdateService getUpdateService() {
357        return IWebViewUpdateService.Stub.asInterface(ServiceManager.getService("webviewupdate"));
358    }
359
360    private static native boolean nativeReserveAddressSpace(long addressSpaceToReserve);
361    private static native boolean nativeCreateRelroFile(String lib32, String lib64,
362                                                        String relro32, String relro64);
363    private static native boolean nativeLoadWithRelroFile(String lib32, String lib64,
364                                                          String relro32, String relro64);
365}
366