WebViewContentsClientAdapter.java revision 245b3a6a2a7850a46536a423594286e04e702006
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.Picture;
24import android.net.http.ErrorStrings;
25import android.net.http.SslError;
26import android.os.Handler;
27import android.os.Looper;
28import android.os.Message;
29import android.provider.Browser;
30import android.util.Log;
31import android.view.KeyEvent;
32import android.view.View;
33import android.webkit.ConsoleMessage;
34import android.webkit.DownloadListener;
35import android.webkit.GeolocationPermissions;
36import android.webkit.JsDialogHelper;
37import android.webkit.JsPromptResult;
38import android.webkit.JsResult;
39import android.webkit.SslErrorHandler;
40import android.webkit.ValueCallback;
41import android.webkit.WebChromeClient;
42import android.webkit.WebChromeClient.CustomViewCallback;
43import android.webkit.WebResourceResponse;
44import android.webkit.WebView;
45import android.webkit.WebViewClient;
46
47import org.chromium.android_webview.AwContentsClient;
48import org.chromium.android_webview.AwHttpAuthHandler;
49import org.chromium.android_webview.InterceptedRequestData;
50import org.chromium.android_webview.JsPromptResultReceiver;
51import org.chromium.android_webview.JsResultReceiver;
52import org.chromium.content.browser.ContentView;
53import org.chromium.content.browser.ContentViewClient;
54import org.chromium.content.common.TraceEvent;
55
56import java.net.URISyntaxException;
57
58/**
59 * An adapter class that forwards the callbacks from {@link ContentViewClient}
60 * to the appropriate {@link WebViewClient} or {@link WebChromeClient}.
61 *
62 * An instance of this class is associated with one {@link WebViewChromium}
63 * instance. A WebViewChromium is a WebView implementation provider (that is
64 * android.webkit.WebView delegates all functionality to it) and has exactly
65 * one corresponding {@link ContentView} instance.
66 *
67 * A {@link ContentViewClient} may be shared between multiple {@link ContentView}s,
68 * and hence multiple WebViews. Many WebViewClient methods pass the source
69 * WebView as an argument. This means that we either need to pass the
70 * corresponding ContentView to the corresponding ContentViewClient methods,
71 * or use an instance of ContentViewClientAdapter per WebViewChromium, to
72 * allow the source WebView to be injected by ContentViewClientAdapter. We
73 * choose the latter, because it makes for a cleaner design.
74 */
75public class WebViewContentsClientAdapter extends AwContentsClient {
76    private static final String TAG = "ContentViewClientAdapter";
77    // The WebView instance that this adapter is serving.
78    private final WebView mWebView;
79    // The WebViewClient instance that was passed to WebView.setWebViewClient().
80    private WebViewClient mWebViewClient;
81    // The WebViewClient instance that was passed to WebView.setContentViewClient().
82    private WebChromeClient mWebChromeClient;
83    // The listener receiving find-in-page API results.
84    private WebView.FindListener mFindListener;
85    // The listener receiving notifications of screen updates.
86    private WebView.PictureListener mPictureListener;
87
88    private DownloadListener mDownloadListener;
89
90    private Handler mUiThreadHandler;
91
92    private static final int NEW_WEBVIEW_CREATED = 100;
93
94    /**
95     * Adapter constructor.
96     *
97     * @param webView the {@link WebView} instance that this adapter is serving.
98     */
99    WebViewContentsClientAdapter(WebView webView) {
100        if (webView == null) {
101            throw new IllegalArgumentException("webView can't be null");
102        }
103
104        mWebView = webView;
105        setWebViewClient(null);
106        setWebChromeClient(null);
107
108        mUiThreadHandler = new Handler() {
109
110            @Override
111            public void handleMessage(Message msg) {
112                switch(msg.what) {
113                    case NEW_WEBVIEW_CREATED:
114                        WebView.WebViewTransport t = (WebView.WebViewTransport) msg.obj;
115                        WebView newWebView = t.getWebView();
116                        if (newWebView == mWebView) {
117                            throw new IllegalArgumentException(
118                                    "Parent WebView cannot host it's own popup window. Please " +
119                                    "use WebSettings.setSupportMultipleWindows(false)");
120                        }
121
122                        if (newWebView != null && newWebView.copyBackForwardList().getSize() != 0) {
123                            throw new IllegalArgumentException(
124                                    "New WebView for popup window must not have been previously " +
125                                    "navigated.");
126                        }
127
128                        WebViewChromium.completeWindowCreation(mWebView, newWebView);
129                        break;
130                    default:
131                        throw new IllegalStateException();
132                }
133            }
134        };
135
136    }
137
138    // WebViewClassic is coded in such a way that even if a null WebViewClient is set,
139    // certain actions take place.
140    // We choose to replicate this behavior by using a NullWebViewClient implementation (also known
141    // as the Null Object pattern) rather than duplicating the WebViewClassic approach in
142    // ContentView.
143    static class NullWebViewClient extends WebViewClient {
144        @Override
145        public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
146            // TODO: Investigate more and add a test case.
147            // This is a copy of what Clank does. The WebViewCore key handling code and Clank key
148            // handling code differ enough that it's not trivial to figure out how keycodes are
149            // being filtered.
150            int keyCode = event.getKeyCode();
151            if (keyCode == KeyEvent.KEYCODE_MENU ||
152                keyCode == KeyEvent.KEYCODE_HOME ||
153                keyCode == KeyEvent.KEYCODE_BACK ||
154                keyCode == KeyEvent.KEYCODE_CALL ||
155                keyCode == KeyEvent.KEYCODE_ENDCALL ||
156                keyCode == KeyEvent.KEYCODE_POWER ||
157                keyCode == KeyEvent.KEYCODE_HEADSETHOOK ||
158                keyCode == KeyEvent.KEYCODE_CAMERA ||
159                keyCode == KeyEvent.KEYCODE_FOCUS ||
160                keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ||
161                keyCode == KeyEvent.KEYCODE_VOLUME_MUTE ||
162                keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
163                return true;
164            }
165            return false;
166        }
167
168        @Override
169        public boolean shouldOverrideUrlLoading(WebView view, String url) {
170            Intent intent;
171            // Perform generic parsing of the URI to turn it into an Intent.
172            try {
173                intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
174            } catch (URISyntaxException ex) {
175                Log.w(TAG, "Bad URI " + url + ": " + ex.getMessage());
176                return false;
177            }
178            // Sanitize the Intent, ensuring web pages can not bypass browser
179            // security (only access to BROWSABLE activities).
180            intent.addCategory(Intent.CATEGORY_BROWSABLE);
181            intent.setComponent(null);
182            // Pass the package name as application ID so that the intent from the
183            // same application can be opened in the same tab.
184            intent.putExtra(Browser.EXTRA_APPLICATION_ID,
185                    view.getContext().getPackageName());
186            try {
187                view.getContext().startActivity(intent);
188            } catch (ActivityNotFoundException ex) {
189                Log.w(TAG, "No application can handle " + url);
190                return false;
191            }
192            return true;
193        }
194    }
195
196    void setWebViewClient(WebViewClient client) {
197        if (client != null) {
198            mWebViewClient = client;
199        } else {
200            mWebViewClient = new NullWebViewClient();
201        }
202    }
203
204    void setWebChromeClient(WebChromeClient client) {
205        if (client != null) {
206            mWebChromeClient = client;
207        } else {
208            // WebViewClassic doesn't implement any special behavior for a null WebChromeClient.
209            mWebChromeClient = new WebChromeClient();
210        }
211    }
212
213    void setDownloadListener(DownloadListener listener) {
214        mDownloadListener = listener;
215    }
216
217    void setFindListener(WebView.FindListener listener) {
218        mFindListener = listener;
219    }
220
221    void setPictureListener(WebView.PictureListener listener) {
222        mPictureListener = listener;
223    }
224
225    //--------------------------------------------------------------------------------------------
226    //                        Adapter for all the methods.
227    //--------------------------------------------------------------------------------------------
228
229    /**
230     * @see AwContentsClient#getVisitedHistory
231     */
232    @Override
233    public void getVisitedHistory(ValueCallback<String[]> callback) {
234        TraceEvent.begin();
235        mWebChromeClient.getVisitedHistory(callback);
236        TraceEvent.end();
237    }
238
239    /**
240     * @see AwContentsClient#doUpdateVisiteHistory(String, boolean)
241     */
242    @Override
243    public void doUpdateVisitedHistory(String url, boolean isReload) {
244        TraceEvent.begin();
245        mWebViewClient.doUpdateVisitedHistory(mWebView, url, isReload);
246        TraceEvent.end();
247    }
248
249    /**
250     * @see AwContentsClient#onProgressChanged(int)
251     */
252    @Override
253    public void onProgressChanged(int progress) {
254        TraceEvent.begin();
255        mWebChromeClient.onProgressChanged(mWebView, progress);
256        TraceEvent.end();
257    }
258
259    /**
260     * @see AwContentsClient#shouldInterceptRequest(java.lang.String)
261     */
262    @Override
263    public InterceptedRequestData shouldInterceptRequest(String url) {
264        TraceEvent.begin();
265        WebResourceResponse response = mWebViewClient.shouldInterceptRequest(mWebView, url);
266        TraceEvent.end();
267        if (response == null) return null;
268        return new InterceptedRequestData(
269                response.getMimeType(),
270                response.getEncoding(),
271                response.getData());
272    }
273
274    /**
275     * @see AwContentsClient#shouldOverrideUrlLoading(java.lang.String)
276     */
277    @Override
278    public boolean shouldOverrideUrlLoading(String url) {
279        TraceEvent.begin();
280        boolean result = mWebViewClient.shouldOverrideUrlLoading(mWebView, url);
281        TraceEvent.end();
282        return result;
283    }
284
285    /**
286     * @see AwContentsClient#onUnhandledKeyEvent(android.view.KeyEvent)
287     */
288    @Override
289    public void onUnhandledKeyEvent(KeyEvent event) {
290        TraceEvent.begin();
291        mWebViewClient.onUnhandledKeyEvent(mWebView, event);
292        TraceEvent.end();
293    }
294
295    /**
296     * @see AwContentsClient#onConsoleMessage(android.webkit.ConsoleMessage)
297     */
298    @Override
299    public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
300        TraceEvent.begin();
301        boolean result = mWebChromeClient.onConsoleMessage(consoleMessage);
302        TraceEvent.end();
303        return result;
304    }
305
306    /**
307     * @see AwContentsClient#onFindResultReceived(int,int,boolean)
308     */
309    @Override
310    public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
311            boolean isDoneCounting) {
312        if (mFindListener == null) return;
313        TraceEvent.begin();
314        mFindListener.onFindResultReceived(activeMatchOrdinal, numberOfMatches, isDoneCounting);
315        TraceEvent.end();
316    }
317
318    /**
319     * @See AwContentsClient#onNewPicture(Picture)
320     */
321    @Override
322    public void onNewPicture(Picture picture) {
323        if (mPictureListener == null) return;
324        TraceEvent.begin();
325        mPictureListener.onNewPicture(mWebView, picture);
326        TraceEvent.end();
327    }
328
329    @Override
330    public void onLoadResource(String url) {
331        TraceEvent.begin();
332        mWebViewClient.onLoadResource(mWebView, url);
333        TraceEvent.end();
334    }
335
336    @Override
337    public boolean onCreateWindow(boolean isDialog, boolean isUserGesture) {
338        Message m = mUiThreadHandler.obtainMessage(
339                NEW_WEBVIEW_CREATED, mWebView.new WebViewTransport());
340        TraceEvent.begin();
341        boolean result = mWebChromeClient.onCreateWindow(mWebView, isDialog, isUserGesture, m);
342        TraceEvent.end();
343        return result;
344    }
345
346    /**
347     * @see AwContentsClient#onCloseWindow()
348     */
349    @Override
350    public void onCloseWindow() {
351        TraceEvent.begin();
352        mWebChromeClient.onCloseWindow(mWebView);
353        TraceEvent.end();
354    }
355
356    /**
357     * @see AwContentsClient#onRequestFocus()
358     */
359    @Override
360    public void onRequestFocus() {
361        TraceEvent.begin();
362        mWebChromeClient.onRequestFocus(mWebView);
363        TraceEvent.end();
364    }
365
366    /**
367     * @see AwContentsClient#onReceivedTouchIconUrl(String url, boolean precomposed)
368     */
369    @Override
370    public void onReceivedTouchIconUrl(String url, boolean precomposed) {
371        TraceEvent.begin();
372        mWebChromeClient.onReceivedTouchIconUrl(mWebView, url, precomposed);
373        TraceEvent.end();
374    }
375
376    /**
377     * @see AwContentsClient#onReceivedIcon(Bitmap bitmap)
378     */
379    @Override
380    public void onReceivedIcon(Bitmap bitmap) {
381        TraceEvent.begin();
382        mWebChromeClient.onReceivedIcon(mWebView, bitmap);
383        TraceEvent.end();
384    }
385
386    /**
387     * @see ContentViewClient#onPageStarted(String)
388     */
389    @Override
390    public void onPageStarted(String url) {
391        TraceEvent.begin();
392        mWebViewClient.onPageStarted(mWebView, url, mWebView.getFavicon());
393        TraceEvent.end();
394    }
395
396    /**
397     * @see ContentViewClient#onPageFinished(String)
398     */
399    @Override
400    public void onPageFinished(String url) {
401        TraceEvent.begin();
402        mWebViewClient.onPageFinished(mWebView, url);
403        TraceEvent.end();
404
405        // See b/8208948
406        // This fakes an onNewPicture callback after onPageFinished to allow
407        // CTS tests to run in an un-flaky manner. This is required as the
408        // path for sending Picture updates in Chromium are decoupled from the
409        // page loading callbacks, i.e. the Chrome compositor may draw our
410        // content and send the Picture before onPageStarted or onPageFinished
411        // are invoked. The CTS harness discards any pictures it receives before
412        // onPageStarted is invoked, so in the case we get the Picture before that and
413        // no further updates after onPageStarted, we'll fail the test by timing
414        // out waiting for a Picture.
415        // To ensure backwards compatibility, we need to defer sending Picture updates
416        // until onPageFinished has been invoked. This work is being done
417        // upstream, and we can revert this hack when it lands.
418        if (mPictureListener != null) {
419            new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
420                @Override
421                public void run() {
422                    UnimplementedWebViewApi.invoke();
423                    if (mPictureListener != null) {
424                        mPictureListener.onNewPicture(mWebView, null);
425                    }
426                }
427            }, 100);
428        }
429    }
430
431    /**
432     * @see ContentViewClient#onReceivedError(int,String,String)
433     */
434    @Override
435    public void onReceivedError(int errorCode, String description, String failingUrl) {
436        if (description == null || description.isEmpty()) {
437            // ErrorStrings is @hidden, so we can't do this in AwContents.
438            // Normally the net/ layer will set a valid description, but for synthesized callbacks
439            // (like in the case for intercepted requests) AwContents will pass in null.
440            description = ErrorStrings.getString(errorCode, mWebView.getContext());
441        }
442        TraceEvent.begin();
443        mWebViewClient.onReceivedError(mWebView, errorCode, description, failingUrl);
444        TraceEvent.end();
445    }
446
447    /**
448     * @see ContentViewClient#onReceivedTitle(String)
449     */
450    @Override
451    public void onReceivedTitle(String title) {
452        TraceEvent.begin();
453        mWebChromeClient.onReceivedTitle(mWebView, title);
454        TraceEvent.end();
455    }
456
457
458    /**
459     * @see ContentViewClient#shouldOverrideKeyEvent(KeyEvent)
460     */
461    @Override
462    public boolean shouldOverrideKeyEvent(KeyEvent event) {
463        // TODO(joth): The expression here is a workaround for http://b/7697782 :-
464        // 1. The check for system key should be made in AwContents or ContentViewCore,
465        //    before shouldOverrideKeyEvent() is called at all.
466        // 2. shouldOverrideKeyEvent() should be called in onKeyDown/onKeyUp, not from
467        //    dispatchKeyEvent().
468        if (event.isSystem()) return true;
469        TraceEvent.begin();
470        boolean result = mWebViewClient.shouldOverrideKeyEvent(mWebView, event);
471        TraceEvent.end();
472        return result;
473    }
474
475
476    /**
477     * @see ContentViewClient#onStartContentIntent(Context, String)
478     * Callback when detecting a click on a content link.
479     */
480    // TODO: Delete this method when removed from base class.
481    public void onStartContentIntent(Context context, String contentUrl) {
482        TraceEvent.begin();
483        mWebViewClient.shouldOverrideUrlLoading(mWebView, contentUrl);
484        TraceEvent.end();
485    }
486
487    @Override
488    public void onGeolocationPermissionsShowPrompt(String origin,
489            GeolocationPermissions.Callback callback) {
490        TraceEvent.begin();
491        mWebChromeClient.onGeolocationPermissionsShowPrompt(origin, callback);
492        TraceEvent.end();
493    }
494
495    @Override
496    public void onGeolocationPermissionsHidePrompt() {
497        TraceEvent.begin();
498        mWebChromeClient.onGeolocationPermissionsHidePrompt();
499        TraceEvent.end();
500    }
501
502    private static class JsPromptResultReceiverAdapter implements JsResult.ResultReceiver {
503        private JsPromptResultReceiver mChromePromptResultReceiver;
504        private JsResultReceiver mChromeResultReceiver;
505        // We hold onto the JsPromptResult here, just to avoid the need to downcast
506        // in onJsResultComplete.
507        private final JsPromptResult mPromptResult = new JsPromptResult(this);
508
509        public JsPromptResultReceiverAdapter(JsPromptResultReceiver receiver) {
510            mChromePromptResultReceiver = receiver;
511        }
512
513        public JsPromptResultReceiverAdapter(JsResultReceiver receiver) {
514            mChromeResultReceiver = receiver;
515        }
516
517        public JsPromptResult getPromptResult() {
518            return mPromptResult;
519        }
520
521        @Override
522        public void onJsResultComplete(JsResult result) {
523            if (mChromePromptResultReceiver != null) {
524                if (mPromptResult.getResult()) {
525                    mChromePromptResultReceiver.confirm(mPromptResult.getStringResult());
526                } else {
527                    mChromePromptResultReceiver.cancel();
528                }
529            } else {
530                if (mPromptResult.getResult()) {
531                    mChromeResultReceiver.confirm();
532                } else {
533                    mChromeResultReceiver.cancel();
534                }
535            }
536        }
537    }
538
539    @Override
540    public void handleJsAlert(String url, String message, JsResultReceiver receiver) {
541        TraceEvent.begin();
542        final JsPromptResult res = new JsPromptResultReceiverAdapter(receiver).getPromptResult();
543        if (!mWebChromeClient.onJsAlert(mWebView, url, message, res)) {
544            new JsDialogHelper(res, JsDialogHelper.ALERT, null, message, url)
545                    .showDialog(mWebView.getContext());
546        }
547        TraceEvent.end();
548    }
549
550    @Override
551    public void handleJsBeforeUnload(String url, String message, JsResultReceiver receiver) {
552        TraceEvent.begin();
553        final JsPromptResult res = new JsPromptResultReceiverAdapter(receiver).getPromptResult();
554        if (!mWebChromeClient.onJsBeforeUnload(mWebView, url, message, res)) {
555            new JsDialogHelper(res, JsDialogHelper.UNLOAD, null, message, url)
556                    .showDialog(mWebView.getContext());
557        }
558        TraceEvent.end();
559    }
560
561    @Override
562    public void handleJsConfirm(String url, String message, JsResultReceiver receiver) {
563        TraceEvent.begin();
564        final JsPromptResult res = new JsPromptResultReceiverAdapter(receiver).getPromptResult();
565        if (!mWebChromeClient.onJsConfirm(mWebView, url, message, res)) {
566            new JsDialogHelper(res, JsDialogHelper.CONFIRM, null, message, url)
567                    .showDialog(mWebView.getContext());
568        }
569        TraceEvent.end();
570    }
571
572    @Override
573    public void handleJsPrompt(String url, String message, String defaultValue,
574            JsPromptResultReceiver receiver) {
575        TraceEvent.begin();
576        final JsPromptResult res = new JsPromptResultReceiverAdapter(receiver).getPromptResult();
577        if (!mWebChromeClient.onJsPrompt(mWebView, url, message, defaultValue, res)) {
578            new JsDialogHelper(res, JsDialogHelper.PROMPT, defaultValue, message, url)
579                    .showDialog(mWebView.getContext());
580        }
581        TraceEvent.end();
582    }
583
584    @Override
585    public void onReceivedHttpAuthRequest(AwHttpAuthHandler handler, String host, String realm) {
586        TraceEvent.begin();
587        mWebViewClient.onReceivedHttpAuthRequest(mWebView,
588                new AwHttpAuthHandlerAdapter(handler), host, realm);
589        TraceEvent.end();
590    }
591
592    @Override
593    public void onReceivedSslError(final ValueCallback<Boolean> callback, SslError error) {
594        SslErrorHandler handler = new SslErrorHandler() {
595            @Override
596            public void proceed() {
597                postProceed(true);
598            }
599            @Override
600            public void cancel() {
601                postProceed(false);
602            }
603            private void postProceed(final boolean proceed) {
604                post(new Runnable() {
605                        @Override
606                        public void run() {
607                            callback.onReceiveValue(proceed);
608                        }
609                    });
610            }
611        };
612        TraceEvent.begin();
613        mWebViewClient.onReceivedSslError(mWebView, handler, error);
614        TraceEvent.end();
615    }
616
617    @Override
618    public void onReceivedLoginRequest(String realm, String account, String args) {
619        TraceEvent.begin();
620        mWebViewClient.onReceivedLoginRequest(mWebView, realm, account, args);
621        TraceEvent.end();
622    }
623
624    @Override
625    public void onFormResubmission(Message dontResend, Message resend) {
626        TraceEvent.begin();
627        mWebViewClient.onFormResubmission(mWebView, dontResend, resend);
628        TraceEvent.end();
629    }
630
631    @Override
632    public void onDownloadStart(String url,
633                                String userAgent,
634                                String contentDisposition,
635                                String mimeType,
636                                long contentLength) {
637        if (mDownloadListener != null) {
638            TraceEvent.begin();
639            mDownloadListener.onDownloadStart(url,
640                                              userAgent,
641                                              contentDisposition,
642                                              mimeType,
643                                              contentLength);
644            TraceEvent.end();
645        }
646    }
647
648    @Override
649    public void onScaleChangedScaled(float oldScale, float newScale) {
650        TraceEvent.begin();
651        mWebViewClient.onScaleChanged(mWebView, oldScale, newScale);
652        TraceEvent.end();
653    }
654
655    @Override
656    public void onShowCustomView(View view, CustomViewCallback cb) {
657        TraceEvent.begin();
658        mWebChromeClient.onShowCustomView(view, cb);
659        TraceEvent.end();
660    }
661
662    @Override
663    public void onHideCustomView() {
664        TraceEvent.begin();
665        mWebChromeClient.onHideCustomView();
666        TraceEvent.end();
667    }
668
669    @Override
670    protected View getVideoLoadingProgressView() {
671        TraceEvent.begin();
672        View result = mWebChromeClient.getVideoLoadingProgressView();
673        TraceEvent.end();
674        return result;
675    }
676
677    @Override
678    public Bitmap getDefaultVideoPoster() {
679        TraceEvent.begin();
680        Bitmap result = mWebChromeClient.getDefaultVideoPoster();
681        TraceEvent.end();
682        return result;
683    }
684
685    private static class AwHttpAuthHandlerAdapter extends android.webkit.HttpAuthHandler {
686        private AwHttpAuthHandler mAwHandler;
687
688        public AwHttpAuthHandlerAdapter(AwHttpAuthHandler awHandler) {
689            mAwHandler = awHandler;
690        }
691
692        @Override
693        public void proceed(String username, String password) {
694            if (username == null) {
695                username = "";
696            }
697
698            if (password == null) {
699                password = "";
700            }
701            mAwHandler.proceed(username, password);
702        }
703
704        @Override
705        public void cancel() {
706            mAwHandler.cancel();
707        }
708
709        @Override
710        public boolean useHttpAuthUsernamePassword() {
711            return mAwHandler.isFirstAttempt();
712        }
713    }
714}
715