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