CallbackProxy.java revision d9e22ed8fe08c8c9fcf5c47a6b25d335a3158056
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 public WebChromeClient getWebChromeClient() { 175 return mWebChromeClient; 176 } 177 178 /** 179 * Set the client DownloadListener. 180 * @param client An implementation of DownloadListener. 181 */ 182 public void setDownloadListener(DownloadListener client) { 183 mDownloadListener = client; 184 } 185 186 /** 187 * Get the Back/Forward list to return to the user or to update the cached 188 * history list. 189 */ 190 public WebBackForwardList getBackForwardList() { 191 return mBackForwardList; 192 } 193 194 /** 195 * Called by the UI side. Calling overrideUrlLoading from the WebCore 196 * side will post a message to call this method. 197 */ 198 public boolean uiOverrideUrlLoading(String overrideUrl) { 199 if (overrideUrl == null || overrideUrl.length() == 0) { 200 return false; 201 } 202 boolean override = false; 203 if (mWebViewClient != null) { 204 override = mWebViewClient.shouldOverrideUrlLoading(mWebView, 205 overrideUrl); 206 } else { 207 Intent intent = new Intent(Intent.ACTION_VIEW, 208 Uri.parse(overrideUrl)); 209 intent.addCategory(Intent.CATEGORY_BROWSABLE); 210 // If another application is running a WebView and launches the 211 // Browser through this Intent, we want to reuse the same window if 212 // possible. 213 intent.putExtra(Browser.EXTRA_APPLICATION_ID, 214 mContext.getPackageName()); 215 try { 216 mContext.startActivity(intent); 217 override = true; 218 } catch (ActivityNotFoundException ex) { 219 // If no application can handle the URL, assume that the 220 // browser can handle it. 221 } 222 } 223 return override; 224 } 225 226 /** 227 * Called by UI side. 228 */ 229 public boolean uiOverrideKeyEvent(KeyEvent event) { 230 if (mWebViewClient != null) { 231 return mWebViewClient.shouldOverrideKeyEvent(mWebView, event); 232 } 233 return false; 234 } 235 236 @Override 237 public void handleMessage(Message msg) { 238 // We don't have to do synchronization because this function operates 239 // in the UI thread. The WebViewClient and WebChromeClient functions 240 // that check for a non-null callback are ok because java ensures atomic 241 // 32-bit reads and writes. 242 switch (msg.what) { 243 case PAGE_STARTED: 244 if (mWebViewClient != null) { 245 mWebViewClient.onPageStarted(mWebView, 246 msg.getData().getString("url"), 247 (Bitmap) msg.obj); 248 } 249 break; 250 251 case PAGE_FINISHED: 252 String finishedUrl = (String) msg.obj; 253 mWebView.onPageFinished(finishedUrl); 254 if (mWebViewClient != null) { 255 mWebViewClient.onPageFinished(mWebView, finishedUrl); 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 // Performance probe 781 if (PERF_PROBE) { 782 // un-comment this if PERF_PROBE is true 783// Looper.myQueue().setWaitCallback(null); 784 Log.d("WebCore", "WebCore thread used " + 785 (SystemClock.currentThreadTimeMillis() - mWebCoreThreadTime) 786 + " ms and idled " + mWebCoreIdleTime + " ms"); 787 Network.getInstance(mContext).stopTiming(); 788 } 789 Message msg = obtainMessage(PAGE_FINISHED, url); 790 sendMessage(msg); 791 } 792 793 public void onTooManyRedirects(Message cancelMsg, Message continueMsg) { 794 // Do an unsynchronized quick check to avoid posting if no callback has 795 // been set. 796 if (mWebViewClient == null) { 797 cancelMsg.sendToTarget(); 798 return; 799 } 800 801 Message msg = obtainMessage(TOO_MANY_REDIRECTS); 802 Bundle bundle = msg.getData(); 803 bundle.putParcelable("cancelMsg", cancelMsg); 804 bundle.putParcelable("continueMsg", continueMsg); 805 sendMessage(msg); 806 } 807 808 public void onReceivedError(int errorCode, String description, 809 String failingUrl) { 810 // Do an unsynchronized quick check to avoid posting if no callback has 811 // been set. 812 if (mWebViewClient == null) { 813 return; 814 } 815 816 Message msg = obtainMessage(REPORT_ERROR); 817 msg.arg1 = errorCode; 818 msg.getData().putString("description", description); 819 msg.getData().putString("failingUrl", failingUrl); 820 sendMessage(msg); 821 } 822 823 public void onFormResubmission(Message dontResend, 824 Message resend) { 825 // Do an unsynchronized quick check to avoid posting if no callback has 826 // been set. 827 if (mWebViewClient == null) { 828 dontResend.sendToTarget(); 829 return; 830 } 831 832 Message msg = obtainMessage(RESEND_POST_DATA); 833 Bundle bundle = msg.getData(); 834 bundle.putParcelable("resend", resend); 835 bundle.putParcelable("dontResend", dontResend); 836 sendMessage(msg); 837 } 838 839 /** 840 * Called by the WebCore side 841 */ 842 public boolean shouldOverrideUrlLoading(String url) { 843 // We have a default behavior if no client exists so always send the 844 // message. 845 ResultTransport<Boolean> res = new ResultTransport<Boolean>(false); 846 Message msg = obtainMessage(OVERRIDE_URL); 847 msg.getData().putString("url", url); 848 msg.obj = res; 849 synchronized (this) { 850 sendMessage(msg); 851 try { 852 wait(); 853 } catch (InterruptedException e) { 854 Log.e(LOGTAG, "Caught exception while waiting for overrideUrl"); 855 Log.e(LOGTAG, Log.getStackTraceString(e)); 856 } 857 } 858 return res.getResult().booleanValue(); 859 } 860 861 public void onReceivedHttpAuthRequest(HttpAuthHandler handler, 862 String hostName, String realmName) { 863 // Do an unsynchronized quick check to avoid posting if no callback has 864 // been set. 865 if (mWebViewClient == null) { 866 handler.cancel(); 867 return; 868 } 869 Message msg = obtainMessage(AUTH_REQUEST, handler); 870 msg.getData().putString("host", hostName); 871 msg.getData().putString("realm", realmName); 872 sendMessage(msg); 873 } 874 /** 875 * @hide - hide this because it contains a parameter of type SslError. 876 * SslError is located in a hidden package. 877 */ 878 public void onReceivedSslError(SslErrorHandler handler, SslError error) { 879 // Do an unsynchronized quick check to avoid posting if no callback has 880 // been set. 881 if (mWebViewClient == null) { 882 handler.cancel(); 883 return; 884 } 885 Message msg = obtainMessage(SSL_ERROR); 886 //, handler); 887 HashMap<String, Object> map = new HashMap(); 888 map.put("handler", handler); 889 map.put("error", error); 890 msg.obj = map; 891 sendMessage(msg); 892 } 893 /** 894 * @hide - hide this because it contains a parameter of type SslCertificate, 895 * which is located in a hidden package. 896 */ 897 898 public void onReceivedCertificate(SslCertificate certificate) { 899 // Do an unsynchronized quick check to avoid posting if no callback has 900 // been set. 901 if (mWebViewClient == null) { 902 return; 903 } 904 // here, certificate can be null (if the site is not secure) 905 sendMessage(obtainMessage(RECEIVED_CERTIFICATE, certificate)); 906 } 907 908 public void doUpdateVisitedHistory(String url, boolean isReload) { 909 // Do an unsynchronized quick check to avoid posting if no callback has 910 // been set. 911 if (mWebViewClient == null) { 912 return; 913 } 914 sendMessage(obtainMessage(UPDATE_VISITED, isReload ? 1 : 0, 0, url)); 915 } 916 917 public void onLoadResource(String url) { 918 // Do an unsynchronized quick check to avoid posting if no callback has 919 // been set. 920 if (mWebViewClient == null) { 921 return; 922 } 923 sendMessage(obtainMessage(LOAD_RESOURCE, url)); 924 } 925 926 public void onUnhandledKeyEvent(KeyEvent event) { 927 // Do an unsynchronized quick check to avoid posting if no callback has 928 // been set. 929 if (mWebViewClient == null) { 930 return; 931 } 932 sendMessage(obtainMessage(ASYNC_KEYEVENTS, event)); 933 } 934 935 public void onScaleChanged(float oldScale, float newScale) { 936 // Do an unsynchronized quick check to avoid posting if no callback has 937 // been set. 938 if (mWebViewClient == null) { 939 return; 940 } 941 Message msg = obtainMessage(SCALE_CHANGED); 942 Bundle bundle = msg.getData(); 943 bundle.putFloat("old", oldScale); 944 bundle.putFloat("new", newScale); 945 sendMessage(msg); 946 } 947 948 //-------------------------------------------------------------------------- 949 // DownloadListener functions. 950 //-------------------------------------------------------------------------- 951 952 /** 953 * Starts a download if a download listener has been registered, otherwise 954 * return false. 955 */ 956 public boolean onDownloadStart(String url, String userAgent, 957 String contentDisposition, String mimetype, long contentLength) { 958 // Do an unsynchronized quick check to avoid posting if no callback has 959 // been set. 960 if (mDownloadListener == null) { 961 // Cancel the download if there is no browser client. 962 return false; 963 } 964 965 Message msg = obtainMessage(DOWNLOAD_FILE); 966 Bundle bundle = msg.getData(); 967 bundle.putString("url", url); 968 bundle.putString("userAgent", userAgent); 969 bundle.putString("mimetype", mimetype); 970 bundle.putLong("contentLength", contentLength); 971 bundle.putString("contentDisposition", contentDisposition); 972 sendMessage(msg); 973 return true; 974 } 975 976 977 //-------------------------------------------------------------------------- 978 // WebView specific functions that do not interact with a client. These 979 // functions just need to operate within the UI thread. 980 //-------------------------------------------------------------------------- 981 982 public boolean onSavePassword(String schemePlusHost, String username, 983 String password, Message resumeMsg) { 984 // resumeMsg should be null at this point because we want to create it 985 // within the CallbackProxy. 986 if (DebugFlags.CALLBACK_PROXY) { 987 junit.framework.Assert.assertNull(resumeMsg); 988 } 989 resumeMsg = obtainMessage(NOTIFY); 990 991 Message msg = obtainMessage(SAVE_PASSWORD, resumeMsg); 992 Bundle bundle = msg.getData(); 993 bundle.putString("host", schemePlusHost); 994 bundle.putString("username", username); 995 bundle.putString("password", password); 996 synchronized (this) { 997 sendMessage(msg); 998 try { 999 wait(); 1000 } catch (InterruptedException e) { 1001 Log.e(LOGTAG, 1002 "Caught exception while waiting for onSavePassword"); 1003 Log.e(LOGTAG, Log.getStackTraceString(e)); 1004 } 1005 } 1006 // Doesn't matter here 1007 return false; 1008 } 1009 1010 //-------------------------------------------------------------------------- 1011 // WebChromeClient methods 1012 //-------------------------------------------------------------------------- 1013 1014 public void onProgressChanged(int newProgress) { 1015 // Synchronize so that mLatestProgress is up-to-date. 1016 synchronized (this) { 1017 if (mWebChromeClient == null || mLatestProgress == newProgress) { 1018 return; 1019 } 1020 mLatestProgress = newProgress; 1021 if (!mProgressUpdatePending) { 1022 sendEmptyMessage(PROGRESS); 1023 mProgressUpdatePending = true; 1024 } 1025 } 1026 } 1027 1028 public WebView createWindow(boolean dialog, boolean userGesture) { 1029 // Do an unsynchronized quick check to avoid posting if no callback has 1030 // been set. 1031 if (mWebChromeClient == null) { 1032 return null; 1033 } 1034 1035 WebView.WebViewTransport transport = mWebView.new WebViewTransport(); 1036 final Message msg = obtainMessage(NOTIFY); 1037 msg.obj = transport; 1038 synchronized (this) { 1039 sendMessage(obtainMessage(CREATE_WINDOW, dialog ? 1 : 0, 1040 userGesture ? 1 : 0, msg)); 1041 try { 1042 wait(); 1043 } catch (InterruptedException e) { 1044 Log.e(LOGTAG, 1045 "Caught exception while waiting for createWindow"); 1046 Log.e(LOGTAG, Log.getStackTraceString(e)); 1047 } 1048 } 1049 1050 WebView w = transport.getWebView(); 1051 if (w != null) { 1052 w.getWebViewCore().initializeSubwindow(); 1053 } 1054 return w; 1055 } 1056 1057 public void onRequestFocus() { 1058 // Do an unsynchronized quick check to avoid posting if no callback has 1059 // been set. 1060 if (mWebChromeClient == null) { 1061 return; 1062 } 1063 1064 sendEmptyMessage(REQUEST_FOCUS); 1065 } 1066 1067 public void onCloseWindow(WebView window) { 1068 // Do an unsynchronized quick check to avoid posting if no callback has 1069 // been set. 1070 if (mWebChromeClient == null) { 1071 return; 1072 } 1073 sendMessage(obtainMessage(CLOSE_WINDOW, window)); 1074 } 1075 1076 public void onReceivedIcon(Bitmap icon) { 1077 // The current item might be null if the icon was already stored in the 1078 // database and this is a new WebView. 1079 WebHistoryItem i = mBackForwardList.getCurrentItem(); 1080 if (i != null) { 1081 i.setFavicon(icon); 1082 } 1083 // Do an unsynchronized quick check to avoid posting if no callback has 1084 // been set. 1085 if (mWebChromeClient == null) { 1086 return; 1087 } 1088 sendMessage(obtainMessage(RECEIVED_ICON, icon)); 1089 } 1090 1091 /* package */ void onReceivedTouchIconUrl(String url, boolean precomposed) { 1092 // We should have a current item but we do not want to crash so check 1093 // for null. 1094 WebHistoryItem i = mBackForwardList.getCurrentItem(); 1095 if (i != null) { 1096 if (precomposed || i.getTouchIconUrl() != null) { 1097 i.setTouchIconUrl(url); 1098 } 1099 } 1100 // Do an unsynchronized quick check to avoid posting if no callback has 1101 // been set. 1102 if (mWebChromeClient == null) { 1103 return; 1104 } 1105 sendMessage(obtainMessage(RECEIVED_TOUCH_ICON_URL, 1106 precomposed ? 1 : 0, 0, url)); 1107 } 1108 1109 public void onReceivedTitle(String title) { 1110 // Do an unsynchronized quick check to avoid posting if no callback has 1111 // been set. 1112 if (mWebChromeClient == null) { 1113 return; 1114 } 1115 sendMessage(obtainMessage(RECEIVED_TITLE, title)); 1116 } 1117 1118 public void onJsAlert(String url, String message) { 1119 // Do an unsynchronized quick check to avoid posting if no callback has 1120 // been set. 1121 if (mWebChromeClient == null) { 1122 return; 1123 } 1124 JsResult result = new JsResult(this, false); 1125 Message alert = obtainMessage(JS_ALERT, result); 1126 alert.getData().putString("message", message); 1127 alert.getData().putString("url", url); 1128 synchronized (this) { 1129 sendMessage(alert); 1130 try { 1131 wait(); 1132 } catch (InterruptedException e) { 1133 Log.e(LOGTAG, "Caught exception while waiting for jsAlert"); 1134 Log.e(LOGTAG, Log.getStackTraceString(e)); 1135 } 1136 } 1137 } 1138 1139 public boolean onJsConfirm(String url, String message) { 1140 // Do an unsynchronized quick check to avoid posting if no callback has 1141 // been set. 1142 if (mWebChromeClient == null) { 1143 return false; 1144 } 1145 JsResult result = new JsResult(this, false); 1146 Message confirm = obtainMessage(JS_CONFIRM, result); 1147 confirm.getData().putString("message", message); 1148 confirm.getData().putString("url", url); 1149 synchronized (this) { 1150 sendMessage(confirm); 1151 try { 1152 wait(); 1153 } catch (InterruptedException e) { 1154 Log.e(LOGTAG, "Caught exception while waiting for jsConfirm"); 1155 Log.e(LOGTAG, Log.getStackTraceString(e)); 1156 } 1157 } 1158 return result.getResult(); 1159 } 1160 1161 public String onJsPrompt(String url, String message, String defaultValue) { 1162 // Do an unsynchronized quick check to avoid posting if no callback has 1163 // been set. 1164 if (mWebChromeClient == null) { 1165 return null; 1166 } 1167 JsPromptResult result = new JsPromptResult(this); 1168 Message prompt = obtainMessage(JS_PROMPT, result); 1169 prompt.getData().putString("message", message); 1170 prompt.getData().putString("default", defaultValue); 1171 prompt.getData().putString("url", url); 1172 synchronized (this) { 1173 sendMessage(prompt); 1174 try { 1175 wait(); 1176 } catch (InterruptedException e) { 1177 Log.e(LOGTAG, "Caught exception while waiting for jsPrompt"); 1178 Log.e(LOGTAG, Log.getStackTraceString(e)); 1179 } 1180 } 1181 return result.getStringResult(); 1182 } 1183 1184 public boolean onJsBeforeUnload(String url, String message) { 1185 // Do an unsynchronized quick check to avoid posting if no callback has 1186 // been set. 1187 if (mWebChromeClient == null) { 1188 return true; 1189 } 1190 JsResult result = new JsResult(this, true); 1191 Message confirm = obtainMessage(JS_UNLOAD, result); 1192 confirm.getData().putString("message", message); 1193 confirm.getData().putString("url", url); 1194 synchronized (this) { 1195 sendMessage(confirm); 1196 try { 1197 wait(); 1198 } catch (InterruptedException e) { 1199 Log.e(LOGTAG, "Caught exception while waiting for jsUnload"); 1200 Log.e(LOGTAG, Log.getStackTraceString(e)); 1201 } 1202 } 1203 return result.getResult(); 1204 } 1205 1206 /** 1207 * Called by WebViewCore to inform the Java side that the current origin 1208 * has overflowed it's database quota. Called in the WebCore thread so 1209 * posts a message to the UI thread that will prompt the WebChromeClient 1210 * for what to do. On return back to C++ side, the WebCore thread will 1211 * sleep pending a new quota value. 1212 * @param url The URL that caused the quota overflow. 1213 * @param databaseIdentifier The identifier of the database that the 1214 * transaction that caused the overflow was running on. 1215 * @param currentQuota The current quota the origin is allowed. 1216 * @param estimatedSize The estimated size of the database. 1217 * @param totalUsedQuota is the sum of all origins' quota. 1218 * @param quotaUpdater An instance of a class encapsulating a callback 1219 * to WebViewCore to run when the decision to allow or deny more 1220 * quota has been made. 1221 */ 1222 public void onExceededDatabaseQuota( 1223 String url, String databaseIdentifier, long currentQuota, 1224 long estimatedSize, long totalUsedQuota, 1225 WebStorage.QuotaUpdater quotaUpdater) { 1226 if (mWebChromeClient == null) { 1227 quotaUpdater.updateQuota(currentQuota); 1228 return; 1229 } 1230 1231 Message exceededQuota = obtainMessage(EXCEEDED_DATABASE_QUOTA); 1232 HashMap<String, Object> map = new HashMap(); 1233 map.put("databaseIdentifier", databaseIdentifier); 1234 map.put("url", url); 1235 map.put("currentQuota", currentQuota); 1236 map.put("estimatedSize", estimatedSize); 1237 map.put("totalUsedQuota", totalUsedQuota); 1238 map.put("quotaUpdater", quotaUpdater); 1239 exceededQuota.obj = map; 1240 sendMessage(exceededQuota); 1241 } 1242 1243 /** 1244 * Called by WebViewCore to inform the Java side that the appcache has 1245 * exceeded its max size. 1246 * @param spaceNeeded is the amount of disk space that would be needed 1247 * in order for the last appcache operation to succeed. 1248 * @param totalUsedQuota is the sum of all origins' quota. 1249 * @param quotaUpdater An instance of a class encapsulating a callback 1250 * to WebViewCore to run when the decision to allow or deny a bigger 1251 * app cache size has been made. 1252 */ 1253 public void onReachedMaxAppCacheSize(long spaceNeeded, 1254 long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) { 1255 if (mWebChromeClient == null) { 1256 quotaUpdater.updateQuota(0); 1257 return; 1258 } 1259 1260 Message msg = obtainMessage(REACHED_APPCACHE_MAXSIZE); 1261 HashMap<String, Object> map = new HashMap(); 1262 map.put("spaceNeeded", spaceNeeded); 1263 map.put("totalUsedQuota", totalUsedQuota); 1264 map.put("quotaUpdater", quotaUpdater); 1265 msg.obj = map; 1266 sendMessage(msg); 1267 } 1268 1269 /** 1270 * Called by WebViewCore to instruct the browser to display a prompt to ask 1271 * the user to set the Geolocation permission state for the given origin. 1272 * @param origin The origin requesting Geolocation permsissions. 1273 * @param callback The callback to call once a permission state has been 1274 * obtained. 1275 */ 1276 public void onGeolocationPermissionsShowPrompt(String origin, 1277 GeolocationPermissions.Callback callback) { 1278 if (mWebChromeClient == null) { 1279 return; 1280 } 1281 1282 Message showMessage = 1283 obtainMessage(GEOLOCATION_PERMISSIONS_SHOW_PROMPT); 1284 HashMap<String, Object> map = new HashMap(); 1285 map.put("origin", origin); 1286 map.put("callback", callback); 1287 showMessage.obj = map; 1288 sendMessage(showMessage); 1289 } 1290 1291 /** 1292 * Called by WebViewCore to instruct the browser to hide the Geolocation 1293 * permissions prompt. 1294 */ 1295 public void onGeolocationPermissionsHidePrompt() { 1296 if (mWebChromeClient == null) { 1297 return; 1298 } 1299 1300 Message hideMessage = obtainMessage(GEOLOCATION_PERMISSIONS_HIDE_PROMPT); 1301 sendMessage(hideMessage); 1302 } 1303 1304 /** 1305 * Called by WebViewCore when we have a message to be added to the JavaScript 1306 * error console. Sends a message to the Java side with the details. 1307 * @param message The message to add to the console. 1308 * @param lineNumber The lineNumber of the source file on which the error 1309 * occurred. 1310 * @param sourceID The filename of the source file in which the error 1311 * occurred. 1312 */ 1313 public void addMessageToConsole(String message, int lineNumber, String sourceID) { 1314 if (mWebChromeClient == null) { 1315 return; 1316 } 1317 1318 Message msg = obtainMessage(ADD_MESSAGE_TO_CONSOLE); 1319 msg.getData().putString("message", message); 1320 msg.getData().putString("sourceID", sourceID); 1321 msg.getData().putInt("lineNumber", lineNumber); 1322 sendMessage(msg); 1323 } 1324 1325 public boolean onJsTimeout() { 1326 //always interrupt timedout JS by default 1327 if (mWebChromeClient == null) { 1328 return true; 1329 } 1330 JsResult result = new JsResult(this, true); 1331 Message timeout = obtainMessage(JS_TIMEOUT, result); 1332 synchronized (this) { 1333 sendMessage(timeout); 1334 try { 1335 wait(); 1336 } catch (InterruptedException e) { 1337 Log.e(LOGTAG, "Caught exception while waiting for jsUnload"); 1338 Log.e(LOGTAG, Log.getStackTraceString(e)); 1339 } 1340 } 1341 return result.getResult(); 1342 } 1343 1344 public void getVisitedHistory(ValueCallback<String[]> callback) { 1345 if (mWebChromeClient == null) { 1346 return; 1347 } 1348 Message msg = obtainMessage(GET_VISITED_HISTORY); 1349 msg.obj = callback; 1350 sendMessage(msg); 1351 } 1352 1353 private class UploadFile implements ValueCallback<Uri> { 1354 private Uri mValue; 1355 public void onReceiveValue(Uri value) { 1356 mValue = value; 1357 synchronized (CallbackProxy.this) { 1358 CallbackProxy.this.notify(); 1359 } 1360 } 1361 public Uri getResult() { 1362 return mValue; 1363 } 1364 } 1365 1366 /** 1367 * Called by WebViewCore to open a file chooser. 1368 */ 1369 /* package */ Uri openFileChooser() { 1370 if (mWebChromeClient == null) { 1371 return null; 1372 } 1373 Message myMessage = obtainMessage(OPEN_FILE_CHOOSER); 1374 UploadFile uploadFile = new UploadFile(); 1375 myMessage.obj = uploadFile; 1376 synchronized (this) { 1377 sendMessage(myMessage); 1378 try { 1379 wait(); 1380 } catch (InterruptedException e) { 1381 Log.e(LOGTAG, 1382 "Caught exception while waiting for openFileChooser"); 1383 Log.e(LOGTAG, Log.getStackTraceString(e)); 1384 } 1385 } 1386 return uploadFile.getResult(); 1387 } 1388} 1389