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 WebViewOnUiThread(InstrumentationTestCase test, WebView webView)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 */ WebViewOnUiThread(ActivityTestRule activityTestRule, WebView webView)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 */ cleanUp()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 */ onNewPicture()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 */ onPageStarted()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 */ onPageFinished()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 */ onProgressChanged(int progress)202 synchronized public void onProgressChanged(int progress) { 203 mProgress = progress; 204 this.notifyAll(); 205 } 206 setWebViewClient(final WebViewClient webViewClient)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 setWebChromeClient(final WebChromeClient webChromeClient)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 setPictureListener(final PictureListener pictureListener)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 setNetworkAvailable(final boolean available)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 setDownloadListener(final DownloadListener listener)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 setBackgroundColor(final int color)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 clearCache(final boolean includeDiskFiles)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 clearHistory()270 public void clearHistory() { 271 runOnUiThread(new Runnable() { 272 @Override 273 public void run() { 274 mWebView.clearHistory(); 275 } 276 }); 277 } 278 requestFocus()279 public void requestFocus() { 280 runOnUiThread(new Runnable() { 281 @Override 282 public void run() { 283 mWebView.requestFocus(); 284 } 285 }); 286 } 287 canZoomIn()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 savePicture(final Bundle b, final File dest) { 665 return getValue(new ValueGetter<Boolean>() { 666 @Override 667 public Boolean capture() { 668 return mWebView.savePicture(b, dest); 669 } 670 }); 671 } 672 673 public boolean pageUp(final boolean top) { 674 return getValue(new ValueGetter<Boolean>() { 675 @Override 676 public Boolean capture() { 677 return mWebView.pageUp(top); 678 } 679 }); 680 } 681 682 public boolean pageDown(final boolean bottom) { 683 return getValue(new ValueGetter<Boolean>() { 684 @Override 685 public Boolean capture() { 686 return mWebView.pageDown(bottom); 687 } 688 }); 689 } 690 691 public void postVisualStateCallback(final long requestId, final VisualStateCallback callback) { 692 runOnUiThread(new Runnable() { 693 @Override 694 public void run() { 695 mWebView.postVisualStateCallback(requestId, callback); 696 } 697 }); 698 } 699 700 public int[] getLocationOnScreen() { 701 final int[] location = new int[2]; 702 return getValue(new ValueGetter<int[]>() { 703 @Override 704 public int[] capture() { 705 mWebView.getLocationOnScreen(location); 706 return location; 707 } 708 }); 709 } 710 711 public float getScale() { 712 return getValue(new ValueGetter<Float>() { 713 @Override 714 public Float capture() { 715 return mWebView.getScale(); 716 } 717 }); 718 } 719 720 public boolean requestFocus(final int direction, 721 final Rect previouslyFocusedRect) { 722 return getValue(new ValueGetter<Boolean>() { 723 @Override 724 public Boolean capture() { 725 return mWebView.requestFocus(direction, previouslyFocusedRect); 726 } 727 }); 728 } 729 730 public HitTestResult getHitTestResult() { 731 return getValue(new ValueGetter<HitTestResult>() { 732 @Override 733 public HitTestResult capture() { 734 return mWebView.getHitTestResult(); 735 } 736 }); 737 } 738 739 public int getScrollX() { 740 return getValue(new ValueGetter<Integer>() { 741 @Override 742 public Integer capture() { 743 return mWebView.getScrollX(); 744 } 745 }); 746 } 747 748 public int getScrollY() { 749 return getValue(new ValueGetter<Integer>() { 750 @Override 751 public Integer capture() { 752 return mWebView.getScrollY(); 753 } 754 }); 755 } 756 757 public final DisplayMetrics getDisplayMetrics() { 758 return getValue(new ValueGetter<DisplayMetrics>() { 759 @Override 760 public DisplayMetrics capture() { 761 return mWebView.getContext().getResources().getDisplayMetrics(); 762 } 763 }); 764 } 765 766 public boolean requestChildRectangleOnScreen(final View child, 767 final Rect rect, 768 final boolean immediate) { 769 return getValue(new ValueGetter<Boolean>() { 770 @Override 771 public Boolean capture() { 772 return mWebView.requestChildRectangleOnScreen(child, rect, 773 immediate); 774 } 775 }); 776 } 777 778 public int findAll(final String find) { 779 return getValue(new ValueGetter<Integer>() { 780 @Override 781 public Integer capture() { 782 return mWebView.findAll(find); 783 } 784 }); 785 } 786 787 public Picture capturePicture() { 788 return getValue(new ValueGetter<Picture>() { 789 @Override 790 public Picture capture() { 791 return mWebView.capturePicture(); 792 } 793 }); 794 } 795 796 public void evaluateJavascript(final String script, final ValueCallback<String> result) { 797 runOnUiThread(new Runnable() { 798 @Override 799 public void run() { 800 mWebView.evaluateJavascript(script, result); 801 } 802 }); 803 } 804 805 public void saveWebArchive(final String basename, final boolean autoname, 806 final ValueCallback<String> callback) { 807 runOnUiThread(new Runnable() { 808 @Override 809 public void run() { 810 mWebView.saveWebArchive(basename, autoname, callback); 811 } 812 }); 813 } 814 815 public WebView createWebView() { 816 return getValue(new ValueGetter<WebView>() { 817 @Override 818 public WebView capture() { 819 return new WebView(mWebView.getContext()); 820 } 821 }); 822 } 823 824 public PrintDocumentAdapter createPrintDocumentAdapter() { 825 return getValue(new ValueGetter<PrintDocumentAdapter>() { 826 @Override 827 public PrintDocumentAdapter capture() { 828 return mWebView.createPrintDocumentAdapter(); 829 } 830 }); 831 } 832 833 public void setLayoutHeightToMatchParent() { 834 runOnUiThread(new Runnable() { 835 @Override 836 public void run() { 837 ViewParent parent = mWebView.getParent(); 838 if (parent instanceof ViewGroup) { 839 ((ViewGroup) parent).getLayoutParams().height = 840 ViewGroup.LayoutParams.MATCH_PARENT; 841 } 842 mWebView.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT; 843 mWebView.requestLayout(); 844 } 845 }); 846 } 847 848 public void setLayoutToMatchParent() { 849 runOnUiThread(new Runnable() { 850 @Override 851 public void run() { 852 setMatchParent((View) mWebView.getParent()); 853 setMatchParent(mWebView); 854 mWebView.requestLayout(); 855 } 856 }); 857 } 858 859 public void setAcceptThirdPartyCookies(final boolean accept) { 860 runOnUiThread(new Runnable() { 861 @Override 862 public void run() { 863 CookieManager.getInstance().setAcceptThirdPartyCookies(mWebView, accept); 864 } 865 }); 866 } 867 868 public boolean acceptThirdPartyCookies() { 869 return getValue(new ValueGetter<Boolean>() { 870 @Override 871 public Boolean capture() { 872 return CookieManager.getInstance().acceptThirdPartyCookies(mWebView); 873 } 874 }); 875 } 876 877 /** 878 * Helper for running code on the UI thread where an exception is 879 * a test failure. If this is already the UI thread then it runs 880 * the code immediately. 881 * 882 * @see InstrumentationTestCase#runTestOnUiThread(Runnable) 883 * @see ActivityTestRule#runOnUiThread(Runnable) 884 * @param r The code to run in the UI thread 885 */ 886 public void runOnUiThread(Runnable r) { 887 try { 888 if (isUiThread()) { 889 r.run(); 890 } else { 891 if (mActivityTestRule != null) { 892 mActivityTestRule.runOnUiThread(r); 893 } else { 894 mTest.runTestOnUiThread(r); 895 } 896 } 897 } catch (Throwable t) { 898 Assert.fail("Unexpected error while running on UI thread: " 899 + t.getMessage()); 900 } 901 } 902 903 /** 904 * Accessor for underlying WebView. 905 * @return The WebView being wrapped by this class. 906 */ 907 public WebView getWebView() { 908 return mWebView; 909 } 910 911 private<T> T getValue(ValueGetter<T> getter) { 912 runOnUiThread(getter); 913 return getter.getValue(); 914 } 915 916 private abstract class ValueGetter<T> implements Runnable { 917 private T mValue; 918 919 @Override 920 public void run() { 921 mValue = capture(); 922 } 923 924 protected abstract T capture(); 925 926 public T getValue() { 927 return mValue; 928 } 929 } 930 931 /** 932 * Returns true if the current thread is the UI thread based on the 933 * Looper. 934 */ 935 private static boolean isUiThread() { 936 return (Looper.myLooper() == Looper.getMainLooper()); 937 } 938 939 /** 940 * @return Whether or not the load has finished. 941 */ 942 private synchronized boolean isLoaded() { 943 return mLoaded && mNewPicture && mProgress == 100; 944 } 945 946 /** 947 * Makes a WebView call, waits for completion and then resets the 948 * load state in preparation for the next load call. 949 * @param call The call to make on the UI thread prior to waiting. 950 */ 951 private void callAndWait(Runnable call) { 952 Assert.assertTrue("WebViewOnUiThread.load*AndWaitForCompletion calls " 953 + "may not be mixed with load* calls directly on WebView " 954 + "without calling waitForLoadCompletion after the load", 955 !isLoaded()); 956 clearLoad(); // clear any extraneous signals from a previous load. 957 runOnUiThread(call); 958 waitForLoadCompletion(); 959 } 960 961 /** 962 * Called whenever a load has been completed so that a subsequent call to 963 * waitForLoadCompletion doesn't return immediately. 964 */ 965 synchronized private void clearLoad() { 966 mLoaded = false; 967 mNewPicture = false; 968 mProgress = 0; 969 } 970 971 /** 972 * Uses a polling mechanism, while pumping messages to check when the 973 * criteria is met. 974 */ 975 private void waitOnUiThread(long timeout, final Callable<Boolean> doneCriteria) { 976 new PollingCheck(timeout) { 977 @Override 978 protected boolean check() { 979 pumpMessages(); 980 try { 981 return doneCriteria.call(); 982 } catch (Exception e) { 983 Assert.fail("Unexpected error while checking the criteria: " 984 + e.getMessage()); 985 return true; 986 } 987 } 988 }.run(); 989 } 990 991 /** 992 * Uses a wait/notify to check when the criteria is met. 993 */ 994 private synchronized void waitOnTestThread(long timeout, Callable<Boolean> doneCriteria) { 995 try { 996 long waitEnd = SystemClock.uptimeMillis() + timeout; 997 long timeRemaining = timeout; 998 while (!doneCriteria.call() && timeRemaining > 0) { 999 this.wait(timeRemaining); 1000 timeRemaining = waitEnd - SystemClock.uptimeMillis(); 1001 } 1002 Assert.assertTrue("Action failed to complete before timeout", doneCriteria.call()); 1003 } catch (InterruptedException e) { 1004 // We'll just drop out of the loop and fail 1005 } catch (Exception e) { 1006 Assert.fail("Unexpected error while checking the criteria: " 1007 + e.getMessage()); 1008 } 1009 } 1010 1011 /** 1012 * Pumps all currently-queued messages in the UI thread and then exits. 1013 * This is useful to force processing while running tests in the UI thread. 1014 */ 1015 private void pumpMessages() { 1016 class ExitLoopException extends RuntimeException { 1017 } 1018 1019 // Force loop to exit when processing this. Loop.quit() doesn't 1020 // work because this is the main Loop. 1021 mWebView.getHandler().post(new Runnable() { 1022 @Override 1023 public void run() { 1024 throw new ExitLoopException(); // exit loop! 1025 } 1026 }); 1027 try { 1028 // Pump messages until our message gets through. 1029 Looper.loop(); 1030 } catch (ExitLoopException e) { 1031 } 1032 } 1033 1034 /** 1035 * Set LayoutParams to MATCH_PARENT. 1036 * 1037 * @param view Target view 1038 */ 1039 private void setMatchParent(View view) { 1040 ViewGroup.LayoutParams params = view.getLayoutParams(); 1041 params.height = ViewGroup.LayoutParams.MATCH_PARENT; 1042 params.width = ViewGroup.LayoutParams.MATCH_PARENT; 1043 view.setLayoutParams(params); 1044 } 1045 1046 /** 1047 * A WebChromeClient used to capture the onProgressChanged for use 1048 * in waitFor functions. If a test must override the WebChromeClient, 1049 * it can derive from this class or call onProgressChanged 1050 * directly. 1051 */ 1052 public static class WaitForProgressClient extends WebChromeClient { 1053 private WebViewOnUiThread mOnUiThread; 1054 1055 public WaitForProgressClient(WebViewOnUiThread onUiThread) { 1056 mOnUiThread = onUiThread; 1057 } 1058 1059 @Override 1060 public void onProgressChanged(WebView view, int newProgress) { 1061 super.onProgressChanged(view, newProgress); 1062 mOnUiThread.onProgressChanged(newProgress); 1063 } 1064 } 1065 1066 /** 1067 * A WebViewClient that captures the onPageFinished for use in 1068 * waitFor functions. Using initializeWebView sets the WaitForLoadedClient 1069 * into the WebView. If a test needs to set a specific WebViewClient and 1070 * needs the waitForCompletion capability then it should derive from 1071 * WaitForLoadedClient or call WebViewOnUiThread.onPageFinished. 1072 */ 1073 public static class WaitForLoadedClient extends WebViewClient { 1074 private WebViewOnUiThread mOnUiThread; 1075 1076 public WaitForLoadedClient(WebViewOnUiThread onUiThread) { 1077 mOnUiThread = onUiThread; 1078 } 1079 1080 @Override 1081 public void onPageFinished(WebView view, String url) { 1082 super.onPageFinished(view, url); 1083 mOnUiThread.onPageFinished(); 1084 } 1085 1086 @Override 1087 public void onPageStarted(WebView view, String url, Bitmap favicon) { 1088 super.onPageStarted(view, url, favicon); 1089 mOnUiThread.onPageStarted(); 1090 } 1091 } 1092 1093 /** 1094 * A PictureListener that captures the onNewPicture for use in 1095 * waitForLoadCompletion. Using initializeWebView sets the PictureListener 1096 * into the WebView. If a test needs to set a specific PictureListener and 1097 * needs the waitForCompletion capability then it should call 1098 * WebViewOnUiThread.onNewPicture. 1099 */ 1100 private class WaitForNewPicture implements PictureListener { 1101 @Override 1102 public void onNewPicture(WebView view, Picture picture) { 1103 WebViewOnUiThread.this.onNewPicture(); 1104 } 1105 } 1106 } 1107