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