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