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