WebViewContentsClientAdapter.java revision 7a7dce808e1545e859b80b86f0279ee68ce3f0cc
1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.webview.chromium;
18
19import android.content.ActivityNotFoundException;
20import android.content.Context;
21import android.content.Intent;
22import android.graphics.Bitmap;
23import android.graphics.BitmapFactory;
24import android.graphics.Canvas;
25import android.graphics.Color;
26import android.graphics.Picture;
27import android.net.http.ErrorStrings;
28import android.net.http.SslError;
29import android.net.Uri;
30import android.os.Build;
31import android.os.Handler;
32import android.os.Looper;
33import android.os.Message;
34import android.provider.Browser;
35import android.util.Log;
36import android.view.KeyEvent;
37import android.view.View;
38import android.webkit.ConsoleMessage;
39import android.webkit.DownloadListener;
40import android.webkit.GeolocationPermissions;
41import android.webkit.JsDialogHelper;
42import android.webkit.JsPromptResult;
43import android.webkit.JsResult;
44import android.webkit.SslErrorHandler;
45import android.webkit.ValueCallback;
46import android.webkit.WebChromeClient;
47import android.webkit.WebChromeClient.CustomViewCallback;
48import android.webkit.WebResourceResponse;
49import android.webkit.WebView;
50import android.webkit.WebViewClient;
51
52import org.chromium.android_webview.AwContentsClient;
53import org.chromium.android_webview.AwHttpAuthHandler;
54import org.chromium.android_webview.InterceptedRequestData;
55import org.chromium.android_webview.JsPromptResultReceiver;
56import org.chromium.android_webview.JsResultReceiver;
57import org.chromium.base.ThreadUtils;
58import org.chromium.content.browser.ContentView;
59import org.chromium.content.browser.ContentViewClient;
60import org.chromium.content.common.TraceEvent;
61
62import java.net.URISyntaxException;
63import java.util.concurrent.atomic.AtomicBoolean;
64
65
66/**
67 * An adapter class that forwards the callbacks from {@link ContentViewClient}
68 * to the appropriate {@link WebViewClient} or {@link WebChromeClient}.
69 *
70 * An instance of this class is associated with one {@link WebViewChromium}
71 * instance. A WebViewChromium is a WebView implementation provider (that is
72 * android.webkit.WebView delegates all functionality to it) and has exactly
73 * one corresponding {@link ContentView} instance.
74 *
75 * A {@link ContentViewClient} may be shared between multiple {@link ContentView}s,
76 * and hence multiple WebViews. Many WebViewClient methods pass the source
77 * WebView as an argument. This means that we either need to pass the
78 * corresponding ContentView to the corresponding ContentViewClient methods,
79 * or use an instance of ContentViewClientAdapter per WebViewChromium, to
80 * allow the source WebView to be injected by ContentViewClientAdapter. We
81 * choose the latter, because it makes for a cleaner design.
82 */
83public class WebViewContentsClientAdapter extends AwContentsClient {
84    // TAG is chosen for consistency with classic webview tracing.
85    private static final String TAG = "WebViewCallback";
86    // Enables API callback tracing
87    private static final boolean TRACE = android.webkit.DebugFlags.TRACE_CALLBACK;
88    // The WebView instance that this adapter is serving.
89    private final WebView mWebView;
90    // The WebViewClient instance that was passed to WebView.setWebViewClient().
91    private WebViewClient mWebViewClient;
92    // The WebChromeClient instance that was passed to WebView.setContentViewClient().
93    private WebChromeClient mWebChromeClient;
94    // The listener receiving find-in-page API results.
95    private WebView.FindListener mFindListener;
96    // The listener receiving notifications of screen updates.
97    private WebView.PictureListener mPictureListener;
98
99    private DownloadListener mDownloadListener;
100
101    private Handler mUiThreadHandler;
102
103    private static final int NEW_WEBVIEW_CREATED = 100;
104
105    /**
106     * Adapter constructor.
107     *
108     * @param webView the {@link WebView} instance that this adapter is serving.
109     */
110    WebViewContentsClientAdapter(WebView webView) {
111        if (webView == null) {
112            throw new IllegalArgumentException("webView can't be null");
113        }
114
115        mWebView = webView;
116        setWebViewClient(null);
117
118        mUiThreadHandler = new Handler() {
119
120            @Override
121            public void handleMessage(Message msg) {
122                switch(msg.what) {
123                    case NEW_WEBVIEW_CREATED:
124                        WebView.WebViewTransport t = (WebView.WebViewTransport) msg.obj;
125                        WebView newWebView = t.getWebView();
126                        if (newWebView == mWebView) {
127                            throw new IllegalArgumentException(
128                                    "Parent WebView cannot host it's own popup window. Please " +
129                                    "use WebSettings.setSupportMultipleWindows(false)");
130                        }
131
132                        if (newWebView != null && newWebView.copyBackForwardList().getSize() != 0) {
133                            throw new IllegalArgumentException(
134                                    "New WebView for popup window must not have been previously " +
135                                    "navigated.");
136                        }
137
138                        WebViewChromium.completeWindowCreation(mWebView, newWebView);
139                        break;
140                    default:
141                        throw new IllegalStateException();
142                }
143            }
144        };
145
146    }
147
148    // WebViewClassic is coded in such a way that even if a null WebViewClient is set,
149    // certain actions take place.
150    // We choose to replicate this behavior by using a NullWebViewClient implementation (also known
151    // as the Null Object pattern) rather than duplicating the WebViewClassic approach in
152    // ContentView.
153    static class NullWebViewClient extends WebViewClient {
154        @Override
155        public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
156            // TODO: Investigate more and add a test case.
157            // This is a copy of what Clank does. The WebViewCore key handling code and Clank key
158            // handling code differ enough that it's not trivial to figure out how keycodes are
159            // being filtered.
160            int keyCode = event.getKeyCode();
161            if (keyCode == KeyEvent.KEYCODE_MENU ||
162                keyCode == KeyEvent.KEYCODE_HOME ||
163                keyCode == KeyEvent.KEYCODE_BACK ||
164                keyCode == KeyEvent.KEYCODE_CALL ||
165                keyCode == KeyEvent.KEYCODE_ENDCALL ||
166                keyCode == KeyEvent.KEYCODE_POWER ||
167                keyCode == KeyEvent.KEYCODE_HEADSETHOOK ||
168                keyCode == KeyEvent.KEYCODE_CAMERA ||
169                keyCode == KeyEvent.KEYCODE_FOCUS ||
170                keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ||
171                keyCode == KeyEvent.KEYCODE_VOLUME_MUTE ||
172                keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
173                return true;
174            }
175            return false;
176        }
177
178        @Override
179        public boolean shouldOverrideUrlLoading(WebView view, String url) {
180            Intent intent;
181            // Perform generic parsing of the URI to turn it into an Intent.
182            try {
183                intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
184            } catch (URISyntaxException ex) {
185                Log.w(TAG, "Bad URI " + url + ": " + ex.getMessage());
186                return false;
187            }
188            // Sanitize the Intent, ensuring web pages can not bypass browser
189            // security (only access to BROWSABLE activities).
190            intent.addCategory(Intent.CATEGORY_BROWSABLE);
191            intent.setComponent(null);
192            Intent selector = intent.getSelector();
193            if (selector != null) {
194                selector.addCategory(Intent.CATEGORY_BROWSABLE);
195                selector.setComponent(null);
196            }
197            // Pass the package name as application ID so that the intent from the
198            // same application can be opened in the same tab.
199            intent.putExtra(Browser.EXTRA_APPLICATION_ID,
200                    view.getContext().getPackageName());
201            try {
202                view.getContext().startActivity(intent);
203            } catch (ActivityNotFoundException ex) {
204                Log.w(TAG, "No application can handle " + url);
205                return false;
206            }
207            return true;
208        }
209    }
210
211    void setWebViewClient(WebViewClient client) {
212        if (client != null) {
213            mWebViewClient = client;
214        } else {
215            mWebViewClient = new NullWebViewClient();
216        }
217    }
218
219    void setWebChromeClient(WebChromeClient client) {
220        mWebChromeClient = client;
221    }
222
223    void setDownloadListener(DownloadListener listener) {
224        mDownloadListener = listener;
225    }
226
227    void setFindListener(WebView.FindListener listener) {
228        mFindListener = listener;
229    }
230
231    void setPictureListener(WebView.PictureListener listener) {
232        mPictureListener = listener;
233    }
234
235    //--------------------------------------------------------------------------------------------
236    //                        Adapter for all the methods.
237    //--------------------------------------------------------------------------------------------
238
239    /**
240     * @see AwContentsClient#getVisitedHistory
241     */
242    @Override
243    public void getVisitedHistory(ValueCallback<String[]> callback) {
244        TraceEvent.begin();
245        if (mWebChromeClient != null) {
246            if (TRACE) Log.d(TAG, "getVisitedHistory");
247            mWebChromeClient.getVisitedHistory(callback);
248        }
249        TraceEvent.end();
250    }
251
252    /**
253     * @see AwContentsClient#doUpdateVisiteHistory(String, boolean)
254     */
255    @Override
256    public void doUpdateVisitedHistory(String url, boolean isReload) {
257        TraceEvent.begin();
258        if (TRACE) Log.d(TAG, "doUpdateVisitedHistory=" + url + " reload=" + isReload);
259        mWebViewClient.doUpdateVisitedHistory(mWebView, url, isReload);
260        TraceEvent.end();
261    }
262
263    /**
264     * @see AwContentsClient#onProgressChanged(int)
265     */
266    @Override
267    public void onProgressChanged(int progress) {
268        TraceEvent.begin();
269        if (mWebChromeClient != null) {
270            if (TRACE) Log.d(TAG, "onProgressChanged=" + progress);
271            mWebChromeClient.onProgressChanged(mWebView, progress);
272        }
273        TraceEvent.end();
274    }
275
276    /**
277     * @see AwContentsClient#shouldInterceptRequest(java.lang.String)
278     */
279    @Override
280    public InterceptedRequestData shouldInterceptRequest(String url) {
281        TraceEvent.begin();
282        if (TRACE) Log.d(TAG, "shouldInterceptRequest=" + url);
283        WebResourceResponse response = mWebViewClient.shouldInterceptRequest(mWebView, url);
284        TraceEvent.end();
285        if (response == null) return null;
286        return new InterceptedRequestData(
287                response.getMimeType(),
288                response.getEncoding(),
289                response.getData());
290    }
291
292    /**
293     * @see AwContentsClient#shouldOverrideUrlLoading(java.lang.String)
294     */
295    @Override
296    public boolean shouldOverrideUrlLoading(String url) {
297        TraceEvent.begin();
298        if (TRACE) Log.d(TAG, "shouldOverrideUrlLoading=" + url);
299        boolean result = mWebViewClient.shouldOverrideUrlLoading(mWebView, url);
300        TraceEvent.end();
301        return result;
302    }
303
304    /**
305     * @see AwContentsClient#onUnhandledKeyEvent(android.view.KeyEvent)
306     */
307    @Override
308    public void onUnhandledKeyEvent(KeyEvent event) {
309        TraceEvent.begin();
310        if (TRACE) Log.d(TAG, "onUnhandledKeyEvent");
311        mWebViewClient.onUnhandledKeyEvent(mWebView, event);
312        TraceEvent.end();
313    }
314
315    /**
316     * @see AwContentsClient#onConsoleMessage(android.webkit.ConsoleMessage)
317     */
318    @Override
319    public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
320        TraceEvent.begin();
321        boolean result;
322        if (mWebChromeClient != null) {
323            if (TRACE) Log.d(TAG, "onConsoleMessage: " + consoleMessage.message());
324            result = mWebChromeClient.onConsoleMessage(consoleMessage);
325            String message = consoleMessage.message();
326            if (result && message != null && message.startsWith("[blocked]")) {
327                Log.e(TAG, "Blocked URL: " + message);
328            }
329        } else {
330            result = false;
331        }
332        TraceEvent.end();
333        return result;
334    }
335
336    /**
337     * @see AwContentsClient#onFindResultReceived(int,int,boolean)
338     */
339    @Override
340    public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
341            boolean isDoneCounting) {
342        if (mFindListener == null) return;
343        TraceEvent.begin();
344        if (TRACE) Log.d(TAG, "onFindResultReceived");
345        mFindListener.onFindResultReceived(activeMatchOrdinal, numberOfMatches, isDoneCounting);
346        TraceEvent.end();
347    }
348
349    /**
350     * @See AwContentsClient#onNewPicture(Picture)
351     */
352    @Override
353    public void onNewPicture(Picture picture) {
354        if (mPictureListener == null) return;
355        TraceEvent.begin();
356        if (TRACE) Log.d(TAG, "onNewPicture");
357        mPictureListener.onNewPicture(mWebView, picture);
358        TraceEvent.end();
359    }
360
361    @Override
362    public void onLoadResource(String url) {
363        TraceEvent.begin();
364        if (TRACE) Log.d(TAG, "onLoadResource=" + url);
365        mWebViewClient.onLoadResource(mWebView, url);
366        TraceEvent.end();
367    }
368
369    @Override
370    public boolean onCreateWindow(boolean isDialog, boolean isUserGesture) {
371        Message m = mUiThreadHandler.obtainMessage(
372                NEW_WEBVIEW_CREATED, mWebView.new WebViewTransport());
373        TraceEvent.begin();
374        boolean result;
375        if (mWebChromeClient != null) {
376            if (TRACE) Log.d(TAG, "onCreateWindow");
377            result = mWebChromeClient.onCreateWindow(mWebView, isDialog, isUserGesture, m);
378        } else {
379            result = false;
380        }
381        TraceEvent.end();
382        return result;
383    }
384
385    /**
386     * @see AwContentsClient#onCloseWindow()
387     */
388    @Override
389    public void onCloseWindow() {
390        TraceEvent.begin();
391        if (mWebChromeClient != null) {
392            if (TRACE) Log.d(TAG, "onCloseWindow");
393            mWebChromeClient.onCloseWindow(mWebView);
394        }
395        TraceEvent.end();
396    }
397
398    /**
399     * @see AwContentsClient#onRequestFocus()
400     */
401    @Override
402    public void onRequestFocus() {
403        TraceEvent.begin();
404        if (mWebChromeClient != null) {
405            if (TRACE) Log.d(TAG, "onRequestFocus");
406            mWebChromeClient.onRequestFocus(mWebView);
407        }
408        TraceEvent.end();
409    }
410
411    /**
412     * @see AwContentsClient#onReceivedTouchIconUrl(String url, boolean precomposed)
413     */
414    @Override
415    public void onReceivedTouchIconUrl(String url, boolean precomposed) {
416        TraceEvent.begin();
417        if (mWebChromeClient != null) {
418            if (TRACE) Log.d(TAG, "onReceivedTouchIconUrl=" + url);
419            mWebChromeClient.onReceivedTouchIconUrl(mWebView, url, precomposed);
420        }
421        TraceEvent.end();
422    }
423
424    /**
425     * @see AwContentsClient#onReceivedIcon(Bitmap bitmap)
426     */
427    @Override
428    public void onReceivedIcon(Bitmap bitmap) {
429        TraceEvent.begin();
430        if (mWebChromeClient != null) {
431            if (TRACE) Log.d(TAG, "onReceivedIcon");
432            mWebChromeClient.onReceivedIcon(mWebView, bitmap);
433        }
434        TraceEvent.end();
435    }
436
437    /**
438     * @see ContentViewClient#onPageStarted(String)
439     */
440    @Override
441    public void onPageStarted(String url) {
442        TraceEvent.begin();
443        if (TRACE) Log.d(TAG, "onPageStarted=" + url);
444        mWebViewClient.onPageStarted(mWebView, url, mWebView.getFavicon());
445        TraceEvent.end();
446    }
447
448    /**
449     * @see ContentViewClient#onPageFinished(String)
450     */
451    @Override
452    public void onPageFinished(String url) {
453        TraceEvent.begin();
454        if (TRACE) Log.d(TAG, "onPageFinished=" + url);
455        mWebViewClient.onPageFinished(mWebView, url);
456        TraceEvent.end();
457
458        // See b/8208948
459        // This fakes an onNewPicture callback after onPageFinished to allow
460        // CTS tests to run in an un-flaky manner. This is required as the
461        // path for sending Picture updates in Chromium are decoupled from the
462        // page loading callbacks, i.e. the Chrome compositor may draw our
463        // content and send the Picture before onPageStarted or onPageFinished
464        // are invoked. The CTS harness discards any pictures it receives before
465        // onPageStarted is invoked, so in the case we get the Picture before that and
466        // no further updates after onPageStarted, we'll fail the test by timing
467        // out waiting for a Picture.
468        // To ensure backwards compatibility, we need to defer sending Picture updates
469        // until onPageFinished has been invoked. This work is being done
470        // upstream, and we can revert this hack when it lands.
471        if (mPictureListener != null) {
472            ThreadUtils.postOnUiThreadDelayed(new Runnable() {
473                @Override
474                public void run() {
475                    UnimplementedWebViewApi.invoke();
476                    if (mPictureListener != null) {
477                        if (TRACE) Log.d(TAG, "onPageFinished-fake");
478                        mPictureListener.onNewPicture(mWebView, new Picture());
479                    }
480                }
481            }, 100);
482        }
483    }
484
485    /**
486     * @see ContentViewClient#onReceivedError(int,String,String)
487     */
488    @Override
489    public void onReceivedError(int errorCode, String description, String failingUrl) {
490        if (description == null || description.isEmpty()) {
491            // ErrorStrings is @hidden, so we can't do this in AwContents.
492            // Normally the net/ layer will set a valid description, but for synthesized callbacks
493            // (like in the case for intercepted requests) AwContents will pass in null.
494            description = ErrorStrings.getString(errorCode, mWebView.getContext());
495        }
496        TraceEvent.begin();
497        if (TRACE) Log.d(TAG, "onReceivedError=" + failingUrl);
498        mWebViewClient.onReceivedError(mWebView, errorCode, description, failingUrl);
499        TraceEvent.end();
500    }
501
502    /**
503     * @see ContentViewClient#onReceivedTitle(String)
504     */
505    @Override
506    public void onReceivedTitle(String title) {
507        TraceEvent.begin();
508        if (mWebChromeClient != null) {
509            if (TRACE) Log.d(TAG, "onReceivedTitle");
510            mWebChromeClient.onReceivedTitle(mWebView, title);
511        }
512        TraceEvent.end();
513    }
514
515
516    /**
517     * @see ContentViewClient#shouldOverrideKeyEvent(KeyEvent)
518     */
519    @Override
520    public boolean shouldOverrideKeyEvent(KeyEvent event) {
521        // TODO(joth): The expression here is a workaround for http://b/7697782 :-
522        // 1. The check for system key should be made in AwContents or ContentViewCore,
523        //    before shouldOverrideKeyEvent() is called at all.
524        // 2. shouldOverrideKeyEvent() should be called in onKeyDown/onKeyUp, not from
525        //    dispatchKeyEvent().
526        if (event.isSystem()) return true;
527        TraceEvent.begin();
528        if (TRACE) Log.d(TAG, "shouldOverrideKeyEvent");
529        boolean result = mWebViewClient.shouldOverrideKeyEvent(mWebView, event);
530        TraceEvent.end();
531        return result;
532    }
533
534
535    /**
536     * @see ContentViewClient#onStartContentIntent(Context, String)
537     * Callback when detecting a click on a content link.
538     */
539    // TODO: Delete this method when removed from base class.
540    public void onStartContentIntent(Context context, String contentUrl) {
541        TraceEvent.begin();
542        if (TRACE) Log.d(TAG, "shouldOverrideUrlLoading=" + contentUrl);
543        mWebViewClient.shouldOverrideUrlLoading(mWebView, contentUrl);
544        TraceEvent.end();
545    }
546
547    @Override
548    public void onGeolocationPermissionsShowPrompt(String origin,
549            GeolocationPermissions.Callback callback) {
550        TraceEvent.begin();
551        if (mWebChromeClient != null) {
552            if (TRACE) Log.d(TAG, "onGeolocationPermissionsShowPrompt");
553            mWebChromeClient.onGeolocationPermissionsShowPrompt(origin, callback);
554        }
555        TraceEvent.end();
556    }
557
558    @Override
559    public void onGeolocationPermissionsHidePrompt() {
560        TraceEvent.begin();
561        if (mWebChromeClient != null) {
562            if (TRACE) Log.d(TAG, "onGeolocationPermissionsHidePrompt");
563            mWebChromeClient.onGeolocationPermissionsHidePrompt();
564        }
565        TraceEvent.end();
566    }
567
568    private static class JsPromptResultReceiverAdapter implements JsResult.ResultReceiver {
569        private JsPromptResultReceiver mChromePromptResultReceiver;
570        private JsResultReceiver mChromeResultReceiver;
571        // We hold onto the JsPromptResult here, just to avoid the need to downcast
572        // in onJsResultComplete.
573        private final JsPromptResult mPromptResult = new JsPromptResult(this);
574
575        public JsPromptResultReceiverAdapter(JsPromptResultReceiver receiver) {
576            mChromePromptResultReceiver = receiver;
577        }
578
579        public JsPromptResultReceiverAdapter(JsResultReceiver receiver) {
580            mChromeResultReceiver = receiver;
581        }
582
583        public JsPromptResult getPromptResult() {
584            return mPromptResult;
585        }
586
587        @Override
588        public void onJsResultComplete(JsResult result) {
589            if (mChromePromptResultReceiver != null) {
590                if (mPromptResult.getResult()) {
591                    mChromePromptResultReceiver.confirm(mPromptResult.getStringResult());
592                } else {
593                    mChromePromptResultReceiver.cancel();
594                }
595            } else {
596                if (mPromptResult.getResult()) {
597                    mChromeResultReceiver.confirm();
598                } else {
599                    mChromeResultReceiver.cancel();
600                }
601            }
602        }
603    }
604
605    @Override
606    public void handleJsAlert(String url, String message, JsResultReceiver receiver) {
607        TraceEvent.begin();
608        if (mWebChromeClient != null) {
609            final JsPromptResult res =
610                    new JsPromptResultReceiverAdapter(receiver).getPromptResult();
611            if (TRACE) Log.d(TAG, "onJsAlert");
612            if (!mWebChromeClient.onJsAlert(mWebView, url, message, res)) {
613                new JsDialogHelper(res, JsDialogHelper.ALERT, null, message, url)
614                        .showDialog(mWebView.getContext());
615            }
616        } else {
617            receiver.cancel();
618        }
619        TraceEvent.end();
620    }
621
622    @Override
623    public void handleJsBeforeUnload(String url, String message, JsResultReceiver receiver) {
624        TraceEvent.begin();
625        if (mWebChromeClient != null) {
626            final JsPromptResult res =
627                    new JsPromptResultReceiverAdapter(receiver).getPromptResult();
628            if (TRACE) Log.d(TAG, "onJsBeforeUnload");
629            if (!mWebChromeClient.onJsBeforeUnload(mWebView, url, message, res)) {
630                new JsDialogHelper(res, JsDialogHelper.UNLOAD, null, message, url)
631                        .showDialog(mWebView.getContext());
632            }
633        } else {
634            receiver.cancel();
635        }
636        TraceEvent.end();
637    }
638
639    @Override
640    public void handleJsConfirm(String url, String message, JsResultReceiver receiver) {
641        TraceEvent.begin();
642        if (mWebChromeClient != null) {
643            final JsPromptResult res =
644                    new JsPromptResultReceiverAdapter(receiver).getPromptResult();
645            if (TRACE) Log.d(TAG, "onJsConfirm");
646            if (!mWebChromeClient.onJsConfirm(mWebView, url, message, res)) {
647                new JsDialogHelper(res, JsDialogHelper.CONFIRM, null, message, url)
648                        .showDialog(mWebView.getContext());
649            }
650        } else {
651            receiver.cancel();
652        }
653        TraceEvent.end();
654    }
655
656    @Override
657    public void handleJsPrompt(String url, String message, String defaultValue,
658            JsPromptResultReceiver receiver) {
659        TraceEvent.begin();
660        if (mWebChromeClient != null) {
661            final JsPromptResult res =
662                    new JsPromptResultReceiverAdapter(receiver).getPromptResult();
663            if (TRACE) Log.d(TAG, "onJsPrompt");
664            if (!mWebChromeClient.onJsPrompt(mWebView, url, message, defaultValue, res)) {
665                new JsDialogHelper(res, JsDialogHelper.PROMPT, defaultValue, message, url)
666                        .showDialog(mWebView.getContext());
667            }
668        } else {
669            receiver.cancel();
670        }
671        TraceEvent.end();
672    }
673
674    @Override
675    public void onReceivedHttpAuthRequest(AwHttpAuthHandler handler, String host, String realm) {
676        TraceEvent.begin();
677        if (TRACE) Log.d(TAG, "onReceivedHttpAuthRequest=" + host);
678        mWebViewClient.onReceivedHttpAuthRequest(mWebView,
679                new AwHttpAuthHandlerAdapter(handler), host, realm);
680        TraceEvent.end();
681    }
682
683    @Override
684    public void onReceivedSslError(final ValueCallback<Boolean> callback, SslError error) {
685        SslErrorHandler handler = new SslErrorHandler() {
686            @Override
687            public void proceed() {
688                postProceed(true);
689            }
690            @Override
691            public void cancel() {
692                postProceed(false);
693            }
694            private void postProceed(final boolean proceed) {
695                post(new Runnable() {
696                        @Override
697                        public void run() {
698                            callback.onReceiveValue(proceed);
699                        }
700                    });
701            }
702        };
703        TraceEvent.begin();
704        if (TRACE) Log.d(TAG, "onReceivedSslError");
705        mWebViewClient.onReceivedSslError(mWebView, handler, error);
706        TraceEvent.end();
707    }
708
709    @Override
710    public void onReceivedLoginRequest(String realm, String account, String args) {
711        TraceEvent.begin();
712        if (TRACE) Log.d(TAG, "onReceivedLoginRequest=" + realm);
713        mWebViewClient.onReceivedLoginRequest(mWebView, realm, account, args);
714        TraceEvent.end();
715    }
716
717    @Override
718    public void onFormResubmission(Message dontResend, Message resend) {
719        TraceEvent.begin();
720        if (TRACE) Log.d(TAG, "onFormResubmission");
721        mWebViewClient.onFormResubmission(mWebView, dontResend, resend);
722        TraceEvent.end();
723    }
724
725    @Override
726    public void onDownloadStart(String url,
727                                String userAgent,
728                                String contentDisposition,
729                                String mimeType,
730                                long contentLength) {
731        if (mDownloadListener != null) {
732            TraceEvent.begin();
733            if (TRACE) Log.d(TAG, "onDownloadStart");
734            mDownloadListener.onDownloadStart(url,
735                                              userAgent,
736                                              contentDisposition,
737                                              mimeType,
738                                              contentLength);
739            TraceEvent.end();
740        }
741    }
742
743    @Override
744    public void showFileChooser(final ValueCallback<String[]> uploadFileCallback,
745            final AwContentsClient.FileChooserParams fileChooserParams) {
746        if (mWebChromeClient == null) {
747            uploadFileCallback.onReceiveValue(null);
748            return;
749        }
750        TraceEvent.begin();
751        /*
752        // TODO: Call new API here when it's added in frameworks/base - see http://ag/335990
753        WebChromeClient.FileChooserParams p = new WebChromeClient.FileChooserParams();
754        p.mode = fileChooserParams.mode;
755        p.acceptTypes = fileChooserParams.acceptTypes;
756        p.title = fileChooserParams.title;
757        p.defaultFilename = fileChooserParams.defaultFilename;
758        p.capture = fileChooserParams.capture;
759        if (TRACE) Log.d(TAG, "showFileChooser");
760        if (Build.VERSION.SDK_INT > VERSION_CODES.KIT_KAT &&
761               !mWebChromeClient.showFileChooser(mWebView, uploadFileCallback, p)) {
762            return;
763        }
764        if (mWebView.getContext().getApplicationInfo().targetSdkVersion >
765                Build.VERSION_CODES.KIT_KAT) {
766            uploadFileCallback.onReceiveValue(null);
767            return;
768        }
769        */
770        ValueCallback<Uri> innerCallback = new ValueCallback<Uri>() {
771            final AtomicBoolean completed = new AtomicBoolean(false);
772            @Override
773            public void onReceiveValue(Uri uri) {
774                if (completed.getAndSet(true)) return;
775                uploadFileCallback.onReceiveValue(
776                        uri == null ? null : new String[] { uri.toString() });
777            }
778        };
779        if (TRACE) Log.d(TAG, "openFileChooser");
780        mWebChromeClient.openFileChooser(innerCallback, fileChooserParams.acceptTypes,
781                fileChooserParams.capture ? "*" : "");
782        TraceEvent.end();
783    }
784
785    @Override
786    public void onScaleChangedScaled(float oldScale, float newScale) {
787        TraceEvent.begin();
788        if (TRACE) Log.d(TAG, " onScaleChangedScaled");
789        mWebViewClient.onScaleChanged(mWebView, oldScale, newScale);
790        TraceEvent.end();
791    }
792
793    @Override
794    public void onShowCustomView(View view, CustomViewCallback cb) {
795        TraceEvent.begin();
796        if (mWebChromeClient != null) {
797            if (TRACE) Log.d(TAG, "onShowCustomView");
798            mWebChromeClient.onShowCustomView(view, cb);
799        }
800        TraceEvent.end();
801    }
802
803    @Override
804    public void onHideCustomView() {
805        TraceEvent.begin();
806        if (mWebChromeClient != null) {
807            if (TRACE) Log.d(TAG, "onHideCustomView");
808            mWebChromeClient.onHideCustomView();
809        }
810        TraceEvent.end();
811    }
812
813    @Override
814    protected View getVideoLoadingProgressView() {
815        TraceEvent.begin();
816        View result;
817        if (mWebChromeClient != null) {
818            if (TRACE) Log.d(TAG, "getVideoLoadingProgressView");
819            result = mWebChromeClient.getVideoLoadingProgressView();
820        } else {
821            result = null;
822        }
823        TraceEvent.end();
824        return result;
825    }
826
827    @Override
828    public Bitmap getDefaultVideoPoster() {
829        TraceEvent.begin();
830        Bitmap result = null;
831        if (mWebChromeClient != null) {
832            if (TRACE) Log.d(TAG, "getDefaultVideoPoster");
833            result = mWebChromeClient.getDefaultVideoPoster();
834        }
835        if (result == null) {
836            // The ic_media_video_poster icon is transparent so we need to draw it on a gray
837            // background.
838            Bitmap poster = BitmapFactory.decodeResource(
839                    mWebView.getContext().getResources(),
840                    com.android.internal.R.drawable.ic_media_video_poster);
841            result = Bitmap.createBitmap(poster.getWidth(), poster.getHeight(), poster.getConfig());
842            result.eraseColor(Color.GRAY);
843            Canvas canvas = new Canvas(result);
844            canvas.drawBitmap(poster, 0f, 0f, null);
845        }
846        TraceEvent.end();
847        return result;
848    }
849
850    private static class AwHttpAuthHandlerAdapter extends android.webkit.HttpAuthHandler {
851        private AwHttpAuthHandler mAwHandler;
852
853        public AwHttpAuthHandlerAdapter(AwHttpAuthHandler awHandler) {
854            mAwHandler = awHandler;
855        }
856
857        @Override
858        public void proceed(String username, String password) {
859            if (username == null) {
860                username = "";
861            }
862
863            if (password == null) {
864                password = "";
865            }
866            mAwHandler.proceed(username, password);
867        }
868
869        @Override
870        public void cancel() {
871            mAwHandler.cancel();
872        }
873
874        @Override
875        public boolean useHttpAuthUsernamePassword() {
876            return mAwHandler.isFirstAttempt();
877        }
878    }
879}
880