WebViewFactory.java revision 1b7977b608cd07366a1708aba36d48203f85bbbd
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.text.TextUtils;
31import android.util.AndroidRuntimeException;
32import android.util.Log;
33import com.android.server.LocalServices;
34import dalvik.system.VMRuntime;
35
36import java.io.File;
37import java.util.Arrays;
38
39import com.android.internal.os.Zygote;
40
41/**
42 * Top level factory, used creating all the main WebView implementation classes.
43 *
44 * @hide
45 */
46public final class WebViewFactory {
47
48    private static final String CHROMIUM_WEBVIEW_FACTORY =
49            "com.android.webview.chromium.WebViewChromiumFactoryProvider";
50
51    private static final String NULL_WEBVIEW_FACTORY =
52            "com.android.webview.nullwebview.NullWebViewFactoryProvider";
53
54    private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_32 =
55            "/data/misc/shared_relro/libwebviewchromium32.relro";
56    private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_64 =
57            "/data/misc/shared_relro/libwebviewchromium64.relro";
58
59    // TODO: remove the library paths below as we now query the package manager.
60    // The remaining step is to refactor address space reservation.
61    private static final String CHROMIUM_WEBVIEW_NATIVE_LIB_32 =
62            "/system/lib/libwebviewchromium.so";
63    private static final String CHROMIUM_WEBVIEW_NATIVE_LIB_64 =
64            "/system/lib64/libwebviewchromium.so";
65
66    private static final String LOGTAG = "WebViewFactory";
67
68    private static final boolean DEBUG = false;
69
70    // Cache the factory both for efficiency, and ensure any one process gets all webviews from the
71    // same provider.
72    private static WebViewFactoryProvider sProviderInstance;
73    private static final Object sProviderLock = new Object();
74    private static boolean sAddressSpaceReserved = false;
75
76    public static String getWebViewPackageName() {
77        // TODO: Make this dynamic based on resource configuration.
78        return "com.android.webview";
79    }
80
81    static WebViewFactoryProvider getProvider() {
82        synchronized (sProviderLock) {
83            // For now the main purpose of this function (and the factory abstraction) is to keep
84            // us honest and minimize usage of WebView internals when binding the proxy.
85            if (sProviderInstance != null) return sProviderInstance;
86
87            loadNativeLibrary();
88
89            Class<WebViewFactoryProvider> providerClass;
90            try {
91                providerClass = getFactoryClass();
92            } catch (ClassNotFoundException e) {
93                Log.e(LOGTAG, "error loading provider", e);
94                throw new AndroidRuntimeException(e);
95            }
96
97            StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
98            try {
99                sProviderInstance = providerClass.newInstance();
100                if (DEBUG) Log.v(LOGTAG, "Loaded provider: " + sProviderInstance);
101                return sProviderInstance;
102            } catch (Exception e) {
103                Log.e(LOGTAG, "error instantiating provider", e);
104                throw new AndroidRuntimeException(e);
105            } finally {
106                StrictMode.setThreadPolicy(oldPolicy);
107            }
108        }
109    }
110
111    private static Class<WebViewFactoryProvider> getFactoryClass() throws ClassNotFoundException {
112        Application initialApplication = AppGlobals.getInitialApplication();
113        try {
114            Context webViewContext = initialApplication.createPackageContext(
115                    getWebViewPackageName(),
116                    Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
117            initialApplication.getAssets().addAssetPath(
118                    webViewContext.getApplicationInfo().sourceDir);
119            ClassLoader clazzLoader = webViewContext.getClassLoader();
120            return (Class<WebViewFactoryProvider>) Class.forName(CHROMIUM_WEBVIEW_FACTORY, true,
121                                                                 clazzLoader);
122        } catch (PackageManager.NameNotFoundException e) {
123            Log.e(LOGTAG, "Chromium WebView package does not exist");
124            return (Class<WebViewFactoryProvider>) Class.forName(NULL_WEBVIEW_FACTORY);
125        }
126    }
127
128    /**
129     * Perform any WebView loading preparations that must happen in the zygote.
130     * Currently, this means allocating address space to load the real JNI library later.
131     */
132    public static void prepareWebViewInZygote() {
133        try {
134            System.loadLibrary("webviewchromium_loader");
135            sAddressSpaceReserved = nativeReserveAddressSpace(CHROMIUM_WEBVIEW_NATIVE_LIB_32,
136                                                              CHROMIUM_WEBVIEW_NATIVE_LIB_64);
137            if (sAddressSpaceReserved) {
138                if (DEBUG) Log.v(LOGTAG, "address space reserved");
139            } else {
140                Log.e(LOGTAG, "reserving address space failed");
141            }
142        } catch (Throwable t) {
143            // Log and discard errors at this stage as we must not crash the zygote.
144            Log.e(LOGTAG, "error preparing native loader", t);
145        }
146    }
147
148    /**
149     * Perform any WebView loading preparations that must happen at boot from the system server,
150     * after the package manager has started.
151     * This must be called in the system server.
152     * Currently, this means spawning the child processes which will create the relro files.
153     */
154    public static void prepareWebViewInSystemServer() {
155        if (DEBUG) Log.v(LOGTAG, "creating relro files");
156        if (new File(CHROMIUM_WEBVIEW_NATIVE_LIB_64).exists()) {
157            createRelroFile(true /* is64Bit */);
158        }
159        if (new File(CHROMIUM_WEBVIEW_NATIVE_LIB_32).exists()) {
160            createRelroFile(false /* is64Bit */);
161        }
162    }
163
164    private static String[] getWebViewNativeLibraryPaths()
165            throws PackageManager.NameNotFoundException {
166        final String NATIVE_LIB_FILE_NAME = "libwebviewchromium.so";
167
168        PackageManager pm = AppGlobals.getInitialApplication().getPackageManager();
169        ApplicationInfo ai = pm.getApplicationInfo(getWebViewPackageName(), 0);
170
171        String path32;
172        String path64;
173        boolean primaryArchIs64bit = VMRuntime.is64BitAbi(ai.primaryCpuAbi);
174        if (!TextUtils.isEmpty(ai.secondaryCpuAbi)) {
175            // Multi-arch case.
176            if (primaryArchIs64bit) {
177                // Primary arch: 64-bit, secondary: 32-bit.
178                path64 = ai.nativeLibraryDir;
179                path32 = ai.secondaryNativeLibraryDir;
180            } else {
181                // Primary arch: 32-bit, secondary: 64-bit.
182                path64 = ai.secondaryNativeLibraryDir;
183                path32 = ai.nativeLibraryDir;
184            }
185        } else if (primaryArchIs64bit) {
186            // Single-arch 64-bit.
187            path64 = ai.nativeLibraryDir;
188            path32 = "";
189        } else {
190            // Single-arch 32-bit.
191            path32 = ai.nativeLibraryDir;
192            path64 = "";
193        }
194        if (!TextUtils.isEmpty(path32)) path32 += "/" + NATIVE_LIB_FILE_NAME;
195        if (!TextUtils.isEmpty(path64)) path64 += "/" + NATIVE_LIB_FILE_NAME;
196        return new String[] { path32, path64 };
197    }
198
199    private static void createRelroFile(final boolean is64Bit) {
200        final String abi =
201                is64Bit ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0];
202
203        // crashHandler is invoked by the ActivityManagerService when the isolated process crashes.
204        Runnable crashHandler = new Runnable() {
205            @Override
206            public void run() {
207                try {
208                    Log.e(LOGTAG, "relro file creator for " + abi + " crashed. Proceeding without");
209                    getUpdateService().notifyRelroCreationCompleted(is64Bit, false);
210                } catch (RemoteException e) {
211                    Log.e(LOGTAG, "Cannot reach WebViewUpdateService. " + e.getMessage());
212                }
213            }
214        };
215
216        try {
217            String[] args = getWebViewNativeLibraryPaths();
218            LocalServices.getService(ActivityManagerInternal.class).startIsolatedProcess(
219                    RelroFileCreator.class.getName(), args, "WebViewLoader-" + abi, abi,
220                    Process.SHARED_RELRO_UID, crashHandler);
221        } catch (Throwable t) {
222            // Log and discard errors as we must not crash the system server.
223            Log.e(LOGTAG, "error starting relro file creator for abi " + abi, t);
224            crashHandler.run();
225        }
226    }
227
228    private static class RelroFileCreator {
229        // Called in an unprivileged child process to create the relro file.
230        public static void main(String[] args) {
231            try{
232                if (args.length != 2 || args[0] == null || args[1] == null) {
233                    Log.e(LOGTAG, "Invalid RelroFileCreator args: " + Arrays.toString(args));
234                    return;
235                }
236
237                boolean is64Bit = VMRuntime.getRuntime().is64Bit();
238                Log.v(LOGTAG, "RelroFileCreator (64bit = " + is64Bit + "), " +
239                        " 32-bit lib: " + args[0] + ", 64-bit lib: " + args[1]);
240                if (!sAddressSpaceReserved) {
241                    Log.e(LOGTAG, "can't create relro file; address space not reserved");
242                    return;
243                }
244                boolean result = nativeCreateRelroFile(args[0] /* path32 */,
245                                                       args[1] /* path64 */,
246                                                       CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
247                                                       CHROMIUM_WEBVIEW_NATIVE_RELRO_64);
248                if (!result) {
249                    Log.e(LOGTAG, "failed to create relro file");
250                } else if (DEBUG) {
251                    Log.v(LOGTAG, "created relro file");
252                }
253                try {
254                    getUpdateService().notifyRelroCreationCompleted(is64Bit, result);
255                } catch (RemoteException e) {
256                    Log.e(LOGTAG, "error notifying update service", e);
257                }
258            } finally {
259                // Must explicitly exit or else this process will just sit around after we return.
260                System.exit(0);
261            }
262        }
263    }
264
265    private static void loadNativeLibrary() {
266        if (!sAddressSpaceReserved) {
267            Log.e(LOGTAG, "can't load with relro file; address space not reserved");
268            return;
269        }
270
271        try {
272            getUpdateService().waitForRelroCreationCompleted(VMRuntime.getRuntime().is64Bit());
273        } catch (RemoteException e) {
274            Log.e(LOGTAG, "error waiting for relro creation, proceeding without", e);
275            return;
276        }
277
278        try {
279            String[] args = getWebViewNativeLibraryPaths();
280            boolean result = nativeLoadWithRelroFile(args[0] /* path32 */,
281                                                     args[1] /* path64 */,
282                                                     CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
283                                                     CHROMIUM_WEBVIEW_NATIVE_RELRO_64);
284            if (!result) {
285                Log.w(LOGTAG, "failed to load with relro file, proceeding without");
286            } else if (DEBUG) {
287                Log.v(LOGTAG, "loaded with relro file");
288            }
289        } catch (PackageManager.NameNotFoundException e) {
290            Log.e(LOGTAG, "Failed to list WebView package libraries for loadNativeLibrary", e);
291        }
292    }
293
294    private static IWebViewUpdateService getUpdateService() {
295        return IWebViewUpdateService.Stub.asInterface(ServiceManager.getService("webviewupdate"));
296    }
297
298    private static native boolean nativeReserveAddressSpace(String lib32, String lib64);
299    private static native boolean nativeCreateRelroFile(String lib32, String lib64,
300                                                        String relro32, String relro64);
301    private static native boolean nativeLoadWithRelroFile(String lib32, String lib64,
302                                                          String relro32, String relro64);
303}
304