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.internal;
8 import android.content.ActivityNotFoundException;
9 import android.content.ContentResolver;
10 import android.content.Intent;
11 import android.database.Cursor;
12 import android.graphics.Bitmap;
13 import android.graphics.Picture;
14 import android.net.Uri;
15 import android.net.http.SslCertificate;
16 import android.net.http.SslError;
17 import android.os.Message;
18 import android.provider.MediaStore;
19 import android.util.Log;
20 import android.view.KeyEvent;
21 import android.view.View;
22 import android.webkit.ConsoleMessage;
23 import android.webkit.ValueCallback;
24 import android.webkit.WebResourceResponse;
26 import org.chromium.base.CalledByNative;
27 import org.chromium.base.JNINamespace;
28 import org.chromium.base.ThreadUtils;
29 import org.chromium.components.navigation_interception.InterceptNavigationDelegate;
30 import org.chromium.components.navigation_interception.NavigationParams;
31 import org.chromium.content.browser.ContentVideoViewClient;
32 import org.chromium.content.browser.ContentViewDownloadDelegate;
33 import org.chromium.content.browser.DownloadInfo;
34 import org.xwalk.core.internal.XWalkUIClientInternal.LoadStatusInternal;
36 // Help bridge callback in XWalkContentsClient to XWalkViewClient and
37 // XWalkWebChromeClient; Also handle the JNI conmmunication logic.
38 @JNINamespace("xwalk")
39 class XWalkContentsClientBridge extends XWalkContentsClient
40 implements ContentViewDownloadDelegate {
41 private static final String TAG = XWalkContentsClientBridge.class.getName();
43 private XWalkViewInternal mXWalkView;
44 private XWalkUIClientInternal mXWalkUIClient;
45 private XWalkResourceClientInternal mXWalkResourceClient;
46 private XWalkClient mXWalkClient;
47 private XWalkWebChromeClient mXWalkWebChromeClient;
48 private Bitmap mFavicon;
49 private DownloadListener mDownloadListener;
50 private InterceptNavigationDelegate mInterceptNavigationDelegate;
51 private PageLoadListener mPageLoadListener;
52 private XWalkNavigationHandler mNavigationHandler;
53 private XWalkNotificationService mNotificationService;
55 /** State recording variables */
56 // For fullscreen state.
57 private boolean mIsFullscreen = false;
59 private LoadStatusInternal mLoadStatus = LoadStatusInternal.FINISHED;
60 private String mLoadingUrl = null;
62 // The native peer of the object
63 private long mNativeContentsClientBridge;
65 private class InterceptNavigationDelegateImpl implements InterceptNavigationDelegate {
66 private XWalkContentsClient mContentsClient;
68 public InterceptNavigationDelegateImpl(XWalkContentsClient client) {
69 mContentsClient = client;
72 public boolean shouldIgnoreNavigation(NavigationParams navigationParams) {
73 final String url = navigationParams.url;
74 boolean ignoreNavigation = shouldOverrideUrlLoading(url) ||
75 (mNavigationHandler != null &&
76 mNavigationHandler.handleNavigation(navigationParams));
78 if (!ignoreNavigation) {
79 // Post a message to UI thread to notify the page is starting to load.
80 mContentsClient.getCallbackHelper().postOnPageStarted(url);
83 return ignoreNavigation;
87 public XWalkContentsClientBridge(XWalkViewInternal xwView) {
90 mInterceptNavigationDelegate = new InterceptNavigationDelegateImpl(this);
93 public void setUIClient(XWalkUIClientInternal client) {
94 // If it's null, use Crosswalk implementation.
96 mXWalkUIClient = client;
99 mXWalkUIClient = new XWalkUIClientInternal(mXWalkView);
102 public void setResourceClient(XWalkResourceClientInternal client) {
103 // If it's null, use Crosswalk implementation.
104 if (client != null) {
105 mXWalkResourceClient = client;
108 mXWalkResourceClient = new XWalkResourceClientInternal(mXWalkView);
112 public void setXWalkWebChromeClient(XWalkWebChromeClient client) {
113 // If it's null, use Crosswalk implementation.
114 if (client == null) return;
115 client.setContentsClient(this);
116 mXWalkWebChromeClient = client;
119 public XWalkWebChromeClient getXWalkWebChromeClient() {
120 return mXWalkWebChromeClient;
123 public void setXWalkClient(XWalkClient client) {
124 mXWalkClient = client;
127 public void setNavigationHandler(XWalkNavigationHandler handler) {
128 mNavigationHandler = handler;
131 void registerPageLoadListener(PageLoadListener listener) {
132 mPageLoadListener = listener;
135 public void setNotificationService(XWalkNotificationService service) {
136 if (mNotificationService != null) mNotificationService.shutdown();
137 mNotificationService = service;
138 if (mNotificationService != null) mNotificationService.setBridge(this);
141 public boolean onNewIntent(Intent intent) {
142 return mNotificationService.maybeHandleIntent(intent);
145 public InterceptNavigationDelegate getInterceptNavigationDelegate() {
146 return mInterceptNavigationDelegate;
149 private boolean isOwnerActivityRunning() {
150 if (mXWalkView != null && mXWalkView.isOwnerActivityRunning()) {
156 // TODO(Xingnan): All the empty functions need to be implemented.
158 public boolean shouldOverrideUrlLoading(String url) {
159 if (mXWalkResourceClient != null && mXWalkView != null) {
160 return mXWalkResourceClient.shouldOverrideUrlLoading(mXWalkView, url);
166 public boolean shouldOverrideKeyEvent(KeyEvent event) {
167 boolean overridden = false;
168 if (mXWalkUIClient != null && mXWalkView != null) {
169 overridden = mXWalkUIClient.shouldOverrideKeyEvent(mXWalkView, event);
172 return super.shouldOverrideKeyEvent(event);
178 public void onUnhandledKeyEvent(KeyEvent event) {
179 if (mXWalkUIClient != null && mXWalkView != null) {
180 mXWalkUIClient.onUnhandledKeyEvent(mXWalkView, event);
185 public void getVisitedHistory(ValueCallback<String[]> callback) {
189 public void doUpdateVisitedHistory(String url, boolean isReload) {
193 public void onProgressChanged(int progress) {
194 if (isOwnerActivityRunning()) {
195 mXWalkResourceClient.onProgressChanged(mXWalkView, progress);
200 public WebResourceResponse shouldInterceptRequest(String url) {
201 if (isOwnerActivityRunning()) {
202 return mXWalkResourceClient.shouldInterceptLoadRequest(mXWalkView, url);
208 public void onResourceLoadStarted(String url) {
209 if (isOwnerActivityRunning()) {
210 mXWalkResourceClient.onLoadStarted(mXWalkView, url);
215 public void onResourceLoadFinished(String url) {
216 if (isOwnerActivityRunning()) {
217 mXWalkResourceClient.onLoadFinished(mXWalkView, url);
222 public void onLoadResource(String url) {
223 if (mXWalkClient != null && isOwnerActivityRunning()) {
224 mXWalkClient.onLoadResource(mXWalkView, url);
229 public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
234 public void onReceivedHttpAuthRequest(XWalkHttpAuthHandler handler, String host, String realm) {
235 if (mXWalkClient != null && isOwnerActivityRunning()) {
236 mXWalkClient.onReceivedHttpAuthRequest(mXWalkView, handler, host, realm);
241 public void onReceivedSslError(ValueCallback<Boolean> callback, SslError error) {
242 if (mXWalkClient != null && isOwnerActivityRunning()) {
243 mXWalkClient.onReceivedSslError(mXWalkView, callback, error);
248 public void onReceivedLoginRequest(String realm, String account, String args) {
252 public void onGeolocationPermissionsShowPrompt(String origin,
253 XWalkGeolocationPermissions.Callback callback) {
254 if (mXWalkWebChromeClient != null && isOwnerActivityRunning()) {
255 mXWalkWebChromeClient.onGeolocationPermissionsShowPrompt(origin, callback);
260 public void onGeolocationPermissionsHidePrompt() {
261 if (mXWalkWebChromeClient != null && isOwnerActivityRunning()) {
262 mXWalkWebChromeClient.onGeolocationPermissionsHidePrompt();
267 public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
268 boolean isDoneCounting) {
272 public void onNewPicture(Picture picture) {
276 public void onPageStarted(String url) {
277 if (mXWalkUIClient != null && isOwnerActivityRunning()) {
279 mLoadStatus = LoadStatusInternal.FINISHED;
280 mXWalkUIClient.onPageLoadStarted(mXWalkView, url);
285 public void onPageFinished(String url) {
286 if (!isOwnerActivityRunning()) return;
287 if (mPageLoadListener != null) mPageLoadListener.onPageFinished(url);
288 if (mXWalkUIClient != null) {
289 if (mLoadStatus == LoadStatusInternal.CANCELLED && mLoadingUrl != null) {
290 mXWalkUIClient.onPageLoadStopped(mXWalkView, mLoadingUrl, mLoadStatus);
292 mXWalkUIClient.onPageLoadStopped(mXWalkView, url, mLoadStatus);
297 // This isn't the accurate point to notify a resource loading is finished,
298 // but it's a workable way. We could enhance this by extending Content
299 // API in future if we have the demand.
300 onResourceLoadFinished(url);
304 protected void onStopLoading() {
305 mLoadStatus = LoadStatusInternal.CANCELLED;
309 public void onReceivedError(int errorCode, String description, String failingUrl) {
310 if (isOwnerActivityRunning()) {
311 if (mLoadingUrl != null && mLoadingUrl.equals(failingUrl)) {
312 mLoadStatus = LoadStatusInternal.FAILED;
314 mXWalkResourceClient.onReceivedLoadError(mXWalkView, errorCode, description, failingUrl);
319 public void onRendererUnresponsive() {
320 if (mXWalkClient != null && isOwnerActivityRunning()) {
321 mXWalkClient.onRendererUnresponsive(mXWalkView);
326 public void onRendererResponsive() {
327 if (mXWalkClient != null && isOwnerActivityRunning()) {
328 mXWalkClient.onRendererResponsive(mXWalkView);
333 public void onFormResubmission(Message dontResend, Message resend) {
334 dontResend.sendToTarget();
338 public void onDownloadStart(String url,
340 String contentDisposition,
342 long contentLength) {
346 public boolean onCreateWindow(boolean isDialog, boolean isUserGesture) {
351 public void onRequestFocus() {
352 if (isOwnerActivityRunning()) {
353 mXWalkUIClient.onRequestFocus(mXWalkView);
358 public void onCloseWindow() {
359 if (isOwnerActivityRunning()) {
360 mXWalkUIClient.onJavascriptCloseWindow(mXWalkView);
365 public void onReceivedIcon(Bitmap bitmap) {
366 if (mXWalkWebChromeClient != null && mXWalkView != null) {
367 mXWalkWebChromeClient.onReceivedIcon(mXWalkView, bitmap);
373 public void onShowCustomView(View view, XWalkWebChromeClient.CustomViewCallback callback) {
374 if (mXWalkWebChromeClient != null && isOwnerActivityRunning()) {
375 mXWalkWebChromeClient.onShowCustomView(view, callback);
380 public void onHideCustomView() {
381 if (mXWalkWebChromeClient != null && isOwnerActivityRunning()) {
382 mXWalkWebChromeClient.onHideCustomView();
387 public void onScaleChangedScaled(float oldScale, float newScale) {
388 if (isOwnerActivityRunning()) {
389 mXWalkUIClient.onScaleChanged(mXWalkView, oldScale, newScale);
394 public void didFinishLoad(String url) {
398 public void onTitleChanged(String title) {
399 if (mXWalkUIClient != null && isOwnerActivityRunning()) {
400 mXWalkUIClient.onReceivedTitle(mXWalkView, title);
405 public void onToggleFullscreen(boolean enterFullscreen) {
406 if (isOwnerActivityRunning()) {
407 mIsFullscreen = enterFullscreen;
408 mXWalkUIClient.onFullscreenToggled(mXWalkView, enterFullscreen);
413 public boolean hasEnteredFullscreen() {
414 return mIsFullscreen;
418 public boolean shouldOpenWithDefaultBrowser(String contentUrl) {
419 Intent intent = new Intent();
420 intent.setAction("android.intent.action.VIEW");
421 Uri url = Uri.parse(contentUrl);
424 mXWalkView.getActivity().startActivity(intent);
425 } catch (ActivityNotFoundException exception) {
426 Log.w(TAG, "Activity not found for Intent:");
434 public boolean shouldOverrideRunFileChooser(
435 final int processId, final int renderId, final int modeFlags,
436 String acceptTypes, boolean capture) {
437 if (!isOwnerActivityRunning()) return false;
438 abstract class UriCallback implements ValueCallback<Uri> {
439 boolean syncNullReceived = false;
440 boolean syncCallFinished = false;
441 protected String resolveFileName(Uri uri, ContentResolver contentResolver) {
442 if (contentResolver == null || uri == null) return "";
443 Cursor cursor = null;
445 cursor = contentResolver.query(uri, null, null, null, null);
447 if (cursor != null && cursor.getCount() >= 1) {
448 cursor.moveToFirst();
449 int index = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME);
450 if (index > -1) return cursor.getString(index);
452 } catch (NullPointerException e) {
453 // Some android models don't handle the provider call correctly.
454 // see crbug.com/345393
457 if (cursor != null) cursor.close();
462 UriCallback uploadFile = new UriCallback() {
463 boolean completed = false;
465 public void onReceiveValue(Uri value) {
467 throw new IllegalStateException("Duplicate openFileChooser result");
470 if (value == null && !syncCallFinished) {
471 syncNullReceived = true;
475 nativeOnFilesNotSelected(mNativeContentsClientBridge,
476 processId, renderId, modeFlags);
479 String displayName = null;
480 if (ContentResolver.SCHEME_FILE.equals(value.getScheme())) {
481 result = value.getSchemeSpecificPart();
482 displayName = value.getLastPathSegment();
483 } else if (ContentResolver.SCHEME_CONTENT.equals(value.getScheme())) {
484 result = value.toString();
485 displayName = resolveFileName(
486 value, mXWalkView.getActivity().getContentResolver());
488 result = value.getPath();
489 displayName = value.getLastPathSegment();
491 if (displayName == null || displayName.isEmpty()) displayName = result;
492 nativeOnFilesSelected(mNativeContentsClientBridge,
493 processId, renderId, modeFlags, result, displayName);
497 mXWalkUIClient.openFileChooser(
498 mXWalkView, uploadFile, acceptTypes, Boolean.toString(capture));
499 uploadFile.syncCallFinished = true;
500 // File chooser requires user interaction, valid derives should handle it in async process.
501 // If the ValueCallback receive a sync result with null value, it is considered the
502 // file chooser is not overridden.
503 return !uploadFile.syncNullReceived;
507 public ContentVideoViewClient getContentVideoViewClient() {
508 return new XWalkContentVideoViewClient(this, mXWalkView.getActivity(), mXWalkView);
511 // Used by the native peer to set/reset a weak ref to the native peer.
513 private void setNativeContentsClientBridge(long nativeContentsClientBridge) {
514 mNativeContentsClientBridge = nativeContentsClientBridge;
517 // If returns false, the request is immediately canceled, and any call to proceedSslError
518 // has no effect. If returns true, the request should be canceled or proceeded using
519 // proceedSslError().
520 // Unlike the webview classic, we do not keep keep a database of certificates that
521 // are allowed by the user, because this functionality is already handled via
522 // ssl_policy in native layers.
524 private boolean allowCertificateError(int certError, byte[] derBytes, final String url,
526 final SslCertificate cert = SslUtil.getCertificateFromDerBytes(derBytes);
528 // if the certificate or the client is null, cancel the request
531 final SslError sslError = SslUtil.sslErrorFromNetErrorCode(certError, cert, url);
532 ValueCallback<Boolean> callback = new ValueCallback<Boolean>() {
534 public void onReceiveValue(Boolean value) {
535 proceedSslError(value.booleanValue(), id);
538 onReceivedSslError(callback, sslError);
542 private void proceedSslError(boolean proceed, int id) {
543 if (mNativeContentsClientBridge == 0) return;
544 nativeProceedSslError(mNativeContentsClientBridge, proceed, id);
548 private void handleJsAlert(String url, String message, int id) {
549 if (isOwnerActivityRunning()) {
550 XWalkJavascriptResultHandlerInternal result =
551 new XWalkJavascriptResultHandlerInternal(this, id);
552 mXWalkUIClient.onJavascriptModalDialog(mXWalkView,
553 XWalkUIClientInternal.JavascriptMessageTypeInternal.JAVASCRIPT_ALERT,
554 url, message, "", result);
559 private void handleJsConfirm(String url, String message, int id) {
560 if (isOwnerActivityRunning()) {
561 XWalkJavascriptResultHandlerInternal result =
562 new XWalkJavascriptResultHandlerInternal(this, id);
563 mXWalkUIClient.onJavascriptModalDialog(mXWalkView,
564 XWalkUIClientInternal.JavascriptMessageTypeInternal.JAVASCRIPT_CONFIRM,
565 url, message, "", result);
570 private void handleJsPrompt(String url, String message, String defaultValue, int id) {
571 if (isOwnerActivityRunning()) {
572 XWalkJavascriptResultHandlerInternal result =
573 new XWalkJavascriptResultHandlerInternal(this, id);
574 mXWalkUIClient.onJavascriptModalDialog(mXWalkView,
575 XWalkUIClientInternal.JavascriptMessageTypeInternal.JAVASCRIPT_PROMPT,
576 url, message, defaultValue, result);
581 private void handleJsBeforeUnload(String url, String message, int id) {
582 if (isOwnerActivityRunning()) {
583 XWalkJavascriptResultHandlerInternal result =
584 new XWalkJavascriptResultHandlerInternal(this, id);
585 mXWalkUIClient.onJavascriptModalDialog(mXWalkView,
586 XWalkUIClientInternal.JavascriptMessageTypeInternal.JAVASCRIPT_BEFOREUNLOAD,
587 url, message, "", result);
592 private void updateNotificationIcon(int notificationId, Bitmap icon) {
593 mNotificationService.updateNotificationIcon(notificationId, icon);
597 private void showNotification(String title, String message, String replaceId,
598 int notificationId, long delegate) {
599 // FIXME(wang16): use replaceId to replace exist notification. It happens when
600 // a notification with same name and tag fires.
601 mNotificationService.showNotification(
602 title, message, notificationId, delegate);
606 private void cancelNotification(int notificationId, long delegate) {
607 mNotificationService.cancelNotification(notificationId, delegate);
610 void confirmJsResult(int id, String prompt) {
611 if (mNativeContentsClientBridge == 0) return;
612 nativeConfirmJsResult(mNativeContentsClientBridge, id, prompt);
615 void cancelJsResult(int id) {
616 if (mNativeContentsClientBridge == 0) return;
617 nativeCancelJsResult(mNativeContentsClientBridge, id);
620 void exitFullscreen(long nativeWebContents) {
621 if (mNativeContentsClientBridge == 0) return;
622 nativeExitFullscreen(mNativeContentsClientBridge, nativeWebContents);
625 public void notificationDisplayed(long delegate) {
626 if (mNativeContentsClientBridge == 0) return;
627 nativeNotificationDisplayed(mNativeContentsClientBridge, delegate);
630 public void notificationError(long delegate) {
631 if (mNativeContentsClientBridge == 0) return;
632 nativeNotificationError(mNativeContentsClientBridge, delegate);
635 public void notificationClicked(int id, long delegate) {
636 if (mNativeContentsClientBridge == 0) return;
637 nativeNotificationClicked(mNativeContentsClientBridge, id, delegate);
640 public void notificationClosed(int id, boolean byUser, long delegate) {
641 if (mNativeContentsClientBridge == 0) return;
642 nativeNotificationClosed(mNativeContentsClientBridge, id, byUser, delegate);
645 void setDownloadListener(DownloadListener listener) {
646 mDownloadListener = listener;
649 // Implement ContentViewDownloadDelegate methods.
650 public void requestHttpGetDownload(DownloadInfo downloadInfo) {
651 if (mDownloadListener != null) {
652 mDownloadListener.onDownloadStart(downloadInfo.getUrl(), downloadInfo.getUserAgent(),
653 downloadInfo.getContentDisposition(), downloadInfo.getMimeType(), downloadInfo.getContentLength());
657 public void onDownloadStarted(String filename, String mimeType) {
660 public void onDangerousDownload(String filename, int downloadId) {
663 //--------------------------------------------------------------------------------------------
665 //--------------------------------------------------------------------------------------------
666 private native void nativeProceedSslError(long nativeXWalkContentsClientBridge,
667 boolean proceed, int id);
669 private native void nativeConfirmJsResult(long nativeXWalkContentsClientBridge, int id,
671 private native void nativeCancelJsResult(long nativeXWalkContentsClientBridge, int id);
672 private native void nativeExitFullscreen(long nativeXWalkContentsClientBridge, long nativeWebContents);
673 private native void nativeNotificationDisplayed(long nativeXWalkContentsClientBridge, long delegate);
674 private native void nativeNotificationError(long nativeXWalkContentsClientBridge, long delegate);
675 private native void nativeNotificationClicked(long nativeXWalkContentsClientBridge, int id, long delegate);
676 private native void nativeNotificationClosed(long nativeXWalkContentsClientBridge, int id, boolean byUser, long delegate);
677 private native void nativeOnFilesSelected(long nativeXWalkContentsClientBridge,
678 int processId, int renderId, int mode_flags, String filepath, String displayName);
679 private native void nativeOnFilesNotSelected(long nativeXWalkContentsClientBridge,
680 int processId, int renderId, int mode_flags);