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