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