1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Copyright (c) 2013-2014 Intel Corporation. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
6 package org.xwalk.core;
8 import android.content.ContentResolver;
9 import android.content.Intent;
10 import android.database.Cursor;
11 import android.graphics.Bitmap;
12 import android.graphics.Picture;
13 import android.net.Uri;
14 import android.net.http.SslCertificate;
15 import android.net.http.SslError;
16 import android.os.Message;
17 import android.provider.MediaStore;
18 import android.util.Log;
19 import android.view.KeyEvent;
20 import android.view.View;
21 import android.webkit.ConsoleMessage;
22 import android.webkit.ValueCallback;
23 import android.webkit.WebResourceResponse;
25 import org.chromium.base.CalledByNative;
26 import org.chromium.base.JNINamespace;
27 import org.chromium.base.ThreadUtils;
28 import org.chromium.components.navigation_interception.InterceptNavigationDelegate;
29 import org.chromium.components.navigation_interception.NavigationParams;
30 import org.chromium.content.browser.ContentVideoViewClient;
31 import org.chromium.content.browser.ContentViewDownloadDelegate;
32 import org.chromium.content.browser.DownloadInfo;
34 // Help bridge callback in XWalkContentsClient to XWalkViewClient and
35 // XWalkWebChromeClient; Also handle the JNI conmmunication logic.
36 @JNINamespace("xwalk")
37 class XWalkContentsClientBridge extends XWalkContentsClient
38 implements ContentViewDownloadDelegate {
40 private XWalkView mXWalkView;
41 private XWalkUIClient mXWalkUIClient;
42 private XWalkResourceClient mXWalkResourceClient;
43 private XWalkClient mXWalkClient;
44 private XWalkWebChromeClient mXWalkWebChromeClient;
45 private Bitmap mFavicon;
46 private DownloadListener mDownloadListener;
47 private InterceptNavigationDelegate mInterceptNavigationDelegate;
48 private PageLoadListener mPageLoadListener;
49 private XWalkNavigationHandler mNavigationHandler;
50 private XWalkNotificationService mNotificationService;
51 private boolean mIsFullscreen = false;
53 // The native peer of the object
54 private long mNativeContentsClientBridge;
56 private class InterceptNavigationDelegateImpl implements InterceptNavigationDelegate {
57 private XWalkContentsClient mContentsClient;
59 public InterceptNavigationDelegateImpl(XWalkContentsClient client) {
60 mContentsClient = client;
63 public boolean shouldIgnoreNavigation(NavigationParams navigationParams) {
64 final String url = navigationParams.url;
65 boolean ignoreNavigation = shouldOverrideUrlLoading(url) ||
66 (mNavigationHandler != null &&
67 mNavigationHandler.handleNavigation(navigationParams));
69 if (!ignoreNavigation) {
70 // Post a message to UI thread to notify the page is starting to load.
71 mContentsClient.getCallbackHelper().postOnPageStarted(url);
74 return ignoreNavigation;
78 public XWalkContentsClientBridge(XWalkView xwView) {
81 mInterceptNavigationDelegate = new InterceptNavigationDelegateImpl(this);
84 public void setUIClient(XWalkUIClient client) {
85 // If it's null, use Crosswalk implementation.
87 mXWalkUIClient = client;
90 mXWalkUIClient = new XWalkUIClient(mXWalkView);
93 public void setResourceClient(XWalkResourceClient client) {
94 // If it's null, use Crosswalk implementation.
96 mXWalkResourceClient = client;
99 mXWalkResourceClient = new XWalkResourceClient(mXWalkView);
103 public void setXWalkWebChromeClient(XWalkWebChromeClient client) {
104 // If it's null, use Crosswalk implementation.
105 if (client == null) return;
106 client.setContentsClient(this);
107 mXWalkWebChromeClient = client;
110 public XWalkWebChromeClient getXWalkWebChromeClient() {
111 return mXWalkWebChromeClient;
114 public void setXWalkClient(XWalkClient client) {
115 mXWalkClient = client;
118 public void setNavigationHandler(XWalkNavigationHandler handler) {
119 mNavigationHandler = handler;
122 void registerPageLoadListener(PageLoadListener listener) {
123 mPageLoadListener = listener;
126 public void setNotificationService(XWalkNotificationService service) {
127 if (mNotificationService != null) mNotificationService.shutdown();
128 mNotificationService = service;
129 if (mNotificationService != null) mNotificationService.setBridge(this);
132 public boolean onNewIntent(Intent intent) {
133 return mNotificationService.maybeHandleIntent(intent);
136 public InterceptNavigationDelegate getInterceptNavigationDelegate() {
137 return mInterceptNavigationDelegate;
140 private boolean isOwnerActivityRunning() {
141 if (mXWalkView != null && mXWalkView.isOwnerActivityRunning()) {
147 // TODO(Xingnan): All the empty functions need to be implemented.
149 public boolean shouldOverrideUrlLoading(String url) {
150 if (mXWalkClient != null && mXWalkView != null)
151 return mXWalkClient.shouldOverrideUrlLoading(mXWalkView, url);
156 public void onUnhandledKeyEvent(KeyEvent event) {
160 public void getVisitedHistory(ValueCallback<String[]> callback) {
164 public void doUpdateVisitedHistory(String url, boolean isReload) {
168 public void onProgressChanged(int progress) {
169 if (isOwnerActivityRunning()) {
170 mXWalkResourceClient.onProgressChanged(mXWalkView, progress);
175 public WebResourceResponse shouldInterceptRequest(String url) {
176 if (isOwnerActivityRunning()) {
177 return mXWalkResourceClient.shouldInterceptLoadRequest(mXWalkView, url);
183 public void onResourceLoadStarted(String url) {
184 if (isOwnerActivityRunning()) {
185 mXWalkResourceClient.onLoadStarted(mXWalkView, url);
190 public void onResourceLoadFinished(String url) {
191 if (isOwnerActivityRunning()) {
192 mXWalkResourceClient.onLoadFinished(mXWalkView, url);
197 public void onLoadResource(String url) {
198 if (mXWalkClient != null && isOwnerActivityRunning()) {
199 mXWalkClient.onLoadResource(mXWalkView, url);
204 public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
209 public void onReceivedHttpAuthRequest(XWalkHttpAuthHandler handler, String host, String realm) {
210 if (mXWalkClient != null && isOwnerActivityRunning()) {
211 mXWalkClient.onReceivedHttpAuthRequest(mXWalkView, handler, host, realm);
216 public void onReceivedSslError(ValueCallback<Boolean> callback, SslError error) {
217 if (mXWalkClient != null && isOwnerActivityRunning()) {
218 mXWalkClient.onReceivedSslError(mXWalkView, callback, error);
223 public void onReceivedLoginRequest(String realm, String account, String args) {
227 public void onGeolocationPermissionsShowPrompt(String origin,
228 XWalkGeolocationPermissions.Callback callback) {
229 if (mXWalkWebChromeClient != null && isOwnerActivityRunning()) {
230 mXWalkWebChromeClient.onGeolocationPermissionsShowPrompt(origin, callback);
235 public void onGeolocationPermissionsHidePrompt() {
236 if (mXWalkWebChromeClient != null && isOwnerActivityRunning()) {
237 mXWalkWebChromeClient.onGeolocationPermissionsHidePrompt();
242 public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
243 boolean isDoneCounting) {
247 public void onNewPicture(Picture picture) {
251 public void onPageStarted(String url) {
252 if (mXWalkClient != null && isOwnerActivityRunning()) {
253 mXWalkClient.onPageStarted(mXWalkView, url);
258 public void onPageFinished(String url) {
259 if (!isOwnerActivityRunning()) return;
260 if (mPageLoadListener != null) mPageLoadListener.onPageFinished(url);
261 if (mXWalkClient != null) {
262 mXWalkClient.onPageFinished(mXWalkView, url);
265 // This isn't the accurate point to notify a resource loading is finished,
266 // but it's a workable way. We could enhance this by extending Content
267 // API in future if we have the demand.
268 onResourceLoadFinished(url);
272 public void onReceivedError(int errorCode, String description, String failingUrl) {
273 if (isOwnerActivityRunning()) {
274 mXWalkResourceClient.onReceivedLoadError(mXWalkView, errorCode, description, failingUrl);
279 public void onRendererUnresponsive() {
280 if (mXWalkClient != null && isOwnerActivityRunning()) {
281 mXWalkClient.onRendererUnresponsive(mXWalkView);
286 public void onRendererResponsive() {
287 if (mXWalkClient != null && isOwnerActivityRunning()) {
288 mXWalkClient.onRendererResponsive(mXWalkView);
293 public void onFormResubmission(Message dontResend, Message resend) {
294 dontResend.sendToTarget();
298 public void onDownloadStart(String url,
300 String contentDisposition,
302 long contentLength) {
306 public boolean onCreateWindow(boolean isDialog, boolean isUserGesture) {
311 public void onRequestFocus() {
312 if (isOwnerActivityRunning()) {
313 mXWalkUIClient.onRequestFocus(mXWalkView);
318 public void onCloseWindow() {
319 if (isOwnerActivityRunning()) {
320 mXWalkUIClient.onJavascriptCloseWindow(mXWalkView);
325 public void onReceivedTouchIconUrl(String url, boolean precomposed) {
326 if (mXWalkWebChromeClient != null && mXWalkView != null) {
327 mXWalkWebChromeClient.onReceivedTouchIconUrl(mXWalkView, url, precomposed);
332 public void onReceivedIcon(Bitmap bitmap) {
333 if (mXWalkWebChromeClient != null && mXWalkView != null) {
334 mXWalkWebChromeClient.onReceivedIcon(mXWalkView, bitmap);
340 public void onShowCustomView(View view, XWalkWebChromeClient.CustomViewCallback callback) {
341 if (mXWalkWebChromeClient != null && isOwnerActivityRunning()) {
342 mXWalkWebChromeClient.onShowCustomView(view, callback);
347 public void onHideCustomView() {
348 if (mXWalkWebChromeClient != null && isOwnerActivityRunning()) {
349 mXWalkWebChromeClient.onHideCustomView();
354 public void onScaleChangedScaled(float oldScale, float newScale) {
355 if (isOwnerActivityRunning()) {
356 mXWalkUIClient.onScaleChanged(mXWalkView, oldScale, newScale);
361 protected View getVideoLoadingProgressView() {
362 if (mXWalkWebChromeClient != null)
363 return mXWalkWebChromeClient.getVideoLoadingProgressView();
368 public Bitmap getDefaultVideoPoster() {
373 public void didFinishLoad(String url) {
377 public void onTitleChanged(String title) {
378 if (mXWalkWebChromeClient != null && isOwnerActivityRunning()) {
379 mXWalkWebChromeClient.onReceivedTitle(mXWalkView, title);
384 public void onToggleFullscreen(boolean enterFullscreen) {
385 if (isOwnerActivityRunning()) {
386 mIsFullscreen = enterFullscreen;
387 mXWalkUIClient.onFullscreenToggled(mXWalkView, enterFullscreen);
392 public boolean hasEnteredFullscreen() {
393 return mIsFullscreen;
397 public boolean shouldOverrideRunFileChooser(
398 final int processId, final int renderId, final int modeFlags,
399 String acceptTypes, boolean capture) {
400 if (!isOwnerActivityRunning()) return false;
401 abstract class UriCallback implements ValueCallback<Uri> {
402 boolean syncNullReceived = false;
403 boolean syncCallFinished = false;
404 protected String resolveFileName(Uri uri, ContentResolver contentResolver) {
405 if (contentResolver == null || uri == null) return "";
406 Cursor cursor = null;
408 cursor = contentResolver.query(uri, null, null, null, null);
410 if (cursor != null && cursor.getCount() >= 1) {
411 cursor.moveToFirst();
412 int index = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME);
413 if (index > -1) return cursor.getString(index);
415 } catch (NullPointerException e) {
416 // Some android models don't handle the provider call correctly.
417 // see crbug.com/345393
420 if (cursor != null) cursor.close();
425 UriCallback uploadFile = new UriCallback() {
426 boolean completed = false;
428 public void onReceiveValue(Uri value) {
430 throw new IllegalStateException("Duplicate openFileChooser result");
433 if (value == null && !syncCallFinished) {
434 syncNullReceived = true;
438 nativeOnFilesNotSelected(mNativeContentsClientBridge,
439 processId, renderId, modeFlags);
442 String displayName = null;
443 if (ContentResolver.SCHEME_FILE.equals(value.getScheme())) {
444 result = value.getSchemeSpecificPart();
445 displayName = value.getLastPathSegment();
446 } else if (ContentResolver.SCHEME_CONTENT.equals(value.getScheme())) {
447 result = value.toString();
448 displayName = resolveFileName(
449 value, mXWalkView.getActivity().getContentResolver());
451 result = value.getPath();
452 displayName = value.getLastPathSegment();
454 if (displayName == null || displayName.isEmpty()) displayName = result;
455 nativeOnFilesSelected(mNativeContentsClientBridge,
456 processId, renderId, modeFlags, result, displayName);
460 mXWalkUIClient.openFileChooser(
461 mXWalkView, uploadFile, acceptTypes, Boolean.toString(capture));
462 uploadFile.syncCallFinished = true;
463 // File chooser requires user interaction, valid derives should handle it in async process.
464 // If the ValueCallback receive a sync result with null value, it is considered the
465 // file chooser is not overridden.
466 return !uploadFile.syncNullReceived;
470 public ContentVideoViewClient getContentVideoViewClient() {
471 return new XWalkContentVideoViewClient(this, mXWalkView.getActivity(), mXWalkView);
474 // Used by the native peer to set/reset a weak ref to the native peer.
476 private void setNativeContentsClientBridge(long nativeContentsClientBridge) {
477 mNativeContentsClientBridge = nativeContentsClientBridge;
480 // If returns false, the request is immediately canceled, and any call to proceedSslError
481 // has no effect. If returns true, the request should be canceled or proceeded using
482 // proceedSslError().
483 // Unlike the webview classic, we do not keep keep a database of certificates that
484 // are allowed by the user, because this functionality is already handled via
485 // ssl_policy in native layers.
487 private boolean allowCertificateError(int certError, byte[] derBytes, final String url,
489 final SslCertificate cert = SslUtil.getCertificateFromDerBytes(derBytes);
491 // if the certificate or the client is null, cancel the request
494 final SslError sslError = SslUtil.sslErrorFromNetErrorCode(certError, cert, url);
495 ValueCallback<Boolean> callback = new ValueCallback<Boolean>() {
497 public void onReceiveValue(Boolean value) {
498 proceedSslError(value.booleanValue(), id);
501 onReceivedSslError(callback, sslError);
505 private void proceedSslError(boolean proceed, int id) {
506 if (mNativeContentsClientBridge == 0) return;
507 nativeProceedSslError(mNativeContentsClientBridge, proceed, id);
511 private void handleJsAlert(String url, String message, int id) {
512 if (isOwnerActivityRunning()) {
513 XWalkJavascriptResultHandler result = new XWalkJavascriptResultHandler(this, id);
514 mXWalkUIClient.onJavascriptModalDialog(mXWalkView,
515 XWalkUIClient.JavascriptMessageType.JAVASCRIPT_ALERT, url, message, "", result);
520 private void handleJsConfirm(String url, String message, int id) {
521 if (isOwnerActivityRunning()) {
522 XWalkJavascriptResultHandler result = new XWalkJavascriptResultHandler(this, id);
523 mXWalkUIClient.onJavascriptModalDialog(mXWalkView,
524 XWalkUIClient.JavascriptMessageType.JAVASCRIPT_CONFIRM, url, message, "", result);
529 private void handleJsPrompt(String url, String message, String defaultValue, int id) {
530 if (isOwnerActivityRunning()) {
531 XWalkJavascriptResultHandler result = new XWalkJavascriptResultHandler(this, id);
532 mXWalkUIClient.onJavascriptModalDialog(mXWalkView,
533 XWalkUIClient.JavascriptMessageType.JAVASCRIPT_PROMPT, url, message, defaultValue,
539 private void handleJsBeforeUnload(String url, String message, int id) {
540 if (isOwnerActivityRunning()) {
541 XWalkJavascriptResultHandler result = new XWalkJavascriptResultHandler(this, id);
542 mXWalkUIClient.onJavascriptModalDialog(mXWalkView,
543 XWalkUIClient.JavascriptMessageType.JAVASCRIPT_BEFOREUNLOAD, url, message, "",
549 private void updateNotificationIcon(int notificationId, Bitmap icon) {
550 mNotificationService.updateNotificationIcon(notificationId, icon);
554 private void showNotification(String title, String message, String replaceId,
555 int notificationId, long delegate) {
556 // FIXME(wang16): use replaceId to replace exist notification. It happens when
557 // a notification with same name and tag fires.
558 mNotificationService.showNotification(
559 title, message, notificationId, delegate);
563 private void cancelNotification(int notificationId, long delegate) {
564 mNotificationService.cancelNotification(notificationId, delegate);
567 void confirmJsResult(int id, String prompt) {
568 if (mNativeContentsClientBridge == 0) return;
569 nativeConfirmJsResult(mNativeContentsClientBridge, id, prompt);
572 void cancelJsResult(int id) {
573 if (mNativeContentsClientBridge == 0) return;
574 nativeCancelJsResult(mNativeContentsClientBridge, id);
577 void exitFullscreen(long nativeWebContents) {
578 if (mNativeContentsClientBridge == 0) return;
579 nativeExitFullscreen(mNativeContentsClientBridge, nativeWebContents);
582 public void notificationDisplayed(long delegate) {
583 if (mNativeContentsClientBridge == 0) return;
584 nativeNotificationDisplayed(mNativeContentsClientBridge, delegate);
587 public void notificationError(long delegate) {
588 if (mNativeContentsClientBridge == 0) return;
589 nativeNotificationError(mNativeContentsClientBridge, delegate);
592 public void notificationClicked(int id, long delegate) {
593 if (mNativeContentsClientBridge == 0) return;
594 nativeNotificationClicked(mNativeContentsClientBridge, id, delegate);
597 public void notificationClosed(int id, boolean byUser, long delegate) {
598 if (mNativeContentsClientBridge == 0) return;
599 nativeNotificationClosed(mNativeContentsClientBridge, id, byUser, delegate);
602 void setDownloadListener(DownloadListener listener) {
603 mDownloadListener = listener;
606 // Implement ContentViewDownloadDelegate methods.
607 public void requestHttpGetDownload(DownloadInfo downloadInfo) {
608 if (mDownloadListener != null) {
609 mDownloadListener.onDownloadStart(downloadInfo.getUrl(), downloadInfo.getUserAgent(),
610 downloadInfo.getContentDisposition(), downloadInfo.getMimeType(), downloadInfo.getContentLength());
614 public void onDownloadStarted(String filename, String mimeType) {
617 public void onDangerousDownload(String filename, int downloadId) {
620 //--------------------------------------------------------------------------------------------
622 //--------------------------------------------------------------------------------------------
623 private native void nativeProceedSslError(long nativeXWalkContentsClientBridge,
624 boolean proceed, int id);
626 private native void nativeConfirmJsResult(long nativeXWalkContentsClientBridge, int id,
628 private native void nativeCancelJsResult(long nativeXWalkContentsClientBridge, int id);
629 private native void nativeExitFullscreen(long nativeXWalkContentsClientBridge, long nativeWebContents);
630 private native void nativeNotificationDisplayed(long nativeXWalkContentsClientBridge, long delegate);
631 private native void nativeNotificationError(long nativeXWalkContentsClientBridge, long delegate);
632 private native void nativeNotificationClicked(long nativeXWalkContentsClientBridge, int id, long delegate);
633 private native void nativeNotificationClosed(long nativeXWalkContentsClientBridge, int id, boolean byUser, long delegate);
634 private native void nativeOnFilesSelected(long nativeXWalkContentsClientBridge,
635 int processId, int renderId, int mode_flags, String filepath, String displayName);
636 private native void nativeOnFilesNotSelected(long nativeXWalkContentsClientBridge,
637 int processId, int renderId, int mode_flags);