blob: bd5dbf6e7b588b348f8f184bd23f2f1e5fe1b34b [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package com.android.webview.chromium;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.net.Uri;
import android.os.Build;
import android.os.StrictMode;
import android.os.SystemClock;
import android.os.UserManager;
import android.provider.Settings;
import android.util.Log;
import android.view.ViewGroup;
import android.webkit.CookieManager;
import android.webkit.GeolocationPermissions;
import android.webkit.ServiceWorkerController;
import android.webkit.TokenBindingService;
import android.webkit.ValueCallback;
import android.webkit.WebStorage;
import android.webkit.WebView;
import android.webkit.WebViewDatabase;
import android.webkit.WebViewFactory;
import android.webkit.WebViewFactoryProvider;
import android.webkit.WebViewProvider;
import com.android.webview.chromium.WebViewDelegateFactory.WebViewDelegate;
import org.chromium.android_webview.AwAutofillProvider;
import org.chromium.android_webview.AwBrowserContext;
import org.chromium.android_webview.AwBrowserProcess;
import org.chromium.android_webview.ResourcesContextWrapperFactory;
import org.chromium.android_webview.ScopedSysTraceEvent;
import org.chromium.android_webview.WebViewChromiumRunQueue;
import org.chromium.android_webview.command_line.CommandLineUtil;
import org.chromium.base.BuildInfo;
import org.chromium.base.CommandLine;
import org.chromium.base.ContextUtils;
import org.chromium.base.PackageUtils;
import org.chromium.base.PathUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.base.library_loader.NativeLibraries;
import org.chromium.base.metrics.CachedMetrics.TimesHistogramSample;
import org.chromium.components.autofill.AutofillProvider;
import org.chromium.content_public.browser.LGEmailActionModeWorkaround;
import java.io.File;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
/**
* Entry point to the WebView. The system framework talks to this class to get instances of the
* implementation classes.
*/
@SuppressWarnings("deprecation")
public class WebViewChromiumFactoryProvider implements WebViewFactoryProvider {
private static final String TAG = "WebViewChromiumFactoryProvider";
private static final String CHROMIUM_PREFS_NAME = "WebViewChromiumPrefs";
private static final String VERSION_CODE_PREF = "lastVersionCodeUsed";
private final static Object sSingletonLock = new Object();
private static WebViewChromiumFactoryProvider sSingleton;
private final WebViewChromiumRunQueue mRunQueue = new WebViewChromiumRunQueue(
() -> { return WebViewChromiumFactoryProvider.this.mAwInit.hasStarted(); });
/* package */ WebViewChromiumRunQueue getRunQueue() {
return mRunQueue;
}
// We have a 4 second timeout to try to detect deadlocks to detect and aid in debuggin
// deadlocks.
// Do not call this method while on the UI thread!
/* package */ void runVoidTaskOnUiThreadBlocking(Runnable r) {
mRunQueue.runVoidTaskOnUiThreadBlocking(r);
}
/* package */ <T> T runOnUiThreadBlocking(Callable<T> c) {
return mRunQueue.runBlockingFuture(new FutureTask<T>(c));
}
/* package */ void addTask(Runnable task) {
mRunQueue.addTask(task);
}
/**
* Class that takes care of chromium lazy initialization.
* This is package-public so that a downstream subclass can access it.
*/
/* package */ WebViewChromiumAwInit mAwInit;
private SharedPreferences mWebViewPrefs;
private WebViewDelegate mWebViewDelegate;
boolean mShouldDisableThreadChecking;
// Initialization guarded by mAwInit.getLock()
private Statics mStaticsAdapter;
private Object mServiceWorkerControllerAdapter;
/**
* Thread-safe way to set the one and only WebViewChromiumFactoryProvider.
*/
private static void setSingleton(WebViewChromiumFactoryProvider provider) {
synchronized (sSingletonLock) {
if (sSingleton != null) {
throw new RuntimeException(
"WebViewChromiumFactoryProvider should only be set once!");
}
sSingleton = provider;
}
}
/**
* Thread-safe way to get the one and only WebViewChromiumFactoryProvider.
*/
static WebViewChromiumFactoryProvider getSingleton() {
synchronized (sSingletonLock) {
if (sSingleton == null) {
throw new RuntimeException("WebViewChromiumFactoryProvider has not been set!");
}
return sSingleton;
}
}
/**
* Entry point for newer versions of Android.
*/
public static WebViewChromiumFactoryProvider create(android.webkit.WebViewDelegate delegate) {
return new WebViewChromiumFactoryProvider(delegate);
}
/**
* Constructor called by the API 21 version of {@link WebViewFactory} and earlier.
*/
public WebViewChromiumFactoryProvider() {
initialize(WebViewDelegateFactory.createApi21CompatibilityDelegate());
}
/**
* Constructor called by the API 22 version of {@link WebViewFactory} and later.
*/
public WebViewChromiumFactoryProvider(android.webkit.WebViewDelegate delegate) {
initialize(WebViewDelegateFactory.createProxyDelegate(delegate));
}
/**
* Constructor for internal use when a proxy delegate has already been created.
*/
WebViewChromiumFactoryProvider(WebViewDelegate delegate) {
initialize(delegate);
}
// Protected to allow downstream to override.
protected WebViewChromiumAwInit createAwInit() {
return new WebViewChromiumAwInit(this);
}
private void deleteContentsOnPackageDowngrade(PackageInfo packageInfo) {
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
try (ScopedSysTraceEvent e2 = ScopedSysTraceEvent.scoped(
"WebViewChromiumFactoryProvider.deleteContentsOnPackageDowngrade")) {
// Use shared preference to check for package downgrade.
// Since N, getSharedPreferences creates the preference dir if it doesn't exist,
// causing a disk write.
mWebViewPrefs = ContextUtils.getApplicationContext().getSharedPreferences(
CHROMIUM_PREFS_NAME, Context.MODE_PRIVATE);
int lastVersion = mWebViewPrefs.getInt(VERSION_CODE_PREF, 0);
int currentVersion = packageInfo.versionCode;
if (!versionCodeGE(currentVersion, lastVersion)) {
// The WebView package has been downgraded since we last ran in this
// application. Delete the WebView data directory's contents.
String dataDir = PathUtils.getDataDirectory();
Log.i(TAG,
"WebView package downgraded from " + lastVersion + " to "
+ currentVersion + "; deleting contents of " + dataDir);
deleteContents(new File(dataDir));
}
if (lastVersion != currentVersion) {
mWebViewPrefs.edit().putInt(VERSION_CODE_PREF, currentVersion).apply();
}
} finally {
StrictMode.setThreadPolicy(oldPolicy);
}
}
@TargetApi(Build.VERSION_CODES.N) // For getSystemService() and isUserUnlocked().
private void initialize(WebViewDelegate webViewDelegate) {
long startTime = SystemClock.elapsedRealtime();
try (ScopedSysTraceEvent e1 =
ScopedSysTraceEvent.scoped("WebViewChromiumFactoryProvider.initialize")) {
// The package is used to locate the services for copying crash minidumps and requesting
// variations seeds. So it must be set before initializing variations and before a
// renderer has a chance to crash.
PackageInfo packageInfo = WebViewFactory.getLoadedPackageInfo();
AwBrowserProcess.setWebViewPackageName(packageInfo.packageName);
mAwInit = createAwInit();
mWebViewDelegate = webViewDelegate;
Context ctx = mWebViewDelegate.getApplication().getApplicationContext();
// If the application context is DE, but we have credentials, use a CE context instead
try (ScopedSysTraceEvent e2 = ScopedSysTraceEvent.scoped(
"WebViewChromiumFactoryProvider.checkStorage")) {
checkStorageIsNotDeviceProtected(mWebViewDelegate.getApplication());
} catch (IllegalArgumentException e) {
assert Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
if (!ctx.getSystemService(UserManager.class).isUserUnlocked()) {
throw e;
}
ctx = ctx.createCredentialProtectedStorageContext();
}
// WebView needs to make sure to always use the wrapped application context.
ContextUtils.initApplicationContext(ResourcesContextWrapperFactory.get(ctx));
mAwInit.setUpResourcesOnBackgroundThread(
packageInfo, ContextUtils.getApplicationContext());
try (ScopedSysTraceEvent e2 = ScopedSysTraceEvent.scoped(
"WebViewChromiumFactoryProvider.initCommandLine")) {
// This may take ~20 ms only on userdebug devices.
CommandLineUtil.initCommandLine();
}
boolean multiProcess = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Ask the system if multiprocess should be enabled on O+.
multiProcess = mWebViewDelegate.isMultiProcessEnabled();
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// Check the multiprocess developer setting directly on N.
multiProcess = Settings.Global.getInt(
ContextUtils.getApplicationContext().getContentResolver(),
Settings.Global.WEBVIEW_MULTIPROCESS, 0)
== 1;
}
if (multiProcess) {
CommandLine cl = CommandLine.getInstance();
cl.appendSwitch("webview-sandboxed-renderer");
}
ThreadUtils.setWillOverrideUiThread();
BuildInfo.setBrowserPackageInfo(packageInfo);
try (ScopedSysTraceEvent e2 = ScopedSysTraceEvent.scoped(
"WebViewChromiumFactoryProvider.loadChromiumLibrary")) {
AwBrowserProcess.loadLibrary(mWebViewDelegate.getDataDirectorySuffix());
}
try (ScopedSysTraceEvent e2 = ScopedSysTraceEvent.scoped(
"WebViewChromiumFactoryProvider.loadGlueLayerPlatSupportLibrary")) {
System.loadLibrary("webviewchromium_plat_support");
}
deleteContentsOnPackageDowngrade(packageInfo);
// Now safe to use WebView data directory.
mAwInit.startVariationsInit();
mShouldDisableThreadChecking =
shouldDisableThreadChecking(ContextUtils.getApplicationContext());
setSingleton(this);
} finally {
TimesHistogramSample histogram = new TimesHistogramSample(
"Android.WebView.Startup.CreationTime.Stage1.FactoryInit",
TimeUnit.MILLISECONDS);
histogram.record(SystemClock.elapsedRealtime() - startTime);
}
}
/* package */ static void checkStorageIsNotDeviceProtected(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && context.isDeviceProtectedStorage()) {
throw new IllegalArgumentException(
"WebView cannot be used with device protected storage");
}
}
/**
* Both versionCodes should be from a WebView provider package implemented by Chromium.
* VersionCodes from other kinds of packages won't make any sense in this method.
*
* An introduction to Chromium versionCode scheme:
* "BBBBPPPAX"
* BBBB: 4 digit branch number. It monotonically increases over time.
* PPP: patch number in the branch. It is padded with zeroes to the left. These three digits may
* change their meaning in the future.
* A: architecture digit.
* X: A digit to differentiate APKs for other reasons.
*
* This method takes the "BBBB" of versionCodes and compare them.
*
* @return true if versionCode1 is higher than or equal to versionCode2.
*/
private static boolean versionCodeGE(int versionCode1, int versionCode2) {
int v1 = versionCode1 / 100000;
int v2 = versionCode2 / 100000;
return v1 >= v2;
}
private static void deleteContents(File dir) {
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
deleteContents(file);
}
if (!file.delete()) {
Log.w(TAG, "Failed to delete " + file);
}
}
}
}
public static boolean preloadInZygote() {
for (String library : NativeLibraries.LIBRARIES) {
System.loadLibrary(library);
}
return true;
}
SharedPreferences getWebViewPrefs() {
return mWebViewPrefs;
}
@Override
public Statics getStatics() {
synchronized (mAwInit.getLock()) {
SharedStatics sharedStatics = mAwInit.getStatics();
if (mStaticsAdapter == null) {
mStaticsAdapter = new WebViewChromiumFactoryProvider.Statics() {
@Override
public String findAddress(String addr) {
return sharedStatics.findAddress(addr);
}
@Override
public String getDefaultUserAgent(Context context) {
return sharedStatics.getDefaultUserAgent(context);
}
@Override
public void setWebContentsDebuggingEnabled(boolean enable) {
sharedStatics.setWebContentsDebuggingEnabled(enable);
}
@Override
public void clearClientCertPreferences(Runnable onCleared) {
sharedStatics.clearClientCertPreferences(onCleared);
}
@Override
public void freeMemoryForTests() {
sharedStatics.freeMemoryForTests();
}
@Override
public void enableSlowWholeDocumentDraw() {
sharedStatics.enableSlowWholeDocumentDraw();
}
@Override
public Uri[] parseFileChooserResult(int resultCode, Intent intent) {
return sharedStatics.parseFileChooserResult(resultCode, intent);
}
@Override
public void initSafeBrowsing(Context context, ValueCallback<Boolean> callback) {
sharedStatics.initSafeBrowsing(
context, CallbackConverter.fromValueCallback(callback));
}
@Override
public void setSafeBrowsingWhitelist(
List<String> urls, ValueCallback<Boolean> callback) {
sharedStatics.setSafeBrowsingWhitelist(
urls, CallbackConverter.fromValueCallback(callback));
}
@Override
public Uri getSafeBrowsingPrivacyPolicyUrl() {
return sharedStatics.getSafeBrowsingPrivacyPolicyUrl();
}
};
}
}
return mStaticsAdapter;
}
@Override
public WebViewProvider createWebView(WebView webView, WebView.PrivateAccess privateAccess) {
return new WebViewChromium(this, webView, privateAccess, mShouldDisableThreadChecking);
}
// Workaround for IME thread crashes on grandfathered OEM apps.
private boolean shouldDisableThreadChecking(Context context) {
String appName = context.getPackageName();
int versionCode = PackageUtils.getPackageVersion(context, appName);
int appTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
if (versionCode == -1) return false;
boolean shouldDisable = false;
// crbug.com/651706
final String lgeMailPackageId = "com.lge.email";
if (lgeMailPackageId.equals(appName)) {
if (appTargetSdkVersion > Build.VERSION_CODES.N) return false;
if (LGEmailActionModeWorkaround.isSafeVersion(versionCode)) return false;
shouldDisable = true;
}
// crbug.com/655759
// Also want to cover ".att" variant suffix package name.
final String yahooMailPackageId = "com.yahoo.mobile.client.android.mail";
if (appName.startsWith(yahooMailPackageId)) {
if (appTargetSdkVersion > Build.VERSION_CODES.M) return false;
if (versionCode > 1315850) return false;
shouldDisable = true;
}
// crbug.com/622151
final String htcMailPackageId = "com.htc.android.mail";
if (htcMailPackageId.equals(appName)) {
if (appTargetSdkVersion > Build.VERSION_CODES.M) return false;
// This value is provided by HTC.
if (versionCode >= 866001861) return false;
shouldDisable = true;
}
if (shouldDisable) {
Log.w(TAG, "Disabling thread check in WebView. "
+ "APK name: " + appName + ", versionCode: " + versionCode
+ ", targetSdkVersion: " + appTargetSdkVersion);
}
return shouldDisable;
}
@Override
public GeolocationPermissions getGeolocationPermissions() {
return mAwInit.getGeolocationPermissions();
}
@Override
public CookieManager getCookieManager() {
return mAwInit.getCookieManager();
}
@Override
public ServiceWorkerController getServiceWorkerController() {
synchronized (mAwInit.getLock()) {
if (mServiceWorkerControllerAdapter == null) {
mServiceWorkerControllerAdapter =
new ServiceWorkerControllerAdapter(mAwInit.getServiceWorkerController());
}
}
return (ServiceWorkerController) mServiceWorkerControllerAdapter;
}
@Override
public TokenBindingService getTokenBindingService() {
return mAwInit.getTokenBindingService();
}
@Override
public android.webkit.WebIconDatabase getWebIconDatabase() {
return mAwInit.getWebIconDatabase();
}
@Override
public WebStorage getWebStorage() {
return mAwInit.getWebStorage();
}
@Override
public WebViewDatabase getWebViewDatabase(final Context context) {
return mAwInit.getWebViewDatabase(context);
}
WebViewDelegate getWebViewDelegate() {
return mWebViewDelegate;
}
WebViewContentsClientAdapter createWebViewContentsClientAdapter(WebView webView,
Context context) {
try (ScopedSysTraceEvent e = ScopedSysTraceEvent.scoped(
"WebViewChromiumFactoryProvider.insideCreateWebViewContentsClientAdapter")) {
return new WebViewContentsClientAdapter(webView, context, mWebViewDelegate);
}
}
AutofillProvider createAutofillProvider(Context context, ViewGroup containerView) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return null;
return new AwAutofillProvider(context, containerView);
}
void startYourEngines(boolean onMainThread) {
try (ScopedSysTraceEvent e1 = ScopedSysTraceEvent.scoped(
"WebViewChromiumFactoryProvider.startYourEngines")) {
mAwInit.startYourEngines(onMainThread);
}
}
boolean hasStarted() {
return mAwInit.hasStarted();
}
// Only on UI thread.
AwBrowserContext getBrowserContextOnUiThread() {
return mAwInit.getBrowserContextOnUiThread();
}
WebViewChromiumAwInit getAwInit() {
return mAwInit;
}
}