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