waitForLoadComplete now uses callbacks instead of sleep.

 Bug 5734102
 Force the waitForLoaded to actually wait until the page is
 loaded and painted rather than waiting for one second. This
 has the benefit of allowing messages to get pumped in the UI
 thread while waiting for a load. It also makes the tests run
 twice as fast as there is no arbitrary wait for loading time.

Change-Id: Ie12e717e3bc334fb2cc1dcb532f0daaf58b8cbd7
diff --git a/tests/src/android/webkit/cts/WaitForLoadUrl.java b/tests/src/android/webkit/cts/WaitForLoadUrl.java
new file mode 100644
index 0000000..417d5c5
--- /dev/null
+++ b/tests/src/android/webkit/cts/WaitForLoadUrl.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit.cts;
+
+import android.cts.util.PollingCheck;
+import android.graphics.Picture;
+import android.os.Handler;
+import android.os.Looper;
+import android.webkit.WebView;
+import android.webkit.WebView.PictureListener;
+import android.webkit.WebViewClient;
+
+/**
+ * This class is used to determine when a page has finished loading. A
+ * WebViewClient listens for the "onPageFinished" and then waits for the
+ * corresponding onNewPicture as received from a PictureListener.
+ *
+ * initializeWebView sets a WebViewClient and a PictureListener. If you
+ * provide your own PictureListener, you must call WaitForLoadUrl.onNewPicture.
+ * If you need to provide your own WebViewClient, then extend
+ * WaitForLoadedClient or call WaitForLoadUrl.onPageFinished.
+ *
+ * This class only really works correctly with a single WebView active because
+ * the picture listening is a static.
+ */
+class WaitForLoadUrl extends WebViewClient {
+    /**
+     * A WebViewClient that captures the onPageFinished for use in
+     * waitForLoadComplete. Using initializeWebView sets the WaitForLoadedClient
+     * into the WebView. If a test needs to set a specific WebViewClient and
+     * needs the waitForLoadComplete capability then it should derive from
+     * WaitForLoadedClient or call WaitForLoadUrl.onPageFinished.
+     */
+    public static class WaitForLoadedClient extends WebViewClient {
+        public void onPageFinished(WebView view, String url) {
+            WaitForLoadUrl.onPageFinished();
+        }
+    }
+
+    /**
+     * A Picture that captures the onNewPicture for use in
+     * waitForLoadComplete. Using initializeWebView sets the PictureListener
+     * into the WebView. If a test needs to set a specific PictureListener and
+     * needs the waitForLoadComplete capability then it should call
+     * WaitForLoadUrl.onNewPicture.
+     */
+    private static class WaitForNewPicture implements PictureListener {
+        public void onNewPicture(WebView view, Picture picture) {
+            WaitForLoadUrl.onNewPicture();
+        }
+    }
+
+    /**
+     * Set to true after onPageFinished is called.
+     */
+    private static boolean mLoaded;
+
+    /**
+     * Set to true after onNewPicture is called. Reset when onPageFinished
+     * is called.
+     */
+    private static boolean mNewPicture;
+
+    /**
+     * Called from WaitForNewPicture, this is used to indicate that
+     * the page has been drawn.
+     */
+    public static void onNewPicture() {
+        mNewPicture = true;
+    }
+
+    /**
+     * Called from WaitForLoadedClient, this is used to indicate that
+     * the page is loaded, but not drawn yet.
+     */
+    public static void onPageFinished() {
+        mLoaded = true;
+        mNewPicture = false; // Earlier paints won't count.
+    }
+
+    /**
+     * Sets the WebViewClient and PictureListener for a WebView to prepare
+     * it for the waitForLoadComplete call.
+     */
+    public static void initializeWebView(WebView view) {
+        view.setWebViewClient(new WaitForLoadedClient());
+        view.setPictureListener(new WaitForNewPicture());
+        clearLoad();
+    }
+
+    /**
+     * Called whenever a load has been completed so that a subsequent call to
+     * waitForLoadComplete doesn't return immediately.
+     */
+    public static void clearLoad() {
+        mLoaded = false;
+        mNewPicture = false;
+    }
+
+    /**
+     * Wait for a page to load for at least timeout milliseconds.
+     * This function requires that initializeWebView be called on the webView
+     * prior to running or a different means for triggering onPageFinished
+     * and onNewPicture calls. A failure for the page to be loaded by timeout
+     * milliseconds will result in an test failure.
+     *
+     * This call may be made on the UI thread or a test thread.
+     */
+    public static void waitForLoadComplete(long timeout) {
+        final boolean isUIThread = (Looper.myLooper() == Looper.getMainLooper());
+        // on the UI thread
+        new PollingCheck(timeout) {
+            @Override
+            protected boolean check() {
+                if (isUIThread) {
+                    pumpMessages();
+                }
+                boolean isLoaded = mLoaded && mNewPicture;
+                if (isLoaded) {
+                    clearLoad();
+                }
+                return isLoaded;
+            }
+        }.run();
+    }
+
+    /**
+     * Pumps all currently-queued messages in the UI thread and then exits.
+     * This is useful to force processing while running tests in the UI thread.
+     */
+    public static void pumpMessages() {
+        class ExitLoopException extends RuntimeException {
+        }
+
+        // Force loop to exit when processing this. Loop.quit() doesn't
+        // work because this is the main Loop.
+        Handler handler = new Handler();
+        handler.post(new Runnable() {
+            public void run() {
+                throw new ExitLoopException(); // exit loop!
+            }
+        });
+        try {
+            // Pump messages until our message gets through.
+            Looper.loop();
+        } catch (ExitLoopException e) {
+        }
+    }
+
+}
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
index 274b31c..3f6770e 100755
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
@@ -51,6 +51,8 @@
 import android.view.View;
 import android.webkit.CacheManager;
 import android.webkit.CacheManager.CacheResult;
+import android.webkit.cts.WaitForLoadUrl;
+import android.webkit.cts.WaitForLoadUrl.WaitForLoadedClient;
 import android.webkit.ConsoleMessage;
 import android.webkit.DownloadListener;
 import android.webkit.SslErrorHandler;
@@ -76,7 +78,6 @@
     private static final String LOGTAG = "WebViewTest";
     private static final int INITIAL_PROGRESS = 100;
     private static long TEST_TIMEOUT = 20000L;
-    private static long TIME_FOR_LAYOUT = 1000L;
 
     private WebView mWebView;
     private CtsTestServer mWebServer;
@@ -100,6 +101,15 @@
         if (f.exists()) {
             f.delete();
         }
+        try {
+            runTestOnUiThread(new Runnable() {
+                public void run() {
+                    WaitForLoadUrl.initializeWebView(mWebView);
+                }
+            });
+        } catch (Throwable t) {
+            Log.w(LOGTAG, "setUp(): Caught exception while preparing for load wait");
+        }
     }
 
     @Override
@@ -440,7 +450,7 @@
                 // handle loading a new URL. We set a WebViewClient as
                 // WebViewClient.shouldOverrideUrlLoading() returns false, so
                 // the WebView will load the new URL.
-                mWebView.setWebViewClient(new WebViewClient());
+                mWebView.setWebViewClient(new WaitForLoadedClient());
                 mWebView.setWebChromeClient(new LoadCompleteWebChromeClient());
                 mWebView.loadUrl(redirectUrl);
             }
@@ -849,6 +859,9 @@
             public Picture picture;
 
             public void onNewPicture(WebView view, Picture picture) {
+                // Need to inform the listener tracking new picture
+                // for the "page loaded" knowledge since it has been replaced.
+                WaitForLoadUrl.onNewPicture();
                 this.callCount += 1;
                 this.webView = view;
                 this.picture = picture;
@@ -1795,6 +1808,7 @@
                 MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN,
                         location[0] + imgWidth / 2,
                         location[1] + imgHeight / 2, 0));
+        getInstrumentation().waitForIdleSync();
         runTestOnUiThread(new Runnable() {
             public void run() {
                 mWebView.requestImageRef(msg);
@@ -2091,7 +2105,7 @@
         args = {WebViewClient.class}
     )
     public void testSetWebViewClient() throws Throwable {
-        final class MockWebViewClient extends WebViewClient {
+        final class MockWebViewClient extends WaitForLoadedClient {
             private boolean mOnScaleChangedCalled = false;
             @Override
             public void onScaleChanged(WebView view, float oldScale, float newScale) {
@@ -2192,7 +2206,7 @@
         )
     })
     public void testSecureSiteSetsCertificate() throws Throwable {
-        final class MockWebViewClient extends WebViewClient {
+        final class MockWebViewClient extends WaitForLoadedClient {
             @Override
             public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
                 handler.proceed();
@@ -2243,7 +2257,7 @@
         args = {}
     )
     public void testOnReceivedSslError() throws Throwable {
-        final class MockWebViewClient extends WebViewClient {
+        final class MockWebViewClient extends WaitForLoadedClient {
             private String mErrorUrl;
             private WebView mWebView;
             @Override
@@ -2283,7 +2297,7 @@
         args = {}
     )
     public void testOnReceivedSslErrorProceed() throws Throwable {
-        final class MockWebViewClient extends WebViewClient {
+        final class MockWebViewClient extends WaitForLoadedClient {
             @Override
             public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
                 handler.proceed();
@@ -2313,7 +2327,7 @@
         args = {}
     )
     public void testOnReceivedSslErrorCancel() throws Throwable {
-        final class MockWebViewClient extends WebViewClient {
+        final class MockWebViewClient extends WaitForLoadedClient {
             @Override
             public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
                 handler.cancel();
@@ -2498,7 +2512,7 @@
                 // handle loading a new URL. We set WebViewClient as
                 // WebViewClient.shouldOverrideUrlLoading() returns false, so
                 // the WebView will load the new URL.
-                mWebView.setWebViewClient(new WebViewClient());
+                mWebView.setWebViewClient(new WaitForLoadedClient());
                 mWebView.setDownloadListener(listener);
                 mWebView.loadData("<html><body><a href=\"" + url + "\">link</a></body></html>",
                         "text/html", null);
@@ -2941,17 +2955,7 @@
     }
 
     private void waitForLoadComplete() {
-        new PollingCheck(TEST_TIMEOUT) {
-            @Override
-            protected boolean check() {
-                return mWebView.getProgress() == 100;
-            }
-        }.run();
-        try {
-            Thread.sleep(TIME_FOR_LAYOUT);
-        } catch (InterruptedException e) {
-            Log.w(LOGTAG, "waitForLoadComplete() interrupted while sleeping for layout delay.");
-        }
+        WaitForLoadUrl.waitForLoadComplete(TEST_TIMEOUT);
     }
 
     private synchronized void notifyUiThreadDone() {
@@ -2983,7 +2987,7 @@
     }
 
     // Note that this class is not thread-safe.
-    final class SslErrorWebViewClient extends WebViewClient {
+    final class SslErrorWebViewClient extends WaitForLoadedClient {
         private boolean mWasOnReceivedSslErrorCalled;
         @Override
         public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {