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