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