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