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