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