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.Activity;
20import android.app.AlertDialog;
21import android.content.ActivityNotFoundException;
22import android.content.Context;
23import android.content.DialogInterface;
24import android.content.Intent;
25import android.graphics.Bitmap;
26import android.net.Uri;
27import android.net.http.SslCertificate;
28import android.net.http.SslError;
29import android.os.Bundle;
30import android.os.Handler;
31import android.os.Message;
32import android.os.SystemClock;
33import android.provider.Browser;
34import android.util.Log;
35import android.view.KeyEvent;
36import android.view.LayoutInflater;
37import android.view.View;
38import android.widget.EditText;
39import android.widget.TextView;
40import com.android.internal.R;
41
42import java.net.MalformedURLException;
43import java.net.URL;
44import java.util.HashMap;
45import java.util.List;
46import java.util.Map;
47
48/**
49 * This class is a proxy class for handling WebCore -> UI thread messaging. All
50 * the callback functions are called from the WebCore thread and messages are
51 * posted to the UI thread for the actual client callback.
52 */
53/*
54 * This class is created in the UI thread so its handler and any private classes
55 * that extend Handler will operate in the UI thread.
56 */
57class CallbackProxy extends Handler {
58    // Logging tag
59    private static final String LOGTAG = "CallbackProxy";
60    // Instance of WebViewClient that is the client callback.
61    private volatile WebViewClient mWebViewClient;
62    // Instance of WebChromeClient for handling all chrome functions.
63    private volatile WebChromeClient mWebChromeClient;
64    // Instance of WebViewClassic for handling UI requests.
65    private final WebViewClassic mWebView;
66    // Client registered callback listener for download events
67    private volatile DownloadListener mDownloadListener;
68    // Keep track of multiple progress updates.
69    private boolean mProgressUpdatePending;
70    // Keep track of the last progress amount.
71    // Start with 100 to indicate it is not in load for the empty page.
72    private volatile int mLatestProgress = 100;
73    // Back/Forward list
74    private final WebBackForwardListClassic mBackForwardList;
75    // Back/Forward list client
76    private volatile WebBackForwardListClient mWebBackForwardListClient;
77    // Used to call startActivity during url override.
78    private final Context mContext;
79    // block messages flag for destroy
80    private boolean mBlockMessages;
81
82    // Message IDs
83    private static final int PAGE_STARTED                         = 100;
84    private static final int RECEIVED_ICON                        = 101;
85    private static final int RECEIVED_TITLE                       = 102;
86    private static final int OVERRIDE_URL                         = 103;
87    private static final int AUTH_REQUEST                         = 104;
88    private static final int SSL_ERROR                            = 105;
89    private static final int PROGRESS                             = 106;
90    private static final int UPDATE_VISITED                       = 107;
91    private static final int LOAD_RESOURCE                        = 108;
92    private static final int CREATE_WINDOW                        = 109;
93    private static final int CLOSE_WINDOW                         = 110;
94    private static final int SAVE_PASSWORD                        = 111;
95    private static final int JS_ALERT                             = 112;
96    private static final int JS_CONFIRM                           = 113;
97    private static final int JS_PROMPT                            = 114;
98    private static final int JS_UNLOAD                            = 115;
99    private static final int ASYNC_KEYEVENTS                      = 116;
100    private static final int DOWNLOAD_FILE                        = 118;
101    private static final int REPORT_ERROR                         = 119;
102    private static final int RESEND_POST_DATA                     = 120;
103    private static final int PAGE_FINISHED                        = 121;
104    private static final int REQUEST_FOCUS                        = 122;
105    private static final int SCALE_CHANGED                        = 123;
106    private static final int RECEIVED_CERTIFICATE                 = 124;
107    private static final int SWITCH_OUT_HISTORY                   = 125;
108    private static final int EXCEEDED_DATABASE_QUOTA              = 126;
109    private static final int REACHED_APPCACHE_MAXSIZE             = 127;
110    private static final int JS_TIMEOUT                           = 128;
111    private static final int ADD_MESSAGE_TO_CONSOLE               = 129;
112    private static final int GEOLOCATION_PERMISSIONS_SHOW_PROMPT  = 130;
113    private static final int GEOLOCATION_PERMISSIONS_HIDE_PROMPT  = 131;
114    private static final int RECEIVED_TOUCH_ICON_URL              = 132;
115    private static final int GET_VISITED_HISTORY                  = 133;
116    private static final int OPEN_FILE_CHOOSER                    = 134;
117    private static final int ADD_HISTORY_ITEM                     = 135;
118    private static final int HISTORY_INDEX_CHANGED                = 136;
119    private static final int AUTH_CREDENTIALS                     = 137;
120    private static final int AUTO_LOGIN                           = 140;
121    private static final int CLIENT_CERT_REQUEST                  = 141;
122    private static final int PROCEEDED_AFTER_SSL_ERROR            = 144;
123
124    // Message triggered by the client to resume execution
125    private static final int NOTIFY                               = 200;
126
127    // Result transportation object for returning results across thread
128    // boundaries.
129    private static class ResultTransport<E> {
130        // Private result object
131        private E mResult;
132
133        public ResultTransport(E defaultResult) {
134            mResult = defaultResult;
135        }
136
137        public synchronized void setResult(E result) {
138            mResult = result;
139        }
140
141        public synchronized E getResult() {
142            return mResult;
143        }
144    }
145
146    private class JsResultReceiver implements JsResult.ResultReceiver {
147        // This prevents a user from interacting with the result before WebCore is
148        // ready to handle it.
149        private boolean mReady;
150        // Tells us if the user tried to confirm or cancel the result before WebCore
151        // is ready.
152        private boolean mTriedToNotifyBeforeReady;
153
154        public JsPromptResult mJsResult = new JsPromptResult(this);
155
156        final void setReady() {
157            mReady = true;
158            if (mTriedToNotifyBeforeReady) {
159                notifyCallbackProxy();
160            }
161        }
162
163        /* Wake up the WebCore thread. */
164        @Override
165        public void onJsResultComplete(JsResult result) {
166            if (mReady) {
167                notifyCallbackProxy();
168            } else {
169                mTriedToNotifyBeforeReady = true;
170            }
171        }
172
173        private void notifyCallbackProxy() {
174            synchronized (CallbackProxy.this) {
175                CallbackProxy.this.notify();
176            }
177        }
178}
179
180    /**
181     * Construct a new CallbackProxy.
182     */
183    public CallbackProxy(Context context, WebViewClassic w) {
184        // Used to start a default activity.
185        mContext = context;
186        mWebView = w;
187        mBackForwardList = new WebBackForwardListClassic(this);
188    }
189
190    protected synchronized void blockMessages() {
191        mBlockMessages = true;
192    }
193
194    protected synchronized boolean messagesBlocked() {
195        return mBlockMessages;
196    }
197
198    protected void shutdown() {
199        removeCallbacksAndMessages(null);
200        setWebViewClient(null);
201        setWebChromeClient(null);
202    }
203
204    /**
205     * Set the WebViewClient.
206     * @param client An implementation of WebViewClient.
207     */
208    public void setWebViewClient(WebViewClient client) {
209        mWebViewClient = client;
210    }
211
212    /**
213     * Get the WebViewClient.
214     * @return the current WebViewClient instance.
215     */
216    public WebViewClient getWebViewClient() {
217       return mWebViewClient;
218    }
219
220    /**
221     * Set the WebChromeClient.
222     * @param client An implementation of WebChromeClient.
223     */
224    public void setWebChromeClient(WebChromeClient client) {
225        mWebChromeClient = client;
226    }
227
228    /**
229     * Get the WebChromeClient.
230     * @return the current WebChromeClient instance.
231     */
232    public WebChromeClient getWebChromeClient() {
233       return mWebChromeClient;
234    }
235
236    /**
237     * Set the client DownloadListener.
238     * @param client An implementation of DownloadListener.
239     */
240    public void setDownloadListener(DownloadListener client) {
241        mDownloadListener = client;
242    }
243
244    /**
245     * Get the Back/Forward list to return to the user or to update the cached
246     * history list.
247     */
248    public WebBackForwardListClassic getBackForwardList() {
249        return mBackForwardList;
250    }
251
252    void setWebBackForwardListClient(WebBackForwardListClient client) {
253        mWebBackForwardListClient = client;
254    }
255
256    WebBackForwardListClient getWebBackForwardListClient() {
257        return mWebBackForwardListClient;
258    }
259
260    /**
261     * Called by the UI side.  Calling overrideUrlLoading from the WebCore
262     * side will post a message to call this method.
263     */
264    public boolean uiOverrideUrlLoading(String overrideUrl) {
265        if (overrideUrl == null || overrideUrl.length() == 0) {
266            return false;
267        }
268        boolean override = false;
269        if (mWebViewClient != null) {
270            override = mWebViewClient.shouldOverrideUrlLoading(mWebView.getWebView(),
271                    overrideUrl);
272        } else {
273            Intent intent = new Intent(Intent.ACTION_VIEW,
274                    Uri.parse(overrideUrl));
275            intent.addCategory(Intent.CATEGORY_BROWSABLE);
276            // If another application is running a WebView and launches the
277            // Browser through this Intent, we want to reuse the same window if
278            // possible.
279            intent.putExtra(Browser.EXTRA_APPLICATION_ID,
280                    mContext.getPackageName());
281            try {
282                mContext.startActivity(intent);
283                override = true;
284            } catch (ActivityNotFoundException ex) {
285                // If no application can handle the URL, assume that the
286                // browser can handle it.
287            }
288        }
289        return override;
290    }
291
292    /**
293     * Called by UI side.
294     */
295    public boolean uiOverrideKeyEvent(KeyEvent event) {
296        if (mWebViewClient != null) {
297            return mWebViewClient.shouldOverrideKeyEvent(mWebView.getWebView(), event);
298        }
299        return false;
300    }
301
302    @Override
303    public void handleMessage(Message msg) {
304        // We don't have to do synchronization because this function operates
305        // in the UI thread. The WebViewClient and WebChromeClient functions
306        // that check for a non-null callback are ok because java ensures atomic
307        // 32-bit reads and writes.
308        if (messagesBlocked()) return;
309        switch (msg.what) {
310            case PAGE_STARTED:
311                String startedUrl = msg.getData().getString("url");
312                mWebView.onPageStarted(startedUrl);
313                if (mWebViewClient != null) {
314                    mWebViewClient.onPageStarted(mWebView.getWebView(), startedUrl,
315                            (Bitmap) msg.obj);
316                }
317                break;
318
319            case PAGE_FINISHED:
320                String finishedUrl = (String) msg.obj;
321                mWebView.onPageFinished(finishedUrl);
322                if (mWebViewClient != null) {
323                    mWebViewClient.onPageFinished(mWebView.getWebView(), finishedUrl);
324                }
325                break;
326
327            case RECEIVED_ICON:
328                if (mWebChromeClient != null) {
329                    mWebChromeClient.onReceivedIcon(mWebView.getWebView(), (Bitmap) msg.obj);
330                }
331                break;
332
333            case RECEIVED_TOUCH_ICON_URL:
334                if (mWebChromeClient != null) {
335                    mWebChromeClient.onReceivedTouchIconUrl(mWebView.getWebView(),
336                            (String) msg.obj, msg.arg1 == 1);
337                }
338                break;
339
340            case RECEIVED_TITLE:
341                if (mWebChromeClient != null) {
342                    mWebChromeClient.onReceivedTitle(mWebView.getWebView(),
343                            (String) msg.obj);
344                }
345                break;
346
347            case REPORT_ERROR:
348                if (mWebViewClient != null) {
349                    int reasonCode = msg.arg1;
350                    final String description  = msg.getData().getString("description");
351                    final String failUrl  = msg.getData().getString("failingUrl");
352                    mWebViewClient.onReceivedError(mWebView.getWebView(), reasonCode,
353                            description, failUrl);
354                }
355                break;
356
357            case RESEND_POST_DATA:
358                Message resend =
359                        (Message) msg.getData().getParcelable("resend");
360                Message dontResend =
361                        (Message) msg.getData().getParcelable("dontResend");
362                if (mWebViewClient != null) {
363                    mWebViewClient.onFormResubmission(mWebView.getWebView(), dontResend,
364                            resend);
365                } else {
366                    dontResend.sendToTarget();
367                }
368                break;
369
370            case OVERRIDE_URL:
371                String overrideUrl = msg.getData().getString("url");
372                boolean override = uiOverrideUrlLoading(overrideUrl);
373                ResultTransport<Boolean> result =
374                        (ResultTransport<Boolean>) msg.obj;
375                synchronized (this) {
376                    result.setResult(override);
377                    notify();
378                }
379                break;
380
381            case AUTH_REQUEST:
382                if (mWebViewClient != null) {
383                    HttpAuthHandler handler = (HttpAuthHandler) msg.obj;
384                    String host = msg.getData().getString("host");
385                    String realm = msg.getData().getString("realm");
386                    mWebViewClient.onReceivedHttpAuthRequest(mWebView.getWebView(), handler,
387                            host, realm);
388                }
389                break;
390
391            case SSL_ERROR:
392                if (mWebViewClient != null) {
393                    HashMap<String, Object> map =
394                        (HashMap<String, Object>) msg.obj;
395                    mWebViewClient.onReceivedSslError(mWebView.getWebView(),
396                            (SslErrorHandler) map.get("handler"),
397                            (SslError) map.get("error"));
398                }
399                break;
400
401            case PROCEEDED_AFTER_SSL_ERROR:
402                if (mWebViewClient != null && mWebViewClient instanceof WebViewClientClassicExt) {
403                    ((WebViewClientClassicExt) mWebViewClient).onProceededAfterSslError(
404                            mWebView.getWebView(),
405                            (SslError) msg.obj);
406                }
407                break;
408
409            case CLIENT_CERT_REQUEST:
410                if (mWebViewClient != null  && mWebViewClient instanceof WebViewClientClassicExt) {
411                    HashMap<String, Object> map = (HashMap<String, Object>) msg.obj;
412                    ((WebViewClientClassicExt) mWebViewClient).onReceivedClientCertRequest(
413                            mWebView.getWebView(),
414                            (ClientCertRequestHandler) map.get("handler"),
415                            (String) map.get("host_and_port"));
416                }
417                break;
418
419            case PROGRESS:
420                // Synchronize to ensure mLatestProgress is not modified after
421                // setProgress is called and before mProgressUpdatePending is
422                // changed.
423                synchronized (this) {
424                    if (mWebChromeClient != null) {
425                        mWebChromeClient.onProgressChanged(mWebView.getWebView(),
426                                mLatestProgress);
427                    }
428                    mProgressUpdatePending = false;
429                }
430                break;
431
432            case UPDATE_VISITED:
433                if (mWebViewClient != null) {
434                    mWebViewClient.doUpdateVisitedHistory(mWebView.getWebView(),
435                            (String) msg.obj, msg.arg1 != 0);
436                }
437                break;
438
439            case LOAD_RESOURCE:
440                if (mWebViewClient != null) {
441                    mWebViewClient.onLoadResource(mWebView.getWebView(), (String) msg.obj);
442                }
443                break;
444
445            case DOWNLOAD_FILE:
446                if (mDownloadListener != null) {
447                    String url = msg.getData().getString("url");
448                    String userAgent = msg.getData().getString("userAgent");
449                    String contentDisposition =
450                        msg.getData().getString("contentDisposition");
451                    String mimetype = msg.getData().getString("mimetype");
452                    String referer = msg.getData().getString("referer");
453                    Long contentLength = msg.getData().getLong("contentLength");
454
455                    if (mDownloadListener instanceof BrowserDownloadListener) {
456                        ((BrowserDownloadListener) mDownloadListener).onDownloadStart(url,
457                             userAgent, contentDisposition, mimetype, referer, contentLength);
458                    } else {
459                        mDownloadListener.onDownloadStart(url, userAgent,
460                             contentDisposition, mimetype, contentLength);
461                    }
462                }
463                break;
464
465            case CREATE_WINDOW:
466                if (mWebChromeClient != null) {
467                    if (!mWebChromeClient.onCreateWindow(mWebView.getWebView(),
468                                msg.arg1 == 1, msg.arg2 == 1,
469                                (Message) msg.obj)) {
470                        synchronized (this) {
471                            notify();
472                        }
473                    }
474                    mWebView.dismissZoomControl();
475                }
476                break;
477
478            case REQUEST_FOCUS:
479                if (mWebChromeClient != null) {
480                    mWebChromeClient.onRequestFocus(mWebView.getWebView());
481                }
482                break;
483
484            case CLOSE_WINDOW:
485                if (mWebChromeClient != null) {
486                    mWebChromeClient.onCloseWindow(((WebViewClassic) msg.obj).getWebView());
487                }
488                break;
489
490            case SAVE_PASSWORD:
491                Bundle bundle = msg.getData();
492                String schemePlusHost = bundle.getString("host");
493                String username = bundle.getString("username");
494                String password = bundle.getString("password");
495                // If the client returned false it means that the notify message
496                // will not be sent and we should notify WebCore ourselves.
497                if (!mWebView.onSavePassword(schemePlusHost, username, password,
498                            (Message) msg.obj)) {
499                    synchronized (this) {
500                        notify();
501                    }
502                }
503                break;
504
505            case ASYNC_KEYEVENTS:
506                if (mWebViewClient != null) {
507                    mWebViewClient.onUnhandledKeyEvent(mWebView.getWebView(),
508                            (KeyEvent) msg.obj);
509                }
510                break;
511
512            case EXCEEDED_DATABASE_QUOTA:
513                if (mWebChromeClient != null) {
514                    HashMap<String, Object> map =
515                            (HashMap<String, Object>) msg.obj;
516                    String databaseIdentifier =
517                            (String) map.get("databaseIdentifier");
518                    String url = (String) map.get("url");
519                    long quota =
520                            ((Long) map.get("quota")).longValue();
521                    long totalQuota =
522                            ((Long) map.get("totalQuota")).longValue();
523                    long estimatedDatabaseSize =
524                            ((Long) map.get("estimatedDatabaseSize")).longValue();
525                    WebStorage.QuotaUpdater quotaUpdater =
526                        (WebStorage.QuotaUpdater) map.get("quotaUpdater");
527
528                    mWebChromeClient.onExceededDatabaseQuota(url,
529                            databaseIdentifier, quota, estimatedDatabaseSize,
530                            totalQuota, quotaUpdater);
531                }
532                break;
533
534            case REACHED_APPCACHE_MAXSIZE:
535                if (mWebChromeClient != null) {
536                    HashMap<String, Object> map =
537                            (HashMap<String, Object>) msg.obj;
538                    long requiredStorage =
539                            ((Long) map.get("requiredStorage")).longValue();
540                    long quota =
541                        ((Long) map.get("quota")).longValue();
542                    WebStorage.QuotaUpdater quotaUpdater =
543                        (WebStorage.QuotaUpdater) map.get("quotaUpdater");
544
545                    mWebChromeClient.onReachedMaxAppCacheSize(requiredStorage,
546                            quota, quotaUpdater);
547                }
548                break;
549
550            case GEOLOCATION_PERMISSIONS_SHOW_PROMPT:
551                if (mWebChromeClient != null) {
552                    HashMap<String, Object> map =
553                            (HashMap<String, Object>) msg.obj;
554                    String origin = (String) map.get("origin");
555                    GeolocationPermissions.Callback callback =
556                            (GeolocationPermissions.Callback)
557                            map.get("callback");
558                    mWebChromeClient.onGeolocationPermissionsShowPrompt(origin,
559                            callback);
560                }
561                break;
562
563            case GEOLOCATION_PERMISSIONS_HIDE_PROMPT:
564                if (mWebChromeClient != null) {
565                    mWebChromeClient.onGeolocationPermissionsHidePrompt();
566                }
567                break;
568
569            case JS_ALERT:
570                if (mWebChromeClient != null) {
571                    final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
572                    final JsResult res = receiver.mJsResult;
573                    String message = msg.getData().getString("message");
574                    String url = msg.getData().getString("url");
575                    if (!mWebChromeClient.onJsAlert(mWebView.getWebView(), url, message,
576                            res)) {
577                        if (!canShowAlertDialog()) {
578                            res.cancel();
579                            receiver.setReady();
580                            break;
581                        }
582                        new AlertDialog.Builder(mContext)
583                                .setTitle(getJsDialogTitle(url))
584                                .setMessage(message)
585                                .setPositiveButton(R.string.ok,
586                                        new DialogInterface.OnClickListener() {
587                                            public void onClick(
588                                                    DialogInterface dialog,
589                                                    int which) {
590                                                res.confirm();
591                                            }
592                                        })
593                                .setOnCancelListener(
594                                        new DialogInterface.OnCancelListener() {
595                                            public void onCancel(
596                                                    DialogInterface dialog) {
597                                                res.cancel();
598                                            }
599                                        })
600                                .show();
601                    }
602                    receiver.setReady();
603                }
604                break;
605
606            case JS_CONFIRM:
607                if (mWebChromeClient != null) {
608                    final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
609                    final JsResult res = receiver.mJsResult;
610                    String message = msg.getData().getString("message");
611                    String url = msg.getData().getString("url");
612                    if (!mWebChromeClient.onJsConfirm(mWebView.getWebView(), url, message,
613                            res)) {
614                        if (!canShowAlertDialog()) {
615                            res.cancel();
616                            receiver.setReady();
617                            break;
618                        }
619                        new AlertDialog.Builder(mContext)
620                                .setTitle(getJsDialogTitle(url))
621                                .setMessage(message)
622                                .setPositiveButton(R.string.ok,
623                                        new DialogInterface.OnClickListener() {
624                                            public void onClick(
625                                                    DialogInterface dialog,
626                                                    int which) {
627                                                res.confirm();
628                                            }})
629                                .setNegativeButton(R.string.cancel,
630                                        new DialogInterface.OnClickListener() {
631                                            public void onClick(
632                                                    DialogInterface dialog,
633                                                    int which) {
634                                                res.cancel();
635                                            }})
636                                .setOnCancelListener(
637                                        new DialogInterface.OnCancelListener() {
638                                            public void onCancel(
639                                                    DialogInterface dialog) {
640                                                res.cancel();
641                                            }
642                                        })
643                                .show();
644                    }
645                    // Tell the JsResult that it is ready for client
646                    // interaction.
647                    receiver.setReady();
648                }
649                break;
650
651            case JS_PROMPT:
652                if (mWebChromeClient != null) {
653                    final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
654                    final JsPromptResult res = receiver.mJsResult;
655                    String message = msg.getData().getString("message");
656                    String defaultVal = msg.getData().getString("default");
657                    String url = msg.getData().getString("url");
658                    if (!mWebChromeClient.onJsPrompt(mWebView.getWebView(), url, message,
659                                defaultVal, res)) {
660                        if (!canShowAlertDialog()) {
661                            res.cancel();
662                            receiver.setReady();
663                            break;
664                        }
665                        final LayoutInflater factory = LayoutInflater
666                                .from(mContext);
667                        final View view = factory.inflate(R.layout.js_prompt,
668                                null);
669                        final EditText v = (EditText) view
670                                .findViewById(R.id.value);
671                        v.setText(defaultVal);
672                        ((TextView) view.findViewById(R.id.message))
673                                .setText(message);
674                        new AlertDialog.Builder(mContext)
675                                .setTitle(getJsDialogTitle(url))
676                                .setView(view)
677                                .setPositiveButton(R.string.ok,
678                                        new DialogInterface.OnClickListener() {
679                                            public void onClick(
680                                                    DialogInterface dialog,
681                                                    int whichButton) {
682                                                res.confirm(v.getText()
683                                                        .toString());
684                                            }
685                                        })
686                                .setNegativeButton(R.string.cancel,
687                                        new DialogInterface.OnClickListener() {
688                                            public void onClick(
689                                                    DialogInterface dialog,
690                                                    int whichButton) {
691                                                res.cancel();
692                                            }
693                                        })
694                                .setOnCancelListener(
695                                        new DialogInterface.OnCancelListener() {
696                                            public void onCancel(
697                                                    DialogInterface dialog) {
698                                                res.cancel();
699                                            }
700                                        })
701                                .show();
702                    }
703                    // Tell the JsResult that it is ready for client
704                    // interaction.
705                    receiver.setReady();
706                }
707                break;
708
709            case JS_UNLOAD:
710                if (mWebChromeClient != null) {
711                    final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
712                    final JsResult res = receiver.mJsResult;
713                    String message = msg.getData().getString("message");
714                    String url = msg.getData().getString("url");
715                    if (!mWebChromeClient.onJsBeforeUnload(mWebView.getWebView(), url,
716                            message, res)) {
717                        if (!canShowAlertDialog()) {
718                            res.cancel();
719                            receiver.setReady();
720                            break;
721                        }
722                        final String m = mContext.getString(
723                                R.string.js_dialog_before_unload, message);
724                        new AlertDialog.Builder(mContext)
725                                .setMessage(m)
726                                .setPositiveButton(R.string.ok,
727                                        new DialogInterface.OnClickListener() {
728                                            public void onClick(
729                                                    DialogInterface dialog,
730                                                    int which) {
731                                                res.confirm();
732                                            }
733                                        })
734                                .setNegativeButton(R.string.cancel,
735                                        new DialogInterface.OnClickListener() {
736                                            public void onClick(
737                                                    DialogInterface dialog,
738                                                    int which) {
739                                                res.cancel();
740                                            }
741                                        })
742                                .setOnCancelListener(
743                                        new DialogInterface.OnCancelListener() {
744                                            @Override
745                                            public void onCancel(
746                                                    DialogInterface dialog) {
747                                                res.cancel();
748                                            }
749                                        })
750                                .show();
751                    }
752                    receiver.setReady();
753                }
754                break;
755
756            case JS_TIMEOUT:
757                if(mWebChromeClient != null) {
758                    final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
759                    final JsResult res = receiver.mJsResult;
760                    if(mWebChromeClient.onJsTimeout()) {
761                        res.confirm();
762                    } else {
763                        res.cancel();
764                    }
765                    receiver.setReady();
766                }
767                break;
768
769            case RECEIVED_CERTIFICATE:
770                mWebView.setCertificate((SslCertificate) msg.obj);
771                break;
772
773            case NOTIFY:
774                synchronized (this) {
775                    notify();
776                }
777                break;
778
779            case SCALE_CHANGED:
780                if (mWebViewClient != null) {
781                    mWebViewClient.onScaleChanged(mWebView.getWebView(), msg.getData()
782                            .getFloat("old"), msg.getData().getFloat("new"));
783                }
784                break;
785
786            case SWITCH_OUT_HISTORY:
787                mWebView.switchOutDrawHistory();
788                break;
789
790            case ADD_MESSAGE_TO_CONSOLE:
791                if (mWebChromeClient == null) {
792                    break;
793                }
794                String message = msg.getData().getString("message");
795                String sourceID = msg.getData().getString("sourceID");
796                int lineNumber = msg.getData().getInt("lineNumber");
797                int msgLevel = msg.getData().getInt("msgLevel");
798                int numberOfMessageLevels = ConsoleMessage.MessageLevel.values().length;
799                // Sanity bounds check as we'll index an array with msgLevel
800                if (msgLevel < 0 || msgLevel >= numberOfMessageLevels) {
801                    msgLevel = 0;
802                }
803
804                ConsoleMessage.MessageLevel messageLevel =
805                        ConsoleMessage.MessageLevel.values()[msgLevel];
806
807                if (!mWebChromeClient.onConsoleMessage(new ConsoleMessage(message, sourceID,
808                        lineNumber, messageLevel))) {
809                    // If false was returned the user did not provide their own console function so
810                    //  we should output some default messages to the system log.
811                    String logTag = "Web Console";
812                    String logMessage = message + " at " + sourceID + ":" + lineNumber;
813
814                    switch (messageLevel) {
815                        case TIP:
816                            Log.v(logTag, logMessage);
817                            break;
818                        case LOG:
819                            Log.i(logTag, logMessage);
820                            break;
821                        case WARNING:
822                            Log.w(logTag, logMessage);
823                            break;
824                        case ERROR:
825                            Log.e(logTag, logMessage);
826                            break;
827                        case DEBUG:
828                            Log.d(logTag, logMessage);
829                            break;
830                    }
831                }
832
833                break;
834
835            case GET_VISITED_HISTORY:
836                if (mWebChromeClient != null) {
837                    mWebChromeClient.getVisitedHistory((ValueCallback<String[]>)msg.obj);
838                }
839                break;
840
841            case OPEN_FILE_CHOOSER:
842                if (mWebChromeClient != null) {
843                    UploadFileMessageData data = (UploadFileMessageData)msg.obj;
844                    mWebChromeClient.openFileChooser(data.getUploadFile(), data.getAcceptType(),
845                            data.getCapture());
846                }
847                break;
848
849            case ADD_HISTORY_ITEM:
850                if (mWebBackForwardListClient != null) {
851                    mWebBackForwardListClient.onNewHistoryItem(
852                            (WebHistoryItem) msg.obj);
853                }
854                break;
855
856            case HISTORY_INDEX_CHANGED:
857                if (mWebBackForwardListClient != null) {
858                    mWebBackForwardListClient.onIndexChanged(
859                            (WebHistoryItem) msg.obj, msg.arg1);
860                }
861                break;
862            case AUTH_CREDENTIALS: {
863                String host = msg.getData().getString("host");
864                String realm = msg.getData().getString("realm");
865                username = msg.getData().getString("username");
866                password = msg.getData().getString("password");
867                mWebView.setHttpAuthUsernamePassword(
868                        host, realm, username, password);
869                break;
870            }
871            case AUTO_LOGIN: {
872                if (mWebViewClient != null) {
873                    String realm = msg.getData().getString("realm");
874                    String account = msg.getData().getString("account");
875                    String args = msg.getData().getString("args");
876                    mWebViewClient.onReceivedLoginRequest(mWebView.getWebView(), realm,
877                            account, args);
878                }
879                break;
880            }
881        }
882    }
883
884    /**
885     * Return the latest progress.
886     */
887    public int getProgress() {
888        return mLatestProgress;
889    }
890
891    /**
892     * Called by WebCore side to switch out of history Picture drawing mode
893     */
894    void switchOutDrawHistory() {
895        sendMessage(obtainMessage(SWITCH_OUT_HISTORY));
896    }
897
898    private String getJsDialogTitle(String url) {
899        String title = url;
900        if (URLUtil.isDataUrl(url)) {
901            // For data: urls, we just display 'JavaScript' similar to Safari.
902            title = mContext.getString(R.string.js_dialog_title_default);
903        } else {
904            try {
905                URL aUrl = new URL(url);
906                // For example: "The page at 'http://www.mit.edu' says:"
907                title = mContext.getString(R.string.js_dialog_title,
908                        aUrl.getProtocol() + "://" + aUrl.getHost());
909            } catch (MalformedURLException ex) {
910                // do nothing. just use the url as the title
911            }
912        }
913        return title;
914    }
915
916    //--------------------------------------------------------------------------
917    // WebViewClient functions.
918    // NOTE: shouldOverrideKeyEvent is never called from the WebCore thread so
919    // it is not necessary to include it here.
920    //--------------------------------------------------------------------------
921
922    // Performance probe
923    private static final boolean PERF_PROBE = false;
924    private long mWebCoreThreadTime;
925    private long mWebCoreIdleTime;
926
927    /*
928     * If PERF_PROBE is true, this block needs to be added to MessageQueue.java.
929     * startWait() and finishWait() should be called before and after wait().
930
931    private WaitCallback mWaitCallback = null;
932    public static interface WaitCallback {
933        void startWait();
934        void finishWait();
935    }
936    public final void setWaitCallback(WaitCallback callback) {
937        mWaitCallback = callback;
938    }
939    */
940
941    // un-comment this block if PERF_PROBE is true
942    /*
943    private IdleCallback mIdleCallback = new IdleCallback();
944
945    private final class IdleCallback implements MessageQueue.WaitCallback {
946        private long mStartTime = 0;
947
948        public void finishWait() {
949            mWebCoreIdleTime += SystemClock.uptimeMillis() - mStartTime;
950        }
951
952        public void startWait() {
953            mStartTime = SystemClock.uptimeMillis();
954        }
955    }
956    */
957
958    public void onPageStarted(String url, Bitmap favicon) {
959        // We need to send the message even if no WebViewClient is set, because we need to call
960        // WebView.onPageStarted().
961
962        // Performance probe
963        if (PERF_PROBE) {
964            mWebCoreThreadTime = SystemClock.currentThreadTimeMillis();
965            mWebCoreIdleTime = 0;
966            // un-comment this if PERF_PROBE is true
967//            Looper.myQueue().setWaitCallback(mIdleCallback);
968        }
969        Message msg = obtainMessage(PAGE_STARTED);
970        msg.obj = favicon;
971        msg.getData().putString("url", url);
972        sendMessage(msg);
973    }
974
975    public void onPageFinished(String url) {
976        // Performance probe
977        if (PERF_PROBE) {
978            // un-comment this if PERF_PROBE is true
979//            Looper.myQueue().setWaitCallback(null);
980            Log.d("WebCore", "WebCore thread used " +
981                    (SystemClock.currentThreadTimeMillis() - mWebCoreThreadTime)
982                    + " ms and idled " + mWebCoreIdleTime + " ms");
983        }
984        Message msg = obtainMessage(PAGE_FINISHED, url);
985        sendMessage(msg);
986    }
987
988    // Because this method is public and because CallbackProxy is mistakenly
989    // party of the public classes, we cannot remove this method.
990    public void onTooManyRedirects(Message cancelMsg, Message continueMsg) {
991        // deprecated.
992    }
993
994    public void onReceivedError(int errorCode, String description,
995            String failingUrl) {
996        // Do an unsynchronized quick check to avoid posting if no callback has
997        // been set.
998        if (mWebViewClient == null) {
999            return;
1000        }
1001
1002        Message msg = obtainMessage(REPORT_ERROR);
1003        msg.arg1 = errorCode;
1004        msg.getData().putString("description", description);
1005        msg.getData().putString("failingUrl", failingUrl);
1006        sendMessage(msg);
1007    }
1008
1009    public void onFormResubmission(Message dontResend,
1010            Message resend) {
1011        // Do an unsynchronized quick check to avoid posting if no callback has
1012        // been set.
1013        if (mWebViewClient == null) {
1014            dontResend.sendToTarget();
1015            return;
1016        }
1017
1018        Message msg = obtainMessage(RESEND_POST_DATA);
1019        Bundle bundle = msg.getData();
1020        bundle.putParcelable("resend", resend);
1021        bundle.putParcelable("dontResend", dontResend);
1022        sendMessage(msg);
1023    }
1024
1025    /**
1026     * Called by the WebCore side
1027     */
1028    public boolean shouldOverrideUrlLoading(String url) {
1029        // We have a default behavior if no client exists so always send the
1030        // message.
1031        ResultTransport<Boolean> res = new ResultTransport<Boolean>(false);
1032        Message msg = obtainMessage(OVERRIDE_URL);
1033        msg.getData().putString("url", url);
1034        msg.obj = res;
1035        sendMessageToUiThreadSync(msg);
1036        return res.getResult().booleanValue();
1037    }
1038
1039    public void onReceivedHttpAuthRequest(HttpAuthHandler handler,
1040            String hostName, String realmName) {
1041        // Do an unsynchronized quick check to avoid posting if no callback has
1042        // been set.
1043        if (mWebViewClient == null) {
1044            handler.cancel();
1045            return;
1046        }
1047        Message msg = obtainMessage(AUTH_REQUEST, handler);
1048        msg.getData().putString("host", hostName);
1049        msg.getData().putString("realm", realmName);
1050        sendMessage(msg);
1051    }
1052
1053    public void onReceivedSslError(SslErrorHandler handler, SslError error) {
1054        // Do an unsynchronized quick check to avoid posting if no callback has
1055        // been set.
1056        if (mWebViewClient == null) {
1057            handler.cancel();
1058            return;
1059        }
1060        Message msg = obtainMessage(SSL_ERROR);
1061        HashMap<String, Object> map = new HashMap();
1062        map.put("handler", handler);
1063        map.put("error", error);
1064        msg.obj = map;
1065        sendMessage(msg);
1066    }
1067
1068    public void onProceededAfterSslError(SslError error) {
1069        if (mWebViewClient == null || !(mWebViewClient instanceof WebViewClientClassicExt)) {
1070            return;
1071        }
1072        Message msg = obtainMessage(PROCEEDED_AFTER_SSL_ERROR);
1073        msg.obj = error;
1074        sendMessage(msg);
1075    }
1076
1077    public void onReceivedClientCertRequest(ClientCertRequestHandler handler, String host_and_port) {
1078        // Do an unsynchronized quick check to avoid posting if no callback has
1079        // been set.
1080        if (mWebViewClient == null || !(mWebViewClient instanceof WebViewClientClassicExt)) {
1081            handler.cancel();
1082            return;
1083        }
1084        Message msg = obtainMessage(CLIENT_CERT_REQUEST);
1085        HashMap<String, Object> map = new HashMap();
1086        map.put("handler", handler);
1087        map.put("host_and_port", host_and_port);
1088        msg.obj = map;
1089        sendMessage(msg);
1090    }
1091
1092    public void onReceivedCertificate(SslCertificate certificate) {
1093        // here, certificate can be null (if the site is not secure)
1094        sendMessage(obtainMessage(RECEIVED_CERTIFICATE, certificate));
1095    }
1096
1097    public void doUpdateVisitedHistory(String url, boolean isReload) {
1098        // Do an unsynchronized quick check to avoid posting if no callback has
1099        // been set.
1100        if (mWebViewClient == null) {
1101            return;
1102        }
1103        sendMessage(obtainMessage(UPDATE_VISITED, isReload ? 1 : 0, 0, url));
1104    }
1105
1106    WebResourceResponse shouldInterceptRequest(String url) {
1107        if (mWebViewClient == null) {
1108            return null;
1109        }
1110        // Note: This method does _not_ send a message.
1111        WebResourceResponse r =
1112                mWebViewClient.shouldInterceptRequest(mWebView.getWebView(), url);
1113        if (r == null) {
1114            sendMessage(obtainMessage(LOAD_RESOURCE, url));
1115        }
1116        return r;
1117    }
1118
1119    public void onUnhandledKeyEvent(KeyEvent event) {
1120        // Do an unsynchronized quick check to avoid posting if no callback has
1121        // been set.
1122        if (mWebViewClient == null) {
1123            return;
1124        }
1125        sendMessage(obtainMessage(ASYNC_KEYEVENTS, event));
1126    }
1127
1128    public void onScaleChanged(float oldScale, float newScale) {
1129        // Do an unsynchronized quick check to avoid posting if no callback has
1130        // been set.
1131        if (mWebViewClient == null) {
1132            return;
1133        }
1134        Message msg = obtainMessage(SCALE_CHANGED);
1135        Bundle bundle = msg.getData();
1136        bundle.putFloat("old", oldScale);
1137        bundle.putFloat("new", newScale);
1138        sendMessage(msg);
1139    }
1140
1141    void onReceivedLoginRequest(String realm, String account, String args) {
1142        // Do an unsynchronized quick check to avoid posting if no callback has
1143        // been set.
1144        if (mWebViewClient == null) {
1145            return;
1146        }
1147        Message msg = obtainMessage(AUTO_LOGIN);
1148        Bundle bundle = msg.getData();
1149        bundle.putString("realm", realm);
1150        bundle.putString("account", account);
1151        bundle.putString("args", args);
1152        sendMessage(msg);
1153    }
1154
1155    //--------------------------------------------------------------------------
1156    // DownloadListener functions.
1157    //--------------------------------------------------------------------------
1158
1159    /**
1160     * Starts a download if a download listener has been registered, otherwise
1161     * return false.
1162     */
1163    public boolean onDownloadStart(String url, String userAgent,
1164            String contentDisposition, String mimetype, String referer,
1165            long contentLength) {
1166        // Do an unsynchronized quick check to avoid posting if no callback has
1167        // been set.
1168        if (mDownloadListener == null) {
1169            // Cancel the download if there is no browser client.
1170            return false;
1171        }
1172
1173        Message msg = obtainMessage(DOWNLOAD_FILE);
1174        Bundle bundle = msg.getData();
1175        bundle.putString("url", url);
1176        bundle.putString("userAgent", userAgent);
1177        bundle.putString("mimetype", mimetype);
1178        bundle.putString("referer", referer);
1179        bundle.putLong("contentLength", contentLength);
1180        bundle.putString("contentDisposition", contentDisposition);
1181        sendMessage(msg);
1182        return true;
1183    }
1184
1185
1186    //--------------------------------------------------------------------------
1187    // WebView specific functions that do not interact with a client. These
1188    // functions just need to operate within the UI thread.
1189    //--------------------------------------------------------------------------
1190
1191    public boolean onSavePassword(String schemePlusHost, String username,
1192            String password, Message resumeMsg) {
1193        // resumeMsg should be null at this point because we want to create it
1194        // within the CallbackProxy.
1195        if (DebugFlags.CALLBACK_PROXY) {
1196            junit.framework.Assert.assertNull(resumeMsg);
1197        }
1198        resumeMsg = obtainMessage(NOTIFY);
1199
1200        Message msg = obtainMessage(SAVE_PASSWORD, resumeMsg);
1201        Bundle bundle = msg.getData();
1202        bundle.putString("host", schemePlusHost);
1203        bundle.putString("username", username);
1204        bundle.putString("password", password);
1205        sendMessageToUiThreadSync(msg);
1206        // Doesn't matter here
1207        return false;
1208    }
1209
1210    public void onReceivedHttpAuthCredentials(String host, String realm,
1211            String username, String password) {
1212        Message msg = obtainMessage(AUTH_CREDENTIALS);
1213        msg.getData().putString("host", host);
1214        msg.getData().putString("realm", realm);
1215        msg.getData().putString("username", username);
1216        msg.getData().putString("password", password);
1217        sendMessage(msg);
1218    }
1219
1220    //--------------------------------------------------------------------------
1221    // WebChromeClient methods
1222    //--------------------------------------------------------------------------
1223
1224    public void onProgressChanged(int newProgress) {
1225        // Synchronize so that mLatestProgress is up-to-date.
1226        synchronized (this) {
1227            // update mLatestProgress even mWebChromeClient is null as
1228            // WebView.getProgress() needs it
1229            if (mLatestProgress == newProgress) {
1230                return;
1231            }
1232            mLatestProgress = newProgress;
1233            if (mWebChromeClient == null) {
1234                return;
1235            }
1236            if (!mProgressUpdatePending) {
1237                sendEmptyMessage(PROGRESS);
1238                mProgressUpdatePending = true;
1239            }
1240        }
1241    }
1242
1243    public BrowserFrame createWindow(boolean dialog, boolean userGesture) {
1244        // Do an unsynchronized quick check to avoid posting if no callback has
1245        // been set.
1246        if (mWebChromeClient == null) {
1247            return null;
1248        }
1249
1250        WebView.WebViewTransport transport =
1251            mWebView.getWebView().new WebViewTransport();
1252        final Message msg = obtainMessage(NOTIFY);
1253        msg.obj = transport;
1254        sendMessageToUiThreadSync(obtainMessage(CREATE_WINDOW, dialog ? 1 : 0,
1255                userGesture ? 1 : 0, msg));
1256        WebViewClassic w = WebViewClassic.fromWebView(transport.getWebView());
1257        if (w != null) {
1258            WebViewCore core = w.getWebViewCore();
1259            // If WebView.destroy() has been called, core may be null.  Skip
1260            // initialization in that case and return null.
1261            if (core != null) {
1262                core.initializeSubwindow();
1263                return core.getBrowserFrame();
1264            }
1265        }
1266        return null;
1267    }
1268
1269    public void onRequestFocus() {
1270        // Do an unsynchronized quick check to avoid posting if no callback has
1271        // been set.
1272        if (mWebChromeClient == null) {
1273            return;
1274        }
1275
1276        sendEmptyMessage(REQUEST_FOCUS);
1277    }
1278
1279    public void onCloseWindow(WebViewClassic window) {
1280        // Do an unsynchronized quick check to avoid posting if no callback has
1281        // been set.
1282        if (mWebChromeClient == null) {
1283            return;
1284        }
1285        sendMessage(obtainMessage(CLOSE_WINDOW, window));
1286    }
1287
1288    public void onReceivedIcon(Bitmap icon) {
1289        // The current item might be null if the icon was already stored in the
1290        // database and this is a new WebView.
1291        WebHistoryItemClassic i = mBackForwardList.getCurrentItem();
1292        if (i != null) {
1293            i.setFavicon(icon);
1294        }
1295        // Do an unsynchronized quick check to avoid posting if no callback has
1296        // been set.
1297        if (mWebChromeClient == null) {
1298            return;
1299        }
1300        sendMessage(obtainMessage(RECEIVED_ICON, icon));
1301    }
1302
1303    /* package */ void onReceivedTouchIconUrl(String url, boolean precomposed) {
1304        // We should have a current item but we do not want to crash so check
1305        // for null.
1306        WebHistoryItemClassic i = mBackForwardList.getCurrentItem();
1307        if (i != null) {
1308            i.setTouchIconUrl(url, precomposed);
1309        }
1310        // Do an unsynchronized quick check to avoid posting if no callback has
1311        // been set.
1312        if (mWebChromeClient == null) {
1313            return;
1314        }
1315        sendMessage(obtainMessage(RECEIVED_TOUCH_ICON_URL,
1316                precomposed ? 1 : 0, 0, url));
1317    }
1318
1319    public void onReceivedTitle(String title) {
1320        // Do an unsynchronized quick check to avoid posting if no callback has
1321        // been set.
1322        if (mWebChromeClient == null) {
1323            return;
1324        }
1325        sendMessage(obtainMessage(RECEIVED_TITLE, title));
1326    }
1327
1328    public void onJsAlert(String url, String message) {
1329        // Do an unsynchronized quick check to avoid posting if no callback has
1330        // been set.
1331        if (mWebChromeClient == null) {
1332            return;
1333        }
1334        JsResultReceiver result = new JsResultReceiver();
1335        Message alert = obtainMessage(JS_ALERT, result);
1336        alert.getData().putString("message", message);
1337        alert.getData().putString("url", url);
1338        sendMessageToUiThreadSync(alert);
1339    }
1340
1341    public boolean onJsConfirm(String url, String message) {
1342        // Do an unsynchronized quick check to avoid posting if no callback has
1343        // been set.
1344        if (mWebChromeClient == null) {
1345            return false;
1346        }
1347        JsResultReceiver result = new JsResultReceiver();
1348        Message confirm = obtainMessage(JS_CONFIRM, result);
1349        confirm.getData().putString("message", message);
1350        confirm.getData().putString("url", url);
1351        sendMessageToUiThreadSync(confirm);
1352        return result.mJsResult.getResult();
1353    }
1354
1355    public String onJsPrompt(String url, String message, String defaultValue) {
1356        // Do an unsynchronized quick check to avoid posting if no callback has
1357        // been set.
1358        if (mWebChromeClient == null) {
1359            return null;
1360        }
1361        JsResultReceiver result = new JsResultReceiver();
1362        Message prompt = obtainMessage(JS_PROMPT, result);
1363        prompt.getData().putString("message", message);
1364        prompt.getData().putString("default", defaultValue);
1365        prompt.getData().putString("url", url);
1366        sendMessageToUiThreadSync(prompt);
1367        return result.mJsResult.getStringResult();
1368    }
1369
1370    public boolean onJsBeforeUnload(String url, String message) {
1371        // Do an unsynchronized quick check to avoid posting if no callback has
1372        // been set.
1373        if (mWebChromeClient == null) {
1374            return true;
1375        }
1376        JsResultReceiver result = new JsResultReceiver();
1377        Message confirm = obtainMessage(JS_UNLOAD, result);
1378        confirm.getData().putString("message", message);
1379        confirm.getData().putString("url", url);
1380        sendMessageToUiThreadSync(confirm);
1381        return result.mJsResult.getResult();
1382    }
1383
1384    /**
1385     * Called by WebViewCore to inform the Java side that the current origin
1386     * has overflowed it's database quota. Called in the WebCore thread so
1387     * posts a message to the UI thread that will prompt the WebChromeClient
1388     * for what to do. On return back to C++ side, the WebCore thread will
1389     * sleep pending a new quota value.
1390     * @param url The URL that caused the quota overflow.
1391     * @param databaseIdentifier The identifier of the database that the
1392     *     transaction that caused the overflow was running on.
1393     * @param quota The current quota the origin is allowed.
1394     * @param estimatedDatabaseSize The estimated size of the database.
1395     * @param totalQuota is the sum of all origins' quota.
1396     * @param quotaUpdater An instance of a class encapsulating a callback
1397     *     to WebViewCore to run when the decision to allow or deny more
1398     *     quota has been made.
1399     */
1400    public void onExceededDatabaseQuota(
1401            String url, String databaseIdentifier, long quota,
1402            long estimatedDatabaseSize, long totalQuota,
1403            WebStorage.QuotaUpdater quotaUpdater) {
1404        if (mWebChromeClient == null) {
1405            // Native-side logic prevents the quota being updated to a smaller
1406            // value.
1407            quotaUpdater.updateQuota(quota);
1408            return;
1409        }
1410
1411        Message exceededQuota = obtainMessage(EXCEEDED_DATABASE_QUOTA);
1412        HashMap<String, Object> map = new HashMap();
1413        map.put("databaseIdentifier", databaseIdentifier);
1414        map.put("url", url);
1415        map.put("quota", quota);
1416        map.put("estimatedDatabaseSize", estimatedDatabaseSize);
1417        map.put("totalQuota", totalQuota);
1418        map.put("quotaUpdater", quotaUpdater);
1419        exceededQuota.obj = map;
1420        sendMessage(exceededQuota);
1421    }
1422
1423    /**
1424     * Called by WebViewCore to inform the Java side that the appcache has
1425     * exceeded its max size.
1426     * @param requiredStorage is the amount of storage, in bytes, that would be
1427     * needed in order for the last appcache operation to succeed.
1428     * @param quota is the current quota (for all origins).
1429     * @param quotaUpdater An instance of a class encapsulating a callback
1430     * to WebViewCore to run when the decision to allow or deny a bigger
1431     * app cache size has been made.
1432     */
1433    public void onReachedMaxAppCacheSize(long requiredStorage,
1434            long quota, WebStorage.QuotaUpdater quotaUpdater) {
1435        if (mWebChromeClient == null) {
1436            // Native-side logic prevents the quota being updated to a smaller
1437            // value.
1438            quotaUpdater.updateQuota(quota);
1439            return;
1440        }
1441
1442        Message msg = obtainMessage(REACHED_APPCACHE_MAXSIZE);
1443        HashMap<String, Object> map = new HashMap();
1444        map.put("requiredStorage", requiredStorage);
1445        map.put("quota", quota);
1446        map.put("quotaUpdater", quotaUpdater);
1447        msg.obj = map;
1448        sendMessage(msg);
1449    }
1450
1451    /**
1452     * Called by WebViewCore to instruct the browser to display a prompt to ask
1453     * the user to set the Geolocation permission state for the given origin.
1454     * @param origin The origin requesting Geolocation permsissions.
1455     * @param callback The callback to call once a permission state has been
1456     *     obtained.
1457     */
1458    public void onGeolocationPermissionsShowPrompt(String origin,
1459            GeolocationPermissions.Callback callback) {
1460        if (mWebChromeClient == null) {
1461            return;
1462        }
1463
1464        Message showMessage =
1465                obtainMessage(GEOLOCATION_PERMISSIONS_SHOW_PROMPT);
1466        HashMap<String, Object> map = new HashMap();
1467        map.put("origin", origin);
1468        map.put("callback", callback);
1469        showMessage.obj = map;
1470        sendMessage(showMessage);
1471    }
1472
1473    /**
1474     * Called by WebViewCore to instruct the browser to hide the Geolocation
1475     * permissions prompt.
1476     */
1477    public void onGeolocationPermissionsHidePrompt() {
1478        if (mWebChromeClient == null) {
1479            return;
1480        }
1481
1482        Message hideMessage = obtainMessage(GEOLOCATION_PERMISSIONS_HIDE_PROMPT);
1483        sendMessage(hideMessage);
1484    }
1485
1486    /**
1487     * Called by WebViewCore when we have a message to be added to the JavaScript
1488     * error console. Sends a message to the Java side with the details.
1489     * @param message The message to add to the console.
1490     * @param lineNumber The lineNumber of the source file on which the error
1491     *     occurred.
1492     * @param sourceID The filename of the source file in which the error
1493     *     occurred.
1494     * @param msgLevel The message level, corresponding to the MessageLevel enum in
1495     *     WebCore/page/Console.h
1496     */
1497    public void addMessageToConsole(String message, int lineNumber, String sourceID, int msgLevel) {
1498        if (mWebChromeClient == null) {
1499            return;
1500        }
1501
1502        Message msg = obtainMessage(ADD_MESSAGE_TO_CONSOLE);
1503        msg.getData().putString("message", message);
1504        msg.getData().putString("sourceID", sourceID);
1505        msg.getData().putInt("lineNumber", lineNumber);
1506        msg.getData().putInt("msgLevel", msgLevel);
1507        sendMessage(msg);
1508    }
1509
1510    public boolean onJsTimeout() {
1511        //always interrupt timedout JS by default
1512        if (mWebChromeClient == null) {
1513            return true;
1514        }
1515        JsResultReceiver result = new JsResultReceiver();
1516        Message timeout = obtainMessage(JS_TIMEOUT, result);
1517        sendMessageToUiThreadSync(timeout);
1518        return result.mJsResult.getResult();
1519    }
1520
1521    public void getVisitedHistory(ValueCallback<String[]> callback) {
1522        if (mWebChromeClient == null) {
1523            return;
1524        }
1525        Message msg = obtainMessage(GET_VISITED_HISTORY);
1526        msg.obj = callback;
1527        sendMessage(msg);
1528    }
1529
1530    private static class UploadFileMessageData {
1531        private UploadFile mCallback;
1532        private String mAcceptType;
1533        private String mCapture;
1534
1535        public UploadFileMessageData(UploadFile uploadFile, String acceptType, String capture) {
1536            mCallback = uploadFile;
1537            mAcceptType = acceptType;
1538            mCapture = capture;
1539        }
1540
1541        public UploadFile getUploadFile() {
1542            return mCallback;
1543        }
1544
1545        public String getAcceptType() {
1546            return mAcceptType;
1547        }
1548
1549        public String getCapture() {
1550            return mCapture;
1551        }
1552    }
1553
1554    private class UploadFile implements ValueCallback<Uri> {
1555        private Uri mValue;
1556        public void onReceiveValue(Uri value) {
1557            mValue = value;
1558            synchronized (CallbackProxy.this) {
1559                CallbackProxy.this.notify();
1560            }
1561        }
1562        public Uri getResult() {
1563            return mValue;
1564        }
1565    }
1566
1567    /**
1568     * Called by WebViewCore to open a file chooser.
1569     */
1570    /* package */ Uri openFileChooser(String acceptType, String capture) {
1571        if (mWebChromeClient == null) {
1572            return null;
1573        }
1574        Message myMessage = obtainMessage(OPEN_FILE_CHOOSER);
1575        UploadFile uploadFile = new UploadFile();
1576        UploadFileMessageData data = new UploadFileMessageData(uploadFile, acceptType, capture);
1577        myMessage.obj = data;
1578        sendMessageToUiThreadSync(myMessage);
1579        return uploadFile.getResult();
1580    }
1581
1582    void onNewHistoryItem(WebHistoryItem item) {
1583        if (mWebBackForwardListClient == null) {
1584            return;
1585        }
1586        Message msg = obtainMessage(ADD_HISTORY_ITEM, item);
1587        sendMessage(msg);
1588    }
1589
1590    void onIndexChanged(WebHistoryItem item, int index) {
1591        if (mWebBackForwardListClient == null) {
1592            return;
1593        }
1594        Message msg = obtainMessage(HISTORY_INDEX_CHANGED, index, 0, item);
1595        sendMessage(msg);
1596    }
1597
1598    boolean canShowAlertDialog() {
1599        // We can only display the alert dialog if mContext is
1600        // an Activity context.
1601        // FIXME: Should we display dialogs if mContext does
1602        // not have the window focus (e.g. if the user is viewing
1603        // another Activity when the alert should be displayed?
1604        // See bug 3166409
1605        return mContext instanceof Activity;
1606    }
1607
1608    private synchronized void sendMessageToUiThreadSync(Message msg) {
1609        sendMessage(msg);
1610        WebCoreThreadWatchdog.pause();
1611        try {
1612            wait();
1613        } catch (InterruptedException e) {
1614            Log.e(LOGTAG, "Caught exception waiting for synchronous UI message to be processed");
1615            Log.e(LOGTAG, Log.getStackTraceString(e));
1616        }
1617        WebCoreThreadWatchdog.resume();
1618    }
1619}
1620