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