WebViewFactory.java revision 161536b5970ba5ab43233e7695ef69ba2bb804f4
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 int pid = LocalServices.getService(ActivityManagerInternal.class).startIsolatedProcess( 219 RelroFileCreator.class.getName(), args, "WebViewLoader-" + abi, abi, 220 Process.SHARED_RELRO_UID, crashHandler); 221 if (pid <= 0) throw new Exception("Failed to start the isolated process"); 222 } catch (Throwable t) { 223 // Log and discard errors as we must not crash the system server. 224 Log.e(LOGTAG, "error starting relro file creator for abi " + abi, t); 225 crashHandler.run(); 226 } 227 } 228 229 private static class RelroFileCreator { 230 // Called in an unprivileged child process to create the relro file. 231 public static void main(String[] args) { 232 boolean result = false; 233 boolean is64Bit = VMRuntime.getRuntime().is64Bit(); 234 try{ 235 if (args.length != 2 || args[0] == null || args[1] == null) { 236 Log.e(LOGTAG, "Invalid RelroFileCreator args: " + Arrays.toString(args)); 237 return; 238 } 239 Log.v(LOGTAG, "RelroFileCreator (64bit = " + is64Bit + "), " + 240 " 32-bit lib: " + args[0] + ", 64-bit lib: " + args[1]); 241 if (!sAddressSpaceReserved) { 242 Log.e(LOGTAG, "can't create relro file; address space not reserved"); 243 return; 244 } 245 result = nativeCreateRelroFile(args[0] /* path32 */, 246 args[1] /* path64 */, 247 CHROMIUM_WEBVIEW_NATIVE_RELRO_32, 248 CHROMIUM_WEBVIEW_NATIVE_RELRO_64); 249 if (DEBUG) Log.v(LOGTAG, "created relro file"); 250 } finally { 251 // We must do our best to always notify the update service, even if something fails. 252 try { 253 getUpdateService().notifyRelroCreationCompleted(is64Bit, result); 254 } catch (RemoteException e) { 255 Log.e(LOGTAG, "error notifying update service", e); 256 } 257 258 if (!result) Log.e(LOGTAG, "failed to create relro file"); 259 260 // Must explicitly exit or else this process will just sit around after we return. 261 System.exit(0); 262 } 263 } 264 } 265 266 private static void loadNativeLibrary() { 267 if (!sAddressSpaceReserved) { 268 Log.e(LOGTAG, "can't load with relro file; address space not reserved"); 269 return; 270 } 271 272 try { 273 getUpdateService().waitForRelroCreationCompleted(VMRuntime.getRuntime().is64Bit()); 274 } catch (RemoteException e) { 275 Log.e(LOGTAG, "error waiting for relro creation, proceeding without", e); 276 return; 277 } 278 279 try { 280 String[] args = getWebViewNativeLibraryPaths(); 281 boolean result = nativeLoadWithRelroFile(args[0] /* path32 */, 282 args[1] /* path64 */, 283 CHROMIUM_WEBVIEW_NATIVE_RELRO_32, 284 CHROMIUM_WEBVIEW_NATIVE_RELRO_64); 285 if (!result) { 286 Log.w(LOGTAG, "failed to load with relro file, proceeding without"); 287 } else if (DEBUG) { 288 Log.v(LOGTAG, "loaded with relro file"); 289 } 290 } catch (PackageManager.NameNotFoundException e) { 291 Log.e(LOGTAG, "Failed to list WebView package libraries for loadNativeLibrary", e); 292 } 293 } 294 295 private static IWebViewUpdateService getUpdateService() { 296 return IWebViewUpdateService.Stub.asInterface(ServiceManager.getService("webviewupdate")); 297 } 298 299 private static native boolean nativeReserveAddressSpace(String lib32, String lib64); 300 private static native boolean nativeCreateRelroFile(String lib32, String lib64, 301 String relro32, String relro64); 302 private static native boolean nativeLoadWithRelroFile(String lib32, String lib64, 303 String relro32, String relro64); 304} 305