1 /* 2 * Copyright (C) 2011 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.cts; 18 19 import android.cts.util.PollingCheck; 20 import android.cts.util.TestThread; 21 import android.graphics.Bitmap; 22 import android.graphics.Picture; 23 import android.graphics.Rect; 24 import android.net.Uri; 25 import android.os.Bundle; 26 import android.os.Looper; 27 import android.os.Message; 28 import android.os.SystemClock; 29 import android.print.PrintDocumentAdapter; 30 import android.test.InstrumentationTestCase; 31 import android.util.DisplayMetrics; 32 import android.view.View; 33 import android.view.ViewGroup; 34 import android.view.ViewParent; 35 import android.webkit.DownloadListener; 36 import android.webkit.CookieManager; 37 import android.webkit.ValueCallback; 38 import android.webkit.WebBackForwardList; 39 import android.webkit.WebChromeClient; 40 import android.webkit.WebMessage; 41 import android.webkit.WebMessagePort; 42 import android.webkit.WebSettings; 43 import android.webkit.WebView.HitTestResult; 44 import android.webkit.WebView.PictureListener; 45 import android.webkit.WebView.VisualStateCallback; 46 import android.webkit.WebView; 47 import android.webkit.WebViewClient; 48 49 import junit.framework.Assert; 50 51 import java.io.File; 52 import java.util.concurrent.Callable; 53 import java.util.Map; 54 55 /** 56 * Many tests need to run WebView code in the UI thread. This class 57 * wraps a WebView so that calls are ensured to arrive on the UI thread. 58 * 59 * All methods may be run on either the UI thread or test thread. 60 */ 61 public class WebViewOnUiThread { 62 /** 63 * The maximum time, in milliseconds (10 seconds) to wait for a load 64 * to be triggered. 65 */ 66 private static final long LOAD_TIMEOUT = 10000; 67 68 /** 69 * Set to true after onPageFinished is called. 70 */ 71 private boolean mLoaded; 72 73 /** 74 * Set to true after onNewPicture is called. Reset when onPageStarted 75 * is called. 76 */ 77 private boolean mNewPicture; 78 79 /** 80 * The progress, in percentage, of the page load. Valid values are between 81 * 0 and 100. 82 */ 83 private int mProgress; 84 85 /** 86 * The test that this class is being used in. Used for runTestOnUiThread. 87 */ 88 private InstrumentationTestCase mTest; 89 90 /** 91 * The WebView that calls will be made on. 92 */ 93 private WebView mWebView; 94 95 /** 96 * Initializes the webView with a WebViewClient, WebChromeClient, 97 * and PictureListener to prepare for loadUrlAndWaitForCompletion. 98 * 99 * A new WebViewOnUiThread should be called during setUp so as to 100 * reinitialize between calls. 101 * 102 * @param test The test in which this is being run. 103 * @param webView The webView that the methods should call. 104 * @see loadUrlAndWaitForCompletion 105 */ 106 public WebViewOnUiThread(InstrumentationTestCase test, WebView webView) { 107 mTest = test; 108 mWebView = webView; 109 final WebViewClient webViewClient = new WaitForLoadedClient(this); 110 final WebChromeClient webChromeClient = new WaitForProgressClient(this); 111 runOnUiThread(new Runnable() { 112 @Override 113 public void run() { 114 mWebView.setWebViewClient(webViewClient); 115 mWebView.setWebChromeClient(webChromeClient); 116 mWebView.setPictureListener(new WaitForNewPicture()); 117 } 118 }); 119 } 120 121 /** 122 * Called after a test is complete and the WebView should be disengaged from 123 * the tests. 124 */ 125 public void cleanUp() { 126 clearHistory(); 127 clearCache(true); 128 setPictureListener(null); 129 setWebChromeClient(null); 130 setWebViewClient(null); 131 } 132 133 /** 134 * Called from WaitForNewPicture, this is used to indicate that 135 * the page has been drawn. 136 */ 137 synchronized public void onNewPicture() { 138 mNewPicture = true; 139 this.notifyAll(); 140 } 141 142 /** 143 * Called from WaitForLoadedClient, this is used to clear the picture 144 * draw state so that draws before the URL begins loading don't count. 145 */ 146 synchronized public void onPageStarted() { 147 mNewPicture = false; // Earlier paints won't count. 148 } 149 150 /** 151 * Called from WaitForLoadedClient, this is used to indicate that 152 * the page is loaded, but not drawn yet. 153 */ 154 synchronized public void onPageFinished() { 155 mLoaded = true; 156 this.notifyAll(); 157 } 158 159 /** 160 * Called from the WebChrome client, this sets the current progress 161 * for a page. 162 * @param progress The progress made so far between 0 and 100. 163 */ 164 synchronized public void onProgressChanged(int progress) { 165 mProgress = progress; 166 this.notifyAll(); 167 } 168 169 public void setWebViewClient(final WebViewClient webViewClient) { 170 runOnUiThread(new Runnable() { 171 @Override 172 public void run() { 173 mWebView.setWebViewClient(webViewClient); 174 } 175 }); 176 } 177 178 public void setWebChromeClient(final WebChromeClient webChromeClient) { 179 runOnUiThread(new Runnable() { 180 @Override 181 public void run() { 182 mWebView.setWebChromeClient(webChromeClient); 183 } 184 }); 185 } 186 187 public void setPictureListener(final PictureListener pictureListener) { 188 runOnUiThread(new Runnable() { 189 @Override 190 public void run() { 191 mWebView.setPictureListener(pictureListener); 192 } 193 }); 194 } 195 196 public void setNetworkAvailable(final boolean available) { 197 runOnUiThread(new Runnable() { 198 @Override 199 public void run() { 200 mWebView.setNetworkAvailable(available); 201 } 202 }); 203 } 204 205 public void setDownloadListener(final DownloadListener listener) { 206 runOnUiThread(new Runnable() { 207 @Override 208 public void run() { 209 mWebView.setDownloadListener(listener); 210 } 211 }); 212 } 213 214 public void setBackgroundColor(final int color) { 215 runOnUiThread(new Runnable() { 216 @Override 217 public void run() { 218 mWebView.setBackgroundColor(color); 219 } 220 }); 221 } 222 223 public void clearCache(final boolean includeDiskFiles) { 224 runOnUiThread(new Runnable() { 225 @Override 226 public void run() { 227 mWebView.clearCache(includeDiskFiles); 228 } 229 }); 230 } 231 232 public void clearHistory() { 233 runOnUiThread(new Runnable() { 234 @Override 235 public void run() { 236 mWebView.clearHistory(); 237 } 238 }); 239 } 240 241 public void requestFocus() { 242 runOnUiThread(new Runnable() { 243 @Override 244 public void run() { 245 mWebView.requestFocus(); 246 } 247 }); 248 } 249 250 public boolean canZoomIn() { 251 return getValue(new ValueGetter<Boolean>() { 252 @Override 253 public Boolean capture() { 254 return mWebView.canZoomIn(); 255 } 256 }); 257 } 258 259 public boolean canZoomOut() { 260 return getValue(new ValueGetter<Boolean>() { 261 @Override 262 public Boolean capture() { 263 return mWebView.canZoomOut(); 264 } 265 }); 266 } 267 268 public boolean zoomIn() { 269 return getValue(new ValueGetter<Boolean>() { 270 @Override 271 public Boolean capture() { 272 return mWebView.zoomIn(); 273 } 274 }); 275 } 276 277 public boolean zoomOut() { 278 return getValue(new ValueGetter<Boolean>() { 279 @Override 280 public Boolean capture() { 281 return mWebView.zoomOut(); 282 } 283 }); 284 } 285 286 public void zoomBy(final float zoomFactor) { 287 runOnUiThread(new Runnable() { 288 @Override 289 public void run() { 290 mWebView.zoomBy(zoomFactor); 291 } 292 }); 293 } 294 295 public void setFindListener(final WebView.FindListener listener) { 296 runOnUiThread(new Runnable() { 297 @Override 298 public void run() { 299 mWebView.setFindListener(listener); 300 } 301 }); 302 } 303 304 public void removeJavascriptInterface(final String interfaceName) { 305 runOnUiThread(new Runnable() { 306 @Override 307 public void run() { 308 mWebView.removeJavascriptInterface(interfaceName); 309 } 310 }); 311 } 312 313 public WebMessagePort[] createWebMessageChannel() { 314 return getValue(new ValueGetter<WebMessagePort[]>() { 315 @Override 316 public WebMessagePort[] capture() { 317 return mWebView.createWebMessageChannel(); 318 } 319 }); 320 } 321 322 public void postWebMessage(final WebMessage message, final Uri targetOrigin) { 323 runOnUiThread(new Runnable() { 324 @Override 325 public void run() { 326 mWebView.postWebMessage(message, targetOrigin); 327 } 328 }); 329 } 330 331 public void addJavascriptInterface(final Object object, final String name) { 332 runOnUiThread(new Runnable() { 333 @Override 334 public void run() { 335 mWebView.addJavascriptInterface(object, name); 336 } 337 }); 338 } 339 340 public void flingScroll(final int vx, final int vy) { 341 runOnUiThread(new Runnable() { 342 @Override 343 public void run() { 344 mWebView.flingScroll(vx, vy); 345 } 346 }); 347 } 348 349 public void requestFocusNodeHref(final Message hrefMsg) { 350 runOnUiThread(new Runnable() { 351 @Override 352 public void run() { 353 mWebView.requestFocusNodeHref(hrefMsg); 354 } 355 }); 356 } 357 358 public void requestImageRef(final Message msg) { 359 runOnUiThread(new Runnable() { 360 @Override 361 public void run() { 362 mWebView.requestImageRef(msg); 363 } 364 }); 365 } 366 367 public void setInitialScale(final int scaleInPercent) { 368 runOnUiThread(new Runnable() { 369 @Override 370 public void run() { 371 mWebView.setInitialScale(scaleInPercent); 372 } 373 }); 374 } 375 376 public void clearSslPreferences() { 377 runOnUiThread(new Runnable() { 378 @Override 379 public void run() { 380 mWebView.clearSslPreferences(); 381 } 382 }); 383 } 384 385 public void clearClientCertPreferences(final Runnable onCleared) { 386 runOnUiThread(new Runnable() { 387 @Override 388 public void run() { 389 WebView.clearClientCertPreferences(onCleared); 390 } 391 }); 392 } 393 394 public void resumeTimers() { 395 runOnUiThread(new Runnable() { 396 @Override 397 public void run() { 398 mWebView.resumeTimers(); 399 } 400 }); 401 } 402 403 public void findNext(final boolean forward) { 404 runOnUiThread(new Runnable() { 405 @Override 406 public void run() { 407 mWebView.findNext(forward); 408 } 409 }); 410 } 411 412 public void clearMatches() { 413 runOnUiThread(new Runnable() { 414 @Override 415 public void run() { 416 mWebView.clearMatches(); 417 } 418 }); 419 } 420 421 /** 422 * Calls loadUrl on the WebView and then waits onPageFinished, 423 * onNewPicture and onProgressChange to reach 100. 424 * Test fails if the load timeout elapses. 425 * @param url The URL to load. 426 */ 427 public void loadUrlAndWaitForCompletion(final String url) { 428 callAndWait(new Runnable() { 429 @Override 430 public void run() { 431 mWebView.loadUrl(url); 432 } 433 }); 434 } 435 436 /** 437 * Calls loadUrl on the WebView and then waits onPageFinished, 438 * onNewPicture and onProgressChange to reach 100. 439 * Test fails if the load timeout elapses. 440 * @param url The URL to load. 441 * @param extraHeaders The additional headers to be used in the HTTP request. 442 */ 443 public void loadUrlAndWaitForCompletion(final String url, 444 final Map<String, String> extraHeaders) { 445 callAndWait(new Runnable() { 446 @Override 447 public void run() { 448 mWebView.loadUrl(url, extraHeaders); 449 } 450 }); 451 } 452 453 public void loadUrl(final String url) { 454 runOnUiThread(new Runnable() { 455 @Override 456 public void run() { 457 mWebView.loadUrl(url); 458 } 459 }); 460 } 461 462 public void stopLoading() { 463 runOnUiThread(new Runnable() { 464 @Override 465 public void run() { 466 mWebView.stopLoading(); 467 } 468 }); 469 } 470 471 public void postUrlAndWaitForCompletion(final String url, final byte[] postData) { 472 callAndWait(new Runnable() { 473 @Override 474 public void run() { 475 mWebView.postUrl(url, postData); 476 } 477 }); 478 } 479 480 public void loadDataAndWaitForCompletion(final String data, 481 final String mimeType, final String encoding) { 482 callAndWait(new Runnable() { 483 @Override 484 public void run() { 485 mWebView.loadData(data, mimeType, encoding); 486 } 487 }); 488 } 489 490 public void loadDataWithBaseURLAndWaitForCompletion(final String baseUrl, 491 final String data, final String mimeType, final String encoding, 492 final String historyUrl) { 493 callAndWait(new Runnable() { 494 @Override 495 public void run() { 496 mWebView.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, 497 historyUrl); 498 } 499 }); 500 } 501 502 /** 503 * Reloads a page and waits for it to complete reloading. Use reload 504 * if it is a form resubmission and the onFormResubmission responds 505 * by telling WebView not to resubmit it. 506 */ 507 public void reloadAndWaitForCompletion() { 508 callAndWait(new Runnable() { 509 @Override 510 public void run() { 511 mWebView.reload(); 512 } 513 }); 514 } 515 516 /** 517 * Reload the previous URL. Use reloadAndWaitForCompletion unless 518 * it is a form resubmission and the onFormResubmission responds 519 * by telling WebView not to resubmit it. 520 */ 521 public void reload() { 522 runOnUiThread(new Runnable() { 523 @Override 524 public void run() { 525 mWebView.reload(); 526 } 527 }); 528 } 529 530 /** 531 * Use this only when JavaScript causes a page load to wait for the 532 * page load to complete. Otherwise use loadUrlAndWaitForCompletion or 533 * similar functions. 534 */ 535 public void waitForLoadCompletion() { 536 waitForCriteria(LOAD_TIMEOUT, 537 new Callable<Boolean>() { 538 @Override 539 public Boolean call() { 540 return isLoaded(); 541 } 542 }); 543 clearLoad(); 544 } 545 546 private void waitForCriteria(long timeout, Callable<Boolean> doneCriteria) { 547 if (isUiThread()) { 548 waitOnUiThread(timeout, doneCriteria); 549 } else { 550 waitOnTestThread(timeout, doneCriteria); 551 } 552 } 553 554 public String getTitle() { 555 return getValue(new ValueGetter<String>() { 556 @Override 557 public String capture() { 558 return mWebView.getTitle(); 559 } 560 }); 561 } 562 563 public WebSettings getSettings() { 564 return getValue(new ValueGetter<WebSettings>() { 565 @Override 566 public WebSettings capture() { 567 return mWebView.getSettings(); 568 } 569 }); 570 } 571 572 public WebBackForwardList copyBackForwardList() { 573 return getValue(new ValueGetter<WebBackForwardList>() { 574 @Override 575 public WebBackForwardList capture() { 576 return mWebView.copyBackForwardList(); 577 } 578 }); 579 } 580 581 public Bitmap getFavicon() { 582 return getValue(new ValueGetter<Bitmap>() { 583 @Override 584 public Bitmap capture() { 585 return mWebView.getFavicon(); 586 } 587 }); 588 } 589 590 public String getUrl() { 591 return getValue(new ValueGetter<String>() { 592 @Override 593 public String capture() { 594 return mWebView.getUrl(); 595 } 596 }); 597 } 598 599 public int getProgress() { 600 return getValue(new ValueGetter<Integer>() { 601 @Override 602 public Integer capture() { 603 return mWebView.getProgress(); 604 } 605 }); 606 } 607 608 public int getHeight() { 609 return getValue(new ValueGetter<Integer>() { 610 @Override 611 public Integer capture() { 612 return mWebView.getHeight(); 613 } 614 }); 615 } 616 617 public int getContentHeight() { 618 return getValue(new ValueGetter<Integer>() { 619 @Override 620 public Integer capture() { 621 return mWebView.getContentHeight(); 622 } 623 }); 624 } 625 626 public boolean savePicture(final Bundle b, final File dest) { 627 return getValue(new ValueGetter<Boolean>() { 628 @Override 629 public Boolean capture() { 630 return mWebView.savePicture(b, dest); 631 } 632 }); 633 } 634 635 public boolean pageUp(final boolean top) { 636 return getValue(new ValueGetter<Boolean>() { 637 @Override 638 public Boolean capture() { 639 return mWebView.pageUp(top); 640 } 641 }); 642 } 643 644 public boolean pageDown(final boolean bottom) { 645 return getValue(new ValueGetter<Boolean>() { 646 @Override 647 public Boolean capture() { 648 return mWebView.pageDown(bottom); 649 } 650 }); 651 } 652 653 public void postVisualStateCallback(final long requestId, final VisualStateCallback callback) { 654 runOnUiThread(new Runnable() { 655 @Override 656 public void run() { 657 mWebView.postVisualStateCallback(requestId, callback); 658 } 659 }); 660 } 661 662 public int[] getLocationOnScreen() { 663 final int[] location = new int[2]; 664 return getValue(new ValueGetter<int[]>() { 665 @Override 666 public int[] capture() { 667 mWebView.getLocationOnScreen(location); 668 return location; 669 } 670 }); 671 } 672 673 public float getScale() { 674 return getValue(new ValueGetter<Float>() { 675 @Override 676 public Float capture() { 677 return mWebView.getScale(); 678 } 679 }); 680 } 681 682 public boolean requestFocus(final int direction, 683 final Rect previouslyFocusedRect) { 684 return getValue(new ValueGetter<Boolean>() { 685 @Override 686 public Boolean capture() { 687 return mWebView.requestFocus(direction, previouslyFocusedRect); 688 } 689 }); 690 } 691 692 public HitTestResult getHitTestResult() { 693 return getValue(new ValueGetter<HitTestResult>() { 694 @Override 695 public HitTestResult capture() { 696 return mWebView.getHitTestResult(); 697 } 698 }); 699 } 700 701 public int getScrollX() { 702 return getValue(new ValueGetter<Integer>() { 703 @Override 704 public Integer capture() { 705 return mWebView.getScrollX(); 706 } 707 }); 708 } 709 710 public int getScrollY() { 711 return getValue(new ValueGetter<Integer>() { 712 @Override 713 public Integer capture() { 714 return mWebView.getScrollY(); 715 } 716 }); 717 } 718 719 public final DisplayMetrics getDisplayMetrics() { 720 return getValue(new ValueGetter<DisplayMetrics>() { 721 @Override 722 public DisplayMetrics capture() { 723 return mWebView.getContext().getResources().getDisplayMetrics(); 724 } 725 }); 726 } 727 728 public boolean requestChildRectangleOnScreen(final View child, 729 final Rect rect, 730 final boolean immediate) { 731 return getValue(new ValueGetter<Boolean>() { 732 @Override 733 public Boolean capture() { 734 return mWebView.requestChildRectangleOnScreen(child, rect, 735 immediate); 736 } 737 }); 738 } 739 740 public int findAll(final String find) { 741 return getValue(new ValueGetter<Integer>() { 742 @Override 743 public Integer capture() { 744 return mWebView.findAll(find); 745 } 746 }); 747 } 748 749 public Picture capturePicture() { 750 return getValue(new ValueGetter<Picture>() { 751 @Override 752 public Picture capture() { 753 return mWebView.capturePicture(); 754 } 755 }); 756 } 757 758 public void evaluateJavascript(final String script, final ValueCallback<String> result) { 759 runOnUiThread(new Runnable() { 760 @Override 761 public void run() { 762 mWebView.evaluateJavascript(script, result); 763 } 764 }); 765 } 766 767 public void saveWebArchive(final String basename, final boolean autoname, 768 final ValueCallback<String> callback) { 769 runOnUiThread(new Runnable() { 770 @Override 771 public void run() { 772 mWebView.saveWebArchive(basename, autoname, callback); 773 } 774 }); 775 } 776 777 public WebView createWebView() { 778 return getValue(new ValueGetter<WebView>() { 779 @Override 780 public WebView capture() { 781 return new WebView(mWebView.getContext()); 782 } 783 }); 784 } 785 786 public PrintDocumentAdapter createPrintDocumentAdapter() { 787 return getValue(new ValueGetter<PrintDocumentAdapter>() { 788 @Override 789 public PrintDocumentAdapter capture() { 790 return mWebView.createPrintDocumentAdapter(); 791 } 792 }); 793 } 794 795 public void setLayoutHeightToMatchParent() { 796 runOnUiThread(new Runnable() { 797 @Override 798 public void run() { 799 ViewParent parent = mWebView.getParent(); 800 if (parent instanceof ViewGroup) { 801 ((ViewGroup) parent).getLayoutParams().height = 802 ViewGroup.LayoutParams.MATCH_PARENT; 803 } 804 mWebView.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT; 805 mWebView.requestLayout(); 806 } 807 }); 808 } 809 810 public void setAcceptThirdPartyCookies(final boolean accept) { 811 runOnUiThread(new Runnable() { 812 @Override 813 public void run() { 814 CookieManager.getInstance().setAcceptThirdPartyCookies(mWebView, accept); 815 } 816 }); 817 } 818 819 public boolean acceptThirdPartyCookies() { 820 return getValue(new ValueGetter<Boolean>() { 821 @Override 822 public Boolean capture() { 823 return CookieManager.getInstance().acceptThirdPartyCookies(mWebView); 824 } 825 }); 826 } 827 828 /** 829 * Helper for running code on the UI thread where an exception is 830 * a test failure. If this is already the UI thread then it runs 831 * the code immediately. 832 * 833 * @see runTestOnUiThread 834 * @param r The code to run in the UI thread 835 */ 836 public void runOnUiThread(Runnable r) { 837 try { 838 if (isUiThread()) { 839 r.run(); 840 } else { 841 mTest.runTestOnUiThread(r); 842 } 843 } catch (Throwable t) { 844 Assert.fail("Unexpected error while running on UI thread: " 845 + t.getMessage()); 846 } 847 } 848 849 /** 850 * Accessor for underlying WebView. 851 * @return The WebView being wrapped by this class. 852 */ 853 public WebView getWebView() { 854 return mWebView; 855 } 856 857 private<T> T getValue(ValueGetter<T> getter) { 858 runOnUiThread(getter); 859 return getter.getValue(); 860 } 861 862 private abstract class ValueGetter<T> implements Runnable { 863 private T mValue; 864 865 @Override 866 public void run() { 867 mValue = capture(); 868 } 869 870 protected abstract T capture(); 871 872 public T getValue() { 873 return mValue; 874 } 875 } 876 877 /** 878 * Returns true if the current thread is the UI thread based on the 879 * Looper. 880 */ 881 private static boolean isUiThread() { 882 return (Looper.myLooper() == Looper.getMainLooper()); 883 } 884 885 /** 886 * @return Whether or not the load has finished. 887 */ 888 private synchronized boolean isLoaded() { 889 return mLoaded && mNewPicture && mProgress == 100; 890 } 891 892 /** 893 * Makes a WebView call, waits for completion and then resets the 894 * load state in preparation for the next load call. 895 * @param call The call to make on the UI thread prior to waiting. 896 */ 897 private void callAndWait(Runnable call) { 898 Assert.assertTrue("WebViewOnUiThread.load*AndWaitForCompletion calls " 899 + "may not be mixed with load* calls directly on WebView " 900 + "without calling waitForLoadCompletion after the load", 901 !isLoaded()); 902 clearLoad(); // clear any extraneous signals from a previous load. 903 runOnUiThread(call); 904 waitForLoadCompletion(); 905 } 906 907 /** 908 * Called whenever a load has been completed so that a subsequent call to 909 * waitForLoadCompletion doesn't return immediately. 910 */ 911 synchronized private void clearLoad() { 912 mLoaded = false; 913 mNewPicture = false; 914 mProgress = 0; 915 } 916 917 /** 918 * Uses a polling mechanism, while pumping messages to check when the 919 * criteria is met. 920 */ 921 private void waitOnUiThread(long timeout, final Callable<Boolean> doneCriteria) { 922 new PollingCheck(timeout) { 923 @Override 924 protected boolean check() { 925 pumpMessages(); 926 try { 927 return doneCriteria.call(); 928 } catch (Exception e) { 929 Assert.fail("Unexpected error while checking the criteria: " 930 + e.getMessage()); 931 return true; 932 } 933 } 934 }.run(); 935 } 936 937 /** 938 * Uses a wait/notify to check when the criteria is met. 939 */ 940 private synchronized void waitOnTestThread(long timeout, Callable<Boolean> doneCriteria) { 941 try { 942 long waitEnd = SystemClock.uptimeMillis() + timeout; 943 long timeRemaining = timeout; 944 while (!doneCriteria.call() && timeRemaining > 0) { 945 this.wait(timeRemaining); 946 timeRemaining = waitEnd - SystemClock.uptimeMillis(); 947 } 948 Assert.assertTrue("Action failed to complete before timeout", doneCriteria.call()); 949 } catch (InterruptedException e) { 950 // We'll just drop out of the loop and fail 951 } catch (Exception e) { 952 Assert.fail("Unexpected error while checking the criteria: " 953 + e.getMessage()); 954 } 955 } 956 957 /** 958 * Pumps all currently-queued messages in the UI thread and then exits. 959 * This is useful to force processing while running tests in the UI thread. 960 */ 961 private void pumpMessages() { 962 class ExitLoopException extends RuntimeException { 963 } 964 965 // Force loop to exit when processing this. Loop.quit() doesn't 966 // work because this is the main Loop. 967 mWebView.getHandler().post(new Runnable() { 968 @Override 969 public void run() { 970 throw new ExitLoopException(); // exit loop! 971 } 972 }); 973 try { 974 // Pump messages until our message gets through. 975 Looper.loop(); 976 } catch (ExitLoopException e) { 977 } 978 } 979 980 /** 981 * A WebChromeClient used to capture the onProgressChanged for use 982 * in waitFor functions. If a test must override the WebChromeClient, 983 * it can derive from this class or call onProgressChanged 984 * directly. 985 */ 986 public static class WaitForProgressClient extends WebChromeClient { 987 private WebViewOnUiThread mOnUiThread; 988 989 public WaitForProgressClient(WebViewOnUiThread onUiThread) { 990 mOnUiThread = onUiThread; 991 } 992 993 @Override 994 public void onProgressChanged(WebView view, int newProgress) { 995 super.onProgressChanged(view, newProgress); 996 mOnUiThread.onProgressChanged(newProgress); 997 } 998 } 999 1000 /** 1001 * A WebViewClient that captures the onPageFinished for use in 1002 * waitFor functions. Using initializeWebView sets the WaitForLoadedClient 1003 * into the WebView. If a test needs to set a specific WebViewClient and 1004 * needs the waitForCompletion capability then it should derive from 1005 * WaitForLoadedClient or call WebViewOnUiThread.onPageFinished. 1006 */ 1007 public static class WaitForLoadedClient extends WebViewClient { 1008 private WebViewOnUiThread mOnUiThread; 1009 1010 public WaitForLoadedClient(WebViewOnUiThread onUiThread) { 1011 mOnUiThread = onUiThread; 1012 } 1013 1014 @Override 1015 public void onPageFinished(WebView view, String url) { 1016 super.onPageFinished(view, url); 1017 mOnUiThread.onPageFinished(); 1018 } 1019 1020 @Override 1021 public void onPageStarted(WebView view, String url, Bitmap favicon) { 1022 super.onPageStarted(view, url, favicon); 1023 mOnUiThread.onPageStarted(); 1024 } 1025 } 1026 1027 /** 1028 * A PictureListener that captures the onNewPicture for use in 1029 * waitForLoadCompletion. Using initializeWebView sets the PictureListener 1030 * into the WebView. If a test needs to set a specific PictureListener and 1031 * needs the waitForCompletion capability then it should call 1032 * WebViewOnUiThread.onNewPicture. 1033 */ 1034 private class WaitForNewPicture implements PictureListener { 1035 @Override 1036 public void onNewPicture(WebView view, Picture picture) { 1037 WebViewOnUiThread.this.onNewPicture(); 1038 } 1039 } 1040 } 1041