Home | History | Annotate | Download | only in cts
      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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    160     synchronized public void onProgressChanged(int progress) {
    161         mProgress = progress;
    162         this.notifyAll();
    163     }
    164 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    228     public void clearHistory() {
    229         runOnUiThread(new Runnable() {
    230             @Override
    231             public void run() {
    232                 mWebView.clearHistory();
    233             }
    234         });
    235     }
    236 
    237     public void requestFocus() {
    238         runOnUiThread(new Runnable() {
    239             @Override
    240             public void run() {
    241                 mWebView.requestFocus();
    242             }
    243         });
    244     }
    245 
    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