CallbackProxy.java revision 94ab3b6d0e9a5a914c7706ed1888c9d27756f7e4
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     *@hide pending API council approval.
175     */
176    public WebChromeClient getWebChromeClient() {
177       return mWebChromeClient;
178    }
179
180    /**
181     * Set the client DownloadListener.
182     * @param client An implementation of DownloadListener.
183     */
184    public void setDownloadListener(DownloadListener client) {
185        mDownloadListener = client;
186    }
187
188    /**
189     * Get the Back/Forward list to return to the user or to update the cached
190     * history list.
191     */
192    public WebBackForwardList getBackForwardList() {
193        return mBackForwardList;
194    }
195
196    /**
197     * Called by the UI side.  Calling overrideUrlLoading from the WebCore
198     * side will post a message to call this method.
199     */
200    public boolean uiOverrideUrlLoading(String overrideUrl) {
201        if (overrideUrl == null || overrideUrl.length() == 0) {
202            return false;
203        }
204        boolean override = false;
205        if (mWebViewClient != null) {
206            override = mWebViewClient.shouldOverrideUrlLoading(mWebView,
207                    overrideUrl);
208        } else {
209            Intent intent = new Intent(Intent.ACTION_VIEW,
210                    Uri.parse(overrideUrl));
211            intent.addCategory(Intent.CATEGORY_BROWSABLE);
212            // If another application is running a WebView and launches the
213            // Browser through this Intent, we want to reuse the same window if
214            // possible.
215            intent.putExtra(Browser.EXTRA_APPLICATION_ID,
216                    mContext.getPackageName());
217            try {
218                mContext.startActivity(intent);
219                override = true;
220            } catch (ActivityNotFoundException ex) {
221                // If no application can handle the URL, assume that the
222                // browser can handle it.
223            }
224        }
225        return override;
226    }
227
228    /**
229     * Called by UI side.
230     */
231    public boolean uiOverrideKeyEvent(KeyEvent event) {
232        if (mWebViewClient != null) {
233            return mWebViewClient.shouldOverrideKeyEvent(mWebView, event);
234        }
235        return false;
236    }
237
238    @Override
239    public void handleMessage(Message msg) {
240        // We don't have to do synchronization because this function operates
241        // in the UI thread. The WebViewClient and WebChromeClient functions
242        // that check for a non-null callback are ok because java ensures atomic
243        // 32-bit reads and writes.
244        switch (msg.what) {
245            case PAGE_STARTED:
246                if (mWebViewClient != null) {
247                    mWebViewClient.onPageStarted(mWebView,
248                            msg.getData().getString("url"),
249                            (Bitmap) msg.obj);
250                }
251                break;
252
253            case PAGE_FINISHED:
254                if (mWebViewClient != null) {
255                    mWebViewClient.onPageFinished(mWebView, (String) msg.obj);
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        // Do an unsynchronized quick check to avoid posting if no callback has
781        // been set.
782        if (mWebViewClient == null) {
783            return;
784        }
785        // Performance probe
786        if (PERF_PROBE) {
787            // un-comment this if PERF_PROBE is true
788//            Looper.myQueue().setWaitCallback(null);
789            Log.d("WebCore", "WebCore thread used " +
790                    (SystemClock.currentThreadTimeMillis() - mWebCoreThreadTime)
791                    + " ms and idled " + mWebCoreIdleTime + " ms");
792            Network.getInstance(mContext).stopTiming();
793        }
794        Message msg = obtainMessage(PAGE_FINISHED, url);
795        sendMessage(msg);
796    }
797
798    public void onTooManyRedirects(Message cancelMsg, Message continueMsg) {
799        // Do an unsynchronized quick check to avoid posting if no callback has
800        // been set.
801        if (mWebViewClient == null) {
802            cancelMsg.sendToTarget();
803            return;
804        }
805
806        Message msg = obtainMessage(TOO_MANY_REDIRECTS);
807        Bundle bundle = msg.getData();
808        bundle.putParcelable("cancelMsg", cancelMsg);
809        bundle.putParcelable("continueMsg", continueMsg);
810        sendMessage(msg);
811    }
812
813    public void onReceivedError(int errorCode, String description,
814            String failingUrl) {
815        // Do an unsynchronized quick check to avoid posting if no callback has
816        // been set.
817        if (mWebViewClient == null) {
818            return;
819        }
820
821        Message msg = obtainMessage(REPORT_ERROR);
822        msg.arg1 = errorCode;
823        msg.getData().putString("description", description);
824        msg.getData().putString("failingUrl", failingUrl);
825        sendMessage(msg);
826    }
827
828    public void onFormResubmission(Message dontResend,
829            Message resend) {
830        // Do an unsynchronized quick check to avoid posting if no callback has
831        // been set.
832        if (mWebViewClient == null) {
833            dontResend.sendToTarget();
834            return;
835        }
836
837        Message msg = obtainMessage(RESEND_POST_DATA);
838        Bundle bundle = msg.getData();
839        bundle.putParcelable("resend", resend);
840        bundle.putParcelable("dontResend", dontResend);
841        sendMessage(msg);
842    }
843
844    /**
845     * Called by the WebCore side
846     */
847    public boolean shouldOverrideUrlLoading(String url) {
848        // We have a default behavior if no client exists so always send the
849        // message.
850        ResultTransport<Boolean> res = new ResultTransport<Boolean>(false);
851        Message msg = obtainMessage(OVERRIDE_URL);
852        msg.getData().putString("url", url);
853        msg.obj = res;
854        synchronized (this) {
855            sendMessage(msg);
856            try {
857                wait();
858            } catch (InterruptedException e) {
859                Log.e(LOGTAG, "Caught exception while waiting for overrideUrl");
860                Log.e(LOGTAG, Log.getStackTraceString(e));
861            }
862        }
863        return res.getResult().booleanValue();
864    }
865
866    public void onReceivedHttpAuthRequest(HttpAuthHandler handler,
867            String hostName, String realmName) {
868        // Do an unsynchronized quick check to avoid posting if no callback has
869        // been set.
870        if (mWebViewClient == null) {
871            handler.cancel();
872            return;
873        }
874        Message msg = obtainMessage(AUTH_REQUEST, handler);
875        msg.getData().putString("host", hostName);
876        msg.getData().putString("realm", realmName);
877        sendMessage(msg);
878    }
879    /**
880     * @hide - hide this because it contains a parameter of type SslError.
881     * SslError is located in a hidden package.
882     */
883    public void onReceivedSslError(SslErrorHandler handler, SslError error) {
884        // Do an unsynchronized quick check to avoid posting if no callback has
885        // been set.
886        if (mWebViewClient == null) {
887            handler.cancel();
888            return;
889        }
890        Message msg = obtainMessage(SSL_ERROR);
891        //, handler);
892        HashMap<String, Object> map = new HashMap();
893        map.put("handler", handler);
894        map.put("error", error);
895        msg.obj = map;
896        sendMessage(msg);
897    }
898    /**
899     * @hide - hide this because it contains a parameter of type SslCertificate,
900     * which is located in a hidden package.
901     */
902
903    public void onReceivedCertificate(SslCertificate certificate) {
904        // Do an unsynchronized quick check to avoid posting if no callback has
905        // been set.
906        if (mWebViewClient == null) {
907            return;
908        }
909        // here, certificate can be null (if the site is not secure)
910        sendMessage(obtainMessage(RECEIVED_CERTIFICATE, certificate));
911    }
912
913    public void doUpdateVisitedHistory(String url, boolean isReload) {
914        // Do an unsynchronized quick check to avoid posting if no callback has
915        // been set.
916        if (mWebViewClient == null) {
917            return;
918        }
919        sendMessage(obtainMessage(UPDATE_VISITED, isReload ? 1 : 0, 0, url));
920    }
921
922    public void onLoadResource(String url) {
923        // Do an unsynchronized quick check to avoid posting if no callback has
924        // been set.
925        if (mWebViewClient == null) {
926            return;
927        }
928        sendMessage(obtainMessage(LOAD_RESOURCE, url));
929    }
930
931    public void onUnhandledKeyEvent(KeyEvent event) {
932        // Do an unsynchronized quick check to avoid posting if no callback has
933        // been set.
934        if (mWebViewClient == null) {
935            return;
936        }
937        sendMessage(obtainMessage(ASYNC_KEYEVENTS, event));
938    }
939
940    public void onScaleChanged(float oldScale, float newScale) {
941        // Do an unsynchronized quick check to avoid posting if no callback has
942        // been set.
943        if (mWebViewClient == null) {
944            return;
945        }
946        Message msg = obtainMessage(SCALE_CHANGED);
947        Bundle bundle = msg.getData();
948        bundle.putFloat("old", oldScale);
949        bundle.putFloat("new", newScale);
950        sendMessage(msg);
951    }
952
953    //--------------------------------------------------------------------------
954    // DownloadListener functions.
955    //--------------------------------------------------------------------------
956
957    /**
958     * Starts a download if a download listener has been registered, otherwise
959     * return false.
960     */
961    public boolean onDownloadStart(String url, String userAgent,
962            String contentDisposition, String mimetype, long contentLength) {
963        // Do an unsynchronized quick check to avoid posting if no callback has
964        // been set.
965        if (mDownloadListener == null) {
966            // Cancel the download if there is no browser client.
967            return false;
968        }
969
970        Message msg = obtainMessage(DOWNLOAD_FILE);
971        Bundle bundle = msg.getData();
972        bundle.putString("url", url);
973        bundle.putString("userAgent", userAgent);
974        bundle.putString("mimetype", mimetype);
975        bundle.putLong("contentLength", contentLength);
976        bundle.putString("contentDisposition", contentDisposition);
977        sendMessage(msg);
978        return true;
979    }
980
981
982    //--------------------------------------------------------------------------
983    // WebView specific functions that do not interact with a client. These
984    // functions just need to operate within the UI thread.
985    //--------------------------------------------------------------------------
986
987    public boolean onSavePassword(String schemePlusHost, String username,
988            String password, Message resumeMsg) {
989        // resumeMsg should be null at this point because we want to create it
990        // within the CallbackProxy.
991        if (DebugFlags.CALLBACK_PROXY) {
992            junit.framework.Assert.assertNull(resumeMsg);
993        }
994        resumeMsg = obtainMessage(NOTIFY);
995
996        Message msg = obtainMessage(SAVE_PASSWORD, resumeMsg);
997        Bundle bundle = msg.getData();
998        bundle.putString("host", schemePlusHost);
999        bundle.putString("username", username);
1000        bundle.putString("password", password);
1001        synchronized (this) {
1002            sendMessage(msg);
1003            try {
1004                wait();
1005            } catch (InterruptedException e) {
1006                Log.e(LOGTAG,
1007                        "Caught exception while waiting for onSavePassword");
1008                Log.e(LOGTAG, Log.getStackTraceString(e));
1009            }
1010        }
1011        // Doesn't matter here
1012        return false;
1013    }
1014
1015    //--------------------------------------------------------------------------
1016    // WebChromeClient methods
1017    //--------------------------------------------------------------------------
1018
1019    public void onProgressChanged(int newProgress) {
1020        // Synchronize so that mLatestProgress is up-to-date.
1021        synchronized (this) {
1022            mLatestProgress = newProgress;
1023            if (mWebChromeClient == null) {
1024                return;
1025            }
1026            if (!mProgressUpdatePending) {
1027                sendEmptyMessage(PROGRESS);
1028                mProgressUpdatePending = true;
1029            }
1030        }
1031    }
1032
1033    public WebView createWindow(boolean dialog, boolean userGesture) {
1034        // Do an unsynchronized quick check to avoid posting if no callback has
1035        // been set.
1036        if (mWebChromeClient == null) {
1037            return null;
1038        }
1039
1040        WebView.WebViewTransport transport = mWebView.new WebViewTransport();
1041        final Message msg = obtainMessage(NOTIFY);
1042        msg.obj = transport;
1043        synchronized (this) {
1044            sendMessage(obtainMessage(CREATE_WINDOW, dialog ? 1 : 0,
1045                    userGesture ? 1 : 0, msg));
1046            try {
1047                wait();
1048            } catch (InterruptedException e) {
1049                Log.e(LOGTAG,
1050                        "Caught exception while waiting for createWindow");
1051                Log.e(LOGTAG, Log.getStackTraceString(e));
1052            }
1053        }
1054
1055        WebView w = transport.getWebView();
1056        if (w != null) {
1057            w.getWebViewCore().initializeSubwindow();
1058        }
1059        return w;
1060    }
1061
1062    public void onRequestFocus() {
1063        // Do an unsynchronized quick check to avoid posting if no callback has
1064        // been set.
1065        if (mWebChromeClient == null) {
1066            return;
1067        }
1068
1069        sendEmptyMessage(REQUEST_FOCUS);
1070    }
1071
1072    public void onCloseWindow(WebView window) {
1073        // Do an unsynchronized quick check to avoid posting if no callback has
1074        // been set.
1075        if (mWebChromeClient == null) {
1076            return;
1077        }
1078        sendMessage(obtainMessage(CLOSE_WINDOW, window));
1079    }
1080
1081    public void onReceivedIcon(Bitmap icon) {
1082        // The current item might be null if the icon was already stored in the
1083        // database and this is a new WebView.
1084        WebHistoryItem i = mBackForwardList.getCurrentItem();
1085        if (i != null) {
1086            i.setFavicon(icon);
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_ICON, icon));
1094    }
1095
1096    /* package */ void onReceivedTouchIconUrl(String url, boolean precomposed) {
1097        // We should have a current item but we do not want to crash so check
1098        // for null.
1099        WebHistoryItem i = mBackForwardList.getCurrentItem();
1100        if (i != null) {
1101            if (precomposed || i.getTouchIconUrl() != null) {
1102                i.setTouchIconUrl(url);
1103            }
1104        }
1105        // Do an unsynchronized quick check to avoid posting if no callback has
1106        // been set.
1107        if (mWebChromeClient == null) {
1108            return;
1109        }
1110        sendMessage(obtainMessage(RECEIVED_TOUCH_ICON_URL,
1111                precomposed ? 1 : 0, 0, url));
1112    }
1113
1114    public void onReceivedTitle(String title) {
1115        // Do an unsynchronized quick check to avoid posting if no callback has
1116        // been set.
1117        if (mWebChromeClient == null) {
1118            return;
1119        }
1120        sendMessage(obtainMessage(RECEIVED_TITLE, title));
1121    }
1122
1123    public void onJsAlert(String url, String message) {
1124        // Do an unsynchronized quick check to avoid posting if no callback has
1125        // been set.
1126        if (mWebChromeClient == null) {
1127            return;
1128        }
1129        JsResult result = new JsResult(this, false);
1130        Message alert = obtainMessage(JS_ALERT, result);
1131        alert.getData().putString("message", message);
1132        alert.getData().putString("url", url);
1133        synchronized (this) {
1134            sendMessage(alert);
1135            try {
1136                wait();
1137            } catch (InterruptedException e) {
1138                Log.e(LOGTAG, "Caught exception while waiting for jsAlert");
1139                Log.e(LOGTAG, Log.getStackTraceString(e));
1140            }
1141        }
1142    }
1143
1144    public boolean onJsConfirm(String url, String message) {
1145        // Do an unsynchronized quick check to avoid posting if no callback has
1146        // been set.
1147        if (mWebChromeClient == null) {
1148            return false;
1149        }
1150        JsResult result = new JsResult(this, false);
1151        Message confirm = obtainMessage(JS_CONFIRM, result);
1152        confirm.getData().putString("message", message);
1153        confirm.getData().putString("url", url);
1154        synchronized (this) {
1155            sendMessage(confirm);
1156            try {
1157                wait();
1158            } catch (InterruptedException e) {
1159                Log.e(LOGTAG, "Caught exception while waiting for jsConfirm");
1160                Log.e(LOGTAG, Log.getStackTraceString(e));
1161            }
1162        }
1163        return result.getResult();
1164    }
1165
1166    public String onJsPrompt(String url, String message, String defaultValue) {
1167        // Do an unsynchronized quick check to avoid posting if no callback has
1168        // been set.
1169        if (mWebChromeClient == null) {
1170            return null;
1171        }
1172        JsPromptResult result = new JsPromptResult(this);
1173        Message prompt = obtainMessage(JS_PROMPT, result);
1174        prompt.getData().putString("message", message);
1175        prompt.getData().putString("default", defaultValue);
1176        prompt.getData().putString("url", url);
1177        synchronized (this) {
1178            sendMessage(prompt);
1179            try {
1180                wait();
1181            } catch (InterruptedException e) {
1182                Log.e(LOGTAG, "Caught exception while waiting for jsPrompt");
1183                Log.e(LOGTAG, Log.getStackTraceString(e));
1184            }
1185        }
1186        return result.getStringResult();
1187    }
1188
1189    public boolean onJsBeforeUnload(String url, String message) {
1190        // Do an unsynchronized quick check to avoid posting if no callback has
1191        // been set.
1192        if (mWebChromeClient == null) {
1193            return true;
1194        }
1195        JsResult result = new JsResult(this, true);
1196        Message confirm = obtainMessage(JS_UNLOAD, result);
1197        confirm.getData().putString("message", message);
1198        confirm.getData().putString("url", url);
1199        synchronized (this) {
1200            sendMessage(confirm);
1201            try {
1202                wait();
1203            } catch (InterruptedException e) {
1204                Log.e(LOGTAG, "Caught exception while waiting for jsUnload");
1205                Log.e(LOGTAG, Log.getStackTraceString(e));
1206            }
1207        }
1208        return result.getResult();
1209    }
1210
1211    /**
1212     * Called by WebViewCore to inform the Java side that the current origin
1213     * has overflowed it's database quota. Called in the WebCore thread so
1214     * posts a message to the UI thread that will prompt the WebChromeClient
1215     * for what to do. On return back to C++ side, the WebCore thread will
1216     * sleep pending a new quota value.
1217     * @param url The URL that caused the quota overflow.
1218     * @param databaseIdentifier The identifier of the database that the
1219     *     transaction that caused the overflow was running on.
1220     * @param currentQuota The current quota the origin is allowed.
1221     * @param estimatedSize The estimated size of the database.
1222     * @param totalUsedQuota is the sum of all origins' quota.
1223     * @param quotaUpdater An instance of a class encapsulating a callback
1224     *     to WebViewCore to run when the decision to allow or deny more
1225     *     quota has been made.
1226     */
1227    public void onExceededDatabaseQuota(
1228            String url, String databaseIdentifier, long currentQuota,
1229            long estimatedSize, long totalUsedQuota,
1230            WebStorage.QuotaUpdater quotaUpdater) {
1231        if (mWebChromeClient == null) {
1232            quotaUpdater.updateQuota(currentQuota);
1233            return;
1234        }
1235
1236        Message exceededQuota = obtainMessage(EXCEEDED_DATABASE_QUOTA);
1237        HashMap<String, Object> map = new HashMap();
1238        map.put("databaseIdentifier", databaseIdentifier);
1239        map.put("url", url);
1240        map.put("currentQuota", currentQuota);
1241        map.put("estimatedSize", estimatedSize);
1242        map.put("totalUsedQuota", totalUsedQuota);
1243        map.put("quotaUpdater", quotaUpdater);
1244        exceededQuota.obj = map;
1245        sendMessage(exceededQuota);
1246    }
1247
1248    /**
1249     * Called by WebViewCore to inform the Java side that the appcache has
1250     * exceeded its max size.
1251     * @param spaceNeeded is the amount of disk space that would be needed
1252     * in order for the last appcache operation to succeed.
1253     * @param totalUsedQuota is the sum of all origins' quota.
1254     * @param quotaUpdater An instance of a class encapsulating a callback
1255     * to WebViewCore to run when the decision to allow or deny a bigger
1256     * app cache size has been made.
1257     * @hide pending API council approval.
1258     */
1259    public void onReachedMaxAppCacheSize(long spaceNeeded,
1260            long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
1261        if (mWebChromeClient == null) {
1262            quotaUpdater.updateQuota(0);
1263            return;
1264        }
1265
1266        Message msg = obtainMessage(REACHED_APPCACHE_MAXSIZE);
1267        HashMap<String, Object> map = new HashMap();
1268        map.put("spaceNeeded", spaceNeeded);
1269        map.put("totalUsedQuota", totalUsedQuota);
1270        map.put("quotaUpdater", quotaUpdater);
1271        msg.obj = map;
1272        sendMessage(msg);
1273    }
1274
1275    /**
1276     * Called by WebViewCore to instruct the browser to display a prompt to ask
1277     * the user to set the Geolocation permission state for the given origin.
1278     * @param origin The origin requesting Geolocation permsissions.
1279     * @param callback The callback to call once a permission state has been
1280     *     obtained.
1281     * @hide pending API council review.
1282     */
1283    public void onGeolocationPermissionsShowPrompt(String origin,
1284            GeolocationPermissions.Callback callback) {
1285        if (mWebChromeClient == null) {
1286            return;
1287        }
1288
1289        Message showMessage =
1290                obtainMessage(GEOLOCATION_PERMISSIONS_SHOW_PROMPT);
1291        HashMap<String, Object> map = new HashMap();
1292        map.put("origin", origin);
1293        map.put("callback", callback);
1294        showMessage.obj = map;
1295        sendMessage(showMessage);
1296    }
1297
1298    /**
1299     * Called by WebViewCore to instruct the browser to hide the Geolocation
1300     * permissions prompt.
1301     * origin.
1302     * @hide pending API council review.
1303     */
1304    public void onGeolocationPermissionsHidePrompt() {
1305        if (mWebChromeClient == null) {
1306            return;
1307        }
1308
1309        Message hideMessage = obtainMessage(GEOLOCATION_PERMISSIONS_HIDE_PROMPT);
1310        sendMessage(hideMessage);
1311    }
1312
1313    /**
1314     * Called by WebViewCore when we have a message to be added to the JavaScript
1315     * error console. Sends a message to the Java side with the details.
1316     * @param message The message to add to the console.
1317     * @param lineNumber The lineNumber of the source file on which the error
1318     *     occurred.
1319     * @param sourceID The filename of the source file in which the error
1320     *     occurred.
1321     * @hide pending API counsel.
1322     */
1323    public void addMessageToConsole(String message, int lineNumber, String sourceID) {
1324        if (mWebChromeClient == null) {
1325            return;
1326        }
1327
1328        Message msg = obtainMessage(ADD_MESSAGE_TO_CONSOLE);
1329        msg.getData().putString("message", message);
1330        msg.getData().putString("sourceID", sourceID);
1331        msg.getData().putInt("lineNumber", lineNumber);
1332        sendMessage(msg);
1333    }
1334
1335    /**
1336     * @hide pending API council approval
1337     */
1338    public boolean onJsTimeout() {
1339        //always interrupt timedout JS by default
1340        if (mWebChromeClient == null) {
1341            return true;
1342        }
1343        JsResult result = new JsResult(this, true);
1344        Message timeout = obtainMessage(JS_TIMEOUT, result);
1345        synchronized (this) {
1346            sendMessage(timeout);
1347            try {
1348                wait();
1349            } catch (InterruptedException e) {
1350                Log.e(LOGTAG, "Caught exception while waiting for jsUnload");
1351                Log.e(LOGTAG, Log.getStackTraceString(e));
1352            }
1353        }
1354        return result.getResult();
1355    }
1356
1357    /**
1358     * @hide pending API council approval
1359     */
1360    public void getVisitedHistory(ValueCallback<String[]> callback) {
1361        if (mWebChromeClient == null) {
1362            return;
1363        }
1364        Message msg = obtainMessage(GET_VISITED_HISTORY);
1365        msg.obj = callback;
1366        sendMessage(msg);
1367    }
1368
1369    private class UploadFile implements ValueCallback<Uri> {
1370        private Uri mValue;
1371        public void onReceiveValue(Uri value) {
1372            mValue = value;
1373            synchronized (CallbackProxy.this) {
1374                CallbackProxy.this.notify();
1375            }
1376        }
1377        public Uri getResult() {
1378            return mValue;
1379        }
1380    }
1381
1382    /**
1383     * Called by WebViewCore to open a file chooser.
1384     */
1385    /* package */ Uri openFileChooser() {
1386        if (mWebChromeClient == null) {
1387            return null;
1388        }
1389        Message myMessage = obtainMessage(OPEN_FILE_CHOOSER);
1390        UploadFile uploadFile = new UploadFile();
1391        myMessage.obj = uploadFile;
1392        synchronized (this) {
1393            sendMessage(myMessage);
1394            try {
1395                wait();
1396            } catch (InterruptedException e) {
1397                Log.e(LOGTAG,
1398                        "Caught exception while waiting for openFileChooser");
1399                Log.e(LOGTAG, Log.getStackTraceString(e));
1400            }
1401        }
1402        return uploadFile.getResult();
1403    }
1404}
1405