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