diff options
author | Marcin Kosiba <mkosiba@chromium.org> | 2014-12-11 12:28:01 +0000 |
---|---|---|
committer | Marcin Kosiba <mkosiba@chromium.org> | 2014-12-11 12:29:34 +0000 |
commit | 206d4d3e569857c3e8f11e4f8364225688ea8507 (patch) | |
tree | 0665388f7010ee31e7b16698f224a9ed15eacb6a /android_webview | |
parent | 326e264fa6d71cca3549768b0ccff7f094052fff (diff) | |
download | chromium_src-206d4d3e569857c3e8f11e4f8364225688ea8507.zip chromium_src-206d4d3e569857c3e8f11e4f8364225688ea8507.tar.gz chromium_src-206d4d3e569857c3e8f11e4f8364225688ea8507.tar.bz2 |
[android_webview] Move the glue layer into android_webview/glue
This checks in a copy of the WebView glue layer from master-chromium
at revision d519b07bd3cb09b571689b00250cb5de977b4dfc.
Changes to make the code adhere to the style guide, fix up warnigns
and actually switch over to this from the copy in third_party will
be made as subsequent CLs.
BUG=440792
TEST=None
R=benm@chromium.org, rmcilroy@chromium.org
Review URL: https://codereview.chromium.org/795033002
Cr-Commit-Position: refs/heads/master@{#307888}
Diffstat (limited to 'android_webview')
19 files changed, 5708 insertions, 0 deletions
diff --git a/android_webview/glue/java/DEPS b/android_webview/glue/java/DEPS new file mode 100644 index 0000000..794ecd7e --- /dev/null +++ b/android_webview/glue/java/DEPS @@ -0,0 +1,5 @@ +include_rules = [ + "+android_webview/java", + "+base/android/java", + "+content/public/android/java", +] diff --git a/android_webview/glue/java/src/com/android/webview/chromium/ContentSettingsAdapter.java b/android_webview/glue/java/src/com/android/webview/chromium/ContentSettingsAdapter.java new file mode 100644 index 0000000..88f6551 --- /dev/null +++ b/android_webview/glue/java/src/com/android/webview/chromium/ContentSettingsAdapter.java @@ -0,0 +1,606 @@ +// 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.util.Log; +import android.webkit.WebSettings.LayoutAlgorithm; +import android.webkit.WebSettings.PluginState; +import android.webkit.WebSettings.RenderPriority; +import android.webkit.WebSettings.TextSize; +import android.webkit.WebSettings.ZoomDensity; + +import org.chromium.android_webview.AwSettings; + +public class ContentSettingsAdapter extends android.webkit.WebSettings { + + private static final String LOGTAG = ContentSettingsAdapter.class.getSimpleName(); + private static final boolean TRACE = false; + + private AwSettings mAwSettings; + + public ContentSettingsAdapter(AwSettings awSettings) { + mAwSettings = awSettings; + } + + AwSettings getAwSettings() { + return mAwSettings; + } + + @Override + @Deprecated + public void setNavDump(boolean enabled) { + // Intentional no-op. + } + + @Override + @Deprecated + public boolean getNavDump() { + // Intentional no-op. + return false; + } + + @Override + public void setSupportZoom(boolean support) { + if (TRACE) Log.d(LOGTAG, "setSupportZoom=" + support); + mAwSettings.setSupportZoom(support); + } + + @Override + public boolean supportZoom() { + return mAwSettings.supportZoom(); + } + + @Override + public void setBuiltInZoomControls(boolean enabled) { + if (TRACE) Log.d(LOGTAG, "setBuiltInZoomControls=" + enabled); + mAwSettings.setBuiltInZoomControls(enabled); + } + + @Override + public boolean getBuiltInZoomControls() { + return mAwSettings.getBuiltInZoomControls(); + } + + @Override + public void setDisplayZoomControls(boolean enabled) { + if (TRACE) Log.d(LOGTAG, "setDisplayZoomControls=" + enabled); + mAwSettings.setDisplayZoomControls(enabled); + } + + @Override + public boolean getDisplayZoomControls() { + return mAwSettings.getDisplayZoomControls(); + } + + @Override + public void setAllowFileAccess(boolean allow) { + if (TRACE) Log.d(LOGTAG, "setAllowFileAccess=" + allow); + mAwSettings.setAllowFileAccess(allow); + } + + @Override + public boolean getAllowFileAccess() { + return mAwSettings.getAllowFileAccess(); + } + + @Override + public void setAllowContentAccess(boolean allow) { + if (TRACE) Log.d(LOGTAG, "setAllowContentAccess=" + allow); + mAwSettings.setAllowContentAccess(allow); + } + + @Override + public boolean getAllowContentAccess() { + return mAwSettings.getAllowContentAccess(); + } + + @Override + public void setLoadWithOverviewMode(boolean overview) { + if (TRACE) Log.d(LOGTAG, "setLoadWithOverviewMode=" + overview); + mAwSettings.setLoadWithOverviewMode(overview); + } + + @Override + public boolean getLoadWithOverviewMode() { + return mAwSettings.getLoadWithOverviewMode(); + } + + @Override + public void setAcceptThirdPartyCookies(boolean accept) { + if (TRACE) Log.d(LOGTAG, "setAcceptThirdPartyCookies=" + accept); + mAwSettings.setAcceptThirdPartyCookies(accept); + } + + @Override + public boolean getAcceptThirdPartyCookies() { + return mAwSettings.getAcceptThirdPartyCookies(); + } + + @Override + public void setEnableSmoothTransition(boolean enable) { + // Intentional no-op. + } + + @Override + public boolean enableSmoothTransition() { + // Intentional no-op. + return false; + } + + @Override + public void setUseWebViewBackgroundForOverscrollBackground(boolean view) { + // Intentional no-op. + } + + @Override + public boolean getUseWebViewBackgroundForOverscrollBackground() { + // Intentional no-op. + return false; + } + + @Override + public void setSaveFormData(boolean save) { + if (TRACE) Log.d(LOGTAG, "setSaveFormData=" + save); + mAwSettings.setSaveFormData(save); + } + + @Override + public boolean getSaveFormData() { + return mAwSettings.getSaveFormData(); + } + + @Override + public void setSavePassword(boolean save) { + // Intentional no-op. + } + + @Override + public boolean getSavePassword() { + // Intentional no-op. + return false; + } + + @Override + public synchronized void setTextZoom(int textZoom) { + if (TRACE) Log.d(LOGTAG, "setTextZoom=" + textZoom); + mAwSettings.setTextZoom(textZoom); + } + + @Override + public synchronized int getTextZoom() { + return mAwSettings.getTextZoom(); + } + + @Override + public void setDefaultZoom(ZoomDensity zoom) { + if (zoom != ZoomDensity.MEDIUM) { + Log.w(LOGTAG, "setDefaultZoom not supported, zoom=" + zoom); + } + } + + @Override + public ZoomDensity getDefaultZoom() { + // Intentional no-op. + return ZoomDensity.MEDIUM; + } + + @Override + public void setLightTouchEnabled(boolean enabled) { + // Intentional no-op. + } + + @Override + public boolean getLightTouchEnabled() { + // Intentional no-op. + return false; + } + + @Override + public synchronized void setUserAgent(int ua) { + // Minimal implementation for backwards compatibility: just supports resetting to default. + if (ua == 0) { + setUserAgentString(null); + } else { + Log.w(LOGTAG, "setUserAgent not supported, ua=" + ua); + } + } + + @Override + public synchronized int getUserAgent() { + // Minimal implementation for backwards compatibility: just identifies default vs custom. + return AwSettings.getDefaultUserAgent().equals(getUserAgentString()) ? 0 : -1; + } + + @Override + public synchronized void setUseWideViewPort(boolean use) { + if (TRACE) Log.d(LOGTAG, "setUseWideViewPort=" + use); + mAwSettings.setUseWideViewPort(use); + } + + @Override + public synchronized boolean getUseWideViewPort() { + return mAwSettings.getUseWideViewPort(); + } + + @Override + public synchronized void setSupportMultipleWindows(boolean support) { + if (TRACE) Log.d(LOGTAG, "setSupportMultipleWindows=" + support); + mAwSettings.setSupportMultipleWindows(support); + } + + @Override + public synchronized boolean supportMultipleWindows() { + return mAwSettings.supportMultipleWindows(); + } + + @Override + public synchronized void setLayoutAlgorithm(LayoutAlgorithm l) { + // TODO: Remove the upstream enum and mapping once the new value is in the public API. + final AwSettings.LayoutAlgorithm[] chromiumValues = { + AwSettings.LayoutAlgorithm.NORMAL, + AwSettings.LayoutAlgorithm.SINGLE_COLUMN, + AwSettings.LayoutAlgorithm.NARROW_COLUMNS, + AwSettings.LayoutAlgorithm.TEXT_AUTOSIZING + }; + mAwSettings.setLayoutAlgorithm(chromiumValues[l.ordinal()]); + } + + @Override + public synchronized LayoutAlgorithm getLayoutAlgorithm() { + // TODO: Remove the upstream enum and mapping once the new value is in the public API. + final LayoutAlgorithm[] webViewValues = { + LayoutAlgorithm.NORMAL, + LayoutAlgorithm.SINGLE_COLUMN, + LayoutAlgorithm.NARROW_COLUMNS, + LayoutAlgorithm.TEXT_AUTOSIZING + }; + return webViewValues[mAwSettings.getLayoutAlgorithm().ordinal()]; + } + + @Override + public synchronized void setStandardFontFamily(String font) { + if (TRACE) Log.d(LOGTAG, "setStandardFontFamily=" + font); + mAwSettings.setStandardFontFamily(font); + } + + @Override + public synchronized String getStandardFontFamily() { + return mAwSettings.getStandardFontFamily(); + } + + @Override + public synchronized void setFixedFontFamily(String font) { + if (TRACE) Log.d(LOGTAG, "setFixedFontFamily=" + font); + mAwSettings.setFixedFontFamily(font); + } + + @Override + public synchronized String getFixedFontFamily() { + return mAwSettings.getFixedFontFamily(); + } + + @Override + public synchronized void setSansSerifFontFamily(String font) { + if (TRACE) Log.d(LOGTAG, "setSansSerifFontFamily=" + font); + mAwSettings.setSansSerifFontFamily(font); + } + + @Override + public synchronized String getSansSerifFontFamily() { + return mAwSettings.getSansSerifFontFamily(); + } + + @Override + public synchronized void setSerifFontFamily(String font) { + if (TRACE) Log.d(LOGTAG, "setSerifFontFamily=" + font); + mAwSettings.setSerifFontFamily(font); + } + + @Override + public synchronized String getSerifFontFamily() { + return mAwSettings.getSerifFontFamily(); + } + + @Override + public synchronized void setCursiveFontFamily(String font) { + if (TRACE) Log.d(LOGTAG, "setCursiveFontFamily=" + font); + mAwSettings.setCursiveFontFamily(font); + } + + @Override + public synchronized String getCursiveFontFamily() { + return mAwSettings.getCursiveFontFamily(); + } + + @Override + public synchronized void setFantasyFontFamily(String font) { + if (TRACE) Log.d(LOGTAG, "setFantasyFontFamily=" + font); + mAwSettings.setFantasyFontFamily(font); + } + + @Override + public synchronized String getFantasyFontFamily() { + return mAwSettings.getFantasyFontFamily(); + } + + @Override + public synchronized void setMinimumFontSize(int size) { + if (TRACE) Log.d(LOGTAG, "setMinimumFontSize=" + size); + mAwSettings.setMinimumFontSize(size); + } + + @Override + public synchronized int getMinimumFontSize() { + return mAwSettings.getMinimumFontSize(); + } + + @Override + public synchronized void setMinimumLogicalFontSize(int size) { + if (TRACE) Log.d(LOGTAG, "setMinimumLogicalFontSize=" + size); + mAwSettings.setMinimumLogicalFontSize(size); + } + + @Override + public synchronized int getMinimumLogicalFontSize() { + return mAwSettings.getMinimumLogicalFontSize(); + } + + @Override + public synchronized void setDefaultFontSize(int size) { + if (TRACE) Log.d(LOGTAG, "setDefaultFontSize=" + size); + mAwSettings.setDefaultFontSize(size); + } + + @Override + public synchronized int getDefaultFontSize() { + return mAwSettings.getDefaultFontSize(); + } + + @Override + public synchronized void setDefaultFixedFontSize(int size) { + if (TRACE) Log.d(LOGTAG, "setDefaultFixedFontSize=" + size); + mAwSettings.setDefaultFixedFontSize(size); + } + + @Override + public synchronized int getDefaultFixedFontSize() { + return mAwSettings.getDefaultFixedFontSize(); + } + + @Override + public synchronized void setLoadsImagesAutomatically(boolean flag) { + if (TRACE) Log.d(LOGTAG, "setLoadsImagesAutomatically=" + flag); + mAwSettings.setLoadsImagesAutomatically(flag); + } + + @Override + public synchronized boolean getLoadsImagesAutomatically() { + return mAwSettings.getLoadsImagesAutomatically(); + } + + @Override + public synchronized void setBlockNetworkImage(boolean flag) { + if (TRACE) Log.d(LOGTAG, "setBlockNetworkImage=" + flag); + mAwSettings.setImagesEnabled(!flag); + } + + @Override + public synchronized boolean getBlockNetworkImage() { + return !mAwSettings.getImagesEnabled(); + } + + @Override + public synchronized void setBlockNetworkLoads(boolean flag) { + if (TRACE) Log.d(LOGTAG, "setBlockNetworkLoads=" + flag); + mAwSettings.setBlockNetworkLoads(flag); + } + + @Override + public synchronized boolean getBlockNetworkLoads() { + return mAwSettings.getBlockNetworkLoads(); + } + + @Override + public synchronized void setJavaScriptEnabled(boolean flag) { + if (TRACE) Log.d(LOGTAG, "setJavaScriptEnabled=" + flag); + mAwSettings.setJavaScriptEnabled(flag); + } + + @Override + public void setAllowUniversalAccessFromFileURLs(boolean flag) { + if (TRACE) Log.d(LOGTAG, "setAllowUniversalAccessFromFileURLs=" + flag); + mAwSettings.setAllowUniversalAccessFromFileURLs(flag); + } + + @Override + public void setAllowFileAccessFromFileURLs(boolean flag) { + if (TRACE) Log.d(LOGTAG, "setAllowFileAccessFromFileURLs=" + flag); + mAwSettings.setAllowFileAccessFromFileURLs(flag); + } + + @Override + public synchronized void setPluginsEnabled(boolean flag) { + if (TRACE) Log.d(LOGTAG, "setPluginsEnabled=" + flag); + mAwSettings.setPluginsEnabled(flag); + } + + @Override + public synchronized void setPluginState(PluginState state) { + if (TRACE) Log.d(LOGTAG, "setPluginState=" + state); + mAwSettings.setPluginState(state); + } + + @Override + public synchronized void setDatabasePath(String databasePath) { + // Intentional no-op. + } + + @Override + public synchronized void setGeolocationDatabasePath(String databasePath) { + // Intentional no-op. + } + + @Override + public synchronized void setAppCacheEnabled(boolean flag) { + if (TRACE) Log.d(LOGTAG, "setAppCacheEnabled=" + flag); + mAwSettings.setAppCacheEnabled(flag); + } + + @Override + public synchronized void setAppCachePath(String appCachePath) { + if (TRACE) Log.d(LOGTAG, "setAppCachePath=" + appCachePath); + mAwSettings.setAppCachePath(appCachePath); + } + + @Override + public synchronized void setAppCacheMaxSize(long appCacheMaxSize) { + // Intentional no-op. + } + + @Override + public synchronized void setDatabaseEnabled(boolean flag) { + if (TRACE) Log.d(LOGTAG, "setDatabaseEnabled=" + flag); + mAwSettings.setDatabaseEnabled(flag); + } + + @Override + public synchronized void setDomStorageEnabled(boolean flag) { + if (TRACE) Log.d(LOGTAG, "setDomStorageEnabled=" + flag); + mAwSettings.setDomStorageEnabled(flag); + } + + @Override + public synchronized boolean getDomStorageEnabled() { + return mAwSettings.getDomStorageEnabled(); + } + + @Override + public synchronized String getDatabasePath() { + // Intentional no-op. + return ""; + } + + @Override + public synchronized boolean getDatabaseEnabled() { + return mAwSettings.getDatabaseEnabled(); + } + + @Override + public synchronized void setGeolocationEnabled(boolean flag) { + if (TRACE) Log.d(LOGTAG, "setGeolocationEnabled=" + flag); + mAwSettings.setGeolocationEnabled(flag); + } + + @Override + public synchronized boolean getJavaScriptEnabled() { + return mAwSettings.getJavaScriptEnabled(); + } + + @Override + public boolean getAllowUniversalAccessFromFileURLs() { + return mAwSettings.getAllowUniversalAccessFromFileURLs(); + } + + @Override + public boolean getAllowFileAccessFromFileURLs() { + return mAwSettings.getAllowFileAccessFromFileURLs(); + } + + @Override + public synchronized boolean getPluginsEnabled() { + return mAwSettings.getPluginsEnabled(); + } + + @Override + public synchronized PluginState getPluginState() { + return mAwSettings.getPluginState(); + } + + @Override + public synchronized void setJavaScriptCanOpenWindowsAutomatically(boolean flag) { + if (TRACE) Log.d(LOGTAG, "setJavaScriptCanOpenWindowsAutomatically=" + flag); + mAwSettings.setJavaScriptCanOpenWindowsAutomatically(flag); + } + + @Override + public synchronized boolean getJavaScriptCanOpenWindowsAutomatically() { + return mAwSettings.getJavaScriptCanOpenWindowsAutomatically(); + } + + @Override + public synchronized void setDefaultTextEncodingName(String encoding) { + if (TRACE) Log.d(LOGTAG, "setDefaultTextEncodingName=" + encoding); + mAwSettings.setDefaultTextEncodingName(encoding); + } + + @Override + public synchronized String getDefaultTextEncodingName() { + return mAwSettings.getDefaultTextEncodingName(); + } + + @Override + public synchronized void setUserAgentString(String ua) { + if (TRACE) Log.d(LOGTAG, "setUserAgentString=" + ua); + mAwSettings.setUserAgentString(ua); + } + + @Override + public synchronized String getUserAgentString() { + return mAwSettings.getUserAgentString(); + } + + @Override + public void setNeedInitialFocus(boolean flag) { + if (TRACE) Log.d(LOGTAG, "setNeedInitialFocus=" + flag); + mAwSettings.setShouldFocusFirstNode(flag); + } + + @Override + public synchronized void setRenderPriority(RenderPriority priority) { + // Intentional no-op. + } + + @Override + public void setCacheMode(int mode) { + if (TRACE) Log.d(LOGTAG, "setCacheMode=" + mode); + mAwSettings.setCacheMode(mode); + } + + @Override + public int getCacheMode() { + return mAwSettings.getCacheMode(); + } + + @Override + public void setMediaPlaybackRequiresUserGesture(boolean require) { + if (TRACE) Log.d(LOGTAG, "setMediaPlaybackRequiresUserGesture=" + require); + mAwSettings.setMediaPlaybackRequiresUserGesture(require); + } + + @Override + public boolean getMediaPlaybackRequiresUserGesture() { + return mAwSettings.getMediaPlaybackRequiresUserGesture(); + } + +// @Override + public void setMixedContentMode(int mode) { + mAwSettings.setMixedContentMode(mode); + } + +// @Override + public int getMixedContentMode() { + return mAwSettings.getMixedContentMode(); + } + +// @Override + public void setVideoOverlayForEmbeddedEncryptedVideoEnabled(boolean flag) { + mAwSettings.setVideoOverlayForEmbeddedVideoEnabled(flag); + } + +// @Override + public boolean getVideoOverlayForEmbeddedEncryptedVideoEnabled() { + return mAwSettings.getVideoOverlayForEmbeddedVideoEnabled(); + } +} diff --git a/android_webview/glue/java/src/com/android/webview/chromium/CookieManagerAdapter.java b/android_webview/glue/java/src/com/android/webview/chromium/CookieManagerAdapter.java new file mode 100644 index 0000000..547184a --- /dev/null +++ b/android_webview/glue/java/src/com/android/webview/chromium/CookieManagerAdapter.java @@ -0,0 +1,148 @@ +// 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.net.ParseException; +import android.net.WebAddress; +import android.util.Log; +import android.webkit.CookieManager; +import android.webkit.ValueCallback; +import android.webkit.WebView; + +import org.chromium.android_webview.AwCookieManager; + +public class CookieManagerAdapter extends CookieManager { + + private static final String LOGTAG = "CookieManager"; + + AwCookieManager mChromeCookieManager; + + public CookieManagerAdapter(AwCookieManager chromeCookieManager) { + mChromeCookieManager = chromeCookieManager; + } + + @Override + public synchronized void setAcceptCookie(boolean accept) { + mChromeCookieManager.setAcceptCookie(accept); + } + + @Override + public synchronized boolean acceptCookie() { + return mChromeCookieManager.acceptCookie(); + } + + @Override + public synchronized void setAcceptThirdPartyCookies(WebView webView, boolean accept) { + webView.getSettings().setAcceptThirdPartyCookies(accept); + } + + @Override + public synchronized boolean acceptThirdPartyCookies(WebView webView) { + return webView.getSettings().getAcceptThirdPartyCookies(); + } + + @Override + public void setCookie(String url, String value) { + try { + mChromeCookieManager.setCookie(fixupUrl(url), value); + } catch (ParseException e) { + Log.e(LOGTAG, "Not setting cookie due to error parsing URL: " + url, e); + } + } + + @Override + public void setCookie(String url, String value, ValueCallback<Boolean> callback) { + try { + mChromeCookieManager.setCookie(fixupUrl(url), value, callback); + } catch (ParseException e) { + Log.e(LOGTAG, "Not setting cookie due to error parsing URL: " + url, e); + } + } + + @Override + public String getCookie(String url) { + try { + return mChromeCookieManager.getCookie(fixupUrl(url)); + } catch (ParseException e) { + Log.e(LOGTAG, "Unable to get cookies due to error parsing URL: " + url, e); + return null; + } + } + + @Override + public String getCookie(String url, boolean privateBrowsing) { + return getCookie(url); + } + + // TODO(igsolla): remove this override once the WebView apk does not longer need + // to be binary compatibility with the API 21 version of the framework + /** + * IMPORTANT: This override is required for compatibility with the API 21 version of + * {@link CookieManager}. + */ + @Override + public synchronized String getCookie(WebAddress uri) { + return mChromeCookieManager.getCookie(uri.toString()); + } + + @Override + public void removeSessionCookie() { + mChromeCookieManager.removeSessionCookies(); + } + + @Override + public void removeSessionCookies(ValueCallback<Boolean> callback) { + mChromeCookieManager.removeSessionCookies(callback); + } + + @Override + public void removeAllCookie() { + mChromeCookieManager.removeAllCookies(); + } + + @Override + public void removeAllCookies(ValueCallback<Boolean> callback) { + mChromeCookieManager.removeAllCookies(callback); + } + + @Override + public synchronized boolean hasCookies() { + return mChromeCookieManager.hasCookies(); + } + + @Override + public synchronized boolean hasCookies(boolean privateBrowsing) { + return mChromeCookieManager.hasCookies(); + } + + @Override + public void removeExpiredCookie() { + mChromeCookieManager.removeExpiredCookie(); + } + + @Override + public void flush() { + mChromeCookieManager.flushCookieStore(); + } + + @Override + protected boolean allowFileSchemeCookiesImpl() { + return mChromeCookieManager.allowFileSchemeCookies(); + } + + @Override + protected void setAcceptFileSchemeCookiesImpl(boolean accept) { + mChromeCookieManager.setAcceptFileSchemeCookies(accept); + } + + private static String fixupUrl(String url) throws ParseException { + // WebAddress is a private API in the android framework and a "quirk" + // of the Classic WebView implementation that allowed embedders to + // be relaxed about what URLs they passed into the CookieManager, so we + // do the same normalisation before entering the chromium stack. + return new WebAddress(url).toString(); + } + +} diff --git a/android_webview/glue/java/src/com/android/webview/chromium/DrawGLFunctor.java b/android_webview/glue/java/src/com/android/webview/chromium/DrawGLFunctor.java new file mode 100644 index 0000000..c27287d --- /dev/null +++ b/android_webview/glue/java/src/com/android/webview/chromium/DrawGLFunctor.java @@ -0,0 +1,112 @@ +// 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.view.View; +import android.graphics.Canvas; +import android.util.Log; + +import com.android.webview.chromium.WebViewDelegateFactory.WebViewDelegate; + +import org.chromium.content.common.CleanupReference; + +// Simple Java abstraction and wrapper for the native DrawGLFunctor flow. +// An instance of this class can be constructed, bound to a single view context (i.e. AwContennts) +// and then drawn and detached from the view tree any number of times (using requestDrawGL and +// detach respectively). Then when finished with, it can be explicitly released by calling +// destroy() or will clean itself up as required via finalizer / CleanupReference. +class DrawGLFunctor { + + private static final String TAG = DrawGLFunctor.class.getSimpleName(); + + // Pointer to native side instance + private CleanupReference mCleanupReference; + private DestroyRunnable mDestroyRunnable; + private WebViewDelegate mWebViewDelegate; + + public DrawGLFunctor(long viewContext, WebViewDelegate webViewDelegate) { + mDestroyRunnable = new DestroyRunnable(nativeCreateGLFunctor(viewContext), webViewDelegate); + mCleanupReference = new CleanupReference(this, mDestroyRunnable); + mWebViewDelegate = webViewDelegate; + } + + public void destroy() { + detach(); + if (mCleanupReference != null) { + mCleanupReference.cleanupNow(); + mCleanupReference = null; + mDestroyRunnable = null; + mWebViewDelegate = null; + } + } + + public void detach() { + mDestroyRunnable.detachNativeFunctor(); + } + + public boolean requestDrawGL(Canvas canvas, View containerView, + boolean waitForCompletion) { + if (mDestroyRunnable.mNativeDrawGLFunctor == 0) { + throw new RuntimeException("requested DrawGL on already destroyed DrawGLFunctor"); + } + + if (canvas != null && waitForCompletion) { + throw new IllegalArgumentException("requested a blocking DrawGL with a not null canvas."); + } + + if (!mWebViewDelegate.canInvokeDrawGlFunctor(containerView)) { + return false; + } + + mDestroyRunnable.mContainerView = containerView; + + if (canvas == null) { + mWebViewDelegate.invokeDrawGlFunctor(containerView, + mDestroyRunnable.mNativeDrawGLFunctor, waitForCompletion); + return true; + } + + mWebViewDelegate.callDrawGlFunction(canvas, mDestroyRunnable.mNativeDrawGLFunctor); + return true; + } + + public static void setChromiumAwDrawGLFunction(long functionPointer) { + nativeSetChromiumAwDrawGLFunction(functionPointer); + } + + // Holds the core resources of the class, everything required to correctly cleanup. + // IMPORTANT: this class must not hold any reference back to the outer DrawGLFunctor + // instance, as that will defeat GC of that object. + private static final class DestroyRunnable implements Runnable { + private WebViewDelegate mWebViewDelegate; + View mContainerView; + long mNativeDrawGLFunctor; + DestroyRunnable(long nativeDrawGLFunctor, WebViewDelegate webViewDelegate) { + mNativeDrawGLFunctor = nativeDrawGLFunctor; + mWebViewDelegate = webViewDelegate; + } + + // Called when the outer DrawGLFunctor instance has been GC'ed, i.e this is its finalizer. + @Override + public void run() { + detachNativeFunctor(); + nativeDestroyGLFunctor(mNativeDrawGLFunctor); + mNativeDrawGLFunctor = 0; + } + + void detachNativeFunctor() { + if (mNativeDrawGLFunctor != 0 && mContainerView != null + && mWebViewDelegate != null) { + mWebViewDelegate.detachDrawGlFunctor(mContainerView, mNativeDrawGLFunctor); + } + mContainerView = null; + mWebViewDelegate = null; + } + } + + private static native long nativeCreateGLFunctor(long viewContext); + private static native void nativeDestroyGLFunctor(long functor); + private static native void nativeSetChromiumAwDrawGLFunction(long functionPointer); +} diff --git a/android_webview/glue/java/src/com/android/webview/chromium/FileChooserParamsAdapter.java b/android_webview/glue/java/src/com/android/webview/chromium/FileChooserParamsAdapter.java new file mode 100644 index 0000000..fe7ed55 --- /dev/null +++ b/android_webview/glue/java/src/com/android/webview/chromium/FileChooserParamsAdapter.java @@ -0,0 +1,77 @@ +// 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.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.webkit.WebChromeClient.FileChooserParams; + +import org.chromium.android_webview.AwContentsClient; + +public class FileChooserParamsAdapter extends FileChooserParams { + private AwContentsClient.FileChooserParams mParams; + + public static Uri[] parseFileChooserResult(int resultCode, Intent intent) { + if (resultCode == Activity.RESULT_CANCELED) { + return null; + } + Uri result = intent == null || resultCode != Activity.RESULT_OK ? null + : intent.getData(); + + Uri[] uris = null; + if (result != null) { + uris = new Uri[1]; + uris[0] = result; + } + return uris; + } + + FileChooserParamsAdapter(AwContentsClient.FileChooserParams params, Context context) { + mParams = params; + } + + @Override + public int getMode() { + return mParams.mode; + } + + @Override + public String[] getAcceptTypes() { + if (mParams.acceptTypes == null) + return new String[0]; + return mParams.acceptTypes.split(";"); + } + + @Override + public boolean isCaptureEnabled() { + return mParams.capture; + } + + @Override + public CharSequence getTitle() { + return mParams.title; + } + + @Override + public String getFilenameHint() { + return mParams.defaultFilename; + } + + @Override + public Intent createIntent() { + // TODO: Move this code to Aw. Once code is moved + // and merged to M37 get rid of this. + String mimeType = "*/*"; + if (mParams.acceptTypes != null && !mParams.acceptTypes.trim().isEmpty()) + mimeType = mParams.acceptTypes.split(";")[0]; + + Intent i = new Intent(Intent.ACTION_GET_CONTENT); + i.addCategory(Intent.CATEGORY_OPENABLE); + i.setType(mimeType); + return i; + } +} diff --git a/android_webview/glue/java/src/com/android/webview/chromium/GeolocationPermissionsAdapter.java b/android_webview/glue/java/src/com/android/webview/chromium/GeolocationPermissionsAdapter.java new file mode 100644 index 0000000..6249906 --- /dev/null +++ b/android_webview/glue/java/src/com/android/webview/chromium/GeolocationPermissionsAdapter.java @@ -0,0 +1,50 @@ +// 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.webkit.GeolocationPermissions; +import android.webkit.ValueCallback; + +import org.chromium.android_webview.AwGeolocationPermissions; + +import java.util.Set; + +/** + * Chromium implementation of GeolocationPermissions -- forwards calls to the + * chromium internal implementation. + */ +final class GeolocationPermissionsAdapter extends GeolocationPermissions { + + private AwGeolocationPermissions mChromeGeolocationPermissions; + + public GeolocationPermissionsAdapter(AwGeolocationPermissions chromeGeolocationPermissions) { + mChromeGeolocationPermissions = chromeGeolocationPermissions; + } + + @Override + public void allow(String origin) { + mChromeGeolocationPermissions.allow(origin); + } + + @Override + public void clear(String origin) { + mChromeGeolocationPermissions.clear(origin); + } + + @Override + public void clearAll() { + mChromeGeolocationPermissions.clearAll(); + } + + @Override + public void getAllowed(String origin, ValueCallback<Boolean> callback) { + mChromeGeolocationPermissions.getAllowed(origin, callback); + } + + @Override + public void getOrigins(ValueCallback<Set<String>> callback) { + mChromeGeolocationPermissions.getOrigins(callback); + } +} diff --git a/android_webview/glue/java/src/com/android/webview/chromium/GraphicsUtils.java b/android_webview/glue/java/src/com/android/webview/chromium/GraphicsUtils.java new file mode 100644 index 0000000..20940db --- /dev/null +++ b/android_webview/glue/java/src/com/android/webview/chromium/GraphicsUtils.java @@ -0,0 +1,19 @@ +// 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; + +abstract class GraphicsUtils { + public static long getDrawSWFunctionTable() { + return nativeGetDrawSWFunctionTable(); + } + + public static long getDrawGLFunctionTable() { + return nativeGetDrawGLFunctionTable(); + } + + private static native long nativeGetDrawSWFunctionTable(); + private static native long nativeGetDrawGLFunctionTable(); + +} diff --git a/android_webview/glue/java/src/com/android/webview/chromium/ResourceRewriter.java b/android_webview/glue/java/src/com/android/webview/chromium/ResourceRewriter.java new file mode 100644 index 0000000..bee5020 --- /dev/null +++ b/android_webview/glue/java/src/com/android/webview/chromium/ResourceRewriter.java @@ -0,0 +1,28 @@ +// 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.content.Context; + +/** + * Helper class used to fix up resource ids. + * This is mostly a copy of the code in frameworks/base/core/java/android/app/LoadedApk.java. + * TODO: Remove if a cleaner mechanism is provided (either public API or AAPT is changed to generate + * this code). + */ +class ResourceRewriter { + + /** + * Rewrite the R 'constants' for the WebView library apk. + */ + public static void rewriteRValues(final int packageId) { + // TODO: We should use jarjar to remove the redundant R classes here, but due + // to a bug in jarjar it's not possible to rename classes with '$' in their name. + // See b/15684775. + com.android.webview.chromium.R.onResourcesLoaded(packageId); + org.chromium.ui.R.onResourcesLoaded(packageId); + org.chromium.content.R.onResourcesLoaded(packageId); + } +} diff --git a/android_webview/glue/java/src/com/android/webview/chromium/ResourcesContextWrapperFactory.java b/android_webview/glue/java/src/com/android/webview/chromium/ResourcesContextWrapperFactory.java new file mode 100644 index 0000000..29b5bd9 --- /dev/null +++ b/android_webview/glue/java/src/com/android/webview/chromium/ResourcesContextWrapperFactory.java @@ -0,0 +1,95 @@ +// 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.content.ComponentCallbacks; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.view.LayoutInflater; + +import java.util.WeakHashMap; + +/** + * This class allows us to wrap the application context so that the WebView implementation can + * correctly reference both org.chromium.* and application classes which is necessary to properly + * inflate UI. We keep a weak map from contexts to wrapped contexts to avoid constantly re-wrapping + * or doubly wrapping contexts. + */ +public class ResourcesContextWrapperFactory { + private static WeakHashMap<Context,ContextWrapper> sCtxToWrapper + = new WeakHashMap<Context,ContextWrapper>(); + private static final Object sLock = new Object(); + + private ResourcesContextWrapperFactory() { + } + + public static Context get(Context ctx) { + ContextWrapper wrappedCtx; + synchronized (sLock) { + wrappedCtx = sCtxToWrapper.get(ctx); + if (wrappedCtx == null) { + wrappedCtx = createWrapper(ctx); + sCtxToWrapper.put(ctx, wrappedCtx); + } + } + return wrappedCtx; + } + + private static ContextWrapper createWrapper(final Context ctx) { + return new ContextWrapper(ctx) { + private Context applicationContext; + + @Override + public ClassLoader getClassLoader() { + final ClassLoader appCl = getBaseContext().getClassLoader(); + final ClassLoader webViewCl = this.getClass().getClassLoader(); + return new ClassLoader() { + @Override + protected Class<?> findClass(String name) throws ClassNotFoundException { + // First look in the WebViewProvider class loader. + try { + return webViewCl.loadClass(name); + } catch (ClassNotFoundException e) { + // Look in the app class loader; allowing it to throw ClassNotFoundException. + return appCl.loadClass(name); + } + } + }; + } + + @Override + public Object getSystemService(String name) { + if (Context.LAYOUT_INFLATER_SERVICE.equals(name)) { + LayoutInflater i = (LayoutInflater) getBaseContext().getSystemService(name); + return i.cloneInContext(this); + } else { + return getBaseContext().getSystemService(name); + } + } + + @Override + public Context getApplicationContext() { + if (applicationContext == null) + applicationContext = get(ctx.getApplicationContext()); + return applicationContext; + } + + @Override + public void registerComponentCallbacks(ComponentCallbacks callback) { + // We have to override registerComponentCallbacks and unregisterComponentCallbacks + // since they call getApplicationContext().[un]registerComponentCallbacks() + // which causes us to go into a loop. + ctx.registerComponentCallbacks(callback); + } + + @Override + public void unregisterComponentCallbacks(ComponentCallbacks callback) { + ctx.unregisterComponentCallbacks(callback); + } + }; + } +} diff --git a/android_webview/glue/java/src/com/android/webview/chromium/UnimplementedWebViewApi.java b/android_webview/glue/java/src/com/android/webview/chromium/UnimplementedWebViewApi.java new file mode 100644 index 0000000..39372d3 --- /dev/null +++ b/android_webview/glue/java/src/com/android/webview/chromium/UnimplementedWebViewApi.java @@ -0,0 +1,42 @@ +// 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.util.Log; + +// TODO: remove this when all WebView APIs have been implemented. +public class UnimplementedWebViewApi { + private static String TAG = "UnimplementedWebViewApi"; + + private static class UnimplementedWebViewApiException extends UnsupportedOperationException { + public UnimplementedWebViewApiException() { + super(); + } + } + + private static boolean THROW = false; + // By default we keep the traces down to one frame to reduce noise, but for debugging it might + // be useful to set this to true. + private static boolean FULL_TRACE = false; + + public static void invoke() throws UnimplementedWebViewApiException { + if (THROW) { + throw new UnimplementedWebViewApiException(); + } else { + if (FULL_TRACE) { + Log.w(TAG, "Unimplemented WebView method called in: " + + Log.getStackTraceString(new Throwable())); + } else { + StackTraceElement[] trace = new Throwable().getStackTrace(); + // The stack trace [0] index is this method (invoke()). + StackTraceElement unimplementedMethod = trace[1]; + StackTraceElement caller = trace[2]; + Log.w(TAG, "Unimplemented WebView method " + unimplementedMethod.getMethodName() + + " called from: " + caller.toString()); + } + } + } + +} diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebBackForwardListChromium.java b/android_webview/glue/java/src/com/android/webview/chromium/WebBackForwardListChromium.java new file mode 100644 index 0000000..5e29773 --- /dev/null +++ b/android_webview/glue/java/src/com/android/webview/chromium/WebBackForwardListChromium.java @@ -0,0 +1,91 @@ +// 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 org.chromium.content_public.browser.NavigationHistory; + +import android.webkit.WebBackForwardList; +import android.webkit.WebHistoryItem; + +import java.util.ArrayList; +import java.util.List; + +/** + * WebView Chromium implementation of WebBackForwardList. Simple immutable + * wrapper around NavigationHistory. + */ +public class WebBackForwardListChromium extends WebBackForwardList { + private final List<WebHistoryItemChromium> mHistroryItemList; + private final int mCurrentIndex; + + /* package */ WebBackForwardListChromium(NavigationHistory nav_history) { + mCurrentIndex = nav_history.getCurrentEntryIndex(); + mHistroryItemList = new ArrayList<WebHistoryItemChromium>(nav_history.getEntryCount()); + for (int i = 0; i < nav_history.getEntryCount(); ++i) { + mHistroryItemList.add( + new WebHistoryItemChromium(nav_history.getEntryAtIndex(i))); + } + } + + /** + * See {@link android.webkit.WebBackForwardList#getCurrentItem}. + */ + @Override + public synchronized WebHistoryItem getCurrentItem() { + if (getSize() == 0) { + return null; + } else { + return getItemAtIndex(getCurrentIndex()); + } + } + + /** + * See {@link android.webkit.WebBackForwardList#getCurrentIndex}. + */ + @Override + public synchronized int getCurrentIndex() { + return mCurrentIndex; + } + + /** + * See {@link android.webkit.WebBackForwardList#getItemAtIndex}. + */ + @Override + public synchronized WebHistoryItem getItemAtIndex(int index) { + if (index < 0 || index >= getSize()) { + return null; + } else { + return mHistroryItemList.get(index); + } + } + + /** + * See {@link android.webkit.WebBackForwardList#getSize}. + */ + @Override + public synchronized int getSize() { + return mHistroryItemList.size(); + } + + // Clone constructor. + private WebBackForwardListChromium(List<WebHistoryItemChromium> list, + int currentIndex) { + mHistroryItemList = list; + mCurrentIndex = currentIndex; + } + + /** + * See {@link android.webkit.WebBackForwardList#clone}. + */ + @Override + protected synchronized WebBackForwardListChromium clone() { + List<WebHistoryItemChromium> list = + new ArrayList<WebHistoryItemChromium>(getSize()); + for (int i = 0; i < getSize(); ++i) { + list.add(mHistroryItemList.get(i).clone()); + } + return new WebBackForwardListChromium(list, mCurrentIndex); + } +} diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebHistoryItemChromium.java b/android_webview/glue/java/src/com/android/webview/chromium/WebHistoryItemChromium.java new file mode 100644 index 0000000..b499414 --- /dev/null +++ b/android_webview/glue/java/src/com/android/webview/chromium/WebHistoryItemChromium.java @@ -0,0 +1,86 @@ +// 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 org.chromium.content_public.browser.NavigationEntry; + +import android.graphics.Bitmap; +import android.webkit.WebHistoryItem; + +/** + * WebView Chromium implementation of WebHistoryItem. Simple immutable wrapper + * around NavigationEntry + */ +public class WebHistoryItemChromium extends WebHistoryItem { + private final String mUrl; + private final String mOriginalUrl; + private final String mTitle; + private final Bitmap mFavicon; + + /* package */ WebHistoryItemChromium(NavigationEntry entry) { + mUrl = entry.getUrl(); + mOriginalUrl = entry.getOriginalUrl(); + mTitle = entry.getTitle(); + mFavicon = entry.getFavicon(); + } + + /** + * See {@link android.webkit.WebHistoryItem#getId}. + */ + @Override + public int getId() { + // This method is deprecated in superclass. Returning constant -1 now. + return -1; + } + + /** + * See {@link android.webkit.WebHistoryItem#getUrl}. + */ + @Override + public String getUrl() { + return mUrl; + } + + /** + * See {@link android.webkit.WebHistoryItem#getOriginalUrl}. + */ + @Override + public String getOriginalUrl() { + return mOriginalUrl; + } + + /** + * See {@link android.webkit.WebHistoryItem#getTitle}. + */ + @Override + public String getTitle() { + return mTitle; + } + + /** + * See {@link android.webkit.WebHistoryItem#getFavicon}. + */ + @Override + public Bitmap getFavicon() { + return mFavicon; + } + + // Clone constructor. + private WebHistoryItemChromium( + String url, String originalUrl, String title, Bitmap favicon) { + mUrl = url; + mOriginalUrl = originalUrl; + mTitle = title; + mFavicon = favicon; + } + + /** + * See {@link android.webkit.WebHistoryItem#clone}. + */ + @Override + public synchronized WebHistoryItemChromium clone() { + return new WebHistoryItemChromium(mUrl, mOriginalUrl, mTitle, mFavicon); + } +} diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebIconDatabaseAdapter.java b/android_webview/glue/java/src/com/android/webview/chromium/WebIconDatabaseAdapter.java new file mode 100644 index 0000000..1c970d1 --- /dev/null +++ b/android_webview/glue/java/src/com/android/webview/chromium/WebIconDatabaseAdapter.java @@ -0,0 +1,52 @@ +// 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.content.ContentResolver; +import android.webkit.WebIconDatabase; +import android.webkit.WebIconDatabase.IconListener; + +import org.chromium.android_webview.AwContents; + +/** + * Chromium implementation of WebIconDatabase -- big old no-op (base class is deprecated). + */ +final class WebIconDatabaseAdapter extends WebIconDatabase { + @Override + public void open(String path) { + AwContents.setShouldDownloadFavicons(); + } + + @Override + public void close() { + // Intentional no-op. + } + + @Override + public void removeAllIcons() { + // Intentional no-op: we have no database so nothing to remove. + } + + @Override + public void requestIconForPageUrl(String url, IconListener listener) { + // Intentional no-op. + } + + @Override + public void bulkRequestIconForPageUrl(ContentResolver cr, String where, + IconListener listener) { + // Intentional no-op: hidden in base class. + } + + @Override + public void retainIconForPageUrl(String url) { + // Intentional no-op. + } + + @Override + public void releaseIconForPageUrl(String url) { + // Intentional no-op. + } +} diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebStorageAdapter.java b/android_webview/glue/java/src/com/android/webview/chromium/WebStorageAdapter.java new file mode 100644 index 0000000..9d36fb4 --- /dev/null +++ b/android_webview/glue/java/src/com/android/webview/chromium/WebStorageAdapter.java @@ -0,0 +1,68 @@ +// 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.webkit.ValueCallback; +import android.webkit.WebStorage; + +import java.util.HashMap; +import java.util.Map; + +import org.chromium.android_webview.AwQuotaManagerBridge; + +/** + * Chromium implementation of WebStorage -- forwards calls to the + * chromium internal implementation. + */ +final class WebStorageAdapter extends WebStorage { + private final AwQuotaManagerBridge mQuotaManagerBridge; + WebStorageAdapter(AwQuotaManagerBridge quotaManagerBridge) { + mQuotaManagerBridge = quotaManagerBridge; + } + + @Override + public void getOrigins(final ValueCallback<Map> callback) { + mQuotaManagerBridge.getOrigins(new ValueCallback<AwQuotaManagerBridge.Origins>() { + @Override + public void onReceiveValue(AwQuotaManagerBridge.Origins origins) { + Map<String, Origin> originsMap = new HashMap<String, Origin>(); + for (int i = 0; i < origins.mOrigins.length; ++i) { + Origin origin = new Origin(origins.mOrigins[i], origins.mQuotas[i], + origins.mUsages[i]) { + // Intentionally empty to work around cross-package protected visibility + // of Origin constructor. + }; + originsMap.put(origins.mOrigins[i], origin); + } + callback.onReceiveValue(originsMap); + } + }); + } + + @Override + public void getUsageForOrigin(String origin, ValueCallback<Long> callback) { + mQuotaManagerBridge.getUsageForOrigin(origin, callback); + } + + @Override + public void getQuotaForOrigin(String origin, ValueCallback<Long> callback) { + mQuotaManagerBridge.getQuotaForOrigin(origin, callback); + } + + @Override + public void setQuotaForOrigin(String origin, long quota) { + // Intentional no-op for deprecated method. + } + + @Override + public void deleteOrigin(String origin) { + mQuotaManagerBridge.deleteOrigin(origin); + } + + @Override + public void deleteAllData() { + mQuotaManagerBridge.deleteAllData(); + } +} diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromium.java b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromium.java new file mode 100644 index 0000000..55fe5c8 --- /dev/null +++ b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromium.java @@ -0,0 +1,2236 @@ +// 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.content.Context; +import android.content.pm.PackageManager; +import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Picture; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.net.http.SslCertificate; +import android.os.Build; +import android.os.Bundle; +import android.os.Looper; +import android.os.Handler; +import android.os.Message; +import android.print.PrintDocumentAdapter; +import android.text.TextUtils; +import android.util.Base64; +import android.util.Log; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.MeasureSpec; +import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityNodeProvider; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.webkit.DownloadListener; +import android.webkit.FindActionModeCallback; +import android.webkit.JavascriptInterface; +import android.webkit.ValueCallback; +import android.webkit.WebBackForwardList; +import android.webkit.WebChromeClient; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.webkit.WebViewFactory; +import android.webkit.WebViewProvider; +import android.webkit.WebChromeClient.CustomViewCallback; +import android.widget.TextView; + +import org.chromium.android_webview.AwBrowserContext; +import org.chromium.android_webview.AwContents; +import org.chromium.android_webview.AwContentsStatics; +import org.chromium.android_webview.AwLayoutSizer; +import org.chromium.android_webview.AwSettings; +import org.chromium.android_webview.AwPrintDocumentAdapter; +import org.chromium.base.ThreadUtils; +import org.chromium.content.browser.SmartClipProvider; +import org.chromium.content_public.browser.LoadUrlParams; +import org.chromium.net.NetworkChangeNotifier; + +import java.io.BufferedWriter; +import java.io.File; +import java.lang.annotation.Annotation; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; +import java.util.HashMap; +import java.util.Map; +import java.util.Queue; + +/** + * This class is the delegate to which WebViewProxy forwards all API calls. + * + * Most of the actual functionality is implemented by AwContents (or ContentViewCore within + * it). This class also contains WebView-specific APIs that require the creation of other + * adapters (otherwise org.chromium.content would depend on the webview.chromium package) + * and a small set of no-op deprecated APIs. + */ +class WebViewChromium implements WebViewProvider, + WebViewProvider.ScrollDelegate, WebViewProvider.ViewDelegate, SmartClipProvider { + + private class WebViewChromiumRunQueue { + public WebViewChromiumRunQueue() { + mQueue = new ConcurrentLinkedQueue<Runnable>(); + } + + public void addTask(Runnable task) { + mQueue.add(task); + if (mFactory.hasStarted()) { + ThreadUtils.runOnUiThread(new Runnable() { + @Override + public void run() { + drainQueue(); + } + }); + } + } + + public void drainQueue() { + if (mQueue == null || mQueue.isEmpty()) { + return; + } + + Runnable task = mQueue.poll(); + while(task != null) { + task.run(); + task = mQueue.poll(); + } + } + + private Queue<Runnable> mQueue; + } + + private WebViewChromiumRunQueue mRunQueue; + + private static final String TAG = WebViewChromium.class.getSimpleName(); + + // The WebView that this WebViewChromium is the provider for. + WebView mWebView; + // Lets us access protected View-derived methods on the WebView instance we're backing. + WebView.PrivateAccess mWebViewPrivate; + // The client adapter class. + private WebViewContentsClientAdapter mContentsClientAdapter; + // The wrapped Context. + private Context mContext; + + // Variables for functionality provided by this adapter --------------------------------------- + private ContentSettingsAdapter mWebSettings; + // The WebView wrapper for ContentViewCore and required browser compontents. + private AwContents mAwContents; + // Non-null if this webview is using the GL accelerated draw path. + private DrawGLFunctor mGLfunctor; + + private final WebView.HitTestResult mHitTestResult; + + private final int mAppTargetSdkVersion; + + private WebViewChromiumFactoryProvider mFactory; + + private static boolean sRecordWholeDocumentEnabledByApi = false; + static void enableSlowWholeDocumentDraw() { + sRecordWholeDocumentEnabledByApi = true; + } + + // This does not touch any global / non-threadsafe state, but note that + // init is ofter called right after and is NOT threadsafe. + public WebViewChromium(WebViewChromiumFactoryProvider factory, WebView webView, + WebView.PrivateAccess webViewPrivate) { + mWebView = webView; + mWebViewPrivate = webViewPrivate; + mHitTestResult = new WebView.HitTestResult(); + mContext = ResourcesContextWrapperFactory.get(mWebView.getContext()); + mAppTargetSdkVersion = mContext.getApplicationInfo().targetSdkVersion; + mFactory = factory; + mRunQueue = new WebViewChromiumRunQueue(); + factory.getWebViewDelegate().addWebViewAssetPath(mWebView.getContext()); + } + + static void completeWindowCreation(WebView parent, WebView child) { + AwContents parentContents = ((WebViewChromium) parent.getWebViewProvider()).mAwContents; + AwContents childContents = + child == null ? null : ((WebViewChromium) child.getWebViewProvider()).mAwContents; + parentContents.supplyContentsForPopup(childContents); + } + + private <T> T runBlockingFuture(FutureTask<T> task) { + if (!mFactory.hasStarted()) throw new RuntimeException("Must be started before we block!"); + if (ThreadUtils.runningOnUiThread()) { + throw new IllegalStateException("This method should only be called off the UI thread"); + } + mRunQueue.addTask(task); + try { + return task.get(4, TimeUnit.SECONDS); + } catch (java.util.concurrent.TimeoutException e) { + throw new RuntimeException("Probable deadlock detected due to WebView API being called " + + "on incorrect thread while the UI thread is blocked.", e); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + // 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! + private void runVoidTaskOnUiThreadBlocking(Runnable r) { + FutureTask<Void> task = new FutureTask<Void>(r, null); + runBlockingFuture(task); + } + + private <T> T runOnUiThreadBlocking(Callable<T> c) { + return runBlockingFuture(new FutureTask<T>(c)); + } + + // WebViewProvider methods -------------------------------------------------------------------- + + @Override + // BUG=6790250 |javaScriptInterfaces| was only ever used by the obsolete DumpRenderTree + // so is ignored. TODO: remove it from WebViewProvider. + public void init(final Map<String, Object> javaScriptInterfaces, + final boolean privateBrowsing) { + if (privateBrowsing) { + mFactory.startYourEngines(true); + final String msg = "Private browsing is not supported in WebView."; + if (mAppTargetSdkVersion >= Build.VERSION_CODES.KITKAT) { + throw new IllegalArgumentException(msg); + } else { + Log.w(TAG, msg); + TextView warningLabel = new TextView(mContext); + warningLabel.setText(mContext.getString( + R.string.webviewchromium_private_browsing_warning)); + mWebView.addView(warningLabel); + } + } + + // We will defer real initialization until we know which thread to do it on, unless: + // - we are on the main thread already (common case), + // - the app is targeting >= JB MR2, in which case checkThread enforces that all usage + // comes from a single thread. (Note in JB MR2 this exception was in WebView.java). + if (mAppTargetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + mFactory.startYourEngines(false); + checkThread(); + } else if (!mFactory.hasStarted()) { + if (Looper.myLooper() == Looper.getMainLooper()) { + mFactory.startYourEngines(true); + } + } + + final boolean isAccessFromFileURLsGrantedByDefault = + mAppTargetSdkVersion < Build.VERSION_CODES.JELLY_BEAN; + final boolean areLegacyQuirksEnabled = + mAppTargetSdkVersion < Build.VERSION_CODES.KITKAT; + + mContentsClientAdapter = new WebViewContentsClientAdapter( + mWebView, mContext, mFactory.getWebViewDelegate()); + mWebSettings = new ContentSettingsAdapter(new AwSettings( + mContext, isAccessFromFileURLsGrantedByDefault, + areLegacyQuirksEnabled)); + + if (mAppTargetSdkVersion < Build.VERSION_CODES.LOLLIPOP) { + // Prior to Lollipop we always allowed third party cookies and mixed content. + mWebSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); + mWebSettings.setAcceptThirdPartyCookies(true); + mWebSettings.getAwSettings().setZeroLayoutHeightDisablesViewportQuirk(true); + } + + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + initForReal(); + if (privateBrowsing) { + // Intentionally irreversibly disable the webview instance, so that private + // user data cannot leak through misuse of a non-privateBrowing WebView + // instance. Can't just null out mAwContents as we never null-check it + // before use. + destroy(); + } + } + }); + } + + private void initForReal() { + mAwContents = new AwContents(mFactory.getBrowserContext(), mWebView, mContext, + new InternalAccessAdapter(), new WebViewNativeGLDelegate(), + mContentsClientAdapter, mWebSettings.getAwSettings()); + + if (mAppTargetSdkVersion >= Build.VERSION_CODES.KITKAT) { + // On KK and above, favicons are automatically downloaded as the method + // old apps use to enable that behavior is deprecated. + AwContents.setShouldDownloadFavicons(); + } + + AwContentsStatics.setRecordFullDocument(sRecordWholeDocumentEnabledByApi || + mAppTargetSdkVersion < Build.VERSION_CODES.LOLLIPOP); + + if (mAppTargetSdkVersion < Build.VERSION_CODES.LOLLIPOP) { + // Prior to Lollipop, JavaScript objects injected via addJavascriptInterface + // were not inspectable. + mAwContents.disableJavascriptInterfacesInspection(); + } + + // TODO: This assumes AwContents ignores second Paint param. + mAwContents.setLayerType(mWebView.getLayerType(), null); + } + + void startYourEngine() { + mRunQueue.drainQueue(); + } + + private RuntimeException createThreadException() { + return new IllegalStateException( + "Calling View methods on another thread than the UI thread."); + } + + private boolean checkNeedsPost() { + boolean needsPost = !mFactory.hasStarted() || !ThreadUtils.runningOnUiThread(); + if (!needsPost && mAwContents == null) { + throw new IllegalStateException( + "AwContents must be created if we are not posting!"); + } + return needsPost; + } + + // Intentionally not static, as no need to check thread on static methods + private void checkThread() { + if (!ThreadUtils.runningOnUiThread()) { + final RuntimeException threadViolation = createThreadException(); + ThreadUtils.postOnUiThread(new Runnable() { + @Override + public void run() { + throw threadViolation; + } + }); + throw createThreadException(); + } + } + + @Override + public void setHorizontalScrollbarOverlay(final boolean overlay) { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + setHorizontalScrollbarOverlay(overlay); + } + }); + return; + } + mAwContents.setHorizontalScrollbarOverlay(overlay); + } + + @Override + public void setVerticalScrollbarOverlay(final boolean overlay) { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + setVerticalScrollbarOverlay(overlay); + } + }); + return; + } + mAwContents.setVerticalScrollbarOverlay(overlay); + } + + @Override + public boolean overlayHorizontalScrollbar() { + mFactory.startYourEngines(false); + if (checkNeedsPost()) { + boolean ret = runOnUiThreadBlocking(new Callable<Boolean>() { + @Override + public Boolean call() { + return overlayHorizontalScrollbar(); + } + }); + return ret; + } + return mAwContents.overlayHorizontalScrollbar(); + } + + @Override + public boolean overlayVerticalScrollbar() { + mFactory.startYourEngines(false); + if (checkNeedsPost()) { + boolean ret = runOnUiThreadBlocking(new Callable<Boolean>() { + @Override + public Boolean call() { + return overlayVerticalScrollbar(); + } + }); + return ret; + } + return mAwContents.overlayVerticalScrollbar(); + } + + @Override + public int getVisibleTitleHeight() { + // This is deprecated in WebView and should always return 0. + return 0; + } + + @Override + public SslCertificate getCertificate() { + mFactory.startYourEngines(true); + if (checkNeedsPost()) { + SslCertificate ret = runOnUiThreadBlocking(new Callable<SslCertificate>() { + @Override + public SslCertificate call() { + return getCertificate(); + } + }); + return ret; + } + return mAwContents.getCertificate(); + } + + @Override + public void setCertificate(SslCertificate certificate) { + // intentional no-op + } + + @Override + public void savePassword(String host, String username, String password) { + // This is a deprecated API: intentional no-op. + } + + @Override + public void setHttpAuthUsernamePassword(final String host, final String realm, + final String username, final String password) { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + setHttpAuthUsernamePassword(host, realm, username, password); + } + }); + return; + } + mAwContents.setHttpAuthUsernamePassword(host, realm, username, password); + } + + @Override + public String[] getHttpAuthUsernamePassword(final String host, final String realm) { + mFactory.startYourEngines(true); + if (checkNeedsPost()) { + String[] ret = runOnUiThreadBlocking(new Callable<String[]>() { + @Override + public String[] call() { + return getHttpAuthUsernamePassword(host, realm); + } + }); + return ret; + } + return mAwContents.getHttpAuthUsernamePassword(host, realm); + } + + @Override + public void destroy() { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + destroy(); + } + }); + return; + } + + mAwContents.destroy(); + if (mGLfunctor != null) { + mGLfunctor.destroy(); + mGLfunctor = null; + } + } + + @Override + public void setNetworkAvailable(final boolean networkUp) { + // Note that this purely toggles the JS navigator.online property. + // It does not in affect chromium or network stack state in any way. + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + setNetworkAvailable(networkUp); + } + }); + return; + } + mAwContents.setNetworkAvailable(networkUp); + } + + @Override + public WebBackForwardList saveState(final Bundle outState) { + mFactory.startYourEngines(true); + if (checkNeedsPost()) { + WebBackForwardList ret = runOnUiThreadBlocking(new Callable<WebBackForwardList>() { + @Override + public WebBackForwardList call() { + return saveState(outState); + } + }); + return ret; + } + if (outState == null) return null; + if (!mAwContents.saveState(outState)) return null; + return copyBackForwardList(); + } + + @Override + public boolean savePicture(Bundle b, File dest) { + // Intentional no-op: hidden method on WebView. + return false; + } + + @Override + public boolean restorePicture(Bundle b, File src) { + // Intentional no-op: hidden method on WebView. + return false; + } + + @Override + public WebBackForwardList restoreState(final Bundle inState) { + mFactory.startYourEngines(true); + if (checkNeedsPost()) { + WebBackForwardList ret = runOnUiThreadBlocking(new Callable<WebBackForwardList>() { + @Override + public WebBackForwardList call() { + return restoreState(inState); + } + }); + return ret; + } + if (inState == null) return null; + if (!mAwContents.restoreState(inState)) return null; + return copyBackForwardList(); + } + + @Override + public void loadUrl(final String url, Map<String, String> additionalHttpHeaders) { + // TODO: We may actually want to do some sanity checks here (like filter about://chrome). + + // For backwards compatibility, apps targeting less than K will have JS URLs evaluated + // directly and any result of the evaluation will not replace the current page content. + // Matching Chrome behavior more closely; apps targetting >= K that load a JS URL will + // have the result of that URL replace the content of the current page. + final String JAVASCRIPT_SCHEME = "javascript:"; + if (mAppTargetSdkVersion < Build.VERSION_CODES.KITKAT && + url != null && url.startsWith(JAVASCRIPT_SCHEME)) { + mFactory.startYourEngines(true); + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + mAwContents.evaluateJavaScriptEvenIfNotYetNavigated( + url.substring(JAVASCRIPT_SCHEME.length())); + } + }); + } else { + mAwContents.evaluateJavaScriptEvenIfNotYetNavigated( + url.substring(JAVASCRIPT_SCHEME.length())); + } + return; + } + + LoadUrlParams params = new LoadUrlParams(url); + if (additionalHttpHeaders != null) params.setExtraHeaders(additionalHttpHeaders); + loadUrlOnUiThread(params); + } + + @Override + public void loadUrl(String url) { + // Early out to match old WebView implementation + if (url == null) { + return; + } + loadUrl(url, null); + } + + @Override + public void postUrl(String url, byte[] postData) { + LoadUrlParams params = LoadUrlParams.createLoadHttpPostParams(url, postData); + Map<String,String> headers = new HashMap<String,String>(); + headers.put("Content-Type", "application/x-www-form-urlencoded"); + params.setExtraHeaders(headers); + loadUrlOnUiThread(params); + } + + private static String fixupMimeType(String mimeType) { + return TextUtils.isEmpty(mimeType) ? "text/html" : mimeType; + } + + private static String fixupData(String data) { + return TextUtils.isEmpty(data) ? "" : data; + } + + private static String fixupBase(String url) { + return TextUtils.isEmpty(url) ? "about:blank" : url; + } + + private static String fixupHistory(String url) { + return TextUtils.isEmpty(url) ? "about:blank" : url; + } + + private static boolean isBase64Encoded(String encoding) { + return "base64".equals(encoding); + } + + @Override + public void loadData(String data, String mimeType, String encoding) { + loadUrlOnUiThread(LoadUrlParams.createLoadDataParams( + fixupData(data), fixupMimeType(mimeType), isBase64Encoded(encoding))); + } + + @Override + public void loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, + String historyUrl) { + data = fixupData(data); + mimeType = fixupMimeType(mimeType); + LoadUrlParams loadUrlParams; + baseUrl = fixupBase(baseUrl); + historyUrl = fixupHistory(historyUrl); + + if (baseUrl.startsWith("data:")) { + // For backwards compatibility with WebViewClassic, we use the value of |encoding| + // as the charset, as long as it's not "base64". + boolean isBase64 = isBase64Encoded(encoding); + loadUrlParams = LoadUrlParams.createLoadDataParamsWithBaseUrl( + data, mimeType, isBase64, baseUrl, historyUrl, isBase64 ? null : encoding); + } else { + // When loading data with a non-data: base URL, the classic WebView would effectively + // "dump" that string of data into the WebView without going through regular URL + // loading steps such as decoding URL-encoded entities. We achieve this same behavior by + // base64 encoding the data that is passed here and then loading that as a data: URL. + try { + loadUrlParams = LoadUrlParams.createLoadDataParamsWithBaseUrl( + Base64.encodeToString(data.getBytes("utf-8"), Base64.DEFAULT), mimeType, + true, baseUrl, historyUrl, "utf-8"); + } catch (java.io.UnsupportedEncodingException e) { + Log.wtf(TAG, "Unable to load data string " + data, e); + return; + } + } + loadUrlOnUiThread(loadUrlParams); + } + + private void loadUrlOnUiThread(final LoadUrlParams loadUrlParams) { + // This is the last point that we can delay starting the Chromium backend up + // and if the app has not caused us to bind the Chromium UI thread to a background thread + // we now bind Chromium's notion of the UI thread to the app main thread. + mFactory.startYourEngines(true); + if (checkNeedsPost()) { + // Disallowed in WebView API for apps targetting a new SDK + assert mAppTargetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR2; + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + mAwContents.loadUrl(loadUrlParams); + } + }); + return; + } + mAwContents.loadUrl(loadUrlParams); + } + + public void evaluateJavaScript(String script, ValueCallback<String> resultCallback) { + checkThread(); + mAwContents.evaluateJavaScript(script, resultCallback); + } + + @Override + public void saveWebArchive(String filename) { + saveWebArchive(filename, false, null); + } + + @Override + public void saveWebArchive(final String basename, final boolean autoname, + final ValueCallback<String> callback) { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + saveWebArchive(basename, autoname, callback); + } + }); + return; + } + mAwContents.saveWebArchive(basename, autoname, callback); + } + + @Override + public void stopLoading() { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + stopLoading(); + } + }); + return; + } + + mAwContents.stopLoading(); + } + + @Override + public void reload() { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + reload(); + } + }); + return; + } + mAwContents.reload(); + } + + @Override + public boolean canGoBack() { + mFactory.startYourEngines(true); + if (checkNeedsPost()) { + Boolean ret = runOnUiThreadBlocking(new Callable<Boolean>() { + @Override + public Boolean call() { + return canGoBack(); + } + }); + return ret; + } + return mAwContents.canGoBack(); + } + + @Override + public void goBack() { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + goBack(); + } + }); + return; + } + mAwContents.goBack(); + } + + @Override + public boolean canGoForward() { + mFactory.startYourEngines(true); + if (checkNeedsPost()) { + Boolean ret = runOnUiThreadBlocking(new Callable<Boolean>() { + @Override + public Boolean call() { + return canGoForward(); + } + }); + return ret; + } + return mAwContents.canGoForward(); + } + + @Override + public void goForward() { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + goForward(); + } + }); + return; + } + mAwContents.goForward(); + } + + @Override + public boolean canGoBackOrForward(final int steps) { + mFactory.startYourEngines(true); + if (checkNeedsPost()) { + Boolean ret = runOnUiThreadBlocking(new Callable<Boolean>() { + @Override + public Boolean call() { + return canGoBackOrForward(steps); + } + }); + return ret; + } + return mAwContents.canGoBackOrForward(steps); + } + + @Override + public void goBackOrForward(final int steps) { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + goBackOrForward(steps); + } + }); + return; + } + mAwContents.goBackOrForward(steps); + } + + @Override + public boolean isPrivateBrowsingEnabled() { + // Not supported in this WebView implementation. + return false; + } + + @Override + public boolean pageUp(final boolean top) { + mFactory.startYourEngines(true); + if (checkNeedsPost()) { + Boolean ret = runOnUiThreadBlocking(new Callable<Boolean>() { + @Override + public Boolean call() { + return pageUp(top); + } + }); + return ret; + } + return mAwContents.pageUp(top); + } + + @Override + public boolean pageDown(final boolean bottom) { + mFactory.startYourEngines(true); + if (checkNeedsPost()) { + Boolean ret = runOnUiThreadBlocking(new Callable<Boolean>() { + @Override + public Boolean call() { + return pageDown(bottom); + } + }); + return ret; + } + return mAwContents.pageDown(bottom); + } + + @Override + public void clearView() { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + clearView(); + } + }); + return; + } + mAwContents.clearView(); + } + + @Override + public Picture capturePicture() { + mFactory.startYourEngines(true); + if (checkNeedsPost()) { + Picture ret = runOnUiThreadBlocking(new Callable<Picture>() { + @Override + public Picture call() { + return capturePicture(); + } + }); + return ret; + } + return mAwContents.capturePicture(); + } + + @Override + public float getScale() { + // No checkThread() as it is mostly thread safe (workaround for b/10652991). + mFactory.startYourEngines(true); + return mAwContents.getScale(); + } + + @Override + public void setInitialScale(final int scaleInPercent) { + // No checkThread() as it is thread safe + mWebSettings.getAwSettings().setInitialPageScale(scaleInPercent); + } + + @Override + public void invokeZoomPicker() { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + invokeZoomPicker(); + } + }); + return; + } + mAwContents.invokeZoomPicker(); + } + + @Override + public WebView.HitTestResult getHitTestResult() { + mFactory.startYourEngines(true); + if (checkNeedsPost()) { + WebView.HitTestResult ret = runOnUiThreadBlocking( + new Callable<WebView.HitTestResult>() { + @Override + public WebView.HitTestResult call() { + return getHitTestResult(); + } + }); + return ret; + } + AwContents.HitTestData data = mAwContents.getLastHitTestResult(); + mHitTestResult.setType(data.hitTestResultType); + mHitTestResult.setExtra(data.hitTestResultExtraData); + return mHitTestResult; + } + + @Override + public void requestFocusNodeHref(final Message hrefMsg) { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + requestFocusNodeHref(hrefMsg); + } + }); + return; + } + mAwContents.requestFocusNodeHref(hrefMsg); + } + + @Override + public void requestImageRef(final Message msg) { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + requestImageRef(msg); + } + }); + return; + } + mAwContents.requestImageRef(msg); + } + + @Override + public String getUrl() { + mFactory.startYourEngines(true); + if (checkNeedsPost()) { + String ret = runOnUiThreadBlocking(new Callable<String>() { + @Override + public String call() { + return getUrl(); + } + }); + return ret; + } + String url = mAwContents.getUrl(); + if (url == null || url.trim().isEmpty()) return null; + return url; + } + + @Override + public String getOriginalUrl() { + mFactory.startYourEngines(true); + if (checkNeedsPost()) { + String ret = runOnUiThreadBlocking(new Callable<String>() { + @Override + public String call() { + return getOriginalUrl(); + } + }); + return ret; + } + String url = mAwContents.getOriginalUrl(); + if (url == null || url.trim().isEmpty()) return null; + return url; + } + + @Override + public String getTitle() { + mFactory.startYourEngines(true); + if (checkNeedsPost()) { + String ret = runOnUiThreadBlocking(new Callable<String>() { + @Override + public String call() { + return getTitle(); + } + }); + return ret; + } + return mAwContents.getTitle(); + } + + @Override + public Bitmap getFavicon() { + mFactory.startYourEngines(true); + if (checkNeedsPost()) { + Bitmap ret = runOnUiThreadBlocking(new Callable<Bitmap>() { + @Override + public Bitmap call() { + return getFavicon(); + } + }); + return ret; + } + return mAwContents.getFavicon(); + } + + @Override + public String getTouchIconUrl() { + // Intentional no-op: hidden method on WebView. + return null; + } + + @Override + public int getProgress() { + if (mAwContents == null) return 100; + // No checkThread() because the value is cached java side (workaround for b/10533304). + return mAwContents.getMostRecentProgress(); + } + + @Override + public int getContentHeight() { + if (mAwContents == null) return 0; + // No checkThread() as it is mostly thread safe (workaround for b/10594869). + return mAwContents.getContentHeightCss(); + } + + @Override + public int getContentWidth() { + if (mAwContents == null) return 0; + // No checkThread() as it is mostly thread safe (workaround for b/10594869). + return mAwContents.getContentWidthCss(); + } + + @Override + public void pauseTimers() { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + pauseTimers(); + } + }); + return; + } + mAwContents.pauseTimers(); + } + + @Override + public void resumeTimers() { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + resumeTimers(); + } + }); + return; + } + mAwContents.resumeTimers(); + } + + @Override + public void onPause() { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + onPause(); + } + }); + return; + } + mAwContents.onPause(); + } + + @Override + public void onResume() { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + onResume(); + } + }); + return; + } + mAwContents.onResume(); + } + + @Override + public boolean isPaused() { + mFactory.startYourEngines(true); + if (checkNeedsPost()) { + Boolean ret = runOnUiThreadBlocking(new Callable<Boolean>() { + @Override + public Boolean call() { + return isPaused(); + } + }); + return ret; + } + return mAwContents.isPaused(); + } + + @Override + public void freeMemory() { + // Intentional no-op. Memory is managed automatically by Chromium. + } + + @Override + public void clearCache(final boolean includeDiskFiles) { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + clearCache(includeDiskFiles); + } + }); + return; + } + mAwContents.clearCache(includeDiskFiles); + } + + /** + * This is a poorly named method, but we keep it for historical reasons. + */ + @Override + public void clearFormData() { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + clearFormData(); + } + }); + return; + } + mAwContents.hideAutofillPopup(); + } + + @Override + public void clearHistory() { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + clearHistory(); + } + }); + return; + } + mAwContents.clearHistory(); + } + + @Override + public void clearSslPreferences() { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + clearSslPreferences(); + } + }); + return; + } + mAwContents.clearSslPreferences(); + } + + @Override + public WebBackForwardList copyBackForwardList() { + mFactory.startYourEngines(true); + if (checkNeedsPost()) { + WebBackForwardList ret = runOnUiThreadBlocking(new Callable<WebBackForwardList>() { + @Override + public WebBackForwardList call() { + return copyBackForwardList(); + } + }); + return ret; + } + return new WebBackForwardListChromium( + mAwContents.getNavigationHistory()); + } + + @Override + public void setFindListener(WebView.FindListener listener) { + mContentsClientAdapter.setFindListener(listener); + } + + @Override + public void findNext(final boolean forwards) { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + findNext(forwards); + } + }); + return; + } + mAwContents.findNext(forwards); + } + + @Override + public int findAll(final String searchString) { + findAllAsync(searchString); + return 0; + } + + @Override + public void findAllAsync(final String searchString) { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + findAllAsync(searchString); + } + }); + return; + } + mAwContents.findAllAsync(searchString); + } + + @Override + public boolean showFindDialog(final String text, final boolean showIme) { + mFactory.startYourEngines(false); + if (checkNeedsPost()) { + return false; + } + if (mWebView.getParent() == null) { + return false; + } + + FindActionModeCallback findAction = new FindActionModeCallback(mContext); + if (findAction == null) { + return false; + } + + mWebView.startActionMode(findAction); + findAction.setWebView(mWebView); + if (showIme) { + findAction.showSoftInput(); + } + + if (text != null) { + findAction.setText(text); + findAction.findAll(); + } + + return true; + } + + @Override + public void notifyFindDialogDismissed() { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + notifyFindDialogDismissed(); + } + }); + return; + } + clearMatches(); + } + + @Override + public void clearMatches() { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + clearMatches(); + } + }); + return; + } + mAwContents.clearMatches(); + } + + @Override + public void documentHasImages(final Message response) { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + documentHasImages(response); + } + }); + return; + } + mAwContents.documentHasImages(response); + } + + @Override + public void setWebViewClient(WebViewClient client) { + mContentsClientAdapter.setWebViewClient(client); + } + + @Override + public void setDownloadListener(DownloadListener listener) { + mContentsClientAdapter.setDownloadListener(listener); + } + + @Override + public void setWebChromeClient(WebChromeClient client) { + mWebSettings.getAwSettings().setFullscreenSupported(doesSupportFullscreen(client)); + mContentsClientAdapter.setWebChromeClient(client); + } + + /** + * Returns true if the supplied {@link WebChromeClient} supports fullscreen. + * + * <p>For fullscreen support, implementations of {@link WebChromeClient#onShowCustomView} + * and {@link WebChromeClient#onHideCustomView()} are required. + */ + private boolean doesSupportFullscreen(WebChromeClient client) { + if (client == null) { + return false; + } + Class<?> clientClass = client.getClass(); + boolean foundShowMethod = false; + boolean foundHideMethod = false; + while (clientClass != WebChromeClient.class && (!foundShowMethod || !foundHideMethod)) { + if (!foundShowMethod) { + try { + clientClass.getDeclaredMethod("onShowCustomView", View.class, + CustomViewCallback.class); + foundShowMethod = true; + } catch (NoSuchMethodException e) { } + } + + if (!foundHideMethod) { + try { + clientClass.getDeclaredMethod("onHideCustomView"); + foundHideMethod = true; + } catch (NoSuchMethodException e) { } + } + clientClass = clientClass.getSuperclass(); + } + return foundShowMethod && foundHideMethod; + } + + @Override + public void setPictureListener(final WebView.PictureListener listener) { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + setPictureListener(listener); + } + }); + return; + } + mContentsClientAdapter.setPictureListener(listener); + mAwContents.enableOnNewPicture(listener != null, + mAppTargetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN_MR2); + } + + @Override + public void addJavascriptInterface(final Object obj, final String interfaceName) { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + addJavascriptInterface(obj, interfaceName); + } + }); + return; + } + Class<? extends Annotation> requiredAnnotation = null; + if (mAppTargetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + requiredAnnotation = JavascriptInterface.class; + } + mAwContents.addPossiblyUnsafeJavascriptInterface(obj, interfaceName, requiredAnnotation); + } + + @Override + public void removeJavascriptInterface(final String interfaceName) { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + removeJavascriptInterface(interfaceName); + } + }); + return; + } + mAwContents.removeJavascriptInterface(interfaceName); + } + + @Override + public WebSettings getSettings() { + return mWebSettings; + } + + @Override + public void setMapTrackballToArrowKeys(boolean setMap) { + // This is a deprecated API: intentional no-op. + } + + @Override + public void flingScroll(final int vx, final int vy) { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + flingScroll(vx, vy); + } + }); + return; + } + mAwContents.flingScroll(vx, vy); + } + + @Override + public View getZoomControls() { + mFactory.startYourEngines(false); + if (checkNeedsPost()) { + return null; + } + + // This was deprecated in 2009 and hidden in JB MR1, so just provide the minimum needed + // to stop very out-dated applications from crashing. + Log.w(TAG, "WebView doesn't support getZoomControls"); + return mAwContents.getSettings().supportZoom() ? new View(mContext) : null; + } + + @Override + public boolean canZoomIn() { + if (checkNeedsPost()) { + return false; + } + return mAwContents.canZoomIn(); + } + + @Override + public boolean canZoomOut() { + if (checkNeedsPost()) { + return false; + } + return mAwContents.canZoomOut(); + } + + @Override + public boolean zoomIn() { + mFactory.startYourEngines(true); + if (checkNeedsPost()) { + boolean ret = runOnUiThreadBlocking(new Callable<Boolean>() { + @Override + public Boolean call() { + return zoomIn(); + } + }); + return ret; + } + return mAwContents.zoomIn(); + } + + @Override + public boolean zoomOut() { + mFactory.startYourEngines(true); + if (checkNeedsPost()) { + boolean ret = runOnUiThreadBlocking(new Callable<Boolean>() { + @Override + public Boolean call() { + return zoomOut(); + } + }); + return ret; + } + return mAwContents.zoomOut(); + } + + @Override + public boolean zoomBy(float factor) { + mFactory.startYourEngines(true); + // This is an L API and therefore we can enforce stricter threading constraints. + checkThread(); + return mAwContents.zoomBy(factor); + } + + @Override + public void dumpViewHierarchyWithProperties(BufferedWriter out, int level) { + // Intentional no-op + } + + @Override + public View findHierarchyView(String className, int hashCode) { + // Intentional no-op + return null; + } + + // WebViewProvider glue methods --------------------------------------------------------------- + + @Override + // This needs to be kept thread safe! + public WebViewProvider.ViewDelegate getViewDelegate() { + return this; + } + + @Override + // This needs to be kept thread safe! + public WebViewProvider.ScrollDelegate getScrollDelegate() { + return this; + } + + + // WebViewProvider.ViewDelegate implementation ------------------------------------------------ + + // TODO: remove from WebViewProvider and use default implementation from + // ViewGroup. + // @Override + public boolean shouldDelayChildPressedState() { + mFactory.startYourEngines(false); + if (checkNeedsPost()) { + boolean ret = runOnUiThreadBlocking(new Callable<Boolean>() { + @Override + public Boolean call() { + return shouldDelayChildPressedState(); + } + }); + return ret; + } + return true; + } + +// @Override + public AccessibilityNodeProvider getAccessibilityNodeProvider() { + mFactory.startYourEngines(false); + if (checkNeedsPost()) { + AccessibilityNodeProvider ret = runOnUiThreadBlocking( + new Callable<AccessibilityNodeProvider>() { + @Override + public AccessibilityNodeProvider call() { + return getAccessibilityNodeProvider(); + } + }); + return ret; + } + return mAwContents.getAccessibilityNodeProvider(); + } + + @Override + public void onInitializeAccessibilityNodeInfo(final AccessibilityNodeInfo info) { + mFactory.startYourEngines(false); + if (checkNeedsPost()) { + runVoidTaskOnUiThreadBlocking(new Runnable() { + @Override + public void run() { + onInitializeAccessibilityNodeInfo(info); + } + }); + return; + } + mAwContents.onInitializeAccessibilityNodeInfo(info); + } + + @Override + public void onInitializeAccessibilityEvent(final AccessibilityEvent event) { + mFactory.startYourEngines(false); + if (checkNeedsPost()) { + runVoidTaskOnUiThreadBlocking(new Runnable() { + @Override + public void run() { + onInitializeAccessibilityEvent(event); + } + }); + return; + } + mAwContents.onInitializeAccessibilityEvent(event); + } + + @Override + public boolean performAccessibilityAction(final int action, final Bundle arguments) { + mFactory.startYourEngines(false); + if (checkNeedsPost()) { + boolean ret = runOnUiThreadBlocking(new Callable<Boolean>() { + @Override + public Boolean call() { + return performAccessibilityAction(action, arguments); + } + }); + return ret; + } + if (mAwContents.supportsAccessibilityAction(action)) { + return mAwContents.performAccessibilityAction(action, arguments); + } + return mWebViewPrivate.super_performAccessibilityAction(action, arguments); + } + + @Override + public void setOverScrollMode(final int mode) { + // This gets called from the android.view.View c'tor that WebView inherits from. This + // causes the method to be called when mAwContents == null. + // It's safe to ignore these calls however since AwContents will read the current value of + // this setting when it's created. + if (mAwContents == null) return; + + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + setOverScrollMode(mode); + } + }); + return; + } + mAwContents.setOverScrollMode(mode); + } + + @Override + public void setScrollBarStyle(final int style) { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + setScrollBarStyle(style); + } + }); + return; + } + mAwContents.setScrollBarStyle(style); + } + + @Override + public void onDrawVerticalScrollBar(final Canvas canvas, final Drawable scrollBar, final int l, + final int t, final int r, final int b) { + // WebViewClassic was overriding this method to handle rubberband over-scroll. Since + // WebViewChromium doesn't support that the vanilla implementation of this method can be + // used. + mWebViewPrivate.super_onDrawVerticalScrollBar(canvas, scrollBar, l, t, r, b); + } + + @Override + public void onOverScrolled(final int scrollX, final int scrollY, final boolean clampedX, + final boolean clampedY) { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + onOverScrolled(scrollX, scrollY, clampedX, clampedY); + } + }); + return; + } + mAwContents.onContainerViewOverScrolled(scrollX, scrollY, clampedX, clampedY); + } + + @Override + public void onWindowVisibilityChanged(final int visibility) { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + onWindowVisibilityChanged(visibility); + } + }); + return; + } + mAwContents.onWindowVisibilityChanged(visibility); + } + + @Override + public void onDraw(final Canvas canvas) { + mFactory.startYourEngines(true); + if (checkNeedsPost()) { + runVoidTaskOnUiThreadBlocking(new Runnable() { + @Override + public void run() { + onDraw(canvas); + } + }); + return; + } + mAwContents.onDraw(canvas); + } + + @Override + public void setLayoutParams(final ViewGroup.LayoutParams layoutParams) { + // This API is our strongest signal from the View system that this + // WebView is going to be bound to a View hierarchy and so at this + // point we must bind Chromium's UI thread to the current thread. + mFactory.startYourEngines(false); + checkThread(); + mWebViewPrivate.super_setLayoutParams(layoutParams); + } + + @Override + public boolean performLongClick() { + // Return false unless the WebView is attached to a View with a parent + return mWebView.getParent() != null ? mWebViewPrivate.super_performLongClick() : false; + } + + @Override + public void onConfigurationChanged(final Configuration newConfig) { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + onConfigurationChanged(newConfig); + } + }); + return; + } + mAwContents.onConfigurationChanged(newConfig); + } + + @Override + public InputConnection onCreateInputConnection(final EditorInfo outAttrs) { + mFactory.startYourEngines(false); + if (checkNeedsPost()) { + return null; + } + return mAwContents.onCreateInputConnection(outAttrs); + } + + @Override + public boolean onKeyMultiple(final int keyCode, final int repeatCount, final KeyEvent event) { + mFactory.startYourEngines(false); + if (checkNeedsPost()) { + boolean ret = runOnUiThreadBlocking(new Callable<Boolean>() { + @Override + public Boolean call() { + return onKeyMultiple(keyCode, repeatCount, event); + } + }); + return ret; + } + UnimplementedWebViewApi.invoke(); + return false; + } + + @Override + public boolean onKeyDown(final int keyCode, final KeyEvent event) { + mFactory.startYourEngines(false); + if (checkNeedsPost()) { + boolean ret = runOnUiThreadBlocking(new Callable<Boolean>() { + @Override + public Boolean call() { + return onKeyDown(keyCode, event); + } + }); + return ret; + } + UnimplementedWebViewApi.invoke(); + return false; + } + + @Override + public boolean onKeyUp(final int keyCode, final KeyEvent event) { + mFactory.startYourEngines(false); + if (checkNeedsPost()) { + boolean ret = runOnUiThreadBlocking(new Callable<Boolean>() { + @Override + public Boolean call() { + return onKeyUp(keyCode, event); + } + }); + return ret; + } + return mAwContents.onKeyUp(keyCode, event); + } + + @Override + public void onAttachedToWindow() { + // This API is our strongest signal from the View system that this + // WebView is going to be bound to a View hierarchy and so at this + // point we must bind Chromium's UI thread to the current thread. + mFactory.startYourEngines(false); + checkThread(); + mAwContents.onAttachedToWindow(); + } + + @Override + public void onDetachedFromWindow() { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + onDetachedFromWindow(); + } + }); + return; + } + + mAwContents.onDetachedFromWindow(); + } + + @Override + public void onVisibilityChanged(final View changedView, final int visibility) { + // The AwContents will find out the container view visibility before the first draw so we + // can safely ignore onVisibilityChanged callbacks that happen before init(). + if (mAwContents == null) return; + + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + onVisibilityChanged(changedView, visibility); + } + }); + return; + } + mAwContents.onVisibilityChanged(changedView, visibility); + } + + @Override + public void onWindowFocusChanged(final boolean hasWindowFocus) { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + onWindowFocusChanged(hasWindowFocus); + } + }); + return; + } + mAwContents.onWindowFocusChanged(hasWindowFocus); + } + + @Override + public void onFocusChanged(final boolean focused, final int direction, + final Rect previouslyFocusedRect) { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + onFocusChanged(focused, direction, previouslyFocusedRect); + } + }); + return; + } + mAwContents.onFocusChanged(focused, direction, previouslyFocusedRect); + } + + @Override + public boolean setFrame(final int left, final int top, final int right, final int bottom) { + return mWebViewPrivate.super_setFrame(left, top, right, bottom); + } + + @Override + public void onSizeChanged(final int w, final int h, final int ow, final int oh) { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + onSizeChanged(w, h, ow, oh); + } + }); + return; + } + mAwContents.onSizeChanged(w, h, ow, oh); + } + + @Override + public void onScrollChanged(final int l, final int t, final int oldl, final int oldt) { + if (checkNeedsPost()) { + mRunQueue.addTask(new Runnable() { + @Override + public void run() { + onScrollChanged(l, t, oldl, oldt); + } + }); + return; + } + mAwContents.onContainerViewScrollChanged(l, t, oldl, oldt); + } + + @Override + public boolean dispatchKeyEvent(final KeyEvent event) { + mFactory.startYourEngines(false); + if (checkNeedsPost()) { + boolean ret = runOnUiThreadBlocking(new Callable<Boolean>() { + @Override + public Boolean call() { + return dispatchKeyEvent(event); + } + }); + return ret; + } + return mAwContents.dispatchKeyEvent(event); + } + + @Override + public boolean onTouchEvent(final MotionEvent ev) { + mFactory.startYourEngines(false); + if (checkNeedsPost()) { + boolean ret = runOnUiThreadBlocking(new Callable<Boolean>() { + @Override + public Boolean call() { + return onTouchEvent(ev); + } + }); + return ret; + } + return mAwContents.onTouchEvent(ev); + } + + @Override + public boolean onHoverEvent(final MotionEvent event) { + mFactory.startYourEngines(false); + if (checkNeedsPost()) { + boolean ret = runOnUiThreadBlocking(new Callable<Boolean>() { + @Override + public Boolean call() { + return onHoverEvent(event); + } + }); + return ret; + } + return mAwContents.onHoverEvent(event); + } + + @Override + public boolean onGenericMotionEvent(final MotionEvent event) { + mFactory.startYourEngines(false); + if (checkNeedsPost()) { + boolean ret = runOnUiThreadBlocking(new Callable<Boolean>() { + @Override + public Boolean call() { + return onGenericMotionEvent(event); + } + }); + return ret; + } + return mAwContents.onGenericMotionEvent(event); + } + + @Override + public boolean onTrackballEvent(MotionEvent ev) { + // Trackball event not handled, which eventually gets converted to DPAD keyevents + return false; + } + + @Override + public boolean requestFocus(final int direction, final Rect previouslyFocusedRect) { + mFactory.startYourEngines(false); + if (checkNeedsPost()) { + boolean ret = runOnUiThreadBlocking(new Callable<Boolean>() { + @Override + public Boolean call() { + return requestFocus(direction, previouslyFocusedRect); + } + }); + return ret; + } + mAwContents.requestFocus(); + return mWebViewPrivate.super_requestFocus(direction, previouslyFocusedRect); + } + + @Override + public void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { + mFactory.startYourEngines(false); + if (checkNeedsPost()) { + runVoidTaskOnUiThreadBlocking(new Runnable() { + @Override + public void run() { + onMeasure(widthMeasureSpec, heightMeasureSpec); + } + }); + return; + } + mAwContents.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + @Override + public boolean requestChildRectangleOnScreen(final View child, final Rect rect, + final boolean immediate) { + mFactory.startYourEngines(false); + if (checkNeedsPost()) { + boolean ret = runOnUiThreadBlocking(new Callable<Boolean>() { + @Override + public Boolean call() { + return requestChildRectangleOnScreen(child, rect, immediate); + } + }); + return ret; + } + return mAwContents.requestChildRectangleOnScreen(child, rect, immediate); + } + + @Override + public void setBackgroundColor(final int color) { + mFactory.startYourEngines(false); + if (checkNeedsPost()) { + ThreadUtils.postOnUiThread(new Runnable() { + @Override + public void run() { + setBackgroundColor(color); + } + }); + return; + } + mAwContents.setBackgroundColor(color); + } + + @Override + public void setLayerType(final int layerType, final Paint paint) { + // This can be called from WebView constructor in which case mAwContents + // is still null. We set the layer type in initForReal in that case. + if (mAwContents == null) return; + if (checkNeedsPost()) { + ThreadUtils.postOnUiThread(new Runnable() { + @Override + public void run() { + setLayerType(layerType, paint); + } + }); + return; + } + mAwContents.setLayerType(layerType, paint); + } + + // Remove from superclass + public void preDispatchDraw(Canvas canvas) { + // TODO(leandrogracia): remove this method from WebViewProvider if we think + // we won't need it again. + } + + public void onStartTemporaryDetach() { + mAwContents.onStartTemporaryDetach(); + } + + public void onFinishTemporaryDetach() { + mAwContents.onFinishTemporaryDetach(); + } + + // WebViewProvider.ScrollDelegate implementation ---------------------------------------------- + + @Override + public int computeHorizontalScrollRange() { + mFactory.startYourEngines(false); + if (checkNeedsPost()) { + int ret = runOnUiThreadBlocking(new Callable<Integer>() { + @Override + public Integer call() { + return computeHorizontalScrollRange(); + } + }); + return ret; + } + return mAwContents.computeHorizontalScrollRange(); + } + + @Override + public int computeHorizontalScrollOffset() { + mFactory.startYourEngines(false); + if (checkNeedsPost()) { + int ret = runOnUiThreadBlocking(new Callable<Integer>() { + @Override + public Integer call() { + return computeHorizontalScrollOffset(); + } + }); + return ret; + } + return mAwContents.computeHorizontalScrollOffset(); + } + + @Override + public int computeVerticalScrollRange() { + mFactory.startYourEngines(false); + if (checkNeedsPost()) { + int ret = runOnUiThreadBlocking(new Callable<Integer>() { + @Override + public Integer call() { + return computeVerticalScrollRange(); + } + }); + return ret; + } + return mAwContents.computeVerticalScrollRange(); + } + + @Override + public int computeVerticalScrollOffset() { + mFactory.startYourEngines(false); + if (checkNeedsPost()) { + int ret = runOnUiThreadBlocking(new Callable<Integer>() { + @Override + public Integer call() { + return computeVerticalScrollOffset(); + } + }); + return ret; + } + return mAwContents.computeVerticalScrollOffset(); + } + + @Override + public int computeVerticalScrollExtent() { + mFactory.startYourEngines(false); + if (checkNeedsPost()) { + int ret = runOnUiThreadBlocking(new Callable<Integer>() { + @Override + public Integer call() { + return computeVerticalScrollExtent(); + } + }); + return ret; + } + return mAwContents.computeVerticalScrollExtent(); + } + + @Override + public void computeScroll() { + mFactory.startYourEngines(false); + if (checkNeedsPost()) { + runVoidTaskOnUiThreadBlocking(new Runnable() { + @Override + public void run() { + computeScroll(); + } + }); + return; + } + mAwContents.computeScroll(); + } + + // TODO(sgurun) this is only to have master-gpl compiling. + public PrintDocumentAdapter createPrintDocumentAdapter() { + return createPrintDocumentAdapter("default"); + } + + //@Override TODO(sgurun) commenting this out to have master-gpl compiling. + public PrintDocumentAdapter createPrintDocumentAdapter(String documentName) { + checkThread(); + return new AwPrintDocumentAdapter(mAwContents.getPdfExporter(), documentName); + } + + // AwContents.NativeGLDelegate implementation -------------------------------------- + private class WebViewNativeGLDelegate implements AwContents.NativeGLDelegate { + @Override + public boolean requestDrawGL(Canvas canvas, boolean waitForCompletion, + View containerView) { + if (mGLfunctor == null) { + mGLfunctor = new DrawGLFunctor(mAwContents.getAwDrawGLViewContext(), + mFactory.getWebViewDelegate()); + } + return mGLfunctor.requestDrawGL(canvas, containerView, waitForCompletion); + } + + @Override + public void detachGLFunctor() { + if (mGLfunctor != null) { + mGLfunctor.detach(); + } + } + } + + // AwContents.InternalAccessDelegate implementation -------------------------------------- + private class InternalAccessAdapter implements AwContents.InternalAccessDelegate { + @Override + public boolean drawChild(Canvas arg0, View arg1, long arg2) { + UnimplementedWebViewApi.invoke(); + return false; + } + + @Override + public boolean super_onKeyUp(int arg0, KeyEvent arg1) { + // Intentional no-op + return false; + } + + @Override + public boolean super_dispatchKeyEventPreIme(KeyEvent arg0) { + UnimplementedWebViewApi.invoke(); + return false; + } + + @Override + public boolean super_dispatchKeyEvent(KeyEvent event) { + return mWebViewPrivate.super_dispatchKeyEvent(event); + } + + @Override + public boolean super_onGenericMotionEvent(MotionEvent arg0) { + return mWebViewPrivate.super_onGenericMotionEvent(arg0); + } + + @Override + public void super_onConfigurationChanged(Configuration arg0) { + // Intentional no-op + } + + @Override + public int super_getScrollBarStyle() { + return mWebViewPrivate.super_getScrollBarStyle(); + } + + @Override + public boolean awakenScrollBars() { + mWebViewPrivate.awakenScrollBars(0); + // TODO: modify the WebView.PrivateAccess to provide a return value. + return true; + } + + @Override + public boolean super_awakenScrollBars(int arg0, boolean arg1) { + // TODO: need method on WebView.PrivateAccess? + UnimplementedWebViewApi.invoke(); + return false; + } + + @Override + public void onScrollChanged(int l, int t, int oldl, int oldt) { + // Intentional no-op. + // Chromium calls this directly to trigger accessibility events. That isn't needed + // for WebView since super_scrollTo invokes onScrollChanged for us. + } + + @Override + public void overScrollBy(int deltaX, int deltaY, + int scrollX, int scrollY, + int scrollRangeX, int scrollRangeY, + int maxOverScrollX, int maxOverScrollY, + boolean isTouchEvent) { + mWebViewPrivate.overScrollBy(deltaX, deltaY, scrollX, scrollY, + scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent); + } + + @Override + public void super_scrollTo(int scrollX, int scrollY) { + mWebViewPrivate.super_scrollTo(scrollX, scrollY); + } + + @Override + public void setMeasuredDimension(int measuredWidth, int measuredHeight) { + mWebViewPrivate.setMeasuredDimension(measuredWidth, measuredHeight); + } + + // @Override + public boolean super_onHoverEvent(MotionEvent event) { + return mWebViewPrivate.super_onHoverEvent(event); + } + } + + // Implements SmartClipProvider + @Override + public void extractSmartClipData(int x, int y, int width, int height) { + checkThread(); + mAwContents.extractSmartClipData(x, y, width, height); + } + + // Implements SmartClipProvider + @Override + public void setSmartClipResultHandler(final Handler resultHandler) { + checkThread(); + mAwContents.setSmartClipResultHandler(resultHandler); + } + +} diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumFactoryProvider.java b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumFactoryProvider.java new file mode 100644 index 0000000..60c7966 --- /dev/null +++ b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumFactoryProvider.java @@ -0,0 +1,469 @@ +// 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.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.app.ActivityManager; +import android.content.ComponentCallbacks2; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.Uri; +import android.os.Build; +import android.os.Looper; +import android.os.StrictMode; +import android.util.Log; +import android.webkit.CookieManager; +import android.webkit.GeolocationPermissions; +import android.webkit.WebIconDatabase; +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.AwBrowserContext; +import org.chromium.android_webview.AwBrowserProcess; +import org.chromium.android_webview.AwContents; +import org.chromium.android_webview.AwContentsStatics; +import org.chromium.android_webview.AwCookieManager; +import org.chromium.android_webview.AwDevToolsServer; +import org.chromium.android_webview.AwFormDatabase; +import org.chromium.android_webview.AwGeolocationPermissions; +import org.chromium.android_webview.AwQuotaManagerBridge; +import org.chromium.android_webview.AwResource; +import org.chromium.android_webview.AwSettings; +import org.chromium.base.CommandLine; +import org.chromium.base.MemoryPressureListener; +import org.chromium.base.PathService; +import org.chromium.base.PathUtils; +import org.chromium.base.ResourceExtractor; +import org.chromium.base.ThreadUtils; +import org.chromium.base.TraceEvent; +import org.chromium.base.library_loader.LibraryLoader; +import org.chromium.base.library_loader.ProcessInitException; +import org.chromium.content.app.ContentMain; +import org.chromium.content.browser.ContentViewStatics; + +import java.io.File; +import java.lang.ref.WeakReference; +import java.util.ArrayList; + +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 static final String COMMAND_LINE_FILE = "/data/local/tmp/webview-command-line"; + + // Guards accees to the other members, and is notifyAll() signalled on the UI thread + // when the chromium process has been started. + private final Object mLock = new Object(); + + // Initialization guarded by mLock. + private AwBrowserContext mBrowserContext; + private Statics mStaticMethods; + private GeolocationPermissionsAdapter mGeolocationPermissions; + private CookieManagerAdapter mCookieManager; + private WebIconDatabaseAdapter mWebIconDatabase; + private WebStorageAdapter mWebStorage; + private WebViewDatabaseAdapter mWebViewDatabase; + private AwDevToolsServer mDevToolsServer; + + private ArrayList<WeakReference<WebViewChromium>> mWebViewsToStart = + new ArrayList<WeakReference<WebViewChromium>>(); + + // Read/write protected by mLock. + private boolean mStarted; + + private SharedPreferences mWebViewPrefs; + private WebViewDelegate mWebViewDelegate; + + /** + * 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)); + } + + private void initialize(WebViewDelegate webViewDelegate) { + mWebViewDelegate = webViewDelegate; + if (isBuildDebuggable()) { + // Suppress the StrictMode violation as this codepath is only hit on debugglable builds. + StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); + CommandLine.initFromFile(COMMAND_LINE_FILE); + StrictMode.setThreadPolicy(oldPolicy); + } else { + CommandLine.init(null); + } + + CommandLine cl = CommandLine.getInstance(); + // TODO: currently in a relase build the DCHECKs only log. We either need to insall + // a report handler with SetLogReportHandler to make them assert, or else compile + // them out of the build altogether (b/8284203). Either way, so long they're + // compiled in, we may as unconditionally enable them here. + cl.appendSwitch("enable-dcheck"); + + ThreadUtils.setWillOverrideUiThread(); + // Load chromium library. + AwBrowserProcess.loadLibrary(); + + final PackageInfo packageInfo = WebViewFactory.getLoadedPackageInfo(); + + // Register the handler that will append the WebView version to logcat in case of a crash. + AwContentsStatics.registerCrashHandler( + "Version " + packageInfo.versionName + " (code " + packageInfo.versionCode + ")"); + + // Load glue-layer support library. + System.loadLibrary("webviewchromium_plat_support"); + + // Use shared preference to check for package downgrade. + mWebViewPrefs = mWebViewDelegate.getApplication().getSharedPreferences( + CHROMIUM_PREFS_NAME, Context.MODE_PRIVATE); + int lastVersion = mWebViewPrefs.getInt(VERSION_CODE_PREF, 0); + int currentVersion = packageInfo.versionCode; + if (lastVersion > currentVersion) { + // The WebView package has been downgraded since we last ran in this application. + // Delete the WebView data directory's contents. + String dataDir = PathUtils.getDataDirectory(mWebViewDelegate.getApplication()); + 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(); + } + // Now safe to use WebView data directory. + } + + private static boolean isBuildDebuggable() { + return !Build.TYPE.equals("user"); + } + + 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); + } + } + } + } + + private void initPlatSupportLibrary() { + DrawGLFunctor.setChromiumAwDrawGLFunction(AwContents.getAwDrawGLFunction()); + AwContents.setAwDrawSWFunctionTable(GraphicsUtils.getDrawSWFunctionTable()); + AwContents.setAwDrawGLFunctionTable(GraphicsUtils.getDrawGLFunctionTable()); + } + + private void ensureChromiumStartedLocked(boolean onMainThread) { + assert Thread.holdsLock(mLock); + + if (mStarted) { // Early-out for the common case. + return; + } + + Looper looper = !onMainThread ? Looper.myLooper() : Looper.getMainLooper(); + Log.v(TAG, "Binding Chromium to " + + (Looper.getMainLooper().equals(looper) ? "main":"background") + + " looper " + looper); + ThreadUtils.setUiThread(looper); + + if (ThreadUtils.runningOnUiThread()) { + startChromiumLocked(); + return; + } + + // We must post to the UI thread to cover the case that the user has invoked Chromium + // startup by using the (thread-safe) CookieManager rather than creating a WebView. + ThreadUtils.postOnUiThread(new Runnable() { + @Override + public void run() { + synchronized (mLock) { + startChromiumLocked(); + } + } + }); + while (!mStarted) { + try { + // Important: wait() releases |mLock| the UI thread can take it :-) + mLock.wait(); + } catch (InterruptedException e) { + // Keep trying... eventually the UI thread will process the task we sent it. + } + } + } + + private void startChromiumLocked() { + assert Thread.holdsLock(mLock) && ThreadUtils.runningOnUiThread(); + + // The post-condition of this method is everything is ready, so notify now to cover all + // return paths. (Other threads will not wake-up until we release |mLock|, whatever). + mLock.notifyAll(); + + if (mStarted) { + return; + } + + // We don't need to extract any paks because for WebView, they are + // in the system image. + ResourceExtractor.setMandatoryPaksToExtract(""); + + try { + LibraryLoader.ensureInitialized(); + } catch(ProcessInitException e) { + throw new RuntimeException("Error initializing WebView library", e); + } + + PathService.override(PathService.DIR_MODULE, "/system/lib/"); + // TODO: DIR_RESOURCE_PAKS_ANDROID needs to live somewhere sensible, + // inlined here for simplicity setting up the HTMLViewer demo. Unfortunately + // it can't go into base.PathService, as the native constant it refers to + // lives in the ui/ layer. See ui/base/ui_base_paths.h + final int DIR_RESOURCE_PAKS_ANDROID = 3003; + PathService.override(DIR_RESOURCE_PAKS_ANDROID, + "/system/framework/webview/paks"); + + // Make sure that ResourceProvider is initialized before starting the browser process. + Context context = getWrappedCurrentApplicationContext(); + setUpResources(context); + initPlatSupportLibrary(); + AwBrowserProcess.start(context); + + if (isBuildDebuggable()) { + setWebContentsDebuggingEnabled(true); + } + + TraceEvent.setATraceEnabled(mWebViewDelegate.isTraceTagEnabled()); + mWebViewDelegate.setOnTraceEnabledChangeListener( + new WebViewDelegate.OnTraceEnabledChangeListener() { + @Override + public void onTraceEnabledChange(boolean enabled) { + TraceEvent.setATraceEnabled(enabled); + } + }); + mStarted = true; + + for (WeakReference<WebViewChromium> wvc : mWebViewsToStart) { + WebViewChromium w = wvc.get(); + if (w != null) { + w.startYourEngine(); + } + } + mWebViewsToStart.clear(); + mWebViewsToStart = null; + } + + boolean hasStarted() { + return mStarted; + } + + void startYourEngines(boolean onMainThread) { + synchronized (mLock) { + ensureChromiumStartedLocked(onMainThread); + + } + } + + private Context getWrappedCurrentApplicationContext() { + return ResourcesContextWrapperFactory.get(mWebViewDelegate.getApplication()); + } + + AwBrowserContext getBrowserContext() { + synchronized (mLock) { + return getBrowserContextLocked(); + } + } + + private AwBrowserContext getBrowserContextLocked() { + assert Thread.holdsLock(mLock); + assert mStarted; + if (mBrowserContext == null) { + mBrowserContext = new AwBrowserContext(mWebViewPrefs); + } + return mBrowserContext; + } + + private void setWebContentsDebuggingEnabled(boolean enable) { + if (Looper.myLooper() != ThreadUtils.getUiThreadLooper()) { + throw new RuntimeException( + "Toggling of Web Contents Debugging must be done on the UI thread"); + } + if (mDevToolsServer == null) { + if (!enable) return; + mDevToolsServer = new AwDevToolsServer(); + } + mDevToolsServer.setRemoteDebuggingEnabled(enable); + } + + private void setUpResources(Context context) { + // The resources are always called com.android.webview even if the manifest has had the + // package renamed. + ResourceRewriter.rewriteRValues( + mWebViewDelegate.getPackageId(context.getResources(), "com.android.webview")); + + AwResource.setResources(context.getResources()); + AwResource.setErrorPageResources(android.R.raw.loaderror, + android.R.raw.nodomain); + AwResource.setConfigKeySystemUuidMapping( + android.R.array.config_keySystemUuidMapping); + } + + @Override + public Statics getStatics() { + synchronized (mLock) { + if (mStaticMethods == null) { + // TODO: Optimization potential: most these methods only need the native library + // loaded and initialized, not the entire browser process started. + // See also http://b/7009882 + ensureChromiumStartedLocked(true); + mStaticMethods = new WebViewFactoryProvider.Statics() { + @Override + public String findAddress(String addr) { + return ContentViewStatics.findAddress(addr); + } + + @Override + public String getDefaultUserAgent(Context context) { + return AwSettings.getDefaultUserAgent(); + } + + @Override + public void setWebContentsDebuggingEnabled(boolean enable) { + // Web Contents debugging is always enabled on debug builds. + if (!isBuildDebuggable()) { + WebViewChromiumFactoryProvider.this. + setWebContentsDebuggingEnabled(enable); + } + } + + // TODO enable after L release to AOSP + //@Override + public void clearClientCertPreferences(Runnable onCleared) { + AwContentsStatics.clearClientCertPreferences(onCleared); + } + + @Override + public void freeMemoryForTests() { + if (ActivityManager.isRunningInTestHarness()) { + MemoryPressureListener.maybeNotifyMemoryPresure( + ComponentCallbacks2.TRIM_MEMORY_COMPLETE); + } + } + + // TODO: Add @Override. + public void enableSlowWholeDocumentDraw() { + WebViewChromium.enableSlowWholeDocumentDraw(); + } + + @Override + public Uri[] parseFileChooserResult(int resultCode, Intent intent) { + return FileChooserParamsAdapter.parseFileChooserResult(resultCode, intent); + } + }; + } + } + return mStaticMethods; + } + + @Override + public WebViewProvider createWebView(WebView webView, WebView.PrivateAccess privateAccess) { + WebViewChromium wvc = new WebViewChromium(this, webView, privateAccess); + + synchronized (mLock) { + if (mWebViewsToStart != null) { + mWebViewsToStart.add(new WeakReference<WebViewChromium>(wvc)); + } + } + + return wvc; + } + + @Override + public GeolocationPermissions getGeolocationPermissions() { + synchronized (mLock) { + if (mGeolocationPermissions == null) { + ensureChromiumStartedLocked(true); + mGeolocationPermissions = new GeolocationPermissionsAdapter( + getBrowserContextLocked().getGeolocationPermissions()); + } + } + return mGeolocationPermissions; + } + + @Override + public CookieManager getCookieManager() { + synchronized (mLock) { + if (mCookieManager == null) { + if (!mStarted) { + // We can use CookieManager without starting Chromium; the native code + // will bring up just the parts it needs to make this work on a temporary + // basis until Chromium is started for real. The temporary cookie manager + // needs the application context to have been set. + ContentMain.initApplicationContext(getWrappedCurrentApplicationContext()); + } + mCookieManager = new CookieManagerAdapter(new AwCookieManager()); + } + } + return mCookieManager; + } + + @Override + public WebIconDatabase getWebIconDatabase() { + synchronized (mLock) { + if (mWebIconDatabase == null) { + ensureChromiumStartedLocked(true); + mWebIconDatabase = new WebIconDatabaseAdapter(); + } + } + return mWebIconDatabase; + } + + @Override + public WebStorage getWebStorage() { + synchronized (mLock) { + if (mWebStorage == null) { + ensureChromiumStartedLocked(true); + mWebStorage = new WebStorageAdapter(AwQuotaManagerBridge.getInstance()); + } + } + return mWebStorage; + } + + @Override + public WebViewDatabase getWebViewDatabase(Context context) { + synchronized (mLock) { + if (mWebViewDatabase == null) { + ensureChromiumStartedLocked(true); + AwBrowserContext browserContext = getBrowserContextLocked(); + mWebViewDatabase = new WebViewDatabaseAdapter( + browserContext.getFormDatabase(), + browserContext.getHttpAuthDatabase(context)); + } + } + return mWebViewDatabase; + } + + WebViewDelegate getWebViewDelegate() { + return mWebViewDelegate; + } +} diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebViewContentsClientAdapter.java b/android_webview/glue/java/src/com/android/webview/chromium/WebViewContentsClientAdapter.java new file mode 100644 index 0000000..c6b0c82 --- /dev/null +++ b/android_webview/glue/java/src/com/android/webview/chromium/WebViewContentsClientAdapter.java @@ -0,0 +1,1118 @@ +// 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.app.Activity; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Picture; +import android.net.http.SslError; +import android.net.Uri; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.provider.Browser; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import android.webkit.ClientCertRequest; +import android.webkit.ConsoleMessage; +import android.webkit.DownloadListener; +import android.webkit.GeolocationPermissions; +import android.webkit.JsDialogHelper; +import android.webkit.JsPromptResult; +import android.webkit.JsResult; +import android.webkit.PermissionRequest; +import android.webkit.SslErrorHandler; +import android.webkit.ValueCallback; +import android.webkit.WebChromeClient; +import android.webkit.WebChromeClient.CustomViewCallback; +import android.webkit.WebResourceResponse; +import android.webkit.WebResourceRequest; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +import com.android.webview.chromium.WebViewDelegateFactory.WebViewDelegate; + +import org.chromium.android_webview.AwContentsClient; +import org.chromium.android_webview.AwContentsClientBridge; +import org.chromium.android_webview.AwHttpAuthHandler; +import org.chromium.android_webview.AwWebResourceResponse; +import org.chromium.android_webview.JsPromptResultReceiver; +import org.chromium.android_webview.JsResultReceiver; +import org.chromium.android_webview.permission.AwPermissionRequest; +import org.chromium.base.ThreadUtils; +import org.chromium.base.TraceEvent; +import org.chromium.content.browser.ContentView; +import org.chromium.content.browser.ContentViewClient; + +import java.lang.ref.WeakReference; +import java.net.URISyntaxException; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.WeakHashMap; + +/** + * An adapter class that forwards the callbacks from {@link ContentViewClient} + * to the appropriate {@link WebViewClient} or {@link WebChromeClient}. + * + * An instance of this class is associated with one {@link WebViewChromium} + * instance. A WebViewChromium is a WebView implementation provider (that is + * android.webkit.WebView delegates all functionality to it) and has exactly + * one corresponding {@link ContentView} instance. + * + * A {@link ContentViewClient} may be shared between multiple {@link ContentView}s, + * and hence multiple WebViews. Many WebViewClient methods pass the source + * WebView as an argument. This means that we either need to pass the + * corresponding ContentView to the corresponding ContentViewClient methods, + * or use an instance of ContentViewClientAdapter per WebViewChromium, to + * allow the source WebView to be injected by ContentViewClientAdapter. We + * choose the latter, because it makes for a cleaner design. + */ +public class WebViewContentsClientAdapter extends AwContentsClient { + // TAG is chosen for consistency with classic webview tracing. + private static final String TAG = "WebViewCallback"; + // Enables API callback tracing + private static final boolean TRACE = false; + // The WebView instance that this adapter is serving. + private final WebView mWebView; + // The Context to use. This is different from mWebView.getContext(), which should not be used. + private final Context mContext; + // The WebViewClient instance that was passed to WebView.setWebViewClient(). + private WebViewClient mWebViewClient; + // The WebChromeClient instance that was passed to WebView.setContentViewClient(). + private WebChromeClient mWebChromeClient; + // The listener receiving find-in-page API results. + private WebView.FindListener mFindListener; + // The listener receiving notifications of screen updates. + private WebView.PictureListener mPictureListener; + + private WebViewDelegate mWebViewDelegate; + + private DownloadListener mDownloadListener; + + private Handler mUiThreadHandler; + + private static final int NEW_WEBVIEW_CREATED = 100; + + private WeakHashMap<AwPermissionRequest, WeakReference<PermissionRequestAdapter>> + mOngoingPermissionRequests; + /** + * Adapter constructor. + * + * @param webView the {@link WebView} instance that this adapter is serving. + */ + WebViewContentsClientAdapter(WebView webView, Context context, + WebViewDelegate webViewDelegate) { + if (webView == null || webViewDelegate == null) { + throw new IllegalArgumentException("webView or delegate can't be null."); + } + + if (context == null) { + throw new IllegalArgumentException("context can't be null."); + } + + mContext = context; + mWebView = webView; + mWebViewDelegate = webViewDelegate; + setWebViewClient(null); + + mUiThreadHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + switch(msg.what) { + case NEW_WEBVIEW_CREATED: + WebView.WebViewTransport t = (WebView.WebViewTransport) msg.obj; + WebView newWebView = t.getWebView(); + if (newWebView == mWebView) { + throw new IllegalArgumentException( + "Parent WebView cannot host it's own popup window. Please " + + "use WebSettings.setSupportMultipleWindows(false)"); + } + + if (newWebView != null && newWebView.copyBackForwardList().getSize() != 0) { + throw new IllegalArgumentException( + "New WebView for popup window must not have been previously " + + "navigated."); + } + + WebViewChromium.completeWindowCreation(mWebView, newWebView); + break; + default: + throw new IllegalStateException(); + } + } + }; + + } + + // WebViewClassic is coded in such a way that even if a null WebViewClient is set, + // certain actions take place. + // We choose to replicate this behavior by using a NullWebViewClient implementation (also known + // as the Null Object pattern) rather than duplicating the WebViewClassic approach in + // ContentView. + static class NullWebViewClient extends WebViewClient { + @Override + public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) { + // TODO: Investigate more and add a test case. + // This is reflecting Clank's behavior. + int keyCode = event.getKeyCode(); + return !ContentViewClient.shouldPropagateKey(keyCode); + } + + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + Intent intent; + // Perform generic parsing of the URI to turn it into an Intent. + try { + intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME); + } catch (URISyntaxException ex) { + Log.w(TAG, "Bad URI " + url + ": " + ex.getMessage()); + return false; + } + // Sanitize the Intent, ensuring web pages can not bypass browser + // security (only access to BROWSABLE activities). + intent.addCategory(Intent.CATEGORY_BROWSABLE); + intent.setComponent(null); + Intent selector = intent.getSelector(); + if (selector != null) { + selector.addCategory(Intent.CATEGORY_BROWSABLE); + selector.setComponent(null); + } + // Pass the package name as application ID so that the intent from the + // same application can be opened in the same tab. + intent.putExtra(Browser.EXTRA_APPLICATION_ID, + view.getContext().getPackageName()); + + Context context = view.getContext(); + if (!(context instanceof Activity)) { + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + } + + try { + context.startActivity(intent); + } catch (ActivityNotFoundException ex) { + Log.w(TAG, "No application can handle " + url); + return false; + } + return true; + } + } + + void setWebViewClient(WebViewClient client) { + if (client != null) { + mWebViewClient = client; + } else { + mWebViewClient = new NullWebViewClient(); + } + } + + void setWebChromeClient(WebChromeClient client) { + mWebChromeClient = client; + } + + void setDownloadListener(DownloadListener listener) { + mDownloadListener = listener; + } + + void setFindListener(WebView.FindListener listener) { + mFindListener = listener; + } + + void setPictureListener(WebView.PictureListener listener) { + mPictureListener = listener; + } + + //-------------------------------------------------------------------------------------------- + // Adapter for all the methods. + //-------------------------------------------------------------------------------------------- + + /** + * @see AwContentsClient#getVisitedHistory + */ + @Override + public void getVisitedHistory(ValueCallback<String[]> callback) { + TraceEvent.begin(); + if (mWebChromeClient != null) { + if (TRACE) Log.d(TAG, "getVisitedHistory"); + mWebChromeClient.getVisitedHistory(callback); + } + TraceEvent.end(); + } + + /** + * @see AwContentsClient#doUpdateVisiteHistory(String, boolean) + */ + @Override + public void doUpdateVisitedHistory(String url, boolean isReload) { + TraceEvent.begin(); + if (TRACE) Log.d(TAG, "doUpdateVisitedHistory=" + url + " reload=" + isReload); + mWebViewClient.doUpdateVisitedHistory(mWebView, url, isReload); + TraceEvent.end(); + } + + /** + * @see AwContentsClient#onProgressChanged(int) + */ + @Override + public void onProgressChanged(int progress) { + TraceEvent.begin(); + if (mWebChromeClient != null) { + if (TRACE) Log.d(TAG, "onProgressChanged=" + progress); + mWebChromeClient.onProgressChanged(mWebView, progress); + } + TraceEvent.end(); + } + + private static class WebResourceRequestImpl implements WebResourceRequest { + private final ShouldInterceptRequestParams mParams; + + public WebResourceRequestImpl(ShouldInterceptRequestParams params) { + mParams = params; + } + + @Override + public Uri getUrl() { + return Uri.parse(mParams.url); + } + + @Override + public boolean isForMainFrame() { + return mParams.isMainFrame; + } + + @Override + public boolean hasGesture() { + return mParams.hasUserGesture; + } + + @Override + public String getMethod() { + return mParams.method; + } + + @Override + public Map<String, String> getRequestHeaders() { + return mParams.requestHeaders; + } + } + + /** + * @see AwContentsClient#shouldInterceptRequest(java.lang.String) + */ + @Override + public AwWebResourceResponse shouldInterceptRequest(ShouldInterceptRequestParams params) { + TraceEvent.begin(); + if (TRACE) Log.d(TAG, "shouldInterceptRequest=" + params.url); + WebResourceResponse response = mWebViewClient.shouldInterceptRequest(mWebView, + new WebResourceRequestImpl(params)); + TraceEvent.end(); + if (response == null) return null; + + // AwWebResourceResponse should support null headers. b/16332774. + Map<String, String> responseHeaders = response.getResponseHeaders(); + if (responseHeaders == null) + responseHeaders = new HashMap<String, String>(); + + return new AwWebResourceResponse( + response.getMimeType(), + response.getEncoding(), + response.getData(), + response.getStatusCode(), + response.getReasonPhrase(), + responseHeaders); + } + + /** + * @see AwContentsClient#shouldOverrideUrlLoading(java.lang.String) + */ + @Override + public boolean shouldOverrideUrlLoading(String url) { + TraceEvent.begin(); + if (TRACE) Log.d(TAG, "shouldOverrideUrlLoading=" + url); + boolean result = mWebViewClient.shouldOverrideUrlLoading(mWebView, url); + TraceEvent.end(); + return result; + } + + /** + * @see AwContentsClient#onUnhandledKeyEvent(android.view.KeyEvent) + */ + @Override + public void onUnhandledKeyEvent(KeyEvent event) { + TraceEvent.begin(); + if (TRACE) Log.d(TAG, "onUnhandledKeyEvent"); + mWebViewClient.onUnhandledKeyEvent(mWebView, event); + TraceEvent.end(); + } + + /** + * @see AwContentsClient#onConsoleMessage(android.webkit.ConsoleMessage) + */ + @Override + public boolean onConsoleMessage(ConsoleMessage consoleMessage) { + TraceEvent.begin(); + boolean result; + if (mWebChromeClient != null) { + if (TRACE) Log.d(TAG, "onConsoleMessage: " + consoleMessage.message()); + result = mWebChromeClient.onConsoleMessage(consoleMessage); + String message = consoleMessage.message(); + if (result && message != null && message.startsWith("[blocked]")) { + Log.e(TAG, "Blocked URL: " + message); + } + } else { + result = false; + } + TraceEvent.end(); + return result; + } + + /** + * @see AwContentsClient#onFindResultReceived(int,int,boolean) + */ + @Override + public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, + boolean isDoneCounting) { + if (mFindListener == null) return; + TraceEvent.begin(); + if (TRACE) Log.d(TAG, "onFindResultReceived"); + mFindListener.onFindResultReceived(activeMatchOrdinal, numberOfMatches, isDoneCounting); + TraceEvent.end(); + } + + /** + * @See AwContentsClient#onNewPicture(Picture) + */ + @Override + public void onNewPicture(Picture picture) { + if (mPictureListener == null) return; + TraceEvent.begin(); + if (TRACE) Log.d(TAG, "onNewPicture"); + mPictureListener.onNewPicture(mWebView, picture); + TraceEvent.end(); + } + + @Override + public void onLoadResource(String url) { + TraceEvent.begin(); + if (TRACE) Log.d(TAG, "onLoadResource=" + url); + mWebViewClient.onLoadResource(mWebView, url); + TraceEvent.end(); + } + + @Override + public boolean onCreateWindow(boolean isDialog, boolean isUserGesture) { + Message m = mUiThreadHandler.obtainMessage( + NEW_WEBVIEW_CREATED, mWebView.new WebViewTransport()); + TraceEvent.begin(); + boolean result; + if (mWebChromeClient != null) { + if (TRACE) Log.d(TAG, "onCreateWindow"); + result = mWebChromeClient.onCreateWindow(mWebView, isDialog, isUserGesture, m); + } else { + result = false; + } + TraceEvent.end(); + return result; + } + + /** + * @see AwContentsClient#onCloseWindow() + */ + @Override + public void onCloseWindow() { + TraceEvent.begin(); + if (mWebChromeClient != null) { + if (TRACE) Log.d(TAG, "onCloseWindow"); + mWebChromeClient.onCloseWindow(mWebView); + } + TraceEvent.end(); + } + + /** + * @see AwContentsClient#onRequestFocus() + */ + @Override + public void onRequestFocus() { + TraceEvent.begin(); + if (mWebChromeClient != null) { + if (TRACE) Log.d(TAG, "onRequestFocus"); + mWebChromeClient.onRequestFocus(mWebView); + } + TraceEvent.end(); + } + + /** + * @see AwContentsClient#onReceivedTouchIconUrl(String url, boolean precomposed) + */ + @Override + public void onReceivedTouchIconUrl(String url, boolean precomposed) { + TraceEvent.begin(); + if (mWebChromeClient != null) { + if (TRACE) Log.d(TAG, "onReceivedTouchIconUrl=" + url); + mWebChromeClient.onReceivedTouchIconUrl(mWebView, url, precomposed); + } + TraceEvent.end(); + } + + /** + * @see AwContentsClient#onReceivedIcon(Bitmap bitmap) + */ + @Override + public void onReceivedIcon(Bitmap bitmap) { + TraceEvent.begin(); + if (mWebChromeClient != null) { + if (TRACE) Log.d(TAG, "onReceivedIcon"); + mWebChromeClient.onReceivedIcon(mWebView, bitmap); + } + TraceEvent.end(); + } + + /** + * @see ContentViewClient#onPageStarted(String) + */ + @Override + public void onPageStarted(String url) { + TraceEvent.begin(); + if (TRACE) Log.d(TAG, "onPageStarted=" + url); + mWebViewClient.onPageStarted(mWebView, url, mWebView.getFavicon()); + TraceEvent.end(); + } + + /** + * @see ContentViewClient#onPageFinished(String) + */ + @Override + public void onPageFinished(String url) { + TraceEvent.begin(); + if (TRACE) Log.d(TAG, "onPageFinished=" + url); + mWebViewClient.onPageFinished(mWebView, url); + TraceEvent.end(); + + // See b/8208948 + // This fakes an onNewPicture callback after onPageFinished to allow + // CTS tests to run in an un-flaky manner. This is required as the + // path for sending Picture updates in Chromium are decoupled from the + // page loading callbacks, i.e. the Chrome compositor may draw our + // content and send the Picture before onPageStarted or onPageFinished + // are invoked. The CTS harness discards any pictures it receives before + // onPageStarted is invoked, so in the case we get the Picture before that and + // no further updates after onPageStarted, we'll fail the test by timing + // out waiting for a Picture. + // To ensure backwards compatibility, we need to defer sending Picture updates + // until onPageFinished has been invoked. This work is being done + // upstream, and we can revert this hack when it lands. + if (mPictureListener != null) { + ThreadUtils.postOnUiThreadDelayed(new Runnable() { + @Override + public void run() { + UnimplementedWebViewApi.invoke(); + if (mPictureListener != null) { + if (TRACE) Log.d(TAG, "onPageFinished-fake"); + mPictureListener.onNewPicture(mWebView, new Picture()); + } + } + }, 100); + } + } + + /** + * @see ContentViewClient#onReceivedError(int,String,String) + */ + @Override + public void onReceivedError(int errorCode, String description, String failingUrl) { + if (description == null || description.isEmpty()) { + // ErrorStrings is @hidden, so we can't do this in AwContents. + // Normally the net/ layer will set a valid description, but for synthesized callbacks + // (like in the case for intercepted requests) AwContents will pass in null. + description = mWebViewDelegate.getErrorString(mContext, errorCode); + } + TraceEvent.begin(); + if (TRACE) Log.d(TAG, "onReceivedError=" + failingUrl); + mWebViewClient.onReceivedError(mWebView, errorCode, description, failingUrl); + TraceEvent.end(); + } + + /** + * @see ContentViewClient#onReceivedTitle(String) + */ + @Override + public void onReceivedTitle(String title) { + TraceEvent.begin(); + if (mWebChromeClient != null) { + if (TRACE) Log.d(TAG, "onReceivedTitle"); + mWebChromeClient.onReceivedTitle(mWebView, title); + } + TraceEvent.end(); + } + + + /** + * @see ContentViewClient#shouldOverrideKeyEvent(KeyEvent) + */ + @Override + public boolean shouldOverrideKeyEvent(KeyEvent event) { + // The check below is reflecting Clank's behavior and is a workaround for http://b/7697782. + // 1. The check for system key should be made in AwContents or ContentViewCore, before + // shouldOverrideKeyEvent() is called at all. + // 2. shouldOverrideKeyEvent() should be called in onKeyDown/onKeyUp, not from + // dispatchKeyEvent(). + if (!ContentViewClient.shouldPropagateKey(event.getKeyCode())) return true; + TraceEvent.begin(); + if (TRACE) Log.d(TAG, "shouldOverrideKeyEvent"); + boolean result = mWebViewClient.shouldOverrideKeyEvent(mWebView, event); + TraceEvent.end(); + return result; + } + + + /** + * @see ContentViewClient#onStartContentIntent(Context, String) + * Callback when detecting a click on a content link. + */ + // TODO: Delete this method when removed from base class. + public void onStartContentIntent(Context context, String contentUrl) { + TraceEvent.begin(); + if (TRACE) Log.d(TAG, "shouldOverrideUrlLoading=" + contentUrl); + mWebViewClient.shouldOverrideUrlLoading(mWebView, contentUrl); + TraceEvent.end(); + } + + @Override + public void onGeolocationPermissionsShowPrompt(String origin, + GeolocationPermissions.Callback callback) { + TraceEvent.begin(); + if (mWebChromeClient != null) { + if (TRACE) Log.d(TAG, "onGeolocationPermissionsShowPrompt"); + mWebChromeClient.onGeolocationPermissionsShowPrompt(origin, callback); + } + TraceEvent.end(); + } + + @Override + public void onGeolocationPermissionsHidePrompt() { + TraceEvent.begin(); + if (mWebChromeClient != null) { + if (TRACE) Log.d(TAG, "onGeolocationPermissionsHidePrompt"); + mWebChromeClient.onGeolocationPermissionsHidePrompt(); + } + TraceEvent.end(); + } + + @Override + public void onPermissionRequest(AwPermissionRequest permissionRequest) { + TraceEvent.begin(); + if (mWebChromeClient != null) { + if (TRACE) Log.d(TAG, "onPermissionRequest"); + if (mOngoingPermissionRequests == null) { + mOngoingPermissionRequests = + new WeakHashMap<AwPermissionRequest, WeakReference<PermissionRequestAdapter>>(); + } + PermissionRequestAdapter adapter = new PermissionRequestAdapter(permissionRequest); + mOngoingPermissionRequests.put( + permissionRequest, new WeakReference<PermissionRequestAdapter>(adapter)); + mWebChromeClient.onPermissionRequest(adapter); + } else { + // By default, we deny the permission. + permissionRequest.deny(); + } + TraceEvent.end(); + } + + @Override + public void onPermissionRequestCanceled(AwPermissionRequest permissionRequest) { + TraceEvent.begin(); + if (mWebChromeClient != null && mOngoingPermissionRequests != null) { + if (TRACE) Log.d(TAG, "onPermissionRequestCanceled"); + WeakReference<PermissionRequestAdapter> weakRef = + mOngoingPermissionRequests.get(permissionRequest); + // We don't hold strong reference to PermissionRequestAdpater and don't expect the + // user only holds weak reference to it either, if so, user has no way to call + // grant()/deny(), and no need to be notified the cancellation of request. + if (weakRef != null) { + PermissionRequestAdapter adapter = weakRef.get(); + if (adapter != null) mWebChromeClient.onPermissionRequestCanceled(adapter); + } + } + TraceEvent.end(); + } + + private static class JsPromptResultReceiverAdapter implements JsResult.ResultReceiver { + private JsPromptResultReceiver mChromePromptResultReceiver; + private JsResultReceiver mChromeResultReceiver; + // We hold onto the JsPromptResult here, just to avoid the need to downcast + // in onJsResultComplete. + private final JsPromptResult mPromptResult = new JsPromptResult(this); + + public JsPromptResultReceiverAdapter(JsPromptResultReceiver receiver) { + mChromePromptResultReceiver = receiver; + } + + public JsPromptResultReceiverAdapter(JsResultReceiver receiver) { + mChromeResultReceiver = receiver; + } + + public JsPromptResult getPromptResult() { + return mPromptResult; + } + + @Override + public void onJsResultComplete(JsResult result) { + if (mChromePromptResultReceiver != null) { + if (mPromptResult.getResult()) { + mChromePromptResultReceiver.confirm(mPromptResult.getStringResult()); + } else { + mChromePromptResultReceiver.cancel(); + } + } else { + if (mPromptResult.getResult()) { + mChromeResultReceiver.confirm(); + } else { + mChromeResultReceiver.cancel(); + } + } + } + } + + @Override + public void handleJsAlert(String url, String message, JsResultReceiver receiver) { + TraceEvent.begin(); + if (mWebChromeClient != null) { + final JsPromptResult res = + new JsPromptResultReceiverAdapter(receiver).getPromptResult(); + if (TRACE) Log.d(TAG, "onJsAlert"); + if (!mWebChromeClient.onJsAlert(mWebView, url, message, res)) { + new JsDialogHelper(res, JsDialogHelper.ALERT, null, message, url) + .showDialog(mContext); + } + } else { + receiver.cancel(); + } + TraceEvent.end(); + } + + @Override + public void handleJsBeforeUnload(String url, String message, JsResultReceiver receiver) { + TraceEvent.begin(); + if (mWebChromeClient != null) { + final JsPromptResult res = + new JsPromptResultReceiverAdapter(receiver).getPromptResult(); + if (TRACE) Log.d(TAG, "onJsBeforeUnload"); + if (!mWebChromeClient.onJsBeforeUnload(mWebView, url, message, res)) { + new JsDialogHelper(res, JsDialogHelper.UNLOAD, null, message, url) + .showDialog(mContext); + } + } else { + receiver.cancel(); + } + TraceEvent.end(); + } + + @Override + public void handleJsConfirm(String url, String message, JsResultReceiver receiver) { + TraceEvent.begin(); + if (mWebChromeClient != null) { + final JsPromptResult res = + new JsPromptResultReceiverAdapter(receiver).getPromptResult(); + if (TRACE) Log.d(TAG, "onJsConfirm"); + if (!mWebChromeClient.onJsConfirm(mWebView, url, message, res)) { + new JsDialogHelper(res, JsDialogHelper.CONFIRM, null, message, url) + .showDialog(mContext); + } + } else { + receiver.cancel(); + } + TraceEvent.end(); + } + + @Override + public void handleJsPrompt(String url, String message, String defaultValue, + JsPromptResultReceiver receiver) { + TraceEvent.begin(); + if (mWebChromeClient != null) { + final JsPromptResult res = + new JsPromptResultReceiverAdapter(receiver).getPromptResult(); + if (TRACE) Log.d(TAG, "onJsPrompt"); + if (!mWebChromeClient.onJsPrompt(mWebView, url, message, defaultValue, res)) { + new JsDialogHelper(res, JsDialogHelper.PROMPT, defaultValue, message, url) + .showDialog(mContext); + } + } else { + receiver.cancel(); + } + TraceEvent.end(); + } + + @Override + public void onReceivedHttpAuthRequest(AwHttpAuthHandler handler, String host, String realm) { + TraceEvent.begin(); + if (TRACE) Log.d(TAG, "onReceivedHttpAuthRequest=" + host); + mWebViewClient.onReceivedHttpAuthRequest(mWebView, + new AwHttpAuthHandlerAdapter(handler), host, realm); + TraceEvent.end(); + } + + @Override + public void onReceivedSslError(final ValueCallback<Boolean> callback, SslError error) { + SslErrorHandler handler = new SslErrorHandler() { + @Override + public void proceed() { + callback.onReceiveValue(true); + } + @Override + public void cancel() { + callback.onReceiveValue(false); + } + }; + TraceEvent.begin(); + if (TRACE) Log.d(TAG, "onReceivedSslError"); + mWebViewClient.onReceivedSslError(mWebView, handler, error); + TraceEvent.end(); + } + + private static class ClientCertRequestImpl extends ClientCertRequest { + + final private AwContentsClientBridge.ClientCertificateRequestCallback mCallback; + final private String[] mKeyTypes; + final private Principal[] mPrincipals; + final private String mHost; + final private int mPort; + + public ClientCertRequestImpl( + AwContentsClientBridge.ClientCertificateRequestCallback callback, + String[] keyTypes, Principal[] principals, String host, int port) { + mCallback = callback; + mKeyTypes = keyTypes; + mPrincipals = principals; + mHost = host; + mPort = port; + } + + @Override + public String[] getKeyTypes() { + // This is already a copy of native argument, so return directly. + return mKeyTypes; + } + + @Override + public Principal[] getPrincipals() { + // This is already a copy of native argument, so return directly. + return mPrincipals; + } + + @Override + public String getHost() { + return mHost; + } + + @Override + public int getPort() { + return mPort; + } + + @Override + public void proceed(final PrivateKey privateKey, final X509Certificate[] chain) { + mCallback.proceed(privateKey, chain); + } + + @Override + public void ignore() { + mCallback.ignore(); + } + + @Override + public void cancel() { + mCallback.cancel(); + } + } + + @Override + public void onReceivedClientCertRequest( + AwContentsClientBridge.ClientCertificateRequestCallback callback, + String[] keyTypes, Principal[] principals, String host, int port) { + if (TRACE) Log.d(TAG, "onReceivedClientCertRequest"); + TraceEvent.begin(); + final ClientCertRequestImpl request = new ClientCertRequestImpl(callback, + keyTypes, principals, host, port); + mWebViewClient.onReceivedClientCertRequest(mWebView, request); + TraceEvent.end(); + } + + @Override + public void onReceivedLoginRequest(String realm, String account, String args) { + TraceEvent.begin(); + if (TRACE) Log.d(TAG, "onReceivedLoginRequest=" + realm); + mWebViewClient.onReceivedLoginRequest(mWebView, realm, account, args); + TraceEvent.end(); + } + + @Override + public void onFormResubmission(Message dontResend, Message resend) { + TraceEvent.begin(); + if (TRACE) Log.d(TAG, "onFormResubmission"); + mWebViewClient.onFormResubmission(mWebView, dontResend, resend); + TraceEvent.end(); + } + + @Override + public void onDownloadStart(String url, + String userAgent, + String contentDisposition, + String mimeType, + long contentLength) { + if (mDownloadListener != null) { + TraceEvent.begin(); + if (TRACE) Log.d(TAG, "onDownloadStart"); + mDownloadListener.onDownloadStart(url, + userAgent, + contentDisposition, + mimeType, + contentLength); + TraceEvent.end(); + } + } + + @Override + public void showFileChooser(final ValueCallback<String[]> uploadFileCallback, + final AwContentsClient.FileChooserParams fileChooserParams) { + if (mWebChromeClient == null) { + uploadFileCallback.onReceiveValue(null); + return; + } + TraceEvent.begin(); + FileChooserParamsAdapter adapter = new FileChooserParamsAdapter( + fileChooserParams, mContext); + if (TRACE) Log.d(TAG, "showFileChooser"); + ValueCallback<Uri[]> callbackAdapter = new ValueCallback<Uri[]>() { + private boolean mCompleted; + @Override + public void onReceiveValue(Uri[] uriList) { + if (mCompleted) { + throw new IllegalStateException("showFileChooser result was already called"); + } + mCompleted = true; + String s[] = null; + if (uriList != null) { + s = new String[uriList.length]; + for(int i = 0; i < uriList.length; i++) { + s[i] = uriList[i].toString(); + } + } + uploadFileCallback.onReceiveValue(s); + } + }; + + // Invoke the new callback introduced in Lollipop. If the app handles + // it, we're done here. + if (mWebChromeClient.onShowFileChooser(mWebView, callbackAdapter, adapter)) { + return; + } + + // If the app did not handle it and we are running on Lollipop or newer, then + // abort. + if (mContext.getApplicationInfo().targetSdkVersion >= + Build.VERSION_CODES.LOLLIPOP) { + uploadFileCallback.onReceiveValue(null); + return; + } + + // Otherwise, for older apps, attempt to invoke the legacy (hidden) API for + // backwards compatibility. + ValueCallback<Uri> innerCallback = new ValueCallback<Uri>() { + private boolean mCompleted; + @Override + public void onReceiveValue(Uri uri) { + if (mCompleted) { + throw new IllegalStateException("showFileChooser result was already called"); + } + mCompleted = true; + uploadFileCallback.onReceiveValue( + uri == null ? null : new String[] { uri.toString() }); + } + }; + if (TRACE) Log.d(TAG, "openFileChooser"); + mWebChromeClient.openFileChooser(innerCallback, fileChooserParams.acceptTypes, + fileChooserParams.capture ? "*" : ""); + TraceEvent.end(); + } + + @Override + public void onScaleChangedScaled(float oldScale, float newScale) { + TraceEvent.begin(); + if (TRACE) Log.d(TAG, " onScaleChangedScaled"); + mWebViewClient.onScaleChanged(mWebView, oldScale, newScale); + TraceEvent.end(); + } + + @Override + public void onShowCustomView(View view, CustomViewCallback cb) { + TraceEvent.begin(); + if (mWebChromeClient != null) { + if (TRACE) Log.d(TAG, "onShowCustomView"); + mWebChromeClient.onShowCustomView(view, cb); + } + TraceEvent.end(); + } + + @Override + public void onHideCustomView() { + TraceEvent.begin(); + if (mWebChromeClient != null) { + if (TRACE) Log.d(TAG, "onHideCustomView"); + mWebChromeClient.onHideCustomView(); + } + TraceEvent.end(); + } + + @Override + protected View getVideoLoadingProgressView() { + TraceEvent.begin(); + View result; + if (mWebChromeClient != null) { + if (TRACE) Log.d(TAG, "getVideoLoadingProgressView"); + result = mWebChromeClient.getVideoLoadingProgressView(); + } else { + result = null; + } + TraceEvent.end(); + return result; + } + + @Override + public Bitmap getDefaultVideoPoster() { + TraceEvent.begin(); + Bitmap result = null; + if (mWebChromeClient != null) { + if (TRACE) Log.d(TAG, "getDefaultVideoPoster"); + result = mWebChromeClient.getDefaultVideoPoster(); + } + if (result == null) { + // The ic_media_video_poster icon is transparent so we need to draw it on a gray + // background. + Bitmap poster = BitmapFactory.decodeResource( + mContext.getResources(), + R.drawable.ic_media_video_poster); + result = Bitmap.createBitmap(poster.getWidth(), poster.getHeight(), poster.getConfig()); + result.eraseColor(Color.GRAY); + Canvas canvas = new Canvas(result); + canvas.drawBitmap(poster, 0f, 0f, null); + } + TraceEvent.end(); + return result; + } + + // TODO: Move to upstream. + private static class AwHttpAuthHandlerAdapter extends android.webkit.HttpAuthHandler { + private AwHttpAuthHandler mAwHandler; + + public AwHttpAuthHandlerAdapter(AwHttpAuthHandler awHandler) { + mAwHandler = awHandler; + } + + @Override + public void proceed(String username, String password) { + if (username == null) { + username = ""; + } + + if (password == null) { + password = ""; + } + mAwHandler.proceed(username, password); + } + + @Override + public void cancel() { + mAwHandler.cancel(); + } + + @Override + public boolean useHttpAuthUsernamePassword() { + return mAwHandler.isFirstAttempt(); + } + } + + // TODO: Move to the upstream once the PermissionRequest is part of SDK. + public static class PermissionRequestAdapter extends PermissionRequest { + // TODO: Move the below definitions to AwPermissionRequest. + private static long BITMASK_RESOURCE_VIDEO_CAPTURE = 1 << 1; + private static long BITMASK_RESOURCE_AUDIO_CAPTURE = 1 << 2; + private static long BITMASK_RESOURCE_PROTECTED_MEDIA_ID = 1 << 3; + + public static long toAwPermissionResources(String[] resources) { + long result = 0; + for (String resource : resources) { + if (resource.equals(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) + result |= BITMASK_RESOURCE_VIDEO_CAPTURE; + else if (resource.equals(PermissionRequest.RESOURCE_AUDIO_CAPTURE)) + result |= BITMASK_RESOURCE_AUDIO_CAPTURE; + else if (resource.equals(PermissionRequest.RESOURCE_PROTECTED_MEDIA_ID)) + result |= BITMASK_RESOURCE_PROTECTED_MEDIA_ID; + } + return result; + } + + private static String[] toPermissionResources(long resources) { + ArrayList<String> result = new ArrayList<String>(); + if ((resources & BITMASK_RESOURCE_VIDEO_CAPTURE) != 0) + result.add(PermissionRequest.RESOURCE_VIDEO_CAPTURE); + if ((resources & BITMASK_RESOURCE_AUDIO_CAPTURE) != 0) + result.add(PermissionRequest.RESOURCE_AUDIO_CAPTURE); + if ((resources & BITMASK_RESOURCE_PROTECTED_MEDIA_ID) != 0) + result.add(PermissionRequest.RESOURCE_PROTECTED_MEDIA_ID); + String[] resource_array = new String[result.size()]; + return result.toArray(resource_array); + } + + private AwPermissionRequest mAwPermissionRequest; + private String[] mResources; + + public PermissionRequestAdapter(AwPermissionRequest awPermissionRequest) { + assert awPermissionRequest != null; + mAwPermissionRequest = awPermissionRequest; + } + + @Override + public Uri getOrigin() { + return mAwPermissionRequest.getOrigin(); + } + + @Override + public String[] getResources() { + synchronized (this) { + if (mResources == null) { + mResources = toPermissionResources(mAwPermissionRequest.getResources()); + } + return mResources; + } + } + + @Override + public void grant(String[] resources) { + long requestedResource = mAwPermissionRequest.getResources(); + if ((requestedResource & toAwPermissionResources(resources)) == requestedResource) + mAwPermissionRequest.grant(); + else + mAwPermissionRequest.deny(); + } + + @Override + public void deny() { + mAwPermissionRequest.deny(); + } + + } +} diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebViewDatabaseAdapter.java b/android_webview/glue/java/src/com/android/webview/chromium/WebViewDatabaseAdapter.java new file mode 100644 index 0000000..b7afa7c --- /dev/null +++ b/android_webview/glue/java/src/com/android/webview/chromium/WebViewDatabaseAdapter.java @@ -0,0 +1,57 @@ +// 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.content.Context; +import android.webkit.WebViewDatabase; + +import org.chromium.android_webview.AwFormDatabase; +import org.chromium.android_webview.HttpAuthDatabase; + +/** + * Chromium implementation of WebViewDatabase -- forwards calls to the + * chromium internal implementation. + */ +final class WebViewDatabaseAdapter extends WebViewDatabase { + + private AwFormDatabase mFormDatabase; + private HttpAuthDatabase mHttpAuthDatabase; + + public WebViewDatabaseAdapter(AwFormDatabase formDatabase, HttpAuthDatabase httpAuthDatabase) { + mFormDatabase = formDatabase; + mHttpAuthDatabase = httpAuthDatabase; + } + + @Override + public boolean hasUsernamePassword() { + // This is a deprecated API: intentional no-op. + return false; + } + + @Override + public void clearUsernamePassword() { + // This is a deprecated API: intentional no-op. + } + + @Override + public boolean hasHttpAuthUsernamePassword() { + return mHttpAuthDatabase.hasHttpAuthUsernamePassword(); + } + + @Override + public void clearHttpAuthUsernamePassword() { + mHttpAuthDatabase.clearHttpAuthUsernamePassword(); + } + + @Override + public boolean hasFormData() { + return mFormDatabase.hasFormData(); + } + + @Override + public void clearFormData() { + mFormDatabase.clearFormData(); + } +} diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebViewDelegateFactory.java b/android_webview/glue/java/src/com/android/webview/chromium/WebViewDelegateFactory.java new file mode 100644 index 0000000..8f713dc --- /dev/null +++ b/android_webview/glue/java/src/com/android/webview/chromium/WebViewDelegateFactory.java @@ -0,0 +1,349 @@ +// 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.app.Application; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.os.Trace; +import android.util.Log; +import android.util.SparseArray; +import android.view.View; +import android.webkit.WebViewFactory; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * Factory class for {@link WebViewDelegate com.android.webview.chromium.WebViewDelegate}s. + * + * <p>{@link WebViewDelegate com.android.webview.chromium.WebViewDelegate}s provide the same + * interface as {@link android.webkit.WebViewDelegate android.webkit.WebViewDelegate} but without + * a dependency on the webkit class. Defining our own + * {@link WebViewDelegate com.android.webview.chromium.WebViewDelegate} in frameworks/webview + * allows the WebView apk to be binary compatible with the API 21 version of the framework, in + * which {@link android.webkit.WebViewDelegate android.webkit.WebViewDelegate} had not yet been + * introduced. + * + * <p>The {@link WebViewDelegate com.android.webview.chromium.WebViewDelegate} interface and this + * factory class can be removed once we don't longer need to support WebView apk updates to devices + * running the API 21 version of the framework. At that point, we should use + * {@link android.webkit.WebViewDelegate android.webkit.WebViewDelegate} directly instead. + */ +class WebViewDelegateFactory { + + /** + * Copy of {@link android.webkit.WebViewDelegate android.webkit.WebViewDelegate}'s interface. + * See {@link WebViewDelegateFactory} for the reasons why this copy is needed. + */ + interface WebViewDelegate { + /** @see android.webkit.WebViewDelegate.OnTraceEnabledChangeListener */ + interface OnTraceEnabledChangeListener { + void onTraceEnabledChange(boolean enabled); + } + + /** @see android.webkit.WebViewDelegate#setOnTraceEnabledChangeListener */ + void setOnTraceEnabledChangeListener(final OnTraceEnabledChangeListener listener); + + /** @see android.webkit.WebViewDelegate#isTraceTagEnabled */ + boolean isTraceTagEnabled(); + + /** @see android.webkit.WebViewDelegate#canInvokeDrawGlFunctor */ + boolean canInvokeDrawGlFunctor(View containerView); + + /** @see android.webkit.WebViewDelegate#invokeDrawGlFunctor */ + void invokeDrawGlFunctor(View containerView, long nativeDrawGLFunctor, + boolean waitForCompletion); + + /** @see android.webkit.WebViewDelegate#callDrawGlFunction */ + void callDrawGlFunction(Canvas canvas, long nativeDrawGLFunctor); + + /** @see android.webkit.WebViewDelegate#detachDrawGlFunctor */ + void detachDrawGlFunctor(View containerView, long nativeDrawGLFunctor); + + /** @see android.webkit.WebViewDelegate#getPackageId */ + int getPackageId(Resources resources, String packageName); + + /** @see android.webkit.WebViewDelegate#getApplication */ + Application getApplication(); + + /** @see android.webkit.WebViewDelegate#getErrorString */ + String getErrorString(Context context, int errorCode); + + /** @see android.webkit.WebViewDelegate#addWebViewAssetPath */ + void addWebViewAssetPath(Context context); + } + + /** + * Creates a {@link WebViewDelegate com.android.webview.chromium.WebViewDelegate} that proxies + * requests to the given {@link android.webkit.WebViewDelegate android.webkit.WebViewDelegate}. + * + * @return the created delegate + */ + static WebViewDelegate createProxyDelegate(android.webkit.WebViewDelegate delegate) { + return new ProxyDelegate(delegate); + } + + /** + * Creates a {@link WebViewDelegate com.android.webview.chromium.WebViewDelegate} compatible + * with the API 21 version of the framework in which + * {@link android.webkit.WebViewDelegate android.webkit.WebViewDelegate} had not yet been + * introduced. + * + * @return the created delegate + */ + static WebViewDelegate createApi21CompatibilityDelegate() { + return new Api21CompatibilityDelegate(); + } + + /** + * A {@link WebViewDelegate com.android.webview.chromium.WebViewDelegate} that proxies requests + * to a {@link android.webkit.WebViewDelegate android.webkit.WebViewDelegate}. + */ + private static class ProxyDelegate implements WebViewDelegate { + + android.webkit.WebViewDelegate delegate; + + ProxyDelegate(android.webkit.WebViewDelegate delegate) { + this.delegate = delegate; + } + + @Override + public void setOnTraceEnabledChangeListener(final OnTraceEnabledChangeListener listener) { + delegate.setOnTraceEnabledChangeListener( + new android.webkit.WebViewDelegate.OnTraceEnabledChangeListener() { + @Override + public void onTraceEnabledChange(boolean enabled) { + listener.onTraceEnabledChange(enabled); + ; + } + }); + } + + @Override + public boolean isTraceTagEnabled() { + return delegate.isTraceTagEnabled(); + } + + @Override + public boolean canInvokeDrawGlFunctor(View containerView) { + return delegate.canInvokeDrawGlFunctor(containerView); + } + + @Override + public void invokeDrawGlFunctor(View containerView, long nativeDrawGLFunctor, + boolean waitForCompletion) { + delegate.invokeDrawGlFunctor(containerView, nativeDrawGLFunctor, waitForCompletion); + } + + @Override + public void callDrawGlFunction(Canvas canvas, long nativeDrawGLFunctor) { + delegate.callDrawGlFunction(canvas, nativeDrawGLFunctor); + } + + @Override + public void detachDrawGlFunctor(View containerView, long nativeDrawGLFunctor) { + delegate.detachDrawGlFunctor(containerView, nativeDrawGLFunctor); + } + + @Override + public int getPackageId(Resources resources, String packageName) { + return delegate.getPackageId(resources, packageName); + } + + @Override + public Application getApplication() { + return delegate.getApplication(); + } + + @Override + public String getErrorString(Context context, int errorCode) { + return delegate.getErrorString(context, errorCode); + } + + @Override + public void addWebViewAssetPath(Context context) { + delegate.addWebViewAssetPath(context); + } + } + + /** + * A {@link WebViewDelegate com.android.webview.chromium.WebViewDelegate} compatible with the + * API 21 version of the framework in which + * {@link android.webkit.WebViewDelegate android.webkit.WebViewDelegate} had not yet been + * introduced. + * + * <p>This class implements the + * {@link WebViewDelegate com.android.webview.chromium.WebViewDelegate} functionality by using + * reflection to call into hidden frameworks APIs released in the API-21 version of the + * framework. + */ + private static class Api21CompatibilityDelegate implements WebViewDelegate { + /** Copy of Trace.TRACE_TAG_WEBVIEW */ + private final static long TRACE_TAG_WEBVIEW = 1L << 4; + + /** Hidden APIs released in the API 21 version of the framework */ + private final Method mIsTagEnabledMethod; + private final Method mAddChangeCallbackMethod; + private final Method mGetViewRootImplMethod; + private final Method mInvokeFunctorMethod; + private final Method mCallDrawGLFunctionMethod; + private final Method mDetachFunctorMethod; + private final Method mGetAssignedPackageIdentifiersMethod; + private final Method mAddAssetPathMethod; + private final Method mCurrentApplicationMethod; + private final Method mGetStringMethod; + private final Method mGetLoadedPackageInfoMethod; + + Api21CompatibilityDelegate() { + try { + // Important: This reflection essentially defines a snapshot of some hidden APIs + // at version 21 of the framework for compatibility reasons, and the reflection + // should not be changed even if those hidden APIs change in future releases. + mIsTagEnabledMethod = Trace.class.getMethod("isTagEnabled", long.class); + mAddChangeCallbackMethod = Class.forName("android.os.SystemProperties") + .getMethod("addChangeCallback", Runnable.class); + mGetViewRootImplMethod = View.class.getMethod("getViewRootImpl"); + mInvokeFunctorMethod = Class.forName("android.view.ViewRootImpl") + .getMethod("invokeFunctor", long.class, boolean.class); + mDetachFunctorMethod = Class.forName("android.view.ViewRootImpl") + .getMethod("detachFunctor", long.class); + mCallDrawGLFunctionMethod = Class.forName("android.view.HardwareCanvas") + .getMethod("callDrawGLFunction", long.class); + mGetAssignedPackageIdentifiersMethod = AssetManager.class.getMethod( + "getAssignedPackageIdentifiers"); + mAddAssetPathMethod = AssetManager.class.getMethod( + "addAssetPath", String.class); + mCurrentApplicationMethod = Class.forName("android.app.ActivityThread") + .getMethod("currentApplication"); + mGetStringMethod = Class.forName("android.net.http.ErrorStrings") + .getMethod("getString", int.class, Context.class); + mGetLoadedPackageInfoMethod = Class.forName("android.webkit.WebViewFactory") + .getMethod("getLoadedPackageInfo"); + } catch (Exception e) { + throw new RuntimeException("Invalid reflection", e); + } + } + + @Override + public void setOnTraceEnabledChangeListener(final OnTraceEnabledChangeListener listener) { + try { + mAddChangeCallbackMethod.invoke(null, new Runnable() { + @Override + public void run() { + listener.onTraceEnabledChange(isTraceTagEnabled()); + } + }); + } catch (Exception e) { + throw new RuntimeException("Invalid reflection", e); + } + } + + @Override + public boolean isTraceTagEnabled() { + try { + return ((Boolean) mIsTagEnabledMethod.invoke(null, TRACE_TAG_WEBVIEW)); + } catch (Exception e) { + throw new RuntimeException("Invalid reflection", e); + } + } + + @Override + public boolean canInvokeDrawGlFunctor(View containerView) { + try { + Object viewRootImpl = mGetViewRootImplMethod.invoke(containerView); + // viewRootImpl can be null during teardown when window is leaked. + return viewRootImpl != null; + } catch (Exception e) { + throw new RuntimeException("Invalid reflection", e); + } + } + + @Override + public void invokeDrawGlFunctor(View containerView, long nativeDrawGLFunctor, + boolean waitForCompletion) { + try { + Object viewRootImpl = mGetViewRootImplMethod.invoke(containerView); + if (viewRootImpl != null) { + mInvokeFunctorMethod.invoke(viewRootImpl, nativeDrawGLFunctor, waitForCompletion); + } + } catch (Exception e) { + throw new RuntimeException("Invalid reflection", e); + } + } + + @Override + public void callDrawGlFunction(Canvas canvas, long nativeDrawGLFunctor) { + try { + mCallDrawGLFunctionMethod.invoke(canvas, nativeDrawGLFunctor); + } catch (Exception e) { + throw new RuntimeException("Invalid reflection", e); + } + } + + @Override + public void detachDrawGlFunctor(View containerView, long nativeDrawGLFunctor) { + try { + Object viewRootImpl = mGetViewRootImplMethod.invoke(containerView); + if (viewRootImpl != null) { + mDetachFunctorMethod.invoke(viewRootImpl, nativeDrawGLFunctor); + } + } catch (Exception e) { + throw new RuntimeException("Invalid reflection", e); + } + } + + @Override + public int getPackageId(Resources resources, String packageName) { + try { + SparseArray packageIdentifiers = + (SparseArray) mGetAssignedPackageIdentifiersMethod.invoke( + resources.getAssets()); + for (int i = 0; i < packageIdentifiers.size(); i++) { + final String name = (String) packageIdentifiers.valueAt(i); + + if (packageName.equals(name)) { + return packageIdentifiers.keyAt(i); + } + } + } catch (Exception e) { + throw new RuntimeException("Invalid reflection", e); + } + throw new RuntimeException("Package not found: " + packageName); + } + + @Override + public Application getApplication() { + try { + return (Application) mCurrentApplicationMethod.invoke(null); + } catch (Exception e) { + throw new RuntimeException("Invalid reflection", e); + } + } + + @Override + public String getErrorString(Context context, int errorCode) { + try { + return (String) mGetStringMethod.invoke(null, errorCode, context); + } catch (Exception e) { + throw new RuntimeException("Invalid reflection", e); + } + } + + @Override + public void addWebViewAssetPath(Context context) { + try { + PackageInfo info = (PackageInfo) mGetLoadedPackageInfoMethod.invoke(null); + mAddAssetPathMethod.invoke(context.getAssets(), info.applicationInfo.sourceDir); + } catch (Exception e) { + throw new RuntimeException("Invalid reflection", e); + } + } + } +} + |