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