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