CallbackProxy.java revision 7df1985e86635af006be3dfa65987d60e290b5de
1/* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.webkit; 18 19import android.app.AlertDialog; 20import android.content.ActivityNotFoundException; 21import android.content.Context; 22import android.content.DialogInterface; 23import android.content.Intent; 24import android.graphics.Bitmap; 25import android.net.Uri; 26import android.net.http.SslCertificate; 27import android.net.http.SslError; 28import android.os.Bundle; 29import android.os.Handler; 30import android.os.Message; 31import android.os.SystemClock; 32import android.provider.Browser; 33import android.util.Log; 34import android.view.KeyEvent; 35import android.view.LayoutInflater; 36import android.view.View; 37import android.widget.EditText; 38import android.widget.TextView; 39import com.android.internal.R; 40 41import java.net.MalformedURLException; 42import java.net.URL; 43import java.util.HashMap; 44 45/** 46 * This class is a proxy class for handling WebCore -> UI thread messaging. All 47 * the callback functions are called from the WebCore thread and messages are 48 * posted to the UI thread for the actual client callback. 49 */ 50/* 51 * This class is created in the UI thread so its handler and any private classes 52 * that extend Handler will operate in the UI thread. 53 */ 54class CallbackProxy extends Handler { 55 // Logging tag 56 private static final String LOGTAG = "CallbackProxy"; 57 // Instance of WebViewClient that is the client callback. 58 private volatile WebViewClient mWebViewClient; 59 // Instance of WebChromeClient for handling all chrome functions. 60 private volatile WebChromeClient mWebChromeClient; 61 // Instance of WebView for handling UI requests. 62 private final WebView mWebView; 63 // Client registered callback listener for download events 64 private volatile DownloadListener mDownloadListener; 65 // Keep track of multiple progress updates. 66 private boolean mProgressUpdatePending; 67 // Keep track of the last progress amount. 68 private volatile int mLatestProgress; 69 // Back/Forward list 70 private final WebBackForwardList mBackForwardList; 71 // Used to call startActivity during url override. 72 private final Context mContext; 73 74 // Message Ids 75 private static final int PAGE_STARTED = 100; 76 private static final int RECEIVED_ICON = 101; 77 private static final int RECEIVED_TITLE = 102; 78 private static final int OVERRIDE_URL = 103; 79 private static final int AUTH_REQUEST = 104; 80 private static final int SSL_ERROR = 105; 81 private static final int PROGRESS = 106; 82 private static final int UPDATE_VISITED = 107; 83 private static final int LOAD_RESOURCE = 108; 84 private static final int CREATE_WINDOW = 109; 85 private static final int CLOSE_WINDOW = 110; 86 private static final int SAVE_PASSWORD = 111; 87 private static final int JS_ALERT = 112; 88 private static final int JS_CONFIRM = 113; 89 private static final int JS_PROMPT = 114; 90 private static final int JS_UNLOAD = 115; 91 private static final int ASYNC_KEYEVENTS = 116; 92 private static final int TOO_MANY_REDIRECTS = 117; 93 private static final int DOWNLOAD_FILE = 118; 94 private static final int REPORT_ERROR = 119; 95 private static final int RESEND_POST_DATA = 120; 96 private static final int PAGE_FINISHED = 121; 97 private static final int REQUEST_FOCUS = 122; 98 private static final int SCALE_CHANGED = 123; 99 private static final int RECEIVED_CERTIFICATE = 124; 100 private static final int SWITCH_OUT_HISTORY = 125; 101 private static final int EXCEEDED_DATABASE_QUOTA = 126; 102 103 // Message triggered by the client to resume execution 104 private static final int NOTIFY = 200; 105 106 // Result transportation object for returning results across thread 107 // boundaries. 108 private class ResultTransport<E> { 109 // Private result object 110 private E mResult; 111 112 public synchronized void setResult(E result) { 113 mResult = result; 114 } 115 116 public synchronized E getResult() { 117 return mResult; 118 } 119 } 120 121 /** 122 * Construct a new CallbackProxy. 123 */ 124 public CallbackProxy(Context context, WebView w) { 125 // Used to start a default activity. 126 mContext = context; 127 mWebView = w; 128 mBackForwardList = new WebBackForwardList(); 129 } 130 131 /** 132 * Set the WebViewClient. 133 * @param client An implementation of WebViewClient. 134 */ 135 public void setWebViewClient(WebViewClient client) { 136 mWebViewClient = client; 137 } 138 139 /** 140 * Set the WebChromeClient. 141 * @param client An implementation of WebChromeClient. 142 */ 143 public void setWebChromeClient(WebChromeClient client) { 144 mWebChromeClient = client; 145 } 146 147 /** 148 * Set the client DownloadListener. 149 * @param client An implementation of DownloadListener. 150 */ 151 public void setDownloadListener(DownloadListener client) { 152 mDownloadListener = client; 153 } 154 155 /** 156 * Get the Back/Forward list to return to the user or to update the cached 157 * history list. 158 */ 159 public WebBackForwardList getBackForwardList() { 160 return mBackForwardList; 161 } 162 163 /** 164 * Called by the UI side. Calling overrideUrlLoading from the WebCore 165 * side will post a message to call this method. 166 */ 167 public boolean uiOverrideUrlLoading(String overrideUrl) { 168 if (overrideUrl == null || overrideUrl.length() == 0) { 169 return false; 170 } 171 boolean override = false; 172 if (mWebViewClient != null) { 173 override = mWebViewClient.shouldOverrideUrlLoading(mWebView, 174 overrideUrl); 175 } else { 176 Intent intent = new Intent(Intent.ACTION_VIEW, 177 Uri.parse(overrideUrl)); 178 intent.addCategory(Intent.CATEGORY_BROWSABLE); 179 // If another application is running a WebView and launches the 180 // Browser through this Intent, we want to reuse the same window if 181 // possible. 182 intent.putExtra(Browser.EXTRA_APPLICATION_ID, 183 mContext.getPackageName()); 184 try { 185 mContext.startActivity(intent); 186 override = true; 187 } catch (ActivityNotFoundException ex) { 188 // If no application can handle the URL, assume that the 189 // browser can handle it. 190 } 191 } 192 return override; 193 } 194 195 /** 196 * Called by UI side. 197 */ 198 public boolean uiOverrideKeyEvent(KeyEvent event) { 199 if (mWebViewClient != null) { 200 return mWebViewClient.shouldOverrideKeyEvent(mWebView, event); 201 } 202 return false; 203 } 204 205 @Override 206 public void handleMessage(Message msg) { 207 // We don't have to do synchronization because this function operates 208 // in the UI thread. The WebViewClient and WebChromeClient functions 209 // that check for a non-null callback are ok because java ensures atomic 210 // 32-bit reads and writes. 211 switch (msg.what) { 212 case PAGE_STARTED: 213 if (mWebViewClient != null) { 214 mWebViewClient.onPageStarted(mWebView, 215 msg.getData().getString("url"), 216 (Bitmap) msg.obj); 217 } 218 break; 219 220 case PAGE_FINISHED: 221 if (mWebViewClient != null) { 222 mWebViewClient.onPageFinished(mWebView, (String) msg.obj); 223 } 224 break; 225 226 case RECEIVED_ICON: 227 if (mWebChromeClient != null) { 228 mWebChromeClient.onReceivedIcon(mWebView, (Bitmap) msg.obj); 229 } 230 break; 231 232 case RECEIVED_TITLE: 233 if (mWebChromeClient != null) { 234 mWebChromeClient.onReceivedTitle(mWebView, 235 (String) msg.obj); 236 } 237 break; 238 239 case TOO_MANY_REDIRECTS: 240 Message cancelMsg = 241 (Message) msg.getData().getParcelable("cancelMsg"); 242 Message continueMsg = 243 (Message) msg.getData().getParcelable("continueMsg"); 244 if (mWebViewClient != null) { 245 mWebViewClient.onTooManyRedirects(mWebView, cancelMsg, 246 continueMsg); 247 } else { 248 cancelMsg.sendToTarget(); 249 } 250 break; 251 252 case REPORT_ERROR: 253 if (mWebViewClient != null) { 254 int reasonCode = msg.arg1; 255 final String description = msg.getData().getString("description"); 256 final String failUrl = msg.getData().getString("failingUrl"); 257 mWebViewClient.onReceivedError(mWebView, reasonCode, 258 description, failUrl); 259 } 260 break; 261 262 case RESEND_POST_DATA: 263 Message resend = 264 (Message) msg.getData().getParcelable("resend"); 265 Message dontResend = 266 (Message) msg.getData().getParcelable("dontResend"); 267 if (mWebViewClient != null) { 268 mWebViewClient.onFormResubmission(mWebView, dontResend, 269 resend); 270 } else { 271 dontResend.sendToTarget(); 272 } 273 break; 274 275 case OVERRIDE_URL: 276 String overrideUrl = msg.getData().getString("url"); 277 boolean override = uiOverrideUrlLoading(overrideUrl); 278 ResultTransport<Boolean> result = 279 (ResultTransport<Boolean>) msg.obj; 280 synchronized (this) { 281 result.setResult(override); 282 notify(); 283 } 284 break; 285 286 case AUTH_REQUEST: 287 if (mWebViewClient != null) { 288 HttpAuthHandler handler = (HttpAuthHandler) msg.obj; 289 String host = msg.getData().getString("host"); 290 String realm = msg.getData().getString("realm"); 291 mWebViewClient.onReceivedHttpAuthRequest(mWebView, handler, 292 host, realm); 293 } 294 break; 295 296 case SSL_ERROR: 297 if (mWebViewClient != null) { 298 HashMap<String, Object> map = 299 (HashMap<String, Object>) msg.obj; 300 mWebViewClient.onReceivedSslError(mWebView, 301 (SslErrorHandler) map.get("handler"), 302 (SslError) map.get("error")); 303 } 304 break; 305 306 case PROGRESS: 307 // Synchronize to ensure mLatestProgress is not modified after 308 // setProgress is called and before mProgressUpdatePending is 309 // changed. 310 synchronized (this) { 311 if (mWebChromeClient != null) { 312 mWebChromeClient.onProgressChanged(mWebView, 313 mLatestProgress); 314 } 315 mProgressUpdatePending = false; 316 } 317 break; 318 319 case UPDATE_VISITED: 320 if (mWebViewClient != null) { 321 mWebViewClient.doUpdateVisitedHistory(mWebView, 322 (String) msg.obj, msg.arg1 != 0); 323 } 324 break; 325 326 case LOAD_RESOURCE: 327 if (mWebViewClient != null) { 328 mWebViewClient.onLoadResource(mWebView, (String) msg.obj); 329 } 330 break; 331 332 case DOWNLOAD_FILE: 333 if (mDownloadListener != null) { 334 String url = msg.getData().getString("url"); 335 String userAgent = msg.getData().getString("userAgent"); 336 String contentDisposition = 337 msg.getData().getString("contentDisposition"); 338 String mimetype = msg.getData().getString("mimetype"); 339 Long contentLength = msg.getData().getLong("contentLength"); 340 341 mDownloadListener.onDownloadStart(url, userAgent, 342 contentDisposition, mimetype, contentLength); 343 } 344 break; 345 346 case CREATE_WINDOW: 347 if (mWebChromeClient != null) { 348 if (!mWebChromeClient.onCreateWindow(mWebView, 349 msg.arg1 == 1, msg.arg2 == 1, 350 (Message) msg.obj)) { 351 synchronized (this) { 352 notify(); 353 } 354 } 355 } 356 break; 357 358 case REQUEST_FOCUS: 359 if (mWebChromeClient != null) { 360 mWebChromeClient.onRequestFocus(mWebView); 361 } 362 break; 363 364 case CLOSE_WINDOW: 365 if (mWebChromeClient != null) { 366 mWebChromeClient.onCloseWindow((WebView) msg.obj); 367 } 368 break; 369 370 case SAVE_PASSWORD: 371 Bundle bundle = msg.getData(); 372 String schemePlusHost = bundle.getString("host"); 373 String username = bundle.getString("username"); 374 String password = bundle.getString("password"); 375 // If the client returned false it means that the notify message 376 // will not be sent and we should notify WebCore ourselves. 377 if (!mWebView.onSavePassword(schemePlusHost, username, password, 378 (Message) msg.obj)) { 379 synchronized (this) { 380 notify(); 381 } 382 } 383 break; 384 385 case ASYNC_KEYEVENTS: 386 if (mWebViewClient != null) { 387 mWebViewClient.onUnhandledKeyEvent(mWebView, 388 (KeyEvent) msg.obj); 389 } 390 break; 391 392 case EXCEEDED_DATABASE_QUOTA: 393 if (mWebChromeClient != null) { 394 HashMap<String, Object> map = 395 (HashMap<String, Object>) msg.obj; 396 String databaseIdentifier = 397 (String) map.get("databaseIdentifier"); 398 String url = (String) map.get("url"); 399 long currentQuota = 400 ((Long) map.get("currentQuota")).longValue(); 401 WebStorage.QuotaUpdater quotaUpdater = 402 (WebStorage.QuotaUpdater) map.get("quotaUpdater"); 403 404 mWebChromeClient.onExceededDatabaseQuota(url, 405 databaseIdentifier, currentQuota, quotaUpdater); 406 } 407 break; 408 409 case JS_ALERT: 410 if (mWebChromeClient != null) { 411 final JsResult res = (JsResult) msg.obj; 412 String message = msg.getData().getString("message"); 413 String url = msg.getData().getString("url"); 414 if (!mWebChromeClient.onJsAlert(mWebView, url, message, 415 res)) { 416 new AlertDialog.Builder(mContext) 417 .setTitle(getJsDialogTitle(url)) 418 .setMessage(message) 419 .setPositiveButton(R.string.ok, 420 new AlertDialog.OnClickListener() { 421 public void onClick( 422 DialogInterface dialog, 423 int which) { 424 res.confirm(); 425 } 426 }) 427 .setCancelable(false) 428 .show(); 429 } 430 res.setReady(); 431 } 432 break; 433 434 case JS_CONFIRM: 435 if (mWebChromeClient != null) { 436 final JsResult res = (JsResult) msg.obj; 437 String message = msg.getData().getString("message"); 438 String url = msg.getData().getString("url"); 439 if (!mWebChromeClient.onJsConfirm(mWebView, url, message, 440 res)) { 441 new AlertDialog.Builder(mContext) 442 .setTitle(getJsDialogTitle(url)) 443 .setMessage(message) 444 .setPositiveButton(R.string.ok, 445 new DialogInterface.OnClickListener() { 446 public void onClick( 447 DialogInterface dialog, 448 int which) { 449 res.confirm(); 450 }}) 451 .setNegativeButton(R.string.cancel, 452 new DialogInterface.OnClickListener() { 453 public void onClick( 454 DialogInterface dialog, 455 int which) { 456 res.cancel(); 457 }}) 458 .show(); 459 } 460 // Tell the JsResult that it is ready for client 461 // interaction. 462 res.setReady(); 463 } 464 break; 465 466 case JS_PROMPT: 467 if (mWebChromeClient != null) { 468 final JsPromptResult res = (JsPromptResult) msg.obj; 469 String message = msg.getData().getString("message"); 470 String defaultVal = msg.getData().getString("default"); 471 String url = msg.getData().getString("url"); 472 if (!mWebChromeClient.onJsPrompt(mWebView, url, message, 473 defaultVal, res)) { 474 final LayoutInflater factory = LayoutInflater 475 .from(mContext); 476 final View view = factory.inflate(R.layout.js_prompt, 477 null); 478 final EditText v = (EditText) view 479 .findViewById(R.id.value); 480 v.setText(defaultVal); 481 ((TextView) view.findViewById(R.id.message)) 482 .setText(message); 483 new AlertDialog.Builder(mContext) 484 .setTitle(getJsDialogTitle(url)) 485 .setView(view) 486 .setPositiveButton(R.string.ok, 487 new DialogInterface.OnClickListener() { 488 public void onClick( 489 DialogInterface dialog, 490 int whichButton) { 491 res.confirm(v.getText() 492 .toString()); 493 } 494 }) 495 .setNegativeButton(R.string.cancel, 496 new DialogInterface.OnClickListener() { 497 public void onClick( 498 DialogInterface dialog, 499 int whichButton) { 500 res.cancel(); 501 } 502 }) 503 .setOnCancelListener( 504 new DialogInterface.OnCancelListener() { 505 public void onCancel( 506 DialogInterface dialog) { 507 res.cancel(); 508 } 509 }) 510 .show(); 511 } 512 // Tell the JsResult that it is ready for client 513 // interaction. 514 res.setReady(); 515 } 516 break; 517 518 case JS_UNLOAD: 519 if (mWebChromeClient != null) { 520 final JsResult res = (JsResult) msg.obj; 521 String message = msg.getData().getString("message"); 522 String url = msg.getData().getString("url"); 523 if (!mWebChromeClient.onJsBeforeUnload(mWebView, url, 524 message, res)) { 525 final String m = mContext.getString( 526 R.string.js_dialog_before_unload, message); 527 new AlertDialog.Builder(mContext) 528 .setMessage(m) 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 .setNegativeButton(R.string.cancel, 538 new DialogInterface.OnClickListener() { 539 public void onClick( 540 DialogInterface dialog, 541 int which) { 542 res.cancel(); 543 } 544 }) 545 .show(); 546 } 547 res.setReady(); 548 } 549 break; 550 551 case RECEIVED_CERTIFICATE: 552 mWebView.setCertificate((SslCertificate) msg.obj); 553 break; 554 555 case NOTIFY: 556 synchronized (this) { 557 notify(); 558 } 559 break; 560 561 case SCALE_CHANGED: 562 if (mWebViewClient != null) { 563 mWebViewClient.onScaleChanged(mWebView, msg.getData() 564 .getFloat("old"), msg.getData().getFloat("new")); 565 } 566 break; 567 568 case SWITCH_OUT_HISTORY: 569 mWebView.switchOutDrawHistory(); 570 break; 571 } 572 } 573 574 /** 575 * Return the latest progress. 576 */ 577 public int getProgress() { 578 return mLatestProgress; 579 } 580 581 /** 582 * Called by WebCore side to switch out of history Picture drawing mode 583 */ 584 void switchOutDrawHistory() { 585 sendMessage(obtainMessage(SWITCH_OUT_HISTORY)); 586 } 587 588 private String getJsDialogTitle(String url) { 589 String title = url; 590 if (URLUtil.isDataUrl(url)) { 591 // For data: urls, we just display 'JavaScript' similar to Safari. 592 title = mContext.getString(R.string.js_dialog_title_default); 593 } else { 594 try { 595 URL aUrl = new URL(url); 596 // For example: "The page at 'http://www.mit.edu' says:" 597 title = mContext.getString(R.string.js_dialog_title, 598 aUrl.getProtocol() + "://" + aUrl.getHost()); 599 } catch (MalformedURLException ex) { 600 // do nothing. just use the url as the title 601 } 602 } 603 return title; 604 } 605 606 //-------------------------------------------------------------------------- 607 // WebViewClient functions. 608 // NOTE: shouldOverrideKeyEvent is never called from the WebCore thread so 609 // it is not necessary to include it here. 610 //-------------------------------------------------------------------------- 611 612 // Performance probe 613 private long mWebCoreThreadTime; 614 615 public void onPageStarted(String url, Bitmap favicon) { 616 // Do an unsynchronized quick check to avoid posting if no callback has 617 // been set. 618 if (mWebViewClient == null) { 619 return; 620 } 621 // Performance probe 622 if (false) { 623 mWebCoreThreadTime = SystemClock.currentThreadTimeMillis(); 624 Network.getInstance(mContext).startTiming(); 625 } 626 Message msg = obtainMessage(PAGE_STARTED); 627 msg.obj = favicon; 628 msg.getData().putString("url", url); 629 sendMessage(msg); 630 } 631 632 public void onPageFinished(String url) { 633 // Do an unsynchronized quick check to avoid posting if no callback has 634 // been set. 635 if (mWebViewClient == null) { 636 return; 637 } 638 // Performance probe 639 if (false) { 640 Log.d("WebCore", "WebCore thread used " + 641 (SystemClock.currentThreadTimeMillis() - mWebCoreThreadTime) 642 + " ms"); 643 Network.getInstance(mContext).stopTiming(); 644 } 645 Message msg = obtainMessage(PAGE_FINISHED, url); 646 sendMessage(msg); 647 } 648 649 public void onTooManyRedirects(Message cancelMsg, Message continueMsg) { 650 // Do an unsynchronized quick check to avoid posting if no callback has 651 // been set. 652 if (mWebViewClient == null) { 653 cancelMsg.sendToTarget(); 654 return; 655 } 656 657 Message msg = obtainMessage(TOO_MANY_REDIRECTS); 658 Bundle bundle = msg.getData(); 659 bundle.putParcelable("cancelMsg", cancelMsg); 660 bundle.putParcelable("continueMsg", continueMsg); 661 sendMessage(msg); 662 } 663 664 public void onReceivedError(int errorCode, String description, 665 String failingUrl) { 666 // Do an unsynchronized quick check to avoid posting if no callback has 667 // been set. 668 if (mWebViewClient == null) { 669 return; 670 } 671 672 Message msg = obtainMessage(REPORT_ERROR); 673 msg.arg1 = errorCode; 674 msg.getData().putString("description", description); 675 msg.getData().putString("failingUrl", failingUrl); 676 sendMessage(msg); 677 } 678 679 public void onFormResubmission(Message dontResend, 680 Message resend) { 681 // Do an unsynchronized quick check to avoid posting if no callback has 682 // been set. 683 if (mWebViewClient == null) { 684 dontResend.sendToTarget(); 685 return; 686 } 687 688 Message msg = obtainMessage(RESEND_POST_DATA); 689 Bundle bundle = msg.getData(); 690 bundle.putParcelable("resend", resend); 691 bundle.putParcelable("dontResend", dontResend); 692 sendMessage(msg); 693 } 694 695 /** 696 * Called by the WebCore side 697 */ 698 public boolean shouldOverrideUrlLoading(String url) { 699 // We have a default behavior if no client exists so always send the 700 // message. 701 ResultTransport<Boolean> res = new ResultTransport<Boolean>(); 702 Message msg = obtainMessage(OVERRIDE_URL); 703 msg.getData().putString("url", url); 704 msg.obj = res; 705 synchronized (this) { 706 sendMessage(msg); 707 try { 708 wait(); 709 } catch (InterruptedException e) { 710 Log.e(LOGTAG, "Caught exception while waiting for overrideUrl"); 711 Log.e(LOGTAG, Log.getStackTraceString(e)); 712 } 713 } 714 return res.getResult().booleanValue(); 715 } 716 717 public void onReceivedHttpAuthRequest(HttpAuthHandler handler, 718 String hostName, String realmName) { 719 // Do an unsynchronized quick check to avoid posting if no callback has 720 // been set. 721 if (mWebViewClient == null) { 722 handler.cancel(); 723 return; 724 } 725 Message msg = obtainMessage(AUTH_REQUEST, handler); 726 msg.getData().putString("host", hostName); 727 msg.getData().putString("realm", realmName); 728 sendMessage(msg); 729 } 730 /** 731 * @hide - hide this because it contains a parameter of type SslError. 732 * SslError is located in a hidden package. 733 */ 734 public void onReceivedSslError(SslErrorHandler handler, SslError error) { 735 // Do an unsynchronized quick check to avoid posting if no callback has 736 // been set. 737 if (mWebViewClient == null) { 738 handler.cancel(); 739 return; 740 } 741 Message msg = obtainMessage(SSL_ERROR); 742 //, handler); 743 HashMap<String, Object> map = new HashMap(); 744 map.put("handler", handler); 745 map.put("error", error); 746 msg.obj = map; 747 sendMessage(msg); 748 } 749 /** 750 * @hide - hide this because it contains a parameter of type SslCertificate, 751 * which is located in a hidden package. 752 */ 753 754 public void onReceivedCertificate(SslCertificate certificate) { 755 // Do an unsynchronized quick check to avoid posting if no callback has 756 // been set. 757 if (mWebViewClient == null) { 758 return; 759 } 760 // here, certificate can be null (if the site is not secure) 761 sendMessage(obtainMessage(RECEIVED_CERTIFICATE, certificate)); 762 } 763 764 public void doUpdateVisitedHistory(String url, boolean isReload) { 765 // Do an unsynchronized quick check to avoid posting if no callback has 766 // been set. 767 if (mWebViewClient == null) { 768 return; 769 } 770 sendMessage(obtainMessage(UPDATE_VISITED, isReload ? 1 : 0, 0, url)); 771 } 772 773 public void onLoadResource(String url) { 774 // Do an unsynchronized quick check to avoid posting if no callback has 775 // been set. 776 if (mWebViewClient == null) { 777 return; 778 } 779 sendMessage(obtainMessage(LOAD_RESOURCE, url)); 780 } 781 782 public void onUnhandledKeyEvent(KeyEvent event) { 783 // Do an unsynchronized quick check to avoid posting if no callback has 784 // been set. 785 if (mWebViewClient == null) { 786 return; 787 } 788 sendMessage(obtainMessage(ASYNC_KEYEVENTS, event)); 789 } 790 791 public void onScaleChanged(float oldScale, float newScale) { 792 // Do an unsynchronized quick check to avoid posting if no callback has 793 // been set. 794 if (mWebViewClient == null) { 795 return; 796 } 797 Message msg = obtainMessage(SCALE_CHANGED); 798 Bundle bundle = msg.getData(); 799 bundle.putFloat("old", oldScale); 800 bundle.putFloat("new", newScale); 801 sendMessage(msg); 802 } 803 804 //-------------------------------------------------------------------------- 805 // DownloadListener functions. 806 //-------------------------------------------------------------------------- 807 808 /** 809 * Starts a download if a download listener has been registered, otherwise 810 * return false. 811 */ 812 public boolean onDownloadStart(String url, String userAgent, 813 String contentDisposition, String mimetype, long contentLength) { 814 // Do an unsynchronized quick check to avoid posting if no callback has 815 // been set. 816 if (mDownloadListener == null) { 817 // Cancel the download if there is no browser client. 818 return false; 819 } 820 821 Message msg = obtainMessage(DOWNLOAD_FILE); 822 Bundle bundle = msg.getData(); 823 bundle.putString("url", url); 824 bundle.putString("userAgent", userAgent); 825 bundle.putString("mimetype", mimetype); 826 bundle.putLong("contentLength", contentLength); 827 bundle.putString("contentDisposition", contentDisposition); 828 sendMessage(msg); 829 return true; 830 } 831 832 833 //-------------------------------------------------------------------------- 834 // WebView specific functions that do not interact with a client. These 835 // functions just need to operate within the UI thread. 836 //-------------------------------------------------------------------------- 837 838 public boolean onSavePassword(String schemePlusHost, String username, 839 String password, Message resumeMsg) { 840 // resumeMsg should be null at this point because we want to create it 841 // within the CallbackProxy. 842 if (WebView.DEBUG) { 843 junit.framework.Assert.assertNull(resumeMsg); 844 } 845 resumeMsg = obtainMessage(NOTIFY); 846 847 Message msg = obtainMessage(SAVE_PASSWORD, resumeMsg); 848 Bundle bundle = msg.getData(); 849 bundle.putString("host", schemePlusHost); 850 bundle.putString("username", username); 851 bundle.putString("password", password); 852 synchronized (this) { 853 sendMessage(msg); 854 try { 855 wait(); 856 } catch (InterruptedException e) { 857 Log.e(LOGTAG, 858 "Caught exception while waiting for onSavePassword"); 859 Log.e(LOGTAG, Log.getStackTraceString(e)); 860 } 861 } 862 // Doesn't matter here 863 return false; 864 } 865 866 //-------------------------------------------------------------------------- 867 // WebChromeClient methods 868 //-------------------------------------------------------------------------- 869 870 public void onProgressChanged(int newProgress) { 871 // Synchronize so that mLatestProgress is up-to-date. 872 synchronized (this) { 873 mLatestProgress = newProgress; 874 if (mWebChromeClient == null) { 875 return; 876 } 877 if (!mProgressUpdatePending) { 878 sendEmptyMessage(PROGRESS); 879 mProgressUpdatePending = true; 880 } 881 } 882 } 883 884 public WebView createWindow(boolean dialog, boolean userGesture) { 885 // Do an unsynchronized quick check to avoid posting if no callback has 886 // been set. 887 if (mWebChromeClient == null) { 888 return null; 889 } 890 891 WebView.WebViewTransport transport = mWebView.new WebViewTransport(); 892 final Message msg = obtainMessage(NOTIFY); 893 msg.obj = transport; 894 synchronized (this) { 895 sendMessage(obtainMessage(CREATE_WINDOW, dialog ? 1 : 0, 896 userGesture ? 1 : 0, msg)); 897 try { 898 wait(); 899 } catch (InterruptedException e) { 900 Log.e(LOGTAG, 901 "Caught exception while waiting for createWindow"); 902 Log.e(LOGTAG, Log.getStackTraceString(e)); 903 } 904 } 905 906 WebView w = transport.getWebView(); 907 if (w != null) { 908 w.getWebViewCore().initializeSubwindow(); 909 } 910 return w; 911 } 912 913 public void onRequestFocus() { 914 // Do an unsynchronized quick check to avoid posting if no callback has 915 // been set. 916 if (mWebChromeClient == null) { 917 return; 918 } 919 920 sendEmptyMessage(REQUEST_FOCUS); 921 } 922 923 public void onCloseWindow(WebView window) { 924 // Do an unsynchronized quick check to avoid posting if no callback has 925 // been set. 926 if (mWebChromeClient == null) { 927 return; 928 } 929 sendMessage(obtainMessage(CLOSE_WINDOW, window)); 930 } 931 932 public void onReceivedIcon(Bitmap icon) { 933 // The current item might be null if the icon was already stored in the 934 // database and this is a new WebView. 935 WebHistoryItem i = mBackForwardList.getCurrentItem(); 936 if (i != null) { 937 i.setFavicon(icon); 938 } 939 // Do an unsynchronized quick check to avoid posting if no callback has 940 // been set. 941 if (mWebChromeClient == null) { 942 return; 943 } 944 sendMessage(obtainMessage(RECEIVED_ICON, icon)); 945 } 946 947 public void onReceivedTitle(String title) { 948 // Do an unsynchronized quick check to avoid posting if no callback has 949 // been set. 950 if (mWebChromeClient == null) { 951 return; 952 } 953 sendMessage(obtainMessage(RECEIVED_TITLE, title)); 954 } 955 956 public void onJsAlert(String url, String message) { 957 // Do an unsynchronized quick check to avoid posting if no callback has 958 // been set. 959 if (mWebChromeClient == null) { 960 return; 961 } 962 JsResult result = new JsResult(this, false); 963 Message alert = obtainMessage(JS_ALERT, result); 964 alert.getData().putString("message", message); 965 alert.getData().putString("url", url); 966 synchronized (this) { 967 sendMessage(alert); 968 try { 969 wait(); 970 } catch (InterruptedException e) { 971 Log.e(LOGTAG, "Caught exception while waiting for jsAlert"); 972 Log.e(LOGTAG, Log.getStackTraceString(e)); 973 } 974 } 975 } 976 977 public boolean onJsConfirm(String url, String message) { 978 // Do an unsynchronized quick check to avoid posting if no callback has 979 // been set. 980 if (mWebChromeClient == null) { 981 return false; 982 } 983 JsResult result = new JsResult(this, false); 984 Message confirm = obtainMessage(JS_CONFIRM, result); 985 confirm.getData().putString("message", message); 986 confirm.getData().putString("url", url); 987 synchronized (this) { 988 sendMessage(confirm); 989 try { 990 wait(); 991 } catch (InterruptedException e) { 992 Log.e(LOGTAG, "Caught exception while waiting for jsConfirm"); 993 Log.e(LOGTAG, Log.getStackTraceString(e)); 994 } 995 } 996 return result.getResult(); 997 } 998 999 public String onJsPrompt(String url, String message, String defaultValue) { 1000 // Do an unsynchronized quick check to avoid posting if no callback has 1001 // been set. 1002 if (mWebChromeClient == null) { 1003 return null; 1004 } 1005 JsPromptResult result = new JsPromptResult(this); 1006 Message prompt = obtainMessage(JS_PROMPT, result); 1007 prompt.getData().putString("message", message); 1008 prompt.getData().putString("default", defaultValue); 1009 prompt.getData().putString("url", url); 1010 synchronized (this) { 1011 sendMessage(prompt); 1012 try { 1013 wait(); 1014 } catch (InterruptedException e) { 1015 Log.e(LOGTAG, "Caught exception while waiting for jsPrompt"); 1016 Log.e(LOGTAG, Log.getStackTraceString(e)); 1017 } 1018 } 1019 return result.getStringResult(); 1020 } 1021 1022 public boolean onJsBeforeUnload(String url, String message) { 1023 // Do an unsynchronized quick check to avoid posting if no callback has 1024 // been set. 1025 if (mWebChromeClient == null) { 1026 return true; 1027 } 1028 JsResult result = new JsResult(this, true); 1029 Message confirm = obtainMessage(JS_UNLOAD, result); 1030 confirm.getData().putString("message", message); 1031 confirm.getData().putString("url", url); 1032 synchronized (this) { 1033 sendMessage(confirm); 1034 try { 1035 wait(); 1036 } catch (InterruptedException e) { 1037 Log.e(LOGTAG, "Caught exception while waiting for jsUnload"); 1038 Log.e(LOGTAG, Log.getStackTraceString(e)); 1039 } 1040 } 1041 return result.getResult(); 1042 } 1043 1044 /** 1045 * Called by WebViewCore to inform the Java side that the current origin 1046 * has overflowed it's database quota. Called in the WebCore thread so 1047 * posts a message to the UI thread that will prompt the WebChromeClient 1048 * for what to do. On return back to C++ side, the WebCore thread will 1049 * sleep pending a new quota value. 1050 * @param url The URL that caused the quota overflow. 1051 * @param databaseIdentifier The identifier of the database that the 1052 * transaction that caused the overflow was running on. 1053 * @param currentQuota The current quota the origin is allowed. 1054 * @param quotaUpdater An instance of a class encapsulating a callback 1055 * to WebViewCore to run when the decision to allow or deny more 1056 * quota has been made. 1057 */ 1058 public void onExceededDatabaseQuota( 1059 String url, String databaseIdentifier, long currentQuota, 1060 WebStorage.QuotaUpdater quotaUpdater) { 1061 if (mWebChromeClient == null) { 1062 quotaUpdater.updateQuota(currentQuota); 1063 return; 1064 } 1065 1066 Message exceededQuota = obtainMessage(EXCEEDED_DATABASE_QUOTA); 1067 HashMap<String, Object> map = new HashMap(); 1068 map.put("databaseIdentifier", databaseIdentifier); 1069 map.put("url", url); 1070 map.put("currentQuota", currentQuota); 1071 map.put("quotaUpdater", quotaUpdater); 1072 exceededQuota.obj = map; 1073 sendMessage(exceededQuota); 1074 } 1075 1076} 1077