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