CallbackProxy.java revision 6262ae5c9df44c0673cebaeaf7f655094f5b5485
1/*
2 * Copyright (C) 2007 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 android.webkit;
18
19import android.app.AlertDialog;
20import android.content.ActivityNotFoundException;
21import android.content.Context;
22import android.content.DialogInterface;
23import android.content.Intent;
24import android.graphics.Bitmap;
25import android.net.Uri;
26import android.net.http.SslCertificate;
27import android.net.http.SslError;
28import android.os.Bundle;
29import android.os.Handler;
30import android.os.Message;
31import android.os.SystemClock;
32import android.provider.Browser;
33import android.util.Log;
34import android.view.KeyEvent;
35import android.view.LayoutInflater;
36import android.view.View;
37import android.widget.EditText;
38import android.widget.TextView;
39import com.android.internal.R;
40
41import java.net.MalformedURLException;
42import java.net.URL;
43import java.util.HashMap;
44
45/**
46 * This class is a proxy class for handling WebCore -> UI thread messaging. All
47 * the callback functions are called from the WebCore thread and messages are
48 * posted to the UI thread for the actual client callback.
49 */
50/*
51 * This class is created in the UI thread so its handler and any private classes
52 * that extend Handler will operate in the UI thread.
53 */
54class CallbackProxy extends Handler {
55    // Logging tag
56    private static final String LOGTAG = "CallbackProxy";
57    // Instance of WebViewClient that is the client callback.
58    private volatile WebViewClient mWebViewClient;
59    // Instance of WebChromeClient for handling all chrome functions.
60    private volatile WebChromeClient mWebChromeClient;
61    // Instance of WebView for handling UI requests.
62    private final WebView mWebView;
63    // Client registered callback listener for download events
64    private volatile DownloadListener mDownloadListener;
65    // Keep track of multiple progress updates.
66    private boolean mProgressUpdatePending;
67    // Keep track of the last progress amount.
68    private volatile int mLatestProgress;
69    // Back/Forward list
70    private final WebBackForwardList mBackForwardList;
71    // Used to call startActivity during url override.
72    private final Context mContext;
73
74    // Message Ids
75    private static final int PAGE_STARTED              = 100;
76    private static final int RECEIVED_ICON             = 101;
77    private static final int RECEIVED_TITLE            = 102;
78    private static final int OVERRIDE_URL              = 103;
79    private static final int AUTH_REQUEST              = 104;
80    private static final int SSL_ERROR                 = 105;
81    private static final int PROGRESS                  = 106;
82    private static final int UPDATE_VISITED            = 107;
83    private static final int LOAD_RESOURCE             = 108;
84    private static final int CREATE_WINDOW             = 109;
85    private static final int CLOSE_WINDOW              = 110;
86    private static final int SAVE_PASSWORD             = 111;
87    private static final int JS_ALERT                  = 112;
88    private static final int JS_CONFIRM                = 113;
89    private static final int JS_PROMPT                 = 114;
90    private static final int JS_UNLOAD                 = 115;
91    private static final int ASYNC_KEYEVENTS           = 116;
92    private static final int TOO_MANY_REDIRECTS        = 117;
93    private static final int DOWNLOAD_FILE             = 118;
94    private static final int REPORT_ERROR              = 119;
95    private static final int RESEND_POST_DATA          = 120;
96    private static final int PAGE_FINISHED             = 121;
97    private static final int REQUEST_FOCUS             = 122;
98    private static final int SCALE_CHANGED             = 123;
99    private static final int RECEIVED_CERTIFICATE      = 124;
100    private static final int SWITCH_OUT_HISTORY        = 125;
101    private static final int EXCEEDED_DATABASE_QUOTA   = 126;
102    private static final int JS_TIMEOUT                = 127;
103    private static final int ADD_MESSAGE_TO_CONSOLE    = 128;
104
105    // Message triggered by the client to resume execution
106    private static final int NOTIFY                    = 200;
107
108    // Result transportation object for returning results across thread
109    // boundaries.
110    private class ResultTransport<E> {
111        // Private result object
112        private E mResult;
113
114        public synchronized void setResult(E result) {
115            mResult = result;
116        }
117
118        public synchronized E getResult() {
119            return mResult;
120        }
121    }
122
123    /**
124     * Construct a new CallbackProxy.
125     */
126    public CallbackProxy(Context context, WebView w) {
127        // Used to start a default activity.
128        mContext = context;
129        mWebView = w;
130        mBackForwardList = new WebBackForwardList();
131    }
132
133    /**
134     * Set the WebViewClient.
135     * @param client An implementation of WebViewClient.
136     */
137    public void setWebViewClient(WebViewClient client) {
138        mWebViewClient = client;
139    }
140
141    /**
142     * Set the WebChromeClient.
143     * @param client An implementation of WebChromeClient.
144     */
145    public void setWebChromeClient(WebChromeClient client) {
146        mWebChromeClient = client;
147    }
148
149    /**
150     * Set the client DownloadListener.
151     * @param client An implementation of DownloadListener.
152     */
153    public void setDownloadListener(DownloadListener client) {
154        mDownloadListener = client;
155    }
156
157    /**
158     * Get the Back/Forward list to return to the user or to update the cached
159     * history list.
160     */
161    public WebBackForwardList getBackForwardList() {
162        return mBackForwardList;
163    }
164
165    /**
166     * Called by the UI side.  Calling overrideUrlLoading from the WebCore
167     * side will post a message to call this method.
168     */
169    public boolean uiOverrideUrlLoading(String overrideUrl) {
170        if (overrideUrl == null || overrideUrl.length() == 0) {
171            return false;
172        }
173        boolean override = false;
174        if (mWebViewClient != null) {
175            override = mWebViewClient.shouldOverrideUrlLoading(mWebView,
176                    overrideUrl);
177        } else {
178            Intent intent = new Intent(Intent.ACTION_VIEW,
179                    Uri.parse(overrideUrl));
180            intent.addCategory(Intent.CATEGORY_BROWSABLE);
181            // If another application is running a WebView and launches the
182            // Browser through this Intent, we want to reuse the same window if
183            // possible.
184            intent.putExtra(Browser.EXTRA_APPLICATION_ID,
185                    mContext.getPackageName());
186            try {
187                mContext.startActivity(intent);
188                override = true;
189            } catch (ActivityNotFoundException ex) {
190                // If no application can handle the URL, assume that the
191                // browser can handle it.
192            }
193        }
194        return override;
195    }
196
197    /**
198     * Called by UI side.
199     */
200    public boolean uiOverrideKeyEvent(KeyEvent event) {
201        if (mWebViewClient != null) {
202            return mWebViewClient.shouldOverrideKeyEvent(mWebView, event);
203        }
204        return false;
205    }
206
207    @Override
208    public void handleMessage(Message msg) {
209        // We don't have to do synchronization because this function operates
210        // in the UI thread. The WebViewClient and WebChromeClient functions
211        // that check for a non-null callback are ok because java ensures atomic
212        // 32-bit reads and writes.
213        switch (msg.what) {
214            case PAGE_STARTED:
215                if (mWebViewClient != null) {
216                    mWebViewClient.onPageStarted(mWebView,
217                            msg.getData().getString("url"),
218                            (Bitmap) msg.obj);
219                }
220                break;
221
222            case PAGE_FINISHED:
223                if (mWebViewClient != null) {
224                    mWebViewClient.onPageFinished(mWebView, (String) msg.obj);
225                }
226                break;
227
228            case RECEIVED_ICON:
229                if (mWebChromeClient != null) {
230                    mWebChromeClient.onReceivedIcon(mWebView, (Bitmap) msg.obj);
231                }
232                break;
233
234            case RECEIVED_TITLE:
235                if (mWebChromeClient != null) {
236                    mWebChromeClient.onReceivedTitle(mWebView,
237                            (String) msg.obj);
238                }
239                break;
240
241            case TOO_MANY_REDIRECTS:
242                Message cancelMsg =
243                        (Message) msg.getData().getParcelable("cancelMsg");
244                Message continueMsg =
245                        (Message) msg.getData().getParcelable("continueMsg");
246                if (mWebViewClient != null) {
247                    mWebViewClient.onTooManyRedirects(mWebView, cancelMsg,
248                            continueMsg);
249                } else {
250                    cancelMsg.sendToTarget();
251                }
252                break;
253
254            case REPORT_ERROR:
255                if (mWebViewClient != null) {
256                    int reasonCode = msg.arg1;
257                    final String description  = msg.getData().getString("description");
258                    final String failUrl  = msg.getData().getString("failingUrl");
259                    mWebViewClient.onReceivedError(mWebView, reasonCode,
260                            description, failUrl);
261                }
262                break;
263
264            case RESEND_POST_DATA:
265                Message resend =
266                        (Message) msg.getData().getParcelable("resend");
267                Message dontResend =
268                        (Message) msg.getData().getParcelable("dontResend");
269                if (mWebViewClient != null) {
270                    mWebViewClient.onFormResubmission(mWebView, dontResend,
271                            resend);
272                } else {
273                    dontResend.sendToTarget();
274                }
275                break;
276
277            case OVERRIDE_URL:
278                String overrideUrl = msg.getData().getString("url");
279                boolean override = uiOverrideUrlLoading(overrideUrl);
280                ResultTransport<Boolean> result =
281                        (ResultTransport<Boolean>) msg.obj;
282                synchronized (this) {
283                    result.setResult(override);
284                    notify();
285                }
286                break;
287
288            case AUTH_REQUEST:
289                if (mWebViewClient != null) {
290                    HttpAuthHandler handler = (HttpAuthHandler) msg.obj;
291                    String host = msg.getData().getString("host");
292                    String realm = msg.getData().getString("realm");
293                    mWebViewClient.onReceivedHttpAuthRequest(mWebView, handler,
294                            host, realm);
295                }
296                break;
297
298            case SSL_ERROR:
299                if (mWebViewClient != null) {
300                    HashMap<String, Object> map =
301                        (HashMap<String, Object>) msg.obj;
302                    mWebViewClient.onReceivedSslError(mWebView,
303                            (SslErrorHandler) map.get("handler"),
304                            (SslError) map.get("error"));
305                }
306                break;
307
308            case PROGRESS:
309                // Synchronize to ensure mLatestProgress is not modified after
310                // setProgress is called and before mProgressUpdatePending is
311                // changed.
312                synchronized (this) {
313                    if (mWebChromeClient != null) {
314                        mWebChromeClient.onProgressChanged(mWebView,
315                                mLatestProgress);
316                    }
317                    mProgressUpdatePending = false;
318                }
319                break;
320
321            case UPDATE_VISITED:
322                if (mWebViewClient != null) {
323                    mWebViewClient.doUpdateVisitedHistory(mWebView,
324                            (String) msg.obj, msg.arg1 != 0);
325                }
326                break;
327
328            case LOAD_RESOURCE:
329                if (mWebViewClient != null) {
330                    mWebViewClient.onLoadResource(mWebView, (String) msg.obj);
331                }
332                break;
333
334            case DOWNLOAD_FILE:
335                if (mDownloadListener != null) {
336                    String url = msg.getData().getString("url");
337                    String userAgent = msg.getData().getString("userAgent");
338                    String contentDisposition =
339                        msg.getData().getString("contentDisposition");
340                    String mimetype = msg.getData().getString("mimetype");
341                    Long contentLength = msg.getData().getLong("contentLength");
342
343                    mDownloadListener.onDownloadStart(url, userAgent,
344                            contentDisposition, mimetype, contentLength);
345                }
346                break;
347
348            case CREATE_WINDOW:
349                if (mWebChromeClient != null) {
350                    if (!mWebChromeClient.onCreateWindow(mWebView,
351                                msg.arg1 == 1, msg.arg2 == 1,
352                                (Message) msg.obj)) {
353                        synchronized (this) {
354                            notify();
355                        }
356                    }
357                }
358                break;
359
360            case REQUEST_FOCUS:
361                if (mWebChromeClient != null) {
362                    mWebChromeClient.onRequestFocus(mWebView);
363                }
364                break;
365
366            case CLOSE_WINDOW:
367                if (mWebChromeClient != null) {
368                    mWebChromeClient.onCloseWindow((WebView) msg.obj);
369                }
370                break;
371
372            case SAVE_PASSWORD:
373                Bundle bundle = msg.getData();
374                String schemePlusHost = bundle.getString("host");
375                String username = bundle.getString("username");
376                String password = bundle.getString("password");
377                // If the client returned false it means that the notify message
378                // will not be sent and we should notify WebCore ourselves.
379                if (!mWebView.onSavePassword(schemePlusHost, username, password,
380                            (Message) msg.obj)) {
381                    synchronized (this) {
382                        notify();
383                    }
384                }
385                break;
386
387            case ASYNC_KEYEVENTS:
388                if (mWebViewClient != null) {
389                    mWebViewClient.onUnhandledKeyEvent(mWebView,
390                            (KeyEvent) msg.obj);
391                }
392                break;
393
394            case EXCEEDED_DATABASE_QUOTA:
395                if (mWebChromeClient != null) {
396                    HashMap<String, Object> map =
397                            (HashMap<String, Object>) msg.obj;
398                    String databaseIdentifier =
399                            (String) map.get("databaseIdentifier");
400                    String url = (String) map.get("url");
401                    long currentQuota =
402                            ((Long) map.get("currentQuota")).longValue();
403                    WebStorage.QuotaUpdater quotaUpdater =
404                        (WebStorage.QuotaUpdater) map.get("quotaUpdater");
405
406                    mWebChromeClient.onExceededDatabaseQuota(url,
407                            databaseIdentifier, currentQuota, quotaUpdater);
408                }
409                break;
410
411            case JS_ALERT:
412                if (mWebChromeClient != null) {
413                    final JsResult res = (JsResult) msg.obj;
414                    String message = msg.getData().getString("message");
415                    String url = msg.getData().getString("url");
416                    if (!mWebChromeClient.onJsAlert(mWebView, url, message,
417                            res)) {
418                        new AlertDialog.Builder(mContext)
419                                .setTitle(getJsDialogTitle(url))
420                                .setMessage(message)
421                                .setPositiveButton(R.string.ok,
422                                        new AlertDialog.OnClickListener() {
423                                            public void onClick(
424                                                    DialogInterface dialog,
425                                                    int which) {
426                                                res.confirm();
427                                            }
428                                        })
429                                .setCancelable(false)
430                                .show();
431                    }
432                    res.setReady();
433                }
434                break;
435
436            case JS_CONFIRM:
437                if (mWebChromeClient != null) {
438                    final JsResult res = (JsResult) msg.obj;
439                    String message = msg.getData().getString("message");
440                    String url = msg.getData().getString("url");
441                    if (!mWebChromeClient.onJsConfirm(mWebView, url, message,
442                            res)) {
443                        new AlertDialog.Builder(mContext)
444                                .setTitle(getJsDialogTitle(url))
445                                .setMessage(message)
446                                .setPositiveButton(R.string.ok,
447                                        new DialogInterface.OnClickListener() {
448                                            public void onClick(
449                                                    DialogInterface dialog,
450                                                    int which) {
451                                                res.confirm();
452                                            }})
453                                .setNegativeButton(R.string.cancel,
454                                        new DialogInterface.OnClickListener() {
455                                            public void onClick(
456                                                    DialogInterface dialog,
457                                                    int which) {
458                                                res.cancel();
459                                            }})
460                                .show();
461                    }
462                    // Tell the JsResult that it is ready for client
463                    // interaction.
464                    res.setReady();
465                }
466                break;
467
468            case JS_PROMPT:
469                if (mWebChromeClient != null) {
470                    final JsPromptResult res = (JsPromptResult) msg.obj;
471                    String message = msg.getData().getString("message");
472                    String defaultVal = msg.getData().getString("default");
473                    String url = msg.getData().getString("url");
474                    if (!mWebChromeClient.onJsPrompt(mWebView, url, message,
475                                defaultVal, res)) {
476                        final LayoutInflater factory = LayoutInflater
477                                .from(mContext);
478                        final View view = factory.inflate(R.layout.js_prompt,
479                                null);
480                        final EditText v = (EditText) view
481                                .findViewById(R.id.value);
482                        v.setText(defaultVal);
483                        ((TextView) view.findViewById(R.id.message))
484                                .setText(message);
485                        new AlertDialog.Builder(mContext)
486                                .setTitle(getJsDialogTitle(url))
487                                .setView(view)
488                                .setPositiveButton(R.string.ok,
489                                        new DialogInterface.OnClickListener() {
490                                            public void onClick(
491                                                    DialogInterface dialog,
492                                                    int whichButton) {
493                                                res.confirm(v.getText()
494                                                        .toString());
495                                            }
496                                        })
497                                .setNegativeButton(R.string.cancel,
498                                        new DialogInterface.OnClickListener() {
499                                            public void onClick(
500                                                    DialogInterface dialog,
501                                                    int whichButton) {
502                                                res.cancel();
503                                            }
504                                        })
505                                .setOnCancelListener(
506                                        new DialogInterface.OnCancelListener() {
507                                            public void onCancel(
508                                                    DialogInterface dialog) {
509                                                res.cancel();
510                                            }
511                                        })
512                                .show();
513                    }
514                    // Tell the JsResult that it is ready for client
515                    // interaction.
516                    res.setReady();
517                }
518                break;
519
520            case JS_UNLOAD:
521                if (mWebChromeClient != null) {
522                    final JsResult res = (JsResult) msg.obj;
523                    String message = msg.getData().getString("message");
524                    String url = msg.getData().getString("url");
525                    if (!mWebChromeClient.onJsBeforeUnload(mWebView, url,
526                            message, res)) {
527                        final String m = mContext.getString(
528                                R.string.js_dialog_before_unload, message);
529                        new AlertDialog.Builder(mContext)
530                                .setMessage(m)
531                                .setPositiveButton(R.string.ok,
532                                        new DialogInterface.OnClickListener() {
533                                            public void onClick(
534                                                    DialogInterface dialog,
535                                                    int which) {
536                                                res.confirm();
537                                            }
538                                        })
539                                .setNegativeButton(R.string.cancel,
540                                        new DialogInterface.OnClickListener() {
541                                            public void onClick(
542                                                    DialogInterface dialog,
543                                                    int which) {
544                                                res.cancel();
545                                            }
546                                        })
547                                .show();
548                    }
549                    res.setReady();
550                }
551                break;
552
553            case JS_TIMEOUT:
554                if(mWebChromeClient != null) {
555                    final JsResult res = (JsResult) msg.obj;
556                    if(mWebChromeClient.onJsTimeout()) {
557                        res.confirm();
558                    } else {
559                        res.cancel();
560                    }
561                    res.setReady();
562                }
563                break;
564
565            case RECEIVED_CERTIFICATE:
566                mWebView.setCertificate((SslCertificate) msg.obj);
567                break;
568
569            case NOTIFY:
570                synchronized (this) {
571                    notify();
572                }
573                break;
574
575            case SCALE_CHANGED:
576                if (mWebViewClient != null) {
577                    mWebViewClient.onScaleChanged(mWebView, msg.getData()
578                            .getFloat("old"), msg.getData().getFloat("new"));
579                }
580                break;
581
582            case SWITCH_OUT_HISTORY:
583                mWebView.switchOutDrawHistory();
584                break;
585
586            case ADD_MESSAGE_TO_CONSOLE:
587                String message = msg.getData().getString("message");
588                String sourceID = msg.getData().getString("sourceID");
589                int lineNumber = msg.getData().getInt("lineNumber");
590                mWebChromeClient.addMessageToConsole(message, lineNumber, sourceID);
591                break;
592        }
593    }
594
595    /**
596     * Return the latest progress.
597     */
598    public int getProgress() {
599        return mLatestProgress;
600    }
601
602    /**
603     * Called by WebCore side to switch out of history Picture drawing mode
604     */
605    void switchOutDrawHistory() {
606        sendMessage(obtainMessage(SWITCH_OUT_HISTORY));
607    }
608
609    private String getJsDialogTitle(String url) {
610        String title = url;
611        if (URLUtil.isDataUrl(url)) {
612            // For data: urls, we just display 'JavaScript' similar to Safari.
613            title = mContext.getString(R.string.js_dialog_title_default);
614        } else {
615            try {
616                URL aUrl = new URL(url);
617                // For example: "The page at 'http://www.mit.edu' says:"
618                title = mContext.getString(R.string.js_dialog_title,
619                        aUrl.getProtocol() + "://" + aUrl.getHost());
620            } catch (MalformedURLException ex) {
621                // do nothing. just use the url as the title
622            }
623        }
624        return title;
625    }
626
627    //--------------------------------------------------------------------------
628    // WebViewClient functions.
629    // NOTE: shouldOverrideKeyEvent is never called from the WebCore thread so
630    // it is not necessary to include it here.
631    //--------------------------------------------------------------------------
632
633    // Performance probe
634    private long mWebCoreThreadTime;
635
636    public void onPageStarted(String url, Bitmap favicon) {
637        // Do an unsynchronized quick check to avoid posting if no callback has
638        // been set.
639        if (mWebViewClient == null) {
640            return;
641        }
642        // Performance probe
643        if (false) {
644            mWebCoreThreadTime = SystemClock.currentThreadTimeMillis();
645            Network.getInstance(mContext).startTiming();
646        }
647        Message msg = obtainMessage(PAGE_STARTED);
648        msg.obj = favicon;
649        msg.getData().putString("url", url);
650        sendMessage(msg);
651    }
652
653    public void onPageFinished(String url) {
654        // Do an unsynchronized quick check to avoid posting if no callback has
655        // been set.
656        if (mWebViewClient == null) {
657            return;
658        }
659        // Performance probe
660        if (false) {
661            Log.d("WebCore", "WebCore thread used " +
662                    (SystemClock.currentThreadTimeMillis() - mWebCoreThreadTime)
663                    + " ms");
664            Network.getInstance(mContext).stopTiming();
665        }
666        Message msg = obtainMessage(PAGE_FINISHED, url);
667        sendMessage(msg);
668    }
669
670    public void onTooManyRedirects(Message cancelMsg, Message continueMsg) {
671        // Do an unsynchronized quick check to avoid posting if no callback has
672        // been set.
673        if (mWebViewClient == null) {
674            cancelMsg.sendToTarget();
675            return;
676        }
677
678        Message msg = obtainMessage(TOO_MANY_REDIRECTS);
679        Bundle bundle = msg.getData();
680        bundle.putParcelable("cancelMsg", cancelMsg);
681        bundle.putParcelable("continueMsg", continueMsg);
682        sendMessage(msg);
683    }
684
685    public void onReceivedError(int errorCode, String description,
686            String failingUrl) {
687        // Do an unsynchronized quick check to avoid posting if no callback has
688        // been set.
689        if (mWebViewClient == null) {
690            return;
691        }
692
693        Message msg = obtainMessage(REPORT_ERROR);
694        msg.arg1 = errorCode;
695        msg.getData().putString("description", description);
696        msg.getData().putString("failingUrl", failingUrl);
697        sendMessage(msg);
698    }
699
700    public void onFormResubmission(Message dontResend,
701            Message resend) {
702        // Do an unsynchronized quick check to avoid posting if no callback has
703        // been set.
704        if (mWebViewClient == null) {
705            dontResend.sendToTarget();
706            return;
707        }
708
709        Message msg = obtainMessage(RESEND_POST_DATA);
710        Bundle bundle = msg.getData();
711        bundle.putParcelable("resend", resend);
712        bundle.putParcelable("dontResend", dontResend);
713        sendMessage(msg);
714    }
715
716    /**
717     * Called by the WebCore side
718     */
719    public boolean shouldOverrideUrlLoading(String url) {
720        // We have a default behavior if no client exists so always send the
721        // message.
722        ResultTransport<Boolean> res = new ResultTransport<Boolean>();
723        Message msg = obtainMessage(OVERRIDE_URL);
724        msg.getData().putString("url", url);
725        msg.obj = res;
726        synchronized (this) {
727            sendMessage(msg);
728            try {
729                wait();
730            } catch (InterruptedException e) {
731                Log.e(LOGTAG, "Caught exception while waiting for overrideUrl");
732                Log.e(LOGTAG, Log.getStackTraceString(e));
733            }
734        }
735        return res.getResult().booleanValue();
736    }
737
738    public void onReceivedHttpAuthRequest(HttpAuthHandler handler,
739            String hostName, String realmName) {
740        // Do an unsynchronized quick check to avoid posting if no callback has
741        // been set.
742        if (mWebViewClient == null) {
743            handler.cancel();
744            return;
745        }
746        Message msg = obtainMessage(AUTH_REQUEST, handler);
747        msg.getData().putString("host", hostName);
748        msg.getData().putString("realm", realmName);
749        sendMessage(msg);
750    }
751    /**
752     * @hide - hide this because it contains a parameter of type SslError.
753     * SslError is located in a hidden package.
754     */
755    public void onReceivedSslError(SslErrorHandler handler, SslError error) {
756        // Do an unsynchronized quick check to avoid posting if no callback has
757        // been set.
758        if (mWebViewClient == null) {
759            handler.cancel();
760            return;
761        }
762        Message msg = obtainMessage(SSL_ERROR);
763        //, handler);
764        HashMap<String, Object> map = new HashMap();
765        map.put("handler", handler);
766        map.put("error", error);
767        msg.obj = map;
768        sendMessage(msg);
769    }
770    /**
771     * @hide - hide this because it contains a parameter of type SslCertificate,
772     * which is located in a hidden package.
773     */
774
775    public void onReceivedCertificate(SslCertificate certificate) {
776        // Do an unsynchronized quick check to avoid posting if no callback has
777        // been set.
778        if (mWebViewClient == null) {
779            return;
780        }
781        // here, certificate can be null (if the site is not secure)
782        sendMessage(obtainMessage(RECEIVED_CERTIFICATE, certificate));
783    }
784
785    public void doUpdateVisitedHistory(String url, boolean isReload) {
786        // Do an unsynchronized quick check to avoid posting if no callback has
787        // been set.
788        if (mWebViewClient == null) {
789            return;
790        }
791        sendMessage(obtainMessage(UPDATE_VISITED, isReload ? 1 : 0, 0, url));
792    }
793
794    public void onLoadResource(String url) {
795        // Do an unsynchronized quick check to avoid posting if no callback has
796        // been set.
797        if (mWebViewClient == null) {
798            return;
799        }
800        sendMessage(obtainMessage(LOAD_RESOURCE, url));
801    }
802
803    public void onUnhandledKeyEvent(KeyEvent event) {
804        // Do an unsynchronized quick check to avoid posting if no callback has
805        // been set.
806        if (mWebViewClient == null) {
807            return;
808        }
809        sendMessage(obtainMessage(ASYNC_KEYEVENTS, event));
810    }
811
812    public void onScaleChanged(float oldScale, float newScale) {
813        // Do an unsynchronized quick check to avoid posting if no callback has
814        // been set.
815        if (mWebViewClient == null) {
816            return;
817        }
818        Message msg = obtainMessage(SCALE_CHANGED);
819        Bundle bundle = msg.getData();
820        bundle.putFloat("old", oldScale);
821        bundle.putFloat("new", newScale);
822        sendMessage(msg);
823    }
824
825    //--------------------------------------------------------------------------
826    // DownloadListener functions.
827    //--------------------------------------------------------------------------
828
829    /**
830     * Starts a download if a download listener has been registered, otherwise
831     * return false.
832     */
833    public boolean onDownloadStart(String url, String userAgent,
834            String contentDisposition, String mimetype, long contentLength) {
835        // Do an unsynchronized quick check to avoid posting if no callback has
836        // been set.
837        if (mDownloadListener == null) {
838            // Cancel the download if there is no browser client.
839            return false;
840        }
841
842        Message msg = obtainMessage(DOWNLOAD_FILE);
843        Bundle bundle = msg.getData();
844        bundle.putString("url", url);
845        bundle.putString("userAgent", userAgent);
846        bundle.putString("mimetype", mimetype);
847        bundle.putLong("contentLength", contentLength);
848        bundle.putString("contentDisposition", contentDisposition);
849        sendMessage(msg);
850        return true;
851    }
852
853
854    //--------------------------------------------------------------------------
855    // WebView specific functions that do not interact with a client. These
856    // functions just need to operate within the UI thread.
857    //--------------------------------------------------------------------------
858
859    public boolean onSavePassword(String schemePlusHost, String username,
860            String password, Message resumeMsg) {
861        // resumeMsg should be null at this point because we want to create it
862        // within the CallbackProxy.
863        if (DebugFlags.CALLBACK_PROXY) {
864            junit.framework.Assert.assertNull(resumeMsg);
865        }
866        resumeMsg = obtainMessage(NOTIFY);
867
868        Message msg = obtainMessage(SAVE_PASSWORD, resumeMsg);
869        Bundle bundle = msg.getData();
870        bundle.putString("host", schemePlusHost);
871        bundle.putString("username", username);
872        bundle.putString("password", password);
873        synchronized (this) {
874            sendMessage(msg);
875            try {
876                wait();
877            } catch (InterruptedException e) {
878                Log.e(LOGTAG,
879                        "Caught exception while waiting for onSavePassword");
880                Log.e(LOGTAG, Log.getStackTraceString(e));
881            }
882        }
883        // Doesn't matter here
884        return false;
885    }
886
887    //--------------------------------------------------------------------------
888    // WebChromeClient methods
889    //--------------------------------------------------------------------------
890
891    public void onProgressChanged(int newProgress) {
892        // Synchronize so that mLatestProgress is up-to-date.
893        synchronized (this) {
894            mLatestProgress = newProgress;
895            if (mWebChromeClient == null) {
896                return;
897            }
898            if (!mProgressUpdatePending) {
899                sendEmptyMessage(PROGRESS);
900                mProgressUpdatePending = true;
901            }
902        }
903    }
904
905    public WebView createWindow(boolean dialog, boolean userGesture) {
906        // Do an unsynchronized quick check to avoid posting if no callback has
907        // been set.
908        if (mWebChromeClient == null) {
909            return null;
910        }
911
912        WebView.WebViewTransport transport = mWebView.new WebViewTransport();
913        final Message msg = obtainMessage(NOTIFY);
914        msg.obj = transport;
915        synchronized (this) {
916            sendMessage(obtainMessage(CREATE_WINDOW, dialog ? 1 : 0,
917                    userGesture ? 1 : 0, msg));
918            try {
919                wait();
920            } catch (InterruptedException e) {
921                Log.e(LOGTAG,
922                        "Caught exception while waiting for createWindow");
923                Log.e(LOGTAG, Log.getStackTraceString(e));
924            }
925        }
926
927        WebView w = transport.getWebView();
928        if (w != null) {
929            w.getWebViewCore().initializeSubwindow();
930        }
931        return w;
932    }
933
934    public void onRequestFocus() {
935        // Do an unsynchronized quick check to avoid posting if no callback has
936        // been set.
937        if (mWebChromeClient == null) {
938            return;
939        }
940
941        sendEmptyMessage(REQUEST_FOCUS);
942    }
943
944    public void onCloseWindow(WebView window) {
945        // Do an unsynchronized quick check to avoid posting if no callback has
946        // been set.
947        if (mWebChromeClient == null) {
948            return;
949        }
950        sendMessage(obtainMessage(CLOSE_WINDOW, window));
951    }
952
953    public void onReceivedIcon(Bitmap icon) {
954        // The current item might be null if the icon was already stored in the
955        // database and this is a new WebView.
956        WebHistoryItem i = mBackForwardList.getCurrentItem();
957        if (i != null) {
958            i.setFavicon(icon);
959        }
960        // Do an unsynchronized quick check to avoid posting if no callback has
961        // been set.
962        if (mWebChromeClient == null) {
963            return;
964        }
965        sendMessage(obtainMessage(RECEIVED_ICON, icon));
966    }
967
968    public void onReceivedTitle(String title) {
969        // Do an unsynchronized quick check to avoid posting if no callback has
970        // been set.
971        if (mWebChromeClient == null) {
972            return;
973        }
974        sendMessage(obtainMessage(RECEIVED_TITLE, title));
975    }
976
977    public void onJsAlert(String url, String message) {
978        // Do an unsynchronized quick check to avoid posting if no callback has
979        // been set.
980        if (mWebChromeClient == null) {
981            return;
982        }
983        JsResult result = new JsResult(this, false);
984        Message alert = obtainMessage(JS_ALERT, result);
985        alert.getData().putString("message", message);
986        alert.getData().putString("url", url);
987        synchronized (this) {
988            sendMessage(alert);
989            try {
990                wait();
991            } catch (InterruptedException e) {
992                Log.e(LOGTAG, "Caught exception while waiting for jsAlert");
993                Log.e(LOGTAG, Log.getStackTraceString(e));
994            }
995        }
996    }
997
998    public boolean onJsConfirm(String url, String message) {
999        // Do an unsynchronized quick check to avoid posting if no callback has
1000        // been set.
1001        if (mWebChromeClient == null) {
1002            return false;
1003        }
1004        JsResult result = new JsResult(this, false);
1005        Message confirm = obtainMessage(JS_CONFIRM, result);
1006        confirm.getData().putString("message", message);
1007        confirm.getData().putString("url", url);
1008        synchronized (this) {
1009            sendMessage(confirm);
1010            try {
1011                wait();
1012            } catch (InterruptedException e) {
1013                Log.e(LOGTAG, "Caught exception while waiting for jsConfirm");
1014                Log.e(LOGTAG, Log.getStackTraceString(e));
1015            }
1016        }
1017        return result.getResult();
1018    }
1019
1020    public String onJsPrompt(String url, String message, String defaultValue) {
1021        // Do an unsynchronized quick check to avoid posting if no callback has
1022        // been set.
1023        if (mWebChromeClient == null) {
1024            return null;
1025        }
1026        JsPromptResult result = new JsPromptResult(this);
1027        Message prompt = obtainMessage(JS_PROMPT, result);
1028        prompt.getData().putString("message", message);
1029        prompt.getData().putString("default", defaultValue);
1030        prompt.getData().putString("url", url);
1031        synchronized (this) {
1032            sendMessage(prompt);
1033            try {
1034                wait();
1035            } catch (InterruptedException e) {
1036                Log.e(LOGTAG, "Caught exception while waiting for jsPrompt");
1037                Log.e(LOGTAG, Log.getStackTraceString(e));
1038            }
1039        }
1040        return result.getStringResult();
1041    }
1042
1043    public boolean onJsBeforeUnload(String url, String message) {
1044        // Do an unsynchronized quick check to avoid posting if no callback has
1045        // been set.
1046        if (mWebChromeClient == null) {
1047            return true;
1048        }
1049        JsResult result = new JsResult(this, true);
1050        Message confirm = obtainMessage(JS_UNLOAD, result);
1051        confirm.getData().putString("message", message);
1052        confirm.getData().putString("url", url);
1053        synchronized (this) {
1054            sendMessage(confirm);
1055            try {
1056                wait();
1057            } catch (InterruptedException e) {
1058                Log.e(LOGTAG, "Caught exception while waiting for jsUnload");
1059                Log.e(LOGTAG, Log.getStackTraceString(e));
1060            }
1061        }
1062        return result.getResult();
1063    }
1064
1065    /**
1066     * Called by WebViewCore to inform the Java side that the current origin
1067     * has overflowed it's database quota. Called in the WebCore thread so
1068     * posts a message to the UI thread that will prompt the WebChromeClient
1069     * for what to do. On return back to C++ side, the WebCore thread will
1070     * sleep pending a new quota value.
1071     * @param url The URL that caused the quota overflow.
1072     * @param databaseIdentifier The identifier of the database that the
1073     *     transaction that caused the overflow was running on.
1074     * @param currentQuota The current quota the origin is allowed.
1075     * @param quotaUpdater An instance of a class encapsulating a callback
1076     *     to WebViewCore to run when the decision to allow or deny more
1077     *     quota has been made.
1078     */
1079    public void onExceededDatabaseQuota(
1080            String url, String databaseIdentifier, long currentQuota,
1081            WebStorage.QuotaUpdater quotaUpdater) {
1082        if (mWebChromeClient == null) {
1083            quotaUpdater.updateQuota(currentQuota);
1084            return;
1085        }
1086
1087        Message exceededQuota = obtainMessage(EXCEEDED_DATABASE_QUOTA);
1088        HashMap<String, Object> map = new HashMap();
1089        map.put("databaseIdentifier", databaseIdentifier);
1090        map.put("url", url);
1091        map.put("currentQuota", currentQuota);
1092        map.put("quotaUpdater", quotaUpdater);
1093        exceededQuota.obj = map;
1094        sendMessage(exceededQuota);
1095    }
1096
1097    /**
1098     * Called by WebViewCore when we have a message to be added to the JavaScript
1099     * error console. Sends a message to the Java side with the details.
1100     * @param message The message to add to the console.
1101     * @param lineNumber The lineNumber of the source file on which the error
1102     *     occurred.
1103     * @param sourceID The filename of the source file in which the error
1104     *     occurred.
1105     * @hide pending API counsel.
1106     */
1107    public void addMessageToConsole(String message, int lineNumber, String sourceID) {
1108        if (mWebChromeClient == null) {
1109            return;
1110        }
1111
1112        Message msg = obtainMessage(ADD_MESSAGE_TO_CONSOLE);
1113        msg.getData().putString("message", message);
1114        msg.getData().putString("sourceID", sourceID);
1115        msg.getData().putInt("lineNumber", lineNumber);
1116        sendMessage(msg);
1117    }
1118
1119    /**
1120     * @hide pending API council approval
1121     */
1122    public boolean onJsTimeout() {
1123        //always interrupt timedout JS by default
1124        if (mWebChromeClient == null) {
1125            return true;
1126        }
1127        JsResult result = new JsResult(this, true);
1128        Message timeout = obtainMessage(JS_TIMEOUT, result);
1129        synchronized (this) {
1130            sendMessage(timeout);
1131            try {
1132                wait();
1133            } catch (InterruptedException e) {
1134                Log.e(LOGTAG, "Caught exception while waiting for jsUnload");
1135                Log.e(LOGTAG, Log.getStackTraceString(e));
1136            }
1137        }
1138        return result.getResult();
1139    }
1140}
1141