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