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