WebViewContentsClientAdapter.java revision 48c8233468f8c735d8504c8410b86cc373d679ed
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            // Pass the package name as application ID so that the intent from the
193            // same application can be opened in the same tab.
194            intent.putExtra(Browser.EXTRA_APPLICATION_ID,
195                    view.getContext().getPackageName());
196            try {
197                view.getContext().startActivity(intent);
198            } catch (ActivityNotFoundException ex) {
199                Log.w(TAG, "No application can handle " + url);
200                return false;
201            }
202            return true;
203        }
204    }
205
206    void setWebViewClient(WebViewClient client) {
207        if (client != null) {
208            mWebViewClient = client;
209        } else {
210            mWebViewClient = new NullWebViewClient();
211        }
212    }
213
214    void setWebChromeClient(WebChromeClient client) {
215        mWebChromeClient = client;
216    }
217
218    void setDownloadListener(DownloadListener listener) {
219        mDownloadListener = listener;
220    }
221
222    void setFindListener(WebView.FindListener listener) {
223        mFindListener = listener;
224    }
225
226    void setPictureListener(WebView.PictureListener listener) {
227        mPictureListener = listener;
228    }
229
230    //--------------------------------------------------------------------------------------------
231    //                        Adapter for all the methods.
232    //--------------------------------------------------------------------------------------------
233
234    /**
235     * @see AwContentsClient#getVisitedHistory
236     */
237    @Override
238    public void getVisitedHistory(ValueCallback<String[]> callback) {
239        TraceEvent.begin();
240        if (mWebChromeClient != null) {
241            if (TRACE) Log.d(TAG, "getVisitedHistory");
242            mWebChromeClient.getVisitedHistory(callback);
243        }
244        TraceEvent.end();
245    }
246
247    /**
248     * @see AwContentsClient#doUpdateVisiteHistory(String, boolean)
249     */
250    @Override
251    public void doUpdateVisitedHistory(String url, boolean isReload) {
252        TraceEvent.begin();
253        if (TRACE) Log.d(TAG, "doUpdateVisitedHistory=" + url + " reload=" + isReload);
254        mWebViewClient.doUpdateVisitedHistory(mWebView, url, isReload);
255        TraceEvent.end();
256    }
257
258    /**
259     * @see AwContentsClient#onProgressChanged(int)
260     */
261    @Override
262    public void onProgressChanged(int progress) {
263        TraceEvent.begin();
264        if (mWebChromeClient != null) {
265            if (TRACE) Log.d(TAG, "onProgressChanged=" + progress);
266            mWebChromeClient.onProgressChanged(mWebView, progress);
267        }
268        TraceEvent.end();
269    }
270
271    /**
272     * @see AwContentsClient#shouldInterceptRequest(java.lang.String)
273     */
274    @Override
275    public InterceptedRequestData shouldInterceptRequest(String url) {
276        TraceEvent.begin();
277        if (TRACE) Log.d(TAG, "shouldInterceptRequest=" + url);
278        WebResourceResponse response = mWebViewClient.shouldInterceptRequest(mWebView, url);
279        TraceEvent.end();
280        if (response == null) return null;
281        return new InterceptedRequestData(
282                response.getMimeType(),
283                response.getEncoding(),
284                response.getData());
285    }
286
287    /**
288     * @see AwContentsClient#shouldOverrideUrlLoading(java.lang.String)
289     */
290    @Override
291    public boolean shouldOverrideUrlLoading(String url) {
292        TraceEvent.begin();
293        if (TRACE) Log.d(TAG, "shouldOverrideUrlLoading=" + url);
294        boolean result = mWebViewClient.shouldOverrideUrlLoading(mWebView, url);
295        TraceEvent.end();
296        return result;
297    }
298
299    /**
300     * @see AwContentsClient#onUnhandledKeyEvent(android.view.KeyEvent)
301     */
302    @Override
303    public void onUnhandledKeyEvent(KeyEvent event) {
304        TraceEvent.begin();
305        if (TRACE) Log.d(TAG, "onUnhandledKeyEvent");
306        mWebViewClient.onUnhandledKeyEvent(mWebView, event);
307        TraceEvent.end();
308    }
309
310    /**
311     * @see AwContentsClient#onConsoleMessage(android.webkit.ConsoleMessage)
312     */
313    @Override
314    public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
315        TraceEvent.begin();
316        boolean result;
317        if (mWebChromeClient != null) {
318            if (TRACE) Log.d(TAG, "onConsoleMessage: " + consoleMessage.message());
319            result = mWebChromeClient.onConsoleMessage(consoleMessage);
320            String message = consoleMessage.message();
321            if (result && message != null && message.startsWith("[blocked]")) {
322                Log.e(TAG, "Blocked URL: " + message);
323            }
324        } else {
325            result = false;
326        }
327        TraceEvent.end();
328        return result;
329    }
330
331    /**
332     * @see AwContentsClient#onFindResultReceived(int,int,boolean)
333     */
334    @Override
335    public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
336            boolean isDoneCounting) {
337        if (mFindListener == null) return;
338        TraceEvent.begin();
339        if (TRACE) Log.d(TAG, "onFindResultReceived");
340        mFindListener.onFindResultReceived(activeMatchOrdinal, numberOfMatches, isDoneCounting);
341        TraceEvent.end();
342    }
343
344    /**
345     * @See AwContentsClient#onNewPicture(Picture)
346     */
347    @Override
348    public void onNewPicture(Picture picture) {
349        if (mPictureListener == null) return;
350        TraceEvent.begin();
351        if (TRACE) Log.d(TAG, "onNewPicture");
352        mPictureListener.onNewPicture(mWebView, picture);
353        TraceEvent.end();
354    }
355
356    @Override
357    public void onLoadResource(String url) {
358        TraceEvent.begin();
359        if (TRACE) Log.d(TAG, "onLoadResource=" + url);
360        mWebViewClient.onLoadResource(mWebView, url);
361        TraceEvent.end();
362    }
363
364    @Override
365    public boolean onCreateWindow(boolean isDialog, boolean isUserGesture) {
366        Message m = mUiThreadHandler.obtainMessage(
367                NEW_WEBVIEW_CREATED, mWebView.new WebViewTransport());
368        TraceEvent.begin();
369        boolean result;
370        if (mWebChromeClient != null) {
371            if (TRACE) Log.d(TAG, "onCreateWindow");
372            result = mWebChromeClient.onCreateWindow(mWebView, isDialog, isUserGesture, m);
373        } else {
374            result = false;
375        }
376        TraceEvent.end();
377        return result;
378    }
379
380    /**
381     * @see AwContentsClient#onCloseWindow()
382     */
383    @Override
384    public void onCloseWindow() {
385        TraceEvent.begin();
386        if (mWebChromeClient != null) {
387            if (TRACE) Log.d(TAG, "onCloseWindow");
388            mWebChromeClient.onCloseWindow(mWebView);
389        }
390        TraceEvent.end();
391    }
392
393    /**
394     * @see AwContentsClient#onRequestFocus()
395     */
396    @Override
397    public void onRequestFocus() {
398        TraceEvent.begin();
399        if (mWebChromeClient != null) {
400            if (TRACE) Log.d(TAG, "onRequestFocus");
401            mWebChromeClient.onRequestFocus(mWebView);
402        }
403        TraceEvent.end();
404    }
405
406    /**
407     * @see AwContentsClient#onReceivedTouchIconUrl(String url, boolean precomposed)
408     */
409    @Override
410    public void onReceivedTouchIconUrl(String url, boolean precomposed) {
411        TraceEvent.begin();
412        if (mWebChromeClient != null) {
413            if (TRACE) Log.d(TAG, "onReceivedTouchIconUrl=" + url);
414            mWebChromeClient.onReceivedTouchIconUrl(mWebView, url, precomposed);
415        }
416        TraceEvent.end();
417    }
418
419    /**
420     * @see AwContentsClient#onReceivedIcon(Bitmap bitmap)
421     */
422    @Override
423    public void onReceivedIcon(Bitmap bitmap) {
424        TraceEvent.begin();
425        if (mWebChromeClient != null) {
426            if (TRACE) Log.d(TAG, "onReceivedIcon");
427            mWebChromeClient.onReceivedIcon(mWebView, bitmap);
428        }
429        TraceEvent.end();
430    }
431
432    /**
433     * @see ContentViewClient#onPageStarted(String)
434     */
435    @Override
436    public void onPageStarted(String url) {
437        TraceEvent.begin();
438        if (TRACE) Log.d(TAG, "onPageStarted=" + url);
439        mWebViewClient.onPageStarted(mWebView, url, mWebView.getFavicon());
440        TraceEvent.end();
441    }
442
443    /**
444     * @see ContentViewClient#onPageFinished(String)
445     */
446    @Override
447    public void onPageFinished(String url) {
448        TraceEvent.begin();
449        if (TRACE) Log.d(TAG, "onPageFinished=" + url);
450        mWebViewClient.onPageFinished(mWebView, url);
451        TraceEvent.end();
452
453        // See b/8208948
454        // This fakes an onNewPicture callback after onPageFinished to allow
455        // CTS tests to run in an un-flaky manner. This is required as the
456        // path for sending Picture updates in Chromium are decoupled from the
457        // page loading callbacks, i.e. the Chrome compositor may draw our
458        // content and send the Picture before onPageStarted or onPageFinished
459        // are invoked. The CTS harness discards any pictures it receives before
460        // onPageStarted is invoked, so in the case we get the Picture before that and
461        // no further updates after onPageStarted, we'll fail the test by timing
462        // out waiting for a Picture.
463        // To ensure backwards compatibility, we need to defer sending Picture updates
464        // until onPageFinished has been invoked. This work is being done
465        // upstream, and we can revert this hack when it lands.
466        if (mPictureListener != null) {
467            ThreadUtils.postOnUiThreadDelayed(new Runnable() {
468                @Override
469                public void run() {
470                    UnimplementedWebViewApi.invoke();
471                    if (mPictureListener != null) {
472                        if (TRACE) Log.d(TAG, "onPageFinished-fake");
473                        mPictureListener.onNewPicture(mWebView, new Picture());
474                    }
475                }
476            }, 100);
477        }
478    }
479
480    /**
481     * @see ContentViewClient#onReceivedError(int,String,String)
482     */
483    @Override
484    public void onReceivedError(int errorCode, String description, String failingUrl) {
485        if (description == null || description.isEmpty()) {
486            // ErrorStrings is @hidden, so we can't do this in AwContents.
487            // Normally the net/ layer will set a valid description, but for synthesized callbacks
488            // (like in the case for intercepted requests) AwContents will pass in null.
489            description = ErrorStrings.getString(errorCode, mWebView.getContext());
490        }
491        TraceEvent.begin();
492        if (TRACE) Log.d(TAG, "onReceivedError=" + failingUrl);
493        mWebViewClient.onReceivedError(mWebView, errorCode, description, failingUrl);
494        TraceEvent.end();
495    }
496
497    /**
498     * @see ContentViewClient#onReceivedTitle(String)
499     */
500    @Override
501    public void onReceivedTitle(String title) {
502        TraceEvent.begin();
503        if (mWebChromeClient != null) {
504            if (TRACE) Log.d(TAG, "onReceivedTitle");
505            mWebChromeClient.onReceivedTitle(mWebView, title);
506        }
507        TraceEvent.end();
508    }
509
510
511    /**
512     * @see ContentViewClient#shouldOverrideKeyEvent(KeyEvent)
513     */
514    @Override
515    public boolean shouldOverrideKeyEvent(KeyEvent event) {
516        // TODO(joth): The expression here is a workaround for http://b/7697782 :-
517        // 1. The check for system key should be made in AwContents or ContentViewCore,
518        //    before shouldOverrideKeyEvent() is called at all.
519        // 2. shouldOverrideKeyEvent() should be called in onKeyDown/onKeyUp, not from
520        //    dispatchKeyEvent().
521        if (event.isSystem()) return true;
522        TraceEvent.begin();
523        if (TRACE) Log.d(TAG, "shouldOverrideKeyEvent");
524        boolean result = mWebViewClient.shouldOverrideKeyEvent(mWebView, event);
525        TraceEvent.end();
526        return result;
527    }
528
529
530    /**
531     * @see ContentViewClient#onStartContentIntent(Context, String)
532     * Callback when detecting a click on a content link.
533     */
534    // TODO: Delete this method when removed from base class.
535    public void onStartContentIntent(Context context, String contentUrl) {
536        TraceEvent.begin();
537        if (TRACE) Log.d(TAG, "shouldOverrideUrlLoading=" + contentUrl);
538        mWebViewClient.shouldOverrideUrlLoading(mWebView, contentUrl);
539        TraceEvent.end();
540    }
541
542    @Override
543    public void onGeolocationPermissionsShowPrompt(String origin,
544            GeolocationPermissions.Callback callback) {
545        TraceEvent.begin();
546        if (mWebChromeClient != null) {
547            if (TRACE) Log.d(TAG, "onGeolocationPermissionsShowPrompt");
548            mWebChromeClient.onGeolocationPermissionsShowPrompt(origin, callback);
549        }
550        TraceEvent.end();
551    }
552
553    @Override
554    public void onGeolocationPermissionsHidePrompt() {
555        TraceEvent.begin();
556        if (mWebChromeClient != null) {
557            if (TRACE) Log.d(TAG, "onGeolocationPermissionsHidePrompt");
558            mWebChromeClient.onGeolocationPermissionsHidePrompt();
559        }
560        TraceEvent.end();
561    }
562
563    private static class JsPromptResultReceiverAdapter implements JsResult.ResultReceiver {
564        private JsPromptResultReceiver mChromePromptResultReceiver;
565        private JsResultReceiver mChromeResultReceiver;
566        // We hold onto the JsPromptResult here, just to avoid the need to downcast
567        // in onJsResultComplete.
568        private final JsPromptResult mPromptResult = new JsPromptResult(this);
569
570        public JsPromptResultReceiverAdapter(JsPromptResultReceiver receiver) {
571            mChromePromptResultReceiver = receiver;
572        }
573
574        public JsPromptResultReceiverAdapter(JsResultReceiver receiver) {
575            mChromeResultReceiver = receiver;
576        }
577
578        public JsPromptResult getPromptResult() {
579            return mPromptResult;
580        }
581
582        @Override
583        public void onJsResultComplete(JsResult result) {
584            if (mChromePromptResultReceiver != null) {
585                if (mPromptResult.getResult()) {
586                    mChromePromptResultReceiver.confirm(mPromptResult.getStringResult());
587                } else {
588                    mChromePromptResultReceiver.cancel();
589                }
590            } else {
591                if (mPromptResult.getResult()) {
592                    mChromeResultReceiver.confirm();
593                } else {
594                    mChromeResultReceiver.cancel();
595                }
596            }
597        }
598    }
599
600    @Override
601    public void handleJsAlert(String url, String message, JsResultReceiver receiver) {
602        TraceEvent.begin();
603        if (mWebChromeClient != null) {
604            final JsPromptResult res =
605                    new JsPromptResultReceiverAdapter(receiver).getPromptResult();
606            if (TRACE) Log.d(TAG, "onJsAlert");
607            if (!mWebChromeClient.onJsAlert(mWebView, url, message, res)) {
608                new JsDialogHelper(res, JsDialogHelper.ALERT, null, message, url)
609                        .showDialog(mWebView.getContext());
610            }
611        } else {
612            receiver.cancel();
613        }
614        TraceEvent.end();
615    }
616
617    @Override
618    public void handleJsBeforeUnload(String url, String message, JsResultReceiver receiver) {
619        TraceEvent.begin();
620        if (mWebChromeClient != null) {
621            final JsPromptResult res =
622                    new JsPromptResultReceiverAdapter(receiver).getPromptResult();
623            if (TRACE) Log.d(TAG, "onJsBeforeUnload");
624            if (!mWebChromeClient.onJsBeforeUnload(mWebView, url, message, res)) {
625                new JsDialogHelper(res, JsDialogHelper.UNLOAD, null, message, url)
626                        .showDialog(mWebView.getContext());
627            }
628        } else {
629            receiver.cancel();
630        }
631        TraceEvent.end();
632    }
633
634    @Override
635    public void handleJsConfirm(String url, String message, JsResultReceiver receiver) {
636        TraceEvent.begin();
637        if (mWebChromeClient != null) {
638            final JsPromptResult res =
639                    new JsPromptResultReceiverAdapter(receiver).getPromptResult();
640            if (TRACE) Log.d(TAG, "onJsConfirm");
641            if (!mWebChromeClient.onJsConfirm(mWebView, url, message, res)) {
642                new JsDialogHelper(res, JsDialogHelper.CONFIRM, null, message, url)
643                        .showDialog(mWebView.getContext());
644            }
645        } else {
646            receiver.cancel();
647        }
648        TraceEvent.end();
649    }
650
651    @Override
652    public void handleJsPrompt(String url, String message, String defaultValue,
653            JsPromptResultReceiver receiver) {
654        TraceEvent.begin();
655        if (mWebChromeClient != null) {
656            final JsPromptResult res =
657                    new JsPromptResultReceiverAdapter(receiver).getPromptResult();
658            if (TRACE) Log.d(TAG, "onJsPrompt");
659            if (!mWebChromeClient.onJsPrompt(mWebView, url, message, defaultValue, res)) {
660                new JsDialogHelper(res, JsDialogHelper.PROMPT, defaultValue, message, url)
661                        .showDialog(mWebView.getContext());
662            }
663        } else {
664            receiver.cancel();
665        }
666        TraceEvent.end();
667    }
668
669    @Override
670    public void onReceivedHttpAuthRequest(AwHttpAuthHandler handler, String host, String realm) {
671        TraceEvent.begin();
672        if (TRACE) Log.d(TAG, "onReceivedHttpAuthRequest=" + host);
673        mWebViewClient.onReceivedHttpAuthRequest(mWebView,
674                new AwHttpAuthHandlerAdapter(handler), host, realm);
675        TraceEvent.end();
676    }
677
678    @Override
679    public void onReceivedSslError(final ValueCallback<Boolean> callback, SslError error) {
680        SslErrorHandler handler = new SslErrorHandler() {
681            @Override
682            public void proceed() {
683                postProceed(true);
684            }
685            @Override
686            public void cancel() {
687                postProceed(false);
688            }
689            private void postProceed(final boolean proceed) {
690                post(new Runnable() {
691                        @Override
692                        public void run() {
693                            callback.onReceiveValue(proceed);
694                        }
695                    });
696            }
697        };
698        TraceEvent.begin();
699        if (TRACE) Log.d(TAG, "onReceivedSslError");
700        mWebViewClient.onReceivedSslError(mWebView, handler, error);
701        TraceEvent.end();
702    }
703
704    @Override
705    public void onReceivedLoginRequest(String realm, String account, String args) {
706        TraceEvent.begin();
707        if (TRACE) Log.d(TAG, "onReceivedLoginRequest=" + realm);
708        mWebViewClient.onReceivedLoginRequest(mWebView, realm, account, args);
709        TraceEvent.end();
710    }
711
712    @Override
713    public void onFormResubmission(Message dontResend, Message resend) {
714        TraceEvent.begin();
715        if (TRACE) Log.d(TAG, "onFormResubmission");
716        mWebViewClient.onFormResubmission(mWebView, dontResend, resend);
717        TraceEvent.end();
718    }
719
720    @Override
721    public void onDownloadStart(String url,
722                                String userAgent,
723                                String contentDisposition,
724                                String mimeType,
725                                long contentLength) {
726        if (mDownloadListener != null) {
727            TraceEvent.begin();
728            if (TRACE) Log.d(TAG, "onDownloadStart");
729            mDownloadListener.onDownloadStart(url,
730                                              userAgent,
731                                              contentDisposition,
732                                              mimeType,
733                                              contentLength);
734            TraceEvent.end();
735        }
736    }
737
738    @Override
739    public void showFileChooser(final ValueCallback<String[]> uploadFileCallback,
740            final AwContentsClient.FileChooserParams fileChooserParams) {
741        if (mWebChromeClient == null) {
742            uploadFileCallback.onReceiveValue(null);
743            return;
744        }
745        TraceEvent.begin();
746        /*
747        // TODO: Call new API here when it's added in frameworks/base - see http://ag/335990
748        WebChromeClient.FileChooserParams p = new WebChromeClient.FileChooserParams();
749        p.mode = fileChooserParams.mode;
750        p.acceptTypes = fileChooserParams.acceptTypes;
751        p.title = fileChooserParams.title;
752        p.defaultFilename = fileChooserParams.defaultFilename;
753        p.capture = fileChooserParams.capture;
754        if (TRACE) Log.d(TAG, "showFileChooser");
755        if (Build.VERSION.SDK_INT > VERSION_CODES.KIT_KAT &&
756               !mWebChromeClient.showFileChooser(mWebView, uploadFileCallback, p)) {
757            return;
758        }
759        if (mWebView.getContext().getApplicationInfo().targetSdkVersion >
760                Build.VERSION_CODES.KIT_KAT) {
761            uploadFileCallback.onReceiveValue(null);
762            return;
763        }
764        */
765        ValueCallback<Uri> innerCallback = new ValueCallback<Uri>() {
766            final AtomicBoolean completed = new AtomicBoolean(false);
767            @Override
768            public void onReceiveValue(Uri uri) {
769                if (completed.getAndSet(true)) return;
770                uploadFileCallback.onReceiveValue(
771                        uri == null ? null : new String[] { uri.toString() });
772            }
773        };
774        if (TRACE) Log.d(TAG, "openFileChooser");
775        mWebChromeClient.openFileChooser(innerCallback, fileChooserParams.acceptTypes,
776                fileChooserParams.capture ? "*" : "");
777        TraceEvent.end();
778    }
779
780    @Override
781    public void onScaleChangedScaled(float oldScale, float newScale) {
782        TraceEvent.begin();
783        if (TRACE) Log.d(TAG, " onScaleChangedScaled");
784        mWebViewClient.onScaleChanged(mWebView, oldScale, newScale);
785        TraceEvent.end();
786    }
787
788    @Override
789    public void onShowCustomView(View view, CustomViewCallback cb) {
790        TraceEvent.begin();
791        if (mWebChromeClient != null) {
792            if (TRACE) Log.d(TAG, "onShowCustomView");
793            mWebChromeClient.onShowCustomView(view, cb);
794        }
795        TraceEvent.end();
796    }
797
798    @Override
799    public void onHideCustomView() {
800        TraceEvent.begin();
801        if (mWebChromeClient != null) {
802            if (TRACE) Log.d(TAG, "onHideCustomView");
803            mWebChromeClient.onHideCustomView();
804        }
805        TraceEvent.end();
806    }
807
808    @Override
809    protected View getVideoLoadingProgressView() {
810        TraceEvent.begin();
811        View result;
812        if (mWebChromeClient != null) {
813            if (TRACE) Log.d(TAG, "getVideoLoadingProgressView");
814            result = mWebChromeClient.getVideoLoadingProgressView();
815        } else {
816            result = null;
817        }
818        TraceEvent.end();
819        return result;
820    }
821
822    @Override
823    public Bitmap getDefaultVideoPoster() {
824        TraceEvent.begin();
825        Bitmap result = null;
826        if (mWebChromeClient != null) {
827            if (TRACE) Log.d(TAG, "getDefaultVideoPoster");
828            result = mWebChromeClient.getDefaultVideoPoster();
829        }
830        if (result == null) {
831            // The ic_media_video_poster icon is transparent so we need to draw it on a gray
832            // background.
833            Bitmap poster = BitmapFactory.decodeResource(
834                    mWebView.getContext().getResources(),
835                    com.android.internal.R.drawable.ic_media_video_poster);
836            result = Bitmap.createBitmap(poster.getWidth(), poster.getHeight(), poster.getConfig());
837            result.eraseColor(Color.GRAY);
838            Canvas canvas = new Canvas(result);
839            canvas.drawBitmap(poster, 0f, 0f, null);
840        }
841        TraceEvent.end();
842        return result;
843    }
844
845    private static class AwHttpAuthHandlerAdapter extends android.webkit.HttpAuthHandler {
846        private AwHttpAuthHandler mAwHandler;
847
848        public AwHttpAuthHandlerAdapter(AwHttpAuthHandler awHandler) {
849            mAwHandler = awHandler;
850        }
851
852        @Override
853        public void proceed(String username, String password) {
854            if (username == null) {
855                username = "";
856            }
857
858            if (password == null) {
859                password = "";
860            }
861            mAwHandler.proceed(username, password);
862        }
863
864        @Override
865        public void cancel() {
866            mAwHandler.cancel();
867        }
868
869        @Override
870        public boolean useHttpAuthUsernamePassword() {
871            return mAwHandler.isFirstAttempt();
872        }
873    }
874}
875