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