| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.webview.chromium; |
| |
| 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.ErrorStrings; |
| 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 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 = android.webkit.DebugFlags.TRACE_CALLBACK; |
| // The WebView instance that this adapter is serving. |
| private final WebView mWebView; |
| // 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 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) { |
| if (webView == null) { |
| throw new IllegalArgumentException("webView can't be null"); |
| } |
| |
| mWebView = webView; |
| 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()); |
| try { |
| view.getContext().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; |
| } |
| |
| public boolean hasUserGestureInsecure() { |
| return mParams.hasUserGesture; |
| } |
| |
| 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 = ErrorStrings.getString(errorCode, mWebView.getContext()); |
| } |
| 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(mWebView.getContext()); |
| } |
| } 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(mWebView.getContext()); |
| } |
| } 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(mWebView.getContext()); |
| } |
| } 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(mWebView.getContext()); |
| } |
| } 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, mWebView.getContext()); |
| 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); |
| } |
| }; |
| if (mWebChromeClient.onShowFileChooser(mWebView, callbackAdapter, adapter)) { |
| return; |
| } |
| if (mWebView.getContext().getApplicationInfo().targetSdkVersion > |
| Build.VERSION_CODES.KITKAT) { |
| uploadFileCallback.onReceiveValue(null); |
| return; |
| } |
| 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( |
| mWebView.getContext().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(); |
| } |
| |
| } |
| } |