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