Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / xwalk / runtime / android / core / src / org / xwalk / core / XWalkContentsClientBridge.java
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.
5
6 package org.xwalk.core;
7
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;
24
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;
33
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 {
39
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;
52
53     // The native peer of the object
54     private long mNativeContentsClientBridge;
55
56     private class InterceptNavigationDelegateImpl implements InterceptNavigationDelegate {
57         private XWalkContentsClient mContentsClient;
58
59         public InterceptNavigationDelegateImpl(XWalkContentsClient client) {
60             mContentsClient = client;
61         }
62
63         public boolean shouldIgnoreNavigation(NavigationParams navigationParams) {
64             final String url = navigationParams.url;
65             boolean ignoreNavigation = shouldOverrideUrlLoading(url) ||
66                      (mNavigationHandler != null &&
67                       mNavigationHandler.handleNavigation(navigationParams));
68
69             if (!ignoreNavigation) {
70                 // Post a message to UI thread to notify the page is starting to load.
71                 mContentsClient.getCallbackHelper().postOnPageStarted(url);
72             }
73
74             return ignoreNavigation;
75         }
76     }
77
78     public XWalkContentsClientBridge(XWalkView xwView) {
79         mXWalkView = xwView;
80
81         mInterceptNavigationDelegate = new InterceptNavigationDelegateImpl(this);
82     }
83
84     public void setUIClient(XWalkUIClient client) {
85         // If it's null, use Crosswalk implementation.
86         if (client != null) {
87             mXWalkUIClient = client;
88             return;
89         }
90         mXWalkUIClient = new XWalkUIClient(mXWalkView);
91     }
92
93     public void setResourceClient(XWalkResourceClient client) {
94         // If it's null, use Crosswalk implementation.
95         if (client != null) {
96             mXWalkResourceClient = client;
97             return;
98         }
99         mXWalkResourceClient = new XWalkResourceClient(mXWalkView);
100     }
101
102
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;
108     }
109
110     public XWalkWebChromeClient getXWalkWebChromeClient() {
111         return mXWalkWebChromeClient;
112     }
113
114     public void setXWalkClient(XWalkClient client) {
115         mXWalkClient = client;
116     }
117
118     public void setNavigationHandler(XWalkNavigationHandler handler) {
119         mNavigationHandler = handler;
120     }
121
122     void registerPageLoadListener(PageLoadListener listener) {
123         mPageLoadListener = listener;
124     }
125
126     public void setNotificationService(XWalkNotificationService service) {
127         if (mNotificationService != null) mNotificationService.shutdown();
128         mNotificationService = service;
129         if (mNotificationService != null) mNotificationService.setBridge(this);
130     }
131
132     public boolean onNewIntent(Intent intent) {
133         return mNotificationService.maybeHandleIntent(intent);
134     }
135
136     public InterceptNavigationDelegate getInterceptNavigationDelegate() {
137         return mInterceptNavigationDelegate;
138     }
139
140     private boolean isOwnerActivityRunning() {
141         if (mXWalkView != null && mXWalkView.isOwnerActivityRunning()) {
142             return true;
143         }
144         return false;
145     }
146
147     // TODO(Xingnan): All the empty functions need to be implemented.
148     @Override
149     public boolean shouldOverrideUrlLoading(String url) {
150         if (mXWalkClient != null && mXWalkView != null)
151             return mXWalkClient.shouldOverrideUrlLoading(mXWalkView, url);
152         return false;
153     }
154
155     @Override
156     public void onUnhandledKeyEvent(KeyEvent event) {
157     }
158
159     @Override
160     public void getVisitedHistory(ValueCallback<String[]> callback) {
161     }
162
163     @Override
164     public void doUpdateVisitedHistory(String url, boolean isReload) {
165     }
166
167     @Override
168     public void onProgressChanged(int progress) {
169         if (isOwnerActivityRunning()) {
170             mXWalkResourceClient.onProgressChanged(mXWalkView, progress);
171         }
172     }
173
174     @Override
175     public WebResourceResponse shouldInterceptRequest(String url) {
176         if (isOwnerActivityRunning()) {
177             return mXWalkResourceClient.shouldInterceptLoadRequest(mXWalkView, url);
178         }
179         return null;
180     }
181
182     @Override
183     public void onResourceLoadStarted(String url) {
184         if (isOwnerActivityRunning()) {
185             mXWalkResourceClient.onLoadStarted(mXWalkView, url);
186         }
187     }
188
189     @Override
190     public void onResourceLoadFinished(String url) {
191         if (isOwnerActivityRunning()) {
192             mXWalkResourceClient.onLoadFinished(mXWalkView, url);
193         }
194     }
195
196     @Override
197     public void onLoadResource(String url) {
198         if (mXWalkClient != null && isOwnerActivityRunning()) {
199             mXWalkClient.onLoadResource(mXWalkView, url);
200         }
201     }
202
203     @Override
204     public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
205         return false;
206     }
207
208     @CalledByNative
209     public void onReceivedHttpAuthRequest(XWalkHttpAuthHandler handler, String host, String realm) {
210         if (mXWalkClient != null && isOwnerActivityRunning()) {
211             mXWalkClient.onReceivedHttpAuthRequest(mXWalkView, handler, host, realm);
212         }
213     }
214
215     @Override
216     public void onReceivedSslError(ValueCallback<Boolean> callback, SslError error) {
217         if (mXWalkClient != null && isOwnerActivityRunning()) {
218             mXWalkClient.onReceivedSslError(mXWalkView, callback, error);
219         }
220     }
221
222     @Override
223     public void onReceivedLoginRequest(String realm, String account, String args) {
224     }
225
226     @Override
227     public void onGeolocationPermissionsShowPrompt(String origin,
228             XWalkGeolocationPermissions.Callback callback) {
229         if (mXWalkWebChromeClient != null && isOwnerActivityRunning()) {
230             mXWalkWebChromeClient.onGeolocationPermissionsShowPrompt(origin, callback);
231         }
232     }
233
234     @Override
235     public void onGeolocationPermissionsHidePrompt() {
236         if (mXWalkWebChromeClient != null && isOwnerActivityRunning()) {
237             mXWalkWebChromeClient.onGeolocationPermissionsHidePrompt();
238         }
239     }
240
241     @Override
242     public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
243             boolean isDoneCounting) {
244     }
245
246     @Override
247     public void onNewPicture(Picture picture) {
248     }
249
250     @Override
251     public void onPageStarted(String url) {
252         if (mXWalkClient != null && isOwnerActivityRunning()) {
253             mXWalkClient.onPageStarted(mXWalkView, url);
254         }
255     }
256
257     @Override
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);
263         }
264
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);
269     }
270
271     @Override
272     public void onReceivedError(int errorCode, String description, String failingUrl) {
273         if (isOwnerActivityRunning()) {
274             mXWalkResourceClient.onReceivedLoadError(mXWalkView, errorCode, description, failingUrl);
275         }
276     }
277
278     @Override
279     public void onRendererUnresponsive() {
280         if (mXWalkClient != null && isOwnerActivityRunning()) {
281             mXWalkClient.onRendererUnresponsive(mXWalkView);
282         }
283     }
284
285     @Override
286     public void onRendererResponsive() {
287         if (mXWalkClient != null && isOwnerActivityRunning()) {
288             mXWalkClient.onRendererResponsive(mXWalkView);
289         }
290     }
291
292     @Override
293     public void onFormResubmission(Message dontResend, Message resend) {
294         dontResend.sendToTarget();
295     }
296
297     @Override
298     public void onDownloadStart(String url,
299                                 String userAgent,
300                                 String contentDisposition,
301                                 String mimeType,
302                                 long contentLength) {
303     }
304
305     @Override
306     public boolean onCreateWindow(boolean isDialog, boolean isUserGesture) {
307         return false;
308     }
309
310     @Override
311     public void onRequestFocus() {
312         if (isOwnerActivityRunning()) {
313             mXWalkUIClient.onRequestFocus(mXWalkView);
314         }
315     }
316
317     @Override
318     public void onCloseWindow() {
319         if (isOwnerActivityRunning()) {
320             mXWalkUIClient.onJavascriptCloseWindow(mXWalkView);
321         }
322     }
323
324     @Override
325     public void onReceivedTouchIconUrl(String url, boolean precomposed) {
326         if (mXWalkWebChromeClient != null && mXWalkView != null) {
327             mXWalkWebChromeClient.onReceivedTouchIconUrl(mXWalkView, url, precomposed);
328         }
329     }
330
331     @Override
332     public void onReceivedIcon(Bitmap bitmap) {
333         if (mXWalkWebChromeClient != null && mXWalkView != null) {
334             mXWalkWebChromeClient.onReceivedIcon(mXWalkView, bitmap);
335         }
336         mFavicon = bitmap;
337     }
338
339     @Override
340     public void onShowCustomView(View view, XWalkWebChromeClient.CustomViewCallback callback) {
341         if (mXWalkWebChromeClient != null && isOwnerActivityRunning()) {
342             mXWalkWebChromeClient.onShowCustomView(view, callback);
343         }
344     }
345
346     @Override
347     public void onHideCustomView() {
348         if (mXWalkWebChromeClient != null && isOwnerActivityRunning()) {
349             mXWalkWebChromeClient.onHideCustomView();
350         }
351     }
352
353     @Override
354     public void onScaleChangedScaled(float oldScale, float newScale) {
355         if (isOwnerActivityRunning()) {
356             mXWalkUIClient.onScaleChanged(mXWalkView, oldScale, newScale);
357         }
358     }
359
360     @Override
361     protected View getVideoLoadingProgressView() {
362         if (mXWalkWebChromeClient != null)
363             return mXWalkWebChromeClient.getVideoLoadingProgressView();
364         return null;
365     }
366
367     @Override
368     public Bitmap getDefaultVideoPoster() {
369         return null;
370     }
371
372     @Override
373     public void didFinishLoad(String url) {
374     }
375
376     @Override
377     public void onTitleChanged(String title) {
378         if (mXWalkWebChromeClient != null && isOwnerActivityRunning()) {
379             mXWalkWebChromeClient.onReceivedTitle(mXWalkView, title);
380         }
381     }
382
383     @Override
384     public void onToggleFullscreen(boolean enterFullscreen) {
385         if (isOwnerActivityRunning()) {
386             mIsFullscreen = enterFullscreen;
387             mXWalkUIClient.onFullscreenToggled(mXWalkView, enterFullscreen);
388         }
389     }
390
391     @Override
392     public boolean hasEnteredFullscreen() {
393         return mIsFullscreen;
394     }
395
396     @Override
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;
407                 try {
408                     cursor = contentResolver.query(uri, null, null, null, null);
409
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);
414                     }
415                 } catch (NullPointerException e) {
416                     // Some android models don't handle the provider call correctly.
417                     // see crbug.com/345393
418                     return "";
419                 } finally {
420                     if (cursor != null) cursor.close();
421                 }
422                 return "";
423             }
424         }
425         UriCallback uploadFile = new UriCallback() {
426             boolean completed = false;
427             @Override
428             public void onReceiveValue(Uri value) {
429                 if (completed) {
430                     throw new IllegalStateException("Duplicate openFileChooser result");
431                 }
432                 completed = true;
433                 if (value == null && !syncCallFinished) {
434                     syncNullReceived = true;
435                     return;
436                 }
437                 if (value == null) {
438                     nativeOnFilesNotSelected(mNativeContentsClientBridge,
439                             processId, renderId, modeFlags);
440                 } else {
441                     String result = "";
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());
450                     } else {
451                         result = value.getPath();
452                         displayName = value.getLastPathSegment();
453                     }
454                     if (displayName == null || displayName.isEmpty()) displayName = result;
455                     nativeOnFilesSelected(mNativeContentsClientBridge,
456                             processId, renderId, modeFlags, result, displayName);
457                 }
458             }
459         };
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;
467     }
468
469     @Override
470     public ContentVideoViewClient getContentVideoViewClient() {
471         return new XWalkContentVideoViewClient(this, mXWalkView.getActivity(), mXWalkView);
472     }
473
474     // Used by the native peer to set/reset a weak ref to the native peer.
475     @CalledByNative
476     private void setNativeContentsClientBridge(long nativeContentsClientBridge) {
477         mNativeContentsClientBridge = nativeContentsClientBridge;
478     }
479
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.
486     @CalledByNative
487     private boolean allowCertificateError(int certError, byte[] derBytes, final String url,
488             final int id) {
489         final SslCertificate cert = SslUtil.getCertificateFromDerBytes(derBytes);
490         if (cert == null) {
491             // if the certificate or the client is null, cancel the request
492             return false;
493         }
494         final SslError sslError = SslUtil.sslErrorFromNetErrorCode(certError, cert, url);
495         ValueCallback<Boolean> callback = new ValueCallback<Boolean>() {
496             @Override
497             public void onReceiveValue(Boolean value) {
498                 proceedSslError(value.booleanValue(), id);
499             }
500         };
501         onReceivedSslError(callback, sslError);
502         return true;
503     }
504
505     private void proceedSslError(boolean proceed, int id) {
506         if (mNativeContentsClientBridge == 0) return;
507         nativeProceedSslError(mNativeContentsClientBridge, proceed, id);
508     }
509
510     @CalledByNative
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);
516         }
517     }
518
519     @CalledByNative
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);
525         }
526     }
527
528     @CalledByNative
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,
534                             result);
535         }
536     }
537
538     @CalledByNative
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, "",
544                             result);
545         }
546     }
547
548     @CalledByNative
549     private void updateNotificationIcon(int notificationId, Bitmap icon) {
550         mNotificationService.updateNotificationIcon(notificationId, icon);
551     }
552
553     @CalledByNative
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);
560     }
561
562     @CalledByNative
563     private void cancelNotification(int notificationId, long delegate) {
564         mNotificationService.cancelNotification(notificationId, delegate);
565     }
566
567     void confirmJsResult(int id, String prompt) {
568         if (mNativeContentsClientBridge == 0) return;
569         nativeConfirmJsResult(mNativeContentsClientBridge, id, prompt);
570     }
571
572     void cancelJsResult(int id) {
573         if (mNativeContentsClientBridge == 0) return;
574         nativeCancelJsResult(mNativeContentsClientBridge, id);
575     }
576
577     void exitFullscreen(long nativeWebContents) {
578         if (mNativeContentsClientBridge == 0) return;
579         nativeExitFullscreen(mNativeContentsClientBridge, nativeWebContents);
580     }
581
582     public void notificationDisplayed(long delegate) {
583         if (mNativeContentsClientBridge == 0) return;
584         nativeNotificationDisplayed(mNativeContentsClientBridge, delegate);
585     }
586
587     public void notificationError(long delegate) {
588         if (mNativeContentsClientBridge == 0) return;
589         nativeNotificationError(mNativeContentsClientBridge, delegate);
590     }
591
592     public void notificationClicked(int id, long delegate) {
593         if (mNativeContentsClientBridge == 0) return;
594         nativeNotificationClicked(mNativeContentsClientBridge, id, delegate);
595     }
596
597     public void notificationClosed(int id, boolean byUser, long delegate) {
598         if (mNativeContentsClientBridge == 0) return;
599         nativeNotificationClosed(mNativeContentsClientBridge, id, byUser, delegate);
600     }
601
602     void setDownloadListener(DownloadListener listener) {
603         mDownloadListener = listener;
604     }
605
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());
611         }
612     }
613
614     public void onDownloadStarted(String filename, String mimeType) {
615     }
616
617     public void onDangerousDownload(String filename, int downloadId) {
618     }
619
620     //--------------------------------------------------------------------------------------------
621     //  Native methods
622     //--------------------------------------------------------------------------------------------
623     private native void nativeProceedSslError(long nativeXWalkContentsClientBridge,
624             boolean proceed, int id);
625
626     private native void nativeConfirmJsResult(long nativeXWalkContentsClientBridge, int id,
627             String prompt);
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);
638 }