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