/*
 * Copyright (C) 2009 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 dalvik.annotation.BrokenTest;
import dalvik.annotation.TestLevel;
import dalvik.annotation.TestTargetClass;
import dalvik.annotation.TestTargetNew;
import dalvik.annotation.TestTargets;
import dalvik.annotation.ToBeFixed;

import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Picture;
import android.graphics.Rect;
import android.graphics.Bitmap.Config;
import android.net.http.SslCertificate;
import android.net.http.SslError;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.test.ActivityInstrumentationTestCase2;
import android.test.UiThreadTest;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.cts.DelayedCheck;
import android.webkit.CacheManager;
import android.webkit.DownloadListener;
import android.webkit.SslErrorHandler;
import android.webkit.WebBackForwardList;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.webkit.WebViewDatabase;
import android.webkit.WebView.HitTestResult;
import android.webkit.WebView.PictureListener;
import android.widget.AutoCompleteTextView;
import android.widget.LinearLayout;

import java.io.File;
import java.io.FileInputStream;

@TestTargetClass(android.webkit.WebView.class)
public class WebViewTest extends ActivityInstrumentationTestCase2<WebViewStubActivity> {
    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;

    public WebViewTest() {
        super("com.android.cts.stub", WebViewStubActivity.class);
    }

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        mWebView = getActivity().getWebView();
        File f = getActivity().getFileStreamPath("snapshot");
        if (f.exists()) {
            f.delete();
        }
    }

    @Override
    protected void tearDown() throws Exception {
        mWebView.clearHistory();
        mWebView.clearCache(true);
        if (mWebServer != null) {
            mWebServer.shutdown();
        }
        super.tearDown();
    }

    private void startWebServer(boolean secure) throws Exception {
        assertNull(mWebServer);
        mWebServer = new CtsTestServer(getActivity(), secure);
    }

    @TestTargets({
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "WebView",
            args = {Context.class}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "WebView",
            args = {Context.class, AttributeSet.class}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "WebView",
            args = {Context.class, AttributeSet.class, int.class}
        )
    })
    public void testConstructor() {
        new WebView(getActivity());
        new WebView(getActivity(), null);
        new WebView(getActivity(), null, 0);
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "findAddress",
        args = {String.class}
    )
    public void testFindAddress() {
        /*
         * Info about USPS
         * http://en.wikipedia.org/wiki/Postal_address#United_States
         * http://www.usps.com/
         */
        // full address
        assertEquals("455 LARKSPUR DRIVE CALIFORNIA SPRINGS CALIFORNIA 92926",
                WebView.findAddress("455 LARKSPUR DRIVE CALIFORNIA SPRINGS CALIFORNIA 92926"));
        // full address ( with abbreviated street type and state)
        assertEquals("455 LARKSPUR DR CALIFORNIA SPRINGS CA 92926",
                WebView.findAddress("455 LARKSPUR DR CALIFORNIA SPRINGS CA 92926"));
        // misspell the state ( CALIFORNIA -> CALIFONIA )
        assertNull(WebView.findAddress("455 LARKSPUR DRIVE CALIFORNIA SPRINGS CALIFONIA 92926"));
        // without optional zip code
        assertEquals("455 LARKSPUR DR CALIFORNIA SPRINGS CA",
                WebView.findAddress("455 LARKSPUR DR CALIFORNIA SPRINGS CA"));
        // house number, street name and street type are missing
        assertNull(WebView.findAddress("CALIFORNIA SPRINGS CA"));
        // city & state are missing
        assertNull(WebView.findAddress("455 LARKSPUR DR"));
    }

    @TestTargets({
        @TestTargetNew(
            level = TestLevel.SUFFICIENT,
            method = "getZoomControls",
            args = {}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "getSettings",
            args = {}
        )
    })
    @SuppressWarnings("deprecation")
    @UiThreadTest
    public void testGetZoomControls() {
         WebSettings settings = mWebView.getSettings();
         assertTrue(settings.supportZoom());
         View zoomControls = mWebView.getZoomControls();
         assertNotNull(zoomControls);

         // disable zoom support
         settings.setSupportZoom(false);
         assertFalse(settings.supportZoom());
         assertNull(mWebView.getZoomControls());
    }

    @TestTargetNew(
        level = TestLevel.SUFFICIENT,
        method = "invokeZoomPicker",
        args = {},
        notes = "Cannot test the effect of this method"
    )
    public void testInvokeZoomPicker() throws Exception {
        WebSettings settings = mWebView.getSettings();
        assertTrue(settings.supportZoom());
        startWebServer(false);
        String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
        assertLoadUrlSuccessfully(mWebView, url);
        mWebView.invokeZoomPicker();
    }

    @TestTargets({
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "zoomIn",
            args = {}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "zoomOut",
            args = {}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "getScale",
            args = {}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "getSettings",
            args = {}
        )
    })
    @UiThreadTest
    public void testZoom() {
        WebSettings settings = mWebView.getSettings();
        settings.setSupportZoom(false);
        assertFalse(settings.supportZoom());
        float currScale = mWebView.getScale();
        float previousScale = currScale;

        // can zoom in or out although zoom support is disabled in web settings
        assertTrue(mWebView.zoomIn());
        currScale = mWebView.getScale();
        assertTrue(currScale > previousScale);

        // zoom in
        assertTrue(mWebView.zoomOut());
        previousScale = currScale;
        currScale = mWebView.getScale();
        assertTrue(currScale < previousScale);

        // enable zoom support
        settings.setSupportZoom(true);
        assertTrue(settings.supportZoom());
        currScale = mWebView.getScale();

        assertTrue(mWebView.zoomIn());
        previousScale = currScale;
        currScale = mWebView.getScale();
        assertTrue(currScale > previousScale);

        // zoom in until it reaches maximum scale
        while (currScale > previousScale) {
            mWebView.zoomIn();
            previousScale = currScale;
            currScale = mWebView.getScale();
        }

        // can not zoom in further
        assertFalse(mWebView.zoomIn());
        previousScale = currScale;
        currScale = mWebView.getScale();
        assertEquals(currScale, previousScale);

        // zoom out
        assertTrue(mWebView.zoomOut());
        previousScale = currScale;
        currScale = mWebView.getScale();
        assertTrue(currScale < previousScale);

        // zoom out until it reaches minimum scale
        while (currScale < previousScale) {
            mWebView.zoomOut();
            previousScale = currScale;
            currScale = mWebView.getScale();
        }

        // can not zoom out further
        assertFalse(mWebView.zoomOut());
        previousScale = currScale;
        currScale = mWebView.getScale();
        assertEquals(currScale, previousScale);
    }

    @TestTargets({
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "setScrollBarStyle",
            args = {int.class}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "overlayHorizontalScrollbar",
            args = {}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "overlayVerticalScrollbar",
            args = {}
        )
    })
    @UiThreadTest
    public void testSetScrollBarStyle() {
        mWebView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
        assertFalse(mWebView.overlayHorizontalScrollbar());
        assertFalse(mWebView.overlayVerticalScrollbar());

        mWebView.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
        assertTrue(mWebView.overlayHorizontalScrollbar());
        assertTrue(mWebView.overlayVerticalScrollbar());

        mWebView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_INSET);
        assertFalse(mWebView.overlayHorizontalScrollbar());
        assertFalse(mWebView.overlayVerticalScrollbar());

        mWebView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);
        assertTrue(mWebView.overlayHorizontalScrollbar());
        assertTrue(mWebView.overlayVerticalScrollbar());
    }

    @TestTargets({
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "setHorizontalScrollbarOverlay",
            args = {boolean.class}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "setVerticalScrollbarOverlay",
            args = {boolean.class}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "overlayHorizontalScrollbar",
            args = {}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "overlayVerticalScrollbar",
            args = {}
        )
    })
    public void testScrollBarOverlay() throws Throwable {
        DisplayMetrics metrics = mWebView.getContext().getResources().getDisplayMetrics();
        int dimension = 2 * Math.max(metrics.widthPixels, metrics.heightPixels);

        String p = "<p style=\"height:" + dimension + "px;" +
                "width:" + dimension + "px;margin:0px auto;\">Test scroll bar overlay.</p>";
        mWebView.loadData("<html><body>" + p + "</body></html>", "text/html", "UTF-8");
        waitForLoadComplete(mWebView, TEST_TIMEOUT);
        assertTrue(mWebView.overlayHorizontalScrollbar());
        assertFalse(mWebView.overlayVerticalScrollbar());
        int startX = mWebView.getScrollX();
        int startY = mWebView.getScrollY();

        final int bigVelocity = 10000;
        // fling to the max and wait for ending scroll
        runTestOnUiThread(new Runnable() {
            public void run() {
                mWebView.flingScroll(bigVelocity, bigVelocity);
            }
        });
        getInstrumentation().waitForIdleSync();

        int overlayOffsetX = mWebView.getScrollX() - startX;
        int insetOffsetY = mWebView.getScrollY() - startY;

        // scroll back
        runTestOnUiThread(new Runnable() {
            public void run() {
                mWebView.flingScroll(-bigVelocity, -bigVelocity);
            }
        });
        getInstrumentation().waitForIdleSync();

        mWebView.setHorizontalScrollbarOverlay(false);
        mWebView.setVerticalScrollbarOverlay(true);
        assertFalse(mWebView.overlayHorizontalScrollbar());
        assertTrue(mWebView.overlayVerticalScrollbar());

        // fling to the max and wait for ending scroll
        runTestOnUiThread(new Runnable() {
            public void run() {
                mWebView.flingScroll(bigVelocity, bigVelocity);
            }
        });
        getInstrumentation().waitForIdleSync();

        int insetOffsetX = mWebView.getScrollX() - startX;
        int overlayOffsetY = mWebView.getScrollY() - startY;

        assertTrue(overlayOffsetY > insetOffsetY);
        assertTrue(overlayOffsetX > insetOffsetX);
    }

    @TestTargets({
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "loadUrl",
            args = {String.class}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "getUrl",
            args = {}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "getOriginalUrl",
            args = {}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "getProgress",
            args = {}
        )
    })
    public void testLoadUrl() throws Exception {
        assertNull(mWebView.getUrl());
        assertNull(mWebView.getOriginalUrl());
        assertEquals(INITIAL_PROGRESS, mWebView.getProgress());

        startWebServer(false);
        String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
        mWebView.loadUrl(url);
        waitForLoadComplete(mWebView, TEST_TIMEOUT);
        assertEquals(100, mWebView.getProgress());
        assertEquals(url, mWebView.getUrl());
        assertEquals(url, mWebView.getOriginalUrl());
        assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mWebView.getTitle());
    }

    @TestTargets({
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "getUrl",
            args = {}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "getOriginalUrl",
            args = {}
        )
    })
    public void testGetOriginalUrl() throws Exception {
        assertNull(mWebView.getUrl());
        assertNull(mWebView.getOriginalUrl());

        startWebServer(false);
        String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
        String redirect = mWebServer.getRedirectingAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
        // set the web view client so that redirects are loaded in the WebView itself
        mWebView.setWebViewClient(new WebViewClient());
        mWebView.loadUrl(redirect);

        waitForLoadComplete(mWebView, TEST_TIMEOUT);
        assertEquals(url, mWebView.getUrl());
        assertEquals(redirect, mWebView.getOriginalUrl());
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "stopLoading",
        args = {}
    )
    public void testStopLoading() throws Exception {
        assertNull(mWebView.getUrl());
        assertEquals(INITIAL_PROGRESS, mWebView.getProgress());

        startWebServer(false);
        String url = mWebServer.getDelayedAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
        mWebView.loadUrl(url);
        mWebView.stopLoading();
        new DelayedCheck() {
            @Override
            protected boolean check() {
                return 100 == mWebView.getProgress();
            }
        }.run();
        assertNull(mWebView.getUrl());
    }

    @TestTargets({
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "canGoBack",
            args = {}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "canGoForward",
            args = {}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "canGoBackOrForward",
            args = {int.class}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "goBack",
            args = {}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "goForward",
            args = {}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "goBackOrForward",
            args = {int.class}
        )
    })
    public void testGoBackAndForward() throws Exception {
        assertGoBackOrForwardBySteps(false, -1);
        assertGoBackOrForwardBySteps(false, 1);

        startWebServer(false);
        String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1);
        String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2);
        String url3 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL3);

        assertLoadUrlSuccessfully(mWebView, url1);
        delayedCheckWebBackForwardList(url1, 0, 1);
        assertGoBackOrForwardBySteps(false, -1);
        assertGoBackOrForwardBySteps(false, 1);

        assertLoadUrlSuccessfully(mWebView, url2);
        delayedCheckWebBackForwardList(url2, 1, 2);
        assertGoBackOrForwardBySteps(true, -1);
        assertGoBackOrForwardBySteps(false, 1);

        assertLoadUrlSuccessfully(mWebView, url3);
        delayedCheckWebBackForwardList(url3, 2, 3);
        assertGoBackOrForwardBySteps(true, -2);
        assertGoBackOrForwardBySteps(false, 1);

        mWebView.goBack();
        delayedCheckWebBackForwardList(url2, 1, 3);
        assertGoBackOrForwardBySteps(true, -1);
        assertGoBackOrForwardBySteps(true, 1);

        mWebView.goForward();
        delayedCheckWebBackForwardList(url3, 2, 3);
        assertGoBackOrForwardBySteps(true, -2);
        assertGoBackOrForwardBySteps(false, 1);

        mWebView.goBackOrForward(-2);
        delayedCheckWebBackForwardList(url1, 0, 3);
        assertGoBackOrForwardBySteps(false, -1);
        assertGoBackOrForwardBySteps(true, 2);

        mWebView.goBackOrForward(2);
        delayedCheckWebBackForwardList(url3, 2, 3);
        assertGoBackOrForwardBySteps(true, -2);
        assertGoBackOrForwardBySteps(false, 1);
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "addJavascriptInterface",
        args = {Object.class, String.class}
    )
    public void testAddJavascriptInterface() throws Exception {
        WebSettings settings = mWebView.getSettings();
        settings.setJavaScriptEnabled(true);
        settings.setJavaScriptCanOpenWindowsAutomatically(true);

        final DummyJavaScriptInterface obj = new DummyJavaScriptInterface();
        mWebView.addJavascriptInterface(obj, "dummy");
        assertFalse(obj.hasChangedTitle());

        startWebServer(false);
        String url = mWebServer.getAssetUrl(TestHtmlConstants.ADD_JAVA_SCRIPT_INTERFACE_URL);
        assertLoadUrlSuccessfully(mWebView, url);
        new DelayedCheck() {
            @Override
            protected boolean check() {
                return obj.hasChangedTitle();
            }
        }.run();
    }

    @TestTargets({
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "setBackgroundColor",
            args = {int.class}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "capturePicture",
            args = {}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "reload",
            args = {}
        )
    })
    public void testCapturePicture() throws Exception {
        startWebServer(false);
        String url = mWebServer.getAssetUrl(TestHtmlConstants.BLANK_PAGE_URL);
        // showing the blank page will make the picture filled with background color
        assertLoadUrlSuccessfully(mWebView, url);
        Picture p = mWebView.capturePicture();
        Bitmap b = Bitmap.createBitmap(p.getWidth(), p.getHeight(), Config.ARGB_8888);
        p.draw(new Canvas(b));
        // default color is white
        assertBitmapFillWithColor(b, Color.WHITE);

        mWebView.setBackgroundColor(Color.CYAN);
        mWebView.reload();
        waitForLoadComplete(mWebView, TEST_TIMEOUT);
        // the content of the picture will not be updated automatically
        p.draw(new Canvas(b));
        assertBitmapFillWithColor(b, Color.WHITE);
        // update the content
        p = mWebView.capturePicture();
        p.draw(new Canvas(b));
        assertBitmapFillWithColor(b, Color.CYAN);
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "setPictureListener",
        args = {PictureListener.class}
    )
    public void testSetPictureListener() throws Exception {
        final MyPictureListener listener = new MyPictureListener();
        mWebView.setPictureListener(listener);
        startWebServer(false);
        String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
        assertLoadUrlSuccessfully(mWebView, url);
        new DelayedCheck(TEST_TIMEOUT) {
            protected boolean check() {
                return listener.callCount > 0;
            }
        }.run();
        assertEquals(mWebView, listener.webView);
        assertNotNull(listener.picture);

        final int oldCallCount = listener.callCount;
        url = mWebServer.getAssetUrl(TestHtmlConstants.SMALL_IMG_URL);
        assertLoadUrlSuccessfully(mWebView, url);
        new DelayedCheck(TEST_TIMEOUT) {
            protected boolean check() {
                return listener.callCount > oldCallCount;
            }
        }.run();
    }

    @TestTargets({
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "savePicture",
            args = {Bundle.class, File.class}
        ),
        @TestTargetNew(
            level = TestLevel.SUFFICIENT,
            notes = "Cannot test whether picture has been restored correctly.",
            method = "restorePicture",
            args = {Bundle.class, File.class}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "reload",
            args = {}
        )
    })
    public void testSaveAndRestorePicture() throws Throwable {
        mWebView.setBackgroundColor(Color.CYAN);
        startWebServer(false);
        String url = mWebServer.getAssetUrl(TestHtmlConstants.BLANK_PAGE_URL);
        assertLoadUrlSuccessfully(mWebView, url);

        final Bundle bundle = new Bundle();
        final File f = getActivity().getFileStreamPath("snapshot");
        if (f.exists()) {
            f.delete();
        }

        try {
            assertTrue(bundle.isEmpty());
            assertEquals(0, f.length());
            assertTrue(mWebView.savePicture(bundle, f));

            // File saving is done in a separate thread.
            new DelayedCheck() {
                @Override
                protected boolean check() {
                    return f.length() > 0;
                }
            }.run();

            assertFalse(bundle.isEmpty());

            Picture p = Picture.createFromStream(new FileInputStream(f));
            Bitmap b = Bitmap.createBitmap(p.getWidth(), p.getHeight(), Config.ARGB_8888);
            p.draw(new Canvas(b));
            assertBitmapFillWithColor(b, Color.CYAN);

            mWebView.setBackgroundColor(Color.WHITE);
            mWebView.reload();
            waitForLoadComplete(mWebView, TEST_TIMEOUT);

            b = Bitmap.createBitmap(mWebView.getWidth(), mWebView.getHeight(), Config.ARGB_8888);
            mWebView.draw(new Canvas(b));
            assertBitmapFillWithColor(b, Color.WHITE);
            runTestOnUiThread(new Runnable() {
                public void run() {
                    assertTrue(mWebView.restorePicture(bundle, f));
                }
            });
            getInstrumentation().waitForIdleSync();
            // Cannot test whether the picture has been restored successfully.
            // Drawing the webview into a canvas will draw white, but on the display it is cyan
        } finally {
            if (f.exists()) {
                f.delete();
            }
        }
    }

    @TestTargets({
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "setHttpAuthUsernamePassword",
            args = {String.class, String.class, String.class, String.class}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "getHttpAuthUsernamePassword",
            args = {String.class, String.class}
        )
    })
    public void testAccessHttpAuthUsernamePassword() {
        try {
            WebViewDatabase.getInstance(getActivity()).clearHttpAuthUsernamePassword();

            String host = "http://localhost:8080";
            String realm = "testrealm";
            String userName = "user";
            String password = "password";

            String[] result = mWebView.getHttpAuthUsernamePassword(host, realm);
            assertNull(result);

            mWebView.setHttpAuthUsernamePassword(host, realm, userName, password);
            result = mWebView.getHttpAuthUsernamePassword(host, realm);
            assertNotNull(result);
            assertEquals(userName, result[0]);
            assertEquals(password, result[1]);

            String newPassword = "newpassword";
            mWebView.setHttpAuthUsernamePassword(host, realm, userName, newPassword);
            result = mWebView.getHttpAuthUsernamePassword(host, realm);
            assertNotNull(result);
            assertEquals(userName, result[0]);
            assertEquals(newPassword, result[1]);

            String newUserName = "newuser";
            mWebView.setHttpAuthUsernamePassword(host, realm, newUserName, newPassword);
            result = mWebView.getHttpAuthUsernamePassword(host, realm);
            assertNotNull(result);
            assertEquals(newUserName, result[0]);
            assertEquals(newPassword, result[1]);

            // the user is set to null, can not change any thing in the future
            mWebView.setHttpAuthUsernamePassword(host, realm, null, password);
            result = mWebView.getHttpAuthUsernamePassword(host, realm);
            assertNotNull(result);
            assertNull(result[0]);
            assertEquals(password, result[1]);

            mWebView.setHttpAuthUsernamePassword(host, realm, userName, null);
            result = mWebView.getHttpAuthUsernamePassword(host, realm);
            assertNotNull(result);
            assertEquals(userName, result[0]);
            assertEquals(null, result[1]);

            mWebView.setHttpAuthUsernamePassword(host, realm, null, null);
            result = mWebView.getHttpAuthUsernamePassword(host, realm);
            assertNotNull(result);
            assertNull(result[0]);
            assertNull(result[1]);

            mWebView.setHttpAuthUsernamePassword(host, realm, newUserName, newPassword);
            result = mWebView.getHttpAuthUsernamePassword(host, realm);
            assertNotNull(result);
            assertEquals(newUserName, result[0]);
            assertEquals(newPassword, result[1]);
        } finally {
            WebViewDatabase.getInstance(getActivity()).clearHttpAuthUsernamePassword();
        }
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "savePassword",
        args = {String.class, String.class, String.class}
    )
    public void testSavePassword() {
        WebViewDatabase db = WebViewDatabase.getInstance(getActivity());
        try {
            db.clearUsernamePassword();

            String host = "http://localhost:8080";
            String userName = "user";
            String password = "password";
            assertFalse(db.hasUsernamePassword());
            mWebView.savePassword(host, userName, password);
            assertTrue(db.hasUsernamePassword());
        } finally {
            db.clearUsernamePassword();
        }
    }

    @TestTargets({
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "loadData",
            args = {String.class, String.class, String.class}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "getTitle",
            args = {}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "capturePicture",
            args = {}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "capturePicture",
            args = {}
        )
    })
    public void testLoadData() throws Exception {
        assertNull(mWebView.getTitle());
        mWebView.loadData("<html><head><title>Hello,World!</title></head><body></body></html>",
                "text/html", "UTF-8");
        waitForLoadComplete(mWebView, TEST_TIMEOUT);
        assertEquals("Hello,World!", mWebView.getTitle());

        startWebServer(false);
        String imgUrl = mWebServer.getAssetUrl(TestHtmlConstants.SMALL_IMG_URL);
        mWebView.loadData("<html><body><img src=\"" + imgUrl + "\"/></body></html>",
                "text/html", "UTF-8");
        waitForLoadComplete(mWebView, TEST_TIMEOUT);

        AssetManager assets = getActivity().getAssets();
        Bitmap b1 = BitmapFactory.decodeStream(assets.open(TestHtmlConstants.SMALL_IMG_URL));
        b1 = b1.copy(Config.ARGB_8888, true);

        Picture p = mWebView.capturePicture();
        Bitmap b2 = Bitmap.createBitmap(p.getWidth(), p.getHeight(), Config.ARGB_8888);
        p.draw(new Canvas(b2));
        assertTrue(checkBitmapInsideAnother(b1, b2));
    }

    @TestTargets({
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "loadDataWithBaseURL",
            args = {String.class, String.class, String.class, String.class, String.class}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "getTitle",
            args = {}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "getUrl",
            args = {}
        )
    })
    public void testLoadDataWithBaseUrl() throws Exception {
        assertNull(mWebView.getTitle());
        assertNull(mWebView.getUrl());
        String imgUrl = TestHtmlConstants.SMALL_IMG_URL; // relative

        startWebServer(false);
        String baseUrl = mWebServer.getAssetUrl("foo.html");
        String failUrl = "random";
        mWebView.loadDataWithBaseURL(baseUrl,
                "<html><body><img src=\"" + imgUrl + "\"/></body></html>",
                "text/html", "UTF-8", failUrl);
        waitForLoadComplete(mWebView, TEST_TIMEOUT);
        // check that image was retrieved from the server
        assertTrue(mWebServer.getLastRequestUrl().endsWith(imgUrl));
        // the fail URL is used for the history entry, even if the load succeeds
        assertEquals(failUrl, mWebView.getUrl());

        imgUrl = TestHtmlConstants.LARGE_IMG_URL;
        mWebView.loadDataWithBaseURL(baseUrl,
                "<html><body><img src=\"" + imgUrl + "\"/></body></html>",
                "text/html", "UTF-8", null);
        waitForLoadComplete(mWebView, TEST_TIMEOUT);
        // check that image was retrieved from the server
        assertTrue(mWebServer.getLastRequestUrl().endsWith(imgUrl));
        // no history item saved, URL is still the last one
        assertEquals("about:blank", mWebView.getUrl());
    }

    @TestTargetNew(
        level = TestLevel.SUFFICIENT,
        method = "findAll",
        args = {String.class},
        notes = "Cannot check highlighting"
    )
    @UiThreadTest
    public void testFindAll() throws InterruptedException {
        String p = "<p>Find all instances of find on the page and highlight them.</p>";

        mWebView.loadData("<html><body>" + p + "</body></html>", "text/html", "UTF-8");
        waitForLoadComplete(mWebView, TEST_TIMEOUT);

        assertEquals(2, mWebView.findAll("find"));
    }

    @TestTargets({
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "findNext",
            args = {boolean.class}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "findAll",
            args = {String.class}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "clearMatches",
            args = {}
        )
    })
    public void testFindNext() throws Throwable {
        // Reset the scaling so that finding the next "all" text will require scrolling.
        mWebView.setInitialScale(100);

        DisplayMetrics metrics = mWebView.getContext().getResources().getDisplayMetrics();
        int dimension = Math.max(metrics.widthPixels, metrics.heightPixels);
        // create a paragraph high enough to take up the entire screen
        String p = "<p style=\"height:" + dimension + "px;\">" +
                "Find all instances of a word on the page and highlight them.</p>";

        mWebView.loadData("<html><body>" + p + p + "</body></html>", "text/html", "UTF-8");
        waitForLoadComplete(mWebView, TEST_TIMEOUT);

        // highlight all the strings found
        runTestOnUiThread(new Runnable() {
            public void run() {
                mWebView.findAll("all");
            }
        });
        getInstrumentation().waitForIdleSync();
        int previousScrollY = mWebView.getScrollY();

        // Can not use @UiThreadTest here as we need wait in other thread until the scroll in UI
        // thread finishes
        findNextOnUiThread(true);
        delayedCheckStopScrolling();
        // assert that the view scrolls and focuses "all" in the second page
        assertTrue(mWebView.getScrollY() > previousScrollY);
        previousScrollY = mWebView.getScrollY();

        findNextOnUiThread(true);
        delayedCheckStopScrolling();
        // assert that the view scrolls and focuses "all" in the first page
        assertTrue(mWebView.getScrollY() < previousScrollY);
        previousScrollY = mWebView.getScrollY();

        findNextOnUiThread(false);
        delayedCheckStopScrolling();
        // assert that the view scrolls and focuses "all" in the second page
        assertTrue(mWebView.getScrollY() > previousScrollY);
        previousScrollY = mWebView.getScrollY();

        findNextOnUiThread(false);
        delayedCheckStopScrolling();
        // assert that the view scrolls and focuses "all" in the first page
        assertTrue(mWebView.getScrollY() < previousScrollY);
        previousScrollY = mWebView.getScrollY();

        // clear the result
        runTestOnUiThread(new Runnable() {
            public void run() {
                mWebView.clearMatches();
            }
        });
        getInstrumentation().waitForIdleSync();

        // can not scroll any more
        findNextOnUiThread(false);
        delayedCheckStopScrolling();
        assertTrue(mWebView.getScrollY() == previousScrollY);

        findNextOnUiThread(true);
        delayedCheckStopScrolling();

        assertTrue(mWebView.getScrollY() == previousScrollY);
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "documentHasImages",
        args = {android.os.Message.class}
    )
    public void testDocumentHasImages() throws Exception {
        startWebServer(false);
        String imgUrl = mWebServer.getAssetUrl(TestHtmlConstants.SMALL_IMG_URL);
        mWebView.loadData("<html><body><img src=\"" + imgUrl + "\"/></body></html>",
                "text/html", "UTF-8");
        waitForLoadComplete(mWebView, TEST_TIMEOUT);

        // create the handler in other thread
        final DocumentHasImageCheckHandler handler =
            new DocumentHasImageCheckHandler(mWebView.getHandler().getLooper());
        Message response = new Message();
        response.setTarget(handler);
        assertFalse(handler.hasCalledHandleMessage());
        mWebView.documentHasImages(response);
        new DelayedCheck() {
            @Override
            protected boolean check() {
                return handler.hasCalledHandleMessage();
            }
        }.run();
        assertEquals(1, handler.getMsgArg1());
    }

    @TestTargets({
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "pageDown",
            args = {boolean.class}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "pageUp",
            args = {boolean.class}
        )
    })
    public void testPageScroll() throws Throwable {
        DisplayMetrics metrics = mWebView.getContext().getResources().getDisplayMetrics();
        int dimension = 2 * Math.max(metrics.widthPixels, metrics.heightPixels);
        String p = "<p style=\"height:" + dimension + "px;\">" +
                "Scroll by half the size of the page.</p>";
        mWebView.loadData("<html><body>" + p + p + "</body></html>", "text/html", "UTF-8");
        waitForLoadComplete(mWebView, TEST_TIMEOUT);

        assertTrue(pageDownOnUiThread(false));

        // scroll to the bottom
        while (pageDownOnUiThread(false)) {
            // do nothing
        }
        assertFalse(pageDownOnUiThread(false));
        int bottomScrollY = mWebView.getScrollY();

        assertTrue(pageUpOnUiThread(false));

        // scroll to the top
        while (pageUpOnUiThread(false)) {
            // do nothing
        }
        assertFalse(pageUpOnUiThread(false));
        int topScrollY = mWebView.getScrollY();

        // jump to the bottom
        assertTrue(pageDownOnUiThread(true));
        assertEquals(bottomScrollY, mWebView.getScrollY());

        // jump to the top
        assertTrue(pageUpOnUiThread(true));
        assertEquals(topScrollY, mWebView.getScrollY());
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "getContentHeight",
        args = {}
    )
    public void testGetContentHeight() throws InterruptedException {
        mWebView.loadData("<html><body></body></html>", "text/html", "UTF-8");
        waitForLoadComplete(mWebView, TEST_TIMEOUT);
        assertEquals(mWebView.getHeight(), mWebView.getContentHeight() * mWebView.getScale(), 2f);

        final int pageHeight = 600;
        // set the margin to 0
        String p = "<p style=\"height:" + pageHeight + "px;margin:0px auto;\">Get the height of "
                + "HTML content.</p>";
        mWebView.loadData("<html><body>" + p + "</body></html>", "text/html", "UTF-8");
        waitForLoadComplete(mWebView, TEST_TIMEOUT);
        assertTrue(mWebView.getContentHeight() > pageHeight);
        int extraSpace = mWebView.getContentHeight() - pageHeight;

        mWebView.loadData("<html><body>" + p + p + "</body></html>", "text/html", "UTF-8");
        waitForLoadComplete(mWebView, TEST_TIMEOUT);
        assertEquals(pageHeight + pageHeight + extraSpace, mWebView.getContentHeight());
    }

    @TestTargetNew(
        level = TestLevel.SUFFICIENT,
        notes = "Cannot test whether the view is cleared.",
        method = "clearView",
        args = {}
    )
    public void testClearView() throws Throwable {
        startWebServer(false);
        String imgUrl = mWebServer.getAssetUrl(TestHtmlConstants.SMALL_IMG_URL);
        mWebView.loadData("<html><body><img src=\"" + imgUrl + "\"/></body></html>",
                "text/html", "UTF-8");
        waitForLoadComplete(mWebView, TEST_TIMEOUT);

        AssetManager assets = getActivity().getAssets();
        Bitmap b1 = BitmapFactory.decodeStream(assets.open(TestHtmlConstants.SMALL_IMG_URL));
        b1 = b1.copy(Config.ARGB_8888, true);

        Picture p = mWebView.capturePicture();
        Bitmap b2 = Bitmap.createBitmap(p.getWidth(), p.getHeight(), Config.ARGB_8888);
        p.draw(new Canvas(b2));
        // the image is painted
        assertTrue(checkBitmapInsideAnother(b1, b2));

        mWebView.clearView();
        runTestOnUiThread(new Runnable() {
            public void run() {
                mWebView.invalidate();
            }
        });
        getInstrumentation().waitForIdleSync();
        // Can not check whether method clearView() take effect by automatic testing:
        // 1. Can not use getMeasuredHeight() and getMeasuredWidth() to
        //    check that the onMeasure() returns 0
        // 2. Can not use capturePicture() to check that the content has been cleared.
        //    The result of capturePicture() is not updated after clearView() is called.
    }

    @TestTargetNew(
        level = TestLevel.SUFFICIENT,
        method = "clearCache",
        args = {boolean.class}
    )
    public void testClearCache() throws Exception {
        final File cacheFileBaseDir = CacheManager.getCacheFileBaseDir();
        mWebView.clearCache(true);
        assertEquals(0, cacheFileBaseDir.list().length);

        startWebServer(false);
        mWebView.loadUrl(mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL));
        waitForLoadComplete(mWebView, TEST_TIMEOUT);
        int cacheFileCount = cacheFileBaseDir.list().length;
        assertTrue(cacheFileCount > 0);

        mWebView.clearCache(false);
        // the cache files are still there
        // can not check other effects of the method
        assertEquals(cacheFileCount, cacheFileBaseDir.list().length);

        mWebView.clearCache(true);
        // check the files are deleted
        new DelayedCheck(TEST_TIMEOUT) {
            @Override
            protected boolean check() {
                return cacheFileBaseDir.list().length == 0;
            }
        }.run();
    }

    @TestTargets({
        @TestTargetNew(
            level = TestLevel.SUFFICIENT,
            method = "enablePlatformNotifications",
            args = {},
            notes = "Cannot simulate data state or proxy changes"
        ),
        @TestTargetNew(
            level = TestLevel.SUFFICIENT,
            method = "disablePlatformNotifications",
            args = {},
            notes = "Cannot simulate data state or proxy changes"
        )
    })
    public void testPlatformNotifications() {
        WebView.enablePlatformNotifications();
        WebView.disablePlatformNotifications();
    }

    @TestTargets({
        @TestTargetNew(
            level = TestLevel.SUFFICIENT,
            method = "getPluginList",
            args = {}
        ),
        @TestTargetNew(
            level = TestLevel.SUFFICIENT,
            method = "refreshPlugins",
            args = {boolean.class}
        )
    })
    public void testAccessPluginList() {
        assertNotNull(WebView.getPluginList());

        // can not find a way to install plugins
        mWebView.refreshPlugins(false);
    }

    @TestTargetNew(
        level = TestLevel.SUFFICIENT,
        method = "destroy",
        args = {}
    )
    public void testDestroy() {
        // Create a new WebView, since we cannot call destroy() on a view in the hierarchy
        WebView localWebView = new WebView(getActivity());
        localWebView.destroy();
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "flingScroll",
        args = {int.class, int.class}
    )
    public void testFlingScroll() throws Throwable {
        DisplayMetrics metrics = mWebView.getContext().getResources().getDisplayMetrics();
        int dimension = 2 * Math.max(metrics.widthPixels, metrics.heightPixels);
        String p = "<p style=\"height:" + dimension + "px;" +
                "width:" + dimension + "px\">Test fling scroll.</p>";
        mWebView.loadData("<html><body>" + p + "</body></html>", "text/html", "UTF-8");
        waitForLoadComplete(mWebView, TEST_TIMEOUT);

        int previousScrollX = mWebView.getScrollX();
        int previousScrollY = mWebView.getScrollY();
        runTestOnUiThread(new Runnable() {
            public void run() {
                mWebView.flingScroll(100, 100);
            }
        });

        int timeSlice = 500;
        Thread.sleep(timeSlice);
        assertTrue(mWebView.getScrollX() > previousScrollX);
        assertTrue(mWebView.getScrollY() > previousScrollY);

        previousScrollY = mWebView.getScrollY();
        previousScrollX = mWebView.getScrollX();
        Thread.sleep(timeSlice);
        assertTrue(mWebView.getScrollX() >= previousScrollX);
        assertTrue(mWebView.getScrollY() >= previousScrollY);

        previousScrollY = mWebView.getScrollY();
        previousScrollX = mWebView.getScrollX();
        Thread.sleep(timeSlice);
        assertTrue(mWebView.getScrollX() >= previousScrollX);
        assertTrue(mWebView.getScrollY() >= previousScrollY);
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "requestFocusNodeHref",
        args = {android.os.Message.class}
    )
    public void testRequestFocusNodeHref() throws InterruptedException {
        String links = "<DL><p><DT><A HREF=\"" + TestHtmlConstants.HTML_URL1
                + "\">HTML_URL1</A><DT><A HREF=\"" + TestHtmlConstants.HTML_URL2
                + "\">HTML_URL2</A></DL><p>";
        mWebView.loadData("<html><body>" + links + "</body></html>", "text/html", "UTF-8");
        waitForLoadComplete(mWebView, TEST_TIMEOUT);

        final HrefCheckHandler handler = new HrefCheckHandler(mWebView.getHandler().getLooper());
        Message hrefMsg = new Message();
        hrefMsg.setTarget(handler);

        // focus on first link
        handler.reset();
        getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
        mWebView.requestFocusNodeHref(hrefMsg);
        new DelayedCheck() {
            @Override
            protected boolean check() {
                return handler.hasCalledHandleMessage();
            }
        }.run();
        assertEquals(TestHtmlConstants.HTML_URL1, handler.getResultUrl());

        // focus on second link
        handler.reset();
        hrefMsg = new Message();
        hrefMsg.setTarget(handler);
        getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
        mWebView.requestFocusNodeHref(hrefMsg);
        new DelayedCheck() {
            @Override
            protected boolean check() {
                return handler.hasCalledHandleMessage();
            }
        }.run();
        assertEquals(TestHtmlConstants.HTML_URL2, handler.getResultUrl());

        mWebView.requestFocusNodeHref(null);
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "requestImageRef",
        args = {android.os.Message.class}
    )
    public void testRequestImageRef() throws Exception {
        AssetManager assets = getActivity().getAssets();
        Bitmap bitmap = BitmapFactory.decodeStream(assets.open(TestHtmlConstants.LARGE_IMG_URL));
        int imgWidth = bitmap.getWidth();
        int imgHeight = bitmap.getHeight();

        startWebServer(false);
        String imgUrl = mWebServer.getAssetUrl(TestHtmlConstants.LARGE_IMG_URL);
        mWebView.loadData("<html><title>Title</title><body><img src=\"" + imgUrl
                + "\"/></body></html>", "text/html", "UTF-8");
        waitForLoadComplete(mWebView, TEST_TIMEOUT);

        final HrefCheckHandler handler = new HrefCheckHandler(mWebView.getHandler().getLooper());
        Message msg = new Message();
        msg.setTarget(handler);

        // touch the image
        handler.reset();
        int[] location = new int[2];
        mWebView.getLocationOnScreen(location);
        getInstrumentation().sendPointerSync(
                MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN,
                        location[0] + imgWidth / 2,
                        location[1] + imgHeight / 2, 0));
        mWebView.requestImageRef(msg);
        new DelayedCheck() {
            @Override
            protected boolean check() {
                return handler.hasCalledHandleMessage();
            }
        }.run();
        assertEquals(imgUrl, handler.mResultUrl);
    }

    @TestTargetNew(
        level = TestLevel.SUFFICIENT,
        method = "debugDump",
        args = {}
    )
    public void testDebugDump() {
        mWebView.debugDump();
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "clearFormData",
        args = {}
    )
    @BrokenTest(value = "Causes the process to crash some time after test completion.")
    public void testClearFormData() throws Throwable {
        String form = "<form><input type=\"text\" name=\"testClearFormData\"></form>";
        mWebView.loadData("<html><body>" + form + "</body></html>", "text/html", "UTF-8");
        waitForLoadComplete(mWebView, TEST_TIMEOUT);
        moveFocusDown();
        getInstrumentation().sendStringSync("test");
        sendKeys(KeyEvent.KEYCODE_ENTER);

        mWebView.reload();
        waitForLoadComplete(mWebView, TEST_TIMEOUT);
        moveFocusDown();
        View input = mWebView.findFocus();
        assertTrue(input instanceof AutoCompleteTextView);
        getInstrumentation().sendStringSync("te");
        assertTrue(((AutoCompleteTextView) input).isPopupShowing());

        mWebView.reload();
        waitForLoadComplete(mWebView, TEST_TIMEOUT);
        moveFocusDown();
        mWebView.clearFormData();
        // no auto completion choice after clearing
        input = mWebView.findFocus();
        assertTrue(input instanceof AutoCompleteTextView);
        getInstrumentation().sendStringSync("te");
        assertFalse(((AutoCompleteTextView) input).isPopupShowing());
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "getHitTestResult",
        args = {}
    )
    public void testGetHitTestResult() throws Throwable {
        String anchor = "<p><a href=\"" + TestHtmlConstants.EXT_WEB_URL1
                + "\">normal anchor</a></p>";
        String blankAnchor = "<p><a href=\"\">blank anchor</a></p>";
        String form = "<p><form><input type=\"text\" name=\"Test\"><br>"
                + "<input type=\"submit\" value=\"Submit\"></form></p>";
        String phoneNo = "3106984000";
        String tel = "<p><a href=\"tel:" + phoneNo + "\">Phone</a></p>";
        String email = "test@gmail.com";
        String mailto = "<p><a href=\"mailto:" + email + "\">Email</a></p>";
        String location = "shanghai";
        String geo = "<p><a href=\"geo:0,0?q=" + location + "\">Location</a></p>";
        mWebView.loadDataWithBaseURL("fake://home", "<html><body>" + anchor + blankAnchor + form
                + tel + mailto + geo + "</body></html>", "text/html", "UTF-8", null);
        waitForLoadComplete(mWebView, TEST_TIMEOUT);

        // anchor
        moveFocusDown();
        HitTestResult result = mWebView.getHitTestResult();
        assertEquals(HitTestResult.SRC_ANCHOR_TYPE, result.getType());
        assertEquals(TestHtmlConstants.EXT_WEB_URL1, result.getExtra());

        // blank anchor
        moveFocusDown();
        result = mWebView.getHitTestResult();
        assertEquals(HitTestResult.SRC_ANCHOR_TYPE, result.getType());
        assertEquals("fake://home", result.getExtra());

        // text field
        moveFocusDown();
        result = mWebView.getHitTestResult();
        assertEquals(HitTestResult.EDIT_TEXT_TYPE, result.getType());
        assertNull(result.getExtra());

        // submit button
        moveFocusDown();
        result = mWebView.getHitTestResult();
        assertEquals(HitTestResult.UNKNOWN_TYPE, result.getType());
        assertNull(result.getExtra());

        // phone number
        moveFocusDown();
        result = mWebView.getHitTestResult();
        assertEquals(HitTestResult.PHONE_TYPE, result.getType());
        assertEquals(phoneNo, result.getExtra());

        // email
        moveFocusDown();
        result = mWebView.getHitTestResult();
        assertEquals(HitTestResult.EMAIL_TYPE, result.getType());
        assertEquals(email, result.getExtra());

        // geo address
        moveFocusDown();
        result = mWebView.getHitTestResult();
        assertEquals(HitTestResult.GEO_TYPE, result.getType());
        assertEquals(location, result.getExtra());
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "setInitialScale",
        args = {int.class}
    )
    public void testSetInitialScale() throws InterruptedException {
        String p = "<p style=\"height:1000px;width:1000px\">Test setInitialScale.</p>";
        mWebView.loadData("<html><body>" + p + "</body></html>", "text/html", "UTF-8");
        waitForLoadComplete(mWebView, TEST_TIMEOUT);
        final float defaultScale = getInstrumentation().getTargetContext().getResources().
            getDisplayMetrics().density;
        assertEquals(defaultScale, mWebView.getScale(), 0f);

        mWebView.setInitialScale(0);
        // modify content to fool WebKit into re-loading
        mWebView.loadData("<html><body>" + p + "2" + "</body></html>", "text/html", "UTF-8");
        waitForLoadComplete(mWebView, TEST_TIMEOUT);
        assertEquals(defaultScale, mWebView.getScale(), 0f);

        mWebView.setInitialScale(50);
        mWebView.loadData("<html><body>" + p + "3" + "</body></html>", "text/html", "UTF-8");
        waitForLoadComplete(mWebView, TEST_TIMEOUT);
        assertEquals(0.5f, mWebView.getScale(), .02f);

        mWebView.setInitialScale(0);
        mWebView.loadData("<html><body>" + p + "4" + "</body></html>", "text/html", "UTF-8");
        waitForLoadComplete(mWebView, TEST_TIMEOUT);
        assertEquals(defaultScale, mWebView.getScale(), 0f);
    }

    @TestTargetNew(
        level = TestLevel.SUFFICIENT,
        notes = "No API to trigger receiving an icon. Favicon not loaded automatically.",
        method = "getFavicon",
        args = {}
    )
    @ToBeFixed(explanation = "Favicon is not loaded automatically.")
    public void testGetFavicon() throws Exception {
        startWebServer(false);
        String url = mWebServer.getAssetUrl(TestHtmlConstants.TEST_FAVICON_URL);
        assertLoadUrlSuccessfully(mWebView, url);
        mWebView.getFavicon();
        // ToBeFixed: Favicon is not loaded automatically.
        // assertNotNull(mWebView.getFavicon());
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "clearHistory",
        args = {}
    )
    public void testClearHistory() throws Exception {
        startWebServer(false);
        String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1);
        String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2);
        String url3 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL3);

        assertLoadUrlSuccessfully(mWebView, url1);
        delayedCheckWebBackForwardList(url1, 0, 1);

        assertLoadUrlSuccessfully(mWebView, url2);
        delayedCheckWebBackForwardList(url2, 1, 2);

        assertLoadUrlSuccessfully(mWebView, url3);
        delayedCheckWebBackForwardList(url3, 2, 3);

        mWebView.clearHistory();

        // only current URL is left after clearing
        delayedCheckWebBackForwardList(url3, 0, 1);
    }

    @TestTargets({
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "saveState",
            args = {Bundle.class}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "restoreState",
            args = {Bundle.class}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "copyBackForwardList",
            args = {}
        )
    })
    @ToBeFixed(explanation="Web history items do not get inflated after restore.")
    public void testSaveAndRestoreState() throws Throwable {
        // nothing to save
        assertNull(mWebView.saveState(new Bundle()));

        startWebServer(false);
        String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1);
        String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2);
        String url3 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL3);

        // make a history list
        assertLoadUrlSuccessfully(mWebView, url1);
        delayedCheckWebBackForwardList(url1, 0, 1);
        assertLoadUrlSuccessfully(mWebView, url2);
        delayedCheckWebBackForwardList(url2, 1, 2);
        assertLoadUrlSuccessfully(mWebView, url3);
        delayedCheckWebBackForwardList(url3, 2, 3);

        // save the list
        Bundle bundle = new Bundle();
        WebBackForwardList saveList = mWebView.saveState(bundle);
        assertNotNull(saveList);
        assertEquals(3, saveList.getSize());
        assertEquals(2, saveList.getCurrentIndex());
        assertEquals(url1, saveList.getItemAtIndex(0).getUrl());
        assertEquals(url2, saveList.getItemAtIndex(1).getUrl());
        assertEquals(url3, saveList.getItemAtIndex(2).getUrl());

        // change the content to a new "blank" web view without history
        final WebView newWebView = new WebView(getActivity());

        WebBackForwardList copyListBeforeRestore = newWebView.copyBackForwardList();
        assertNotNull(copyListBeforeRestore);
        assertEquals(0, copyListBeforeRestore.getSize());

        // restore the list
        final WebBackForwardList restoreList = newWebView.restoreState(bundle);
        assertNotNull(restoreList);
        assertEquals(3, restoreList.getSize());
        assertEquals(2, saveList.getCurrentIndex());
        /* ToBeFixed: The WebHistoryItems do not get inflated. Uncomment remaining tests when fixed.
        // wait for the list items to get inflated
        new DelayedCheck(TEST_TIMEOUT) {
            @Override
            protected boolean check() {
                return restoreList.getItemAtIndex(0).getUrl() != null &&
                       restoreList.getItemAtIndex(1).getUrl() != null &&
                       restoreList.getItemAtIndex(2).getUrl() != null;
            }
        }.run();
        assertEquals(url1, restoreList.getItemAtIndex(0).getUrl());
        assertEquals(url2, restoreList.getItemAtIndex(1).getUrl());
        assertEquals(url3, restoreList.getItemAtIndex(2).getUrl());

        WebBackForwardList copyListAfterRestore = newWebView.copyBackForwardList();
        assertNotNull(copyListAfterRestore);
        assertEquals(3, copyListAfterRestore.getSize());
        assertEquals(2, copyListAfterRestore.getCurrentIndex());
        assertEquals(url1, copyListAfterRestore.getItemAtIndex(0).getUrl());
        assertEquals(url2, copyListAfterRestore.getItemAtIndex(1).getUrl());
        assertEquals(url3, copyListAfterRestore.getItemAtIndex(2).getUrl());
        */
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "setWebViewClient",
        args = {WebViewClient.class}
    )
    public void testSetWebViewClient() throws Throwable {
        final MockWebViewClient webViewClient = new MockWebViewClient();
        mWebView.setWebViewClient(webViewClient);

        assertFalse(webViewClient.onScaleChangedCalled());
        runTestOnUiThread(new Runnable() {
            public void run() {
                mWebView.zoomIn();
            }
        });
        getInstrumentation().waitForIdleSync();
        assertTrue(webViewClient.onScaleChangedCalled());
    }

    @TestTargets({
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "setCertificate",
            args = {SslCertificate.class}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "getCertificate",
            args = {}
        )
    })
    public void testAccessCertificate() throws Throwable {
        runTestOnUiThread(new Runnable() {
            public void run() {
                mWebView = new WebView(getActivity());
                getActivity().setContentView(mWebView);
            }
        });
        getInstrumentation().waitForIdleSync();

        // need the client to handle error
        mWebView.setWebViewClient(new MockWebViewClient());

        mWebView.setCertificate(null);
        startWebServer(true);
        String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
        // attempt to load the url.
        mWebView.loadUrl(url);
        new DelayedCheck(TEST_TIMEOUT) {
            @Override
            protected boolean check() {
                return mWebView.getCertificate() != null;
            }
        }.run();
        SslCertificate cert = mWebView.getCertificate();
        assertNotNull(cert);
        assertEquals("Android", cert.getIssuedTo().getUName());
    }

    @TestTargetNew(
        level = TestLevel.NOT_FEASIBLE,
        notes = "WebViewClient.onReceivedSslError() is hidden, cannot store SSL preferences.",
        method = "clearSslPreferences",
        args = {}
    )
    public void testClearSslPreferences() {
        mWebView.clearSslPreferences();
    }

    @TestTargets({
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "pauseTimers",
            args = {}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "resumeTimers",
            args = {}
        )
    })
    @ToBeFixed(explanation = "WebView.pauseTimers() does not pause javascript timers")
    @BrokenTest(value = "Frequently crashes the process some time after test completion.")
    public void testPauseTimers() throws Exception {
        WebSettings settings = mWebView.getSettings();
        settings.setJavaScriptEnabled(true);
        startWebServer(false);
        // load a page which increments the number in its title every second
        String url = mWebServer.getAssetUrl(TestHtmlConstants.TEST_TIMER_URL);
        assertLoadUrlSuccessfully(mWebView, url);
        int counter = Integer.parseInt(mWebView.getTitle());
        Thread.sleep(2000);
        assertTrue(Integer.parseInt(mWebView.getTitle()) > counter);
        mWebView.pauseTimers();
        Thread.sleep(2000); // give the implementation time to stop the timer
        counter = Integer.parseInt(mWebView.getTitle());
        Thread.sleep(2000);
        // ToBeFixed: Uncomment the following line once pauseTimers() is fixed
        // assertEquals(counter, Integer.parseInt(mWebView.getTitle()));
        mWebView.resumeTimers();
        Thread.sleep(2000);
        assertTrue(Integer.parseInt(mWebView.getTitle()) > counter);
    }


    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "requestChildRectangleOnScreen",
        args = {View.class, Rect.class, boolean.class}
    )
    public void testRequestChildRectangleOnScreen() throws Throwable {
        DisplayMetrics metrics = mWebView.getContext().getResources().getDisplayMetrics();
        final int dimension = 2 * Math.max(metrics.widthPixels, metrics.heightPixels);
        String p = "<p style=\"height:" + dimension + "px;width:" + dimension + "px\">&nbsp;</p>";
        mWebView.loadData("<html><body>" + p + "</body></html>", "text/html", "UTF-8");
        waitForLoadComplete(mWebView, TEST_TIMEOUT);
        getInstrumentation().waitForIdleSync();

        runTestOnUiThread(new Runnable() {
            public void run() {
                int origX = mWebView.getScrollX();
                int origY = mWebView.getScrollY();

                int half = dimension / 2;
                Rect rect = new Rect(half, half, half + 1, half + 1);
                assertTrue(mWebView.requestChildRectangleOnScreen(mWebView, rect, true));
                assertTrue(mWebView.getScrollX() > origX);
                assertTrue(mWebView.getScrollY() > origY);
            }
        });
    }

    @TestTargets({
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            method = "setDownloadListener",
            args = {DownloadListener.class}
        ),
        @TestTargetNew(
            level = TestLevel.SUFFICIENT,
            method = "requestFocus",
            args = {int.class, Rect.class}
        )
    })
    @ToBeFixed(explanation="Mime type and content length passed to listener are incorrect.")
    public void testSetDownloadListener() throws Throwable {
        final String mimeType = "application/octet-stream";
        final int length = 100;
        final MyDownloadListener listener = new MyDownloadListener();

        startWebServer(false);
        String url = mWebServer.getBinaryUrl(mimeType, length);
        mWebView.setWebViewClient(new WebViewClient());
        mWebView.setDownloadListener(listener);
        mWebView.loadData("<html><body><a href=\"" + url + "\">link</a></body></html>",
                "text/html", "UTF-8");
        waitForLoadComplete(mWebView, TEST_TIMEOUT);
        // focus on the link
        runTestOnUiThread(new Runnable() {
            public void run() {
                assertTrue(mWebView.requestFocus(View.FOCUS_DOWN, null));
            }
        });
        getInstrumentation().waitForIdleSync();
        getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
        new DelayedCheck(TEST_TIMEOUT) {
            @Override
            protected boolean check() {
                return listener.called;
            }
        }.run();
        assertEquals(url, listener.url);
        assertTrue(listener.contentDisposition.contains("test.bin"));
        // ToBeFixed: uncomment the following tests after fixing the framework
        // assertEquals(mimeType, listener.mimeType);
        // assertEquals(length, listener.contentLength);
    }

    @TestTargetNew(
        level = TestLevel.PARTIAL,
        method = "setLayoutParams",
        args = {android.view.ViewGroup.LayoutParams.class}
    )
    @ToBeFixed(bug = "1695243", explanation = "the javadoc for setLayoutParams() is incomplete.")
    @UiThreadTest
    public void testSetLayoutParams() {
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(600, 800);
        mWebView.setLayoutParams(params);
        assertSame(params, mWebView.getLayoutParams());
    }

    @TestTargetNew(
        level = TestLevel.NOT_FEASIBLE,
        notes = "No documentation",
        method = "setMapTrackballToArrowKeys",
        args = {boolean.class}
    )
    public void testSetMapTrackballToArrowKeys() {
        mWebView.setMapTrackballToArrowKeys(true);
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "setNetworkAvailable",
        args = {boolean.class}
    )
    public void testSetNetworkAvailable() throws Exception {
        WebSettings settings = mWebView.getSettings();
        settings.setJavaScriptEnabled(true);
        startWebServer(false);
        String url = mWebServer.getAssetUrl(TestHtmlConstants.NETWORK_STATE_URL);
        assertLoadUrlSuccessfully(mWebView, url);
        assertEquals("ONLINE", mWebView.getTitle());

        mWebView.setNetworkAvailable(false);
        mWebView.reload();
        waitForLoadComplete(mWebView, TEST_TIMEOUT);
        assertEquals("OFFLINE", mWebView.getTitle());

        mWebView.setNetworkAvailable(true);
        mWebView.reload();
        waitForLoadComplete(mWebView, TEST_TIMEOUT);
        assertEquals("ONLINE", mWebView.getTitle());
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        method = "setWebChromeClient",
        args = {WebChromeClient.class}
    )
    public void testSetWebChromeClient() throws Throwable {
        final MockWebChromeClient webChromeClient = new MockWebChromeClient();
        mWebView.setWebChromeClient(webChromeClient);

        assertFalse(webChromeClient.onProgressChangedCalled());
        startWebServer(false);
        String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
        mWebView.loadUrl(url);

        new DelayedCheck(TEST_TIMEOUT) {
            @Override
            protected boolean check() {
                return webChromeClient.onProgressChangedCalled();
            }
        }.run();
    }

    @TestTargets({
        @TestTargetNew(
            level = TestLevel.NOT_NECESSARY,
            method = "dispatchKeyEvent",
            args = {KeyEvent.class}
        ),
        @TestTargetNew(
            level = TestLevel.NOT_NECESSARY,
            method = "onAttachedToWindow",
            args = {}
        ),
        @TestTargetNew(
            level = TestLevel.NOT_NECESSARY,
            method = "onDetachedFromWindow",
            args = {}
        ),
        @TestTargetNew(
            level = TestLevel.NOT_NECESSARY,
            method = "onChildViewAdded",
            args = {View.class, View.class}
        ),
        @TestTargetNew(
            level = TestLevel.NOT_NECESSARY,
            method = "onChildViewRemoved",
            args = {View.class, View.class}
        ),
        @TestTargetNew(
            level = TestLevel.NOT_NECESSARY,
            method = "onDraw",
            args = {Canvas.class}
        ),
        @TestTargetNew(
            level = TestLevel.NOT_NECESSARY,
            method = "onFocusChanged",
            args = {boolean.class, int.class, Rect.class}
        ),
        @TestTargetNew(
            level = TestLevel.NOT_NECESSARY,
            method = "onGlobalFocusChanged",
            args = {View.class, View.class}
        ),
        @TestTargetNew(
            level = TestLevel.NOT_NECESSARY,
            method = "onKeyDown",
            args = {int.class, KeyEvent.class}
        ),
        @TestTargetNew(
            level = TestLevel.NOT_NECESSARY,
            method = "onKeyUp",
            args = {int.class, KeyEvent.class}
        ),
        @TestTargetNew(
            level = TestLevel.NOT_NECESSARY,
            method = "onMeasure",
            args = {int.class, int.class}
        ),
        @TestTargetNew(
            level = TestLevel.NOT_NECESSARY,
            method = "onScrollChanged",
            args = {int.class, int.class, int.class, int.class}
        ),
        @TestTargetNew(
            level = TestLevel.NOT_NECESSARY,
            method = "onSizeChanged",
            args = {int.class, int.class, int.class, int.class}
        ),
        @TestTargetNew(
            level = TestLevel.NOT_NECESSARY,
            method = "onTouchEvent",
            args = {MotionEvent.class}
        ),
        @TestTargetNew(
            level = TestLevel.NOT_NECESSARY,
            method = "onTrackballEvent",
            args = {MotionEvent.class}
        ),
        @TestTargetNew(
            level = TestLevel.NOT_NECESSARY,
            method = "onWindowFocusChanged",
            args = {boolean.class}
        ),
        @TestTargetNew(
            level = TestLevel.NOT_FEASIBLE,
            method = "computeScroll",
            args = {}
        ),
        @TestTargetNew(
            level = TestLevel.NOT_FEASIBLE,
            method = "computeHorizontalScrollRange",
            args = {}
        ),
        @TestTargetNew(
            level = TestLevel.NOT_FEASIBLE,
            method = "computeVerticalScrollRange",
            args = {}
        )
    })
    public void testInternals() {
        // Do not test these APIs. They are implementation details.
    }

    private static class MockWebViewClient extends WebViewClient {
        private boolean mOnScaleChangedCalled = false;

        public boolean onScaleChangedCalled() {
            return mOnScaleChangedCalled;
        }

        @Override
        public void onScaleChanged(WebView view, float oldScale, float newScale) {
            super.onScaleChanged(view, oldScale, newScale);
            mOnScaleChangedCalled = true;
        }

        @Override
        public void onReceivedSslError(WebView view, SslErrorHandler handler,
                                       SslError error) {
            handler.proceed();
        }
    }

    private static class MockWebChromeClient extends WebChromeClient {
        private boolean mOnProgressChanged = false;

        public boolean onProgressChangedCalled() {
            return mOnProgressChanged;
        }

        @Override
        public void onProgressChanged(WebView view, int newProgress) {
            super.onProgressChanged(view, newProgress);
            mOnProgressChanged = true;
        }
    }

    private static class HrefCheckHandler extends Handler {
        private boolean mHadRecieved;

        private String mResultUrl;

        public HrefCheckHandler(Looper looper) {
            super(looper);
        }

        public boolean hasCalledHandleMessage() {
            return mHadRecieved;
        }

        public String getResultUrl() {
            return mResultUrl;
        }

        public void reset(){
            mResultUrl = null;
            mHadRecieved = false;
        }

        @Override
        public void handleMessage(Message msg) {
            mHadRecieved = true;
            mResultUrl = msg.getData().getString("url");
        }
    }

    private static class DocumentHasImageCheckHandler extends Handler {
        private boolean mReceived;

        private int mMsgArg1;

        public DocumentHasImageCheckHandler(Looper looper) {
            super(looper);
        }

        public boolean hasCalledHandleMessage() {
            return mReceived;
        }

        public int getMsgArg1() {
            return mMsgArg1;
        }

        public void reset(){
            mMsgArg1 = -1;
            mReceived = false;
        }

        @Override
        public void handleMessage(Message msg) {
            mReceived = true;
            mMsgArg1 = msg.arg1;
        }
    };

    private void findNextOnUiThread(final boolean forward) throws Throwable {
        runTestOnUiThread(new Runnable() {
            public void run() {
                mWebView.findNext(forward);
            }
        });
        getInstrumentation().waitForIdleSync();
    }

    private void moveFocusDown() throws Throwable {
        // send down key and wait for idle
        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
        // waiting for idle isn't always sufficient for the key to be fully processed
        Thread.sleep(500);
    }

    private boolean pageDownOnUiThread(final boolean bottom) throws Throwable {
        PageDownRunner runner = new PageDownRunner(bottom);
        runTestOnUiThread(runner);
        getInstrumentation().waitForIdleSync();
        return runner.mResult;
    }

    private class PageDownRunner implements Runnable {
        private boolean mResult, mBottom;

        public PageDownRunner(boolean bottom) {
            mBottom = bottom;
        }

        public void run() {
            mResult = mWebView.pageDown(mBottom);
        }
    }

    private boolean pageUpOnUiThread(final boolean top) throws Throwable {
        PageUpRunner runner = new PageUpRunner(top);
        runTestOnUiThread(runner);
        getInstrumentation().waitForIdleSync();
        return runner.mResult;
    }

    private class PageUpRunner implements Runnable {
        private boolean mResult, mTop;

        public PageUpRunner(boolean top) {
            this.mTop = top;
        }

        public void run() {
            mResult = mWebView.pageUp(mTop);
        }
    }

    private void delayedCheckStopScrolling() {
        new DelayedCheck() {
            private int scrollY = mWebView.getScrollY();

            @Override
            protected boolean check() {
                if (scrollY == mWebView.getScrollY()){
                    return true;
                } else {
                    scrollY = mWebView.getScrollY();
                    return false;
                }
            }
        }.run();
    }

    private void delayedCheckWebBackForwardList(final String currUrl, final int currIndex,
            final int size) {
        new DelayedCheck() {
            @Override
            protected boolean check() {
                WebBackForwardList list = mWebView.copyBackForwardList();
                return checkWebBackForwardList(list, currUrl, currIndex, size);
            }
        }.run();
    }

    private boolean checkWebBackForwardList(WebBackForwardList list, String currUrl,
            int currIndex, int size) {
        return (list != null)
                && (list.getSize() == size)
                && (list.getCurrentIndex() == currIndex)
                && list.getItemAtIndex(currIndex).getUrl().equals(currUrl);
    }

    private void assertGoBackOrForwardBySteps(boolean expected, int steps) {
        // skip if steps equals to 0
        if (steps == 0)
            return;

        int start = steps > 0 ? 1 : steps;
        int end = steps > 0 ? steps : -1;

        // check all the steps in the history
        for (int i = start; i <= end; i++) {
            assertEquals(expected, mWebView.canGoBackOrForward(i));

            // shortcut methods for one step
            if (i == 1) {
                assertEquals(expected, mWebView.canGoForward());
            } else if (i == -1) {
                assertEquals(expected, mWebView.canGoBack());
            }
        }
    }

    private void assertBitmapFillWithColor(Bitmap bitmap, int color) {
        for (int i = 0; i < bitmap.getWidth(); i ++)
            for (int j = 0; j < bitmap.getHeight(); j ++) {
                assertEquals(color, bitmap.getPixel(i, j));
            }
    }

    // Find b1 inside b2
    private boolean checkBitmapInsideAnother(Bitmap b1, Bitmap b2) {
        int w = b1.getWidth();
        int h = b1.getHeight();

        for (int i = 0; i < (b2.getWidth()-w+1); i++) {
            for (int j = 0; j < (b2.getHeight()-h+1); j++) {
                if (checkBitmapInsideAnother(b1, b2, i, j))
                    return true;
            }
        }
        return false;
    }

    private boolean comparePixel(int p1, int p2, int maxError) {
        int err;
        err = Math.abs(((p1&0xff000000)>>>24) - ((p2&0xff000000)>>>24));
        if (err > maxError)
            return false;

        err = Math.abs(((p1&0x00ff0000)>>>16) - ((p2&0x00ff0000)>>>16));
        if (err > maxError)
            return false;

        err = Math.abs(((p1&0x0000ff00)>>>8) - ((p2&0x0000ff00)>>>8));
        if (err > maxError)
            return false;

        err = Math.abs(((p1&0x000000ff)>>>0) - ((p2&0x000000ff)>>>0));
        if (err > maxError)
            return false;

        return true;
    }

    private boolean checkBitmapInsideAnother(Bitmap b1, Bitmap b2, int x, int y) {
        for (int i = 0; i < b1.getWidth(); i++)
            for (int j = 0; j < b1.getHeight(); j++) {
                if (!comparePixel(b1.getPixel(i, j), b2.getPixel(x + i, y + j), 10)) {
                    return false;
                }
            }
        return true;
    }

    private void assertLoadUrlSuccessfully(WebView webView, String url)
            throws InterruptedException {
        webView.loadUrl(url);
        waitForLoadComplete(webView, TEST_TIMEOUT);
    }

    private void waitForLoadComplete(final WebView webView, long timeout)
            throws InterruptedException {
        new DelayedCheck(timeout) {
            @Override
            protected boolean check() {
                return webView.getProgress() == 100;
            }
        }.run();
        Thread.sleep(TIME_FOR_LAYOUT);
    }

    private final class DummyJavaScriptInterface {
        private boolean mTitleChanged;

        private boolean hasChangedTitle() {
            return mTitleChanged;
        }

        public void onLoad(String oldTitle) {
            mWebView.getHandler().post(new Runnable() {
                public void run() {
                    mWebView.loadUrl("javascript:changeTitle(\"new title\")");
                    mTitleChanged = true;
                }
            });
        }
    }

    private final class MyDownloadListener implements DownloadListener {
        public String url;
        public String mimeType;
        public long contentLength;
        public String contentDisposition;
        public boolean called;

        public void onDownloadStart(String url, String userAgent, String contentDisposition,
                String mimetype, long contentLength) {
            this.called = true;
            this.url = url;
            this.mimeType = mimetype;
            this.contentLength = contentLength;
            this.contentDisposition = contentDisposition;
        }
    }

    private static class MyPictureListener implements PictureListener {
        public int callCount;
        public WebView webView;
        public Picture picture;

        public void onNewPicture(WebView view, Picture picture) {
            this.callCount += 1;
            this.webView = view;
            this.picture = picture;
        }
    }
}
