com.baidu.cafe.local.record.WebElementRecorder.java Source code

Java tutorial

Introduction

Here is the source code for com.baidu.cafe.local.record.WebElementRecorder.java

Source

/*
 * Copyright (C) 2013 Baidu.com Inc
 *
 * 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 com.baidu.cafe.local.record;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;

import org.json.JSONObject;

import android.graphics.Bitmap;
import android.net.http.SslError;
import android.os.Build;
import android.os.Message;
import android.os.SystemClock;
import android.view.KeyEvent;
import android.view.View;
import android.webkit.ConsoleMessage;
import android.webkit.GeolocationPermissions.Callback;
import android.webkit.HttpAuthHandler;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.SslErrorHandler;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebChromeClient.CustomViewCallback;
import android.webkit.WebResourceResponse;
import android.webkit.WebStorage.QuotaUpdater;
import android.webkit.WebView;
import android.webkit.WebViewClient;

import com.baidu.cafe.CafeTestCase;
import com.baidu.cafe.local.DESEncryption;
import com.baidu.cafe.local.LocalLib;
import com.baidu.cafe.local.Log;

/**
 * one object one webview
 * 
 * @author leiming@baidu.com
 * @date 2013-4-12
 * @version
 * @todo
 */
public class WebElementRecorder {
    private ViewRecorder viewRecorder = null;
    private WebView webView = null;
    private WebElementEventCreator webElementEventCreator = null;
    private final static int TIMEOUT_NEXT_EVENT = 500;

    /**
     * lock for OutputEventQueue
     */
    private static String mSyncWebElementRecordEventQueue = new String("mSyncWebElementRecordEventQueue");

    private Queue<WebElementRecordEvent> mWebElementRecordEventQueue = new LinkedList<WebElementRecordEvent>();

    public WebElementRecorder(ViewRecorder viewRecorder) {
        this.viewRecorder = viewRecorder;
        this.webElementEventCreator = new WebElementEventCreator(viewRecorder);
        this.mWebElementRecordEventQueue.clear();
    }

    public boolean offerWebElementRecordEventQueue(WebElementRecordEvent event) {
        synchronized (mSyncWebElementRecordEventQueue) {
            return mWebElementRecordEventQueue.offer(event);
        }
    }

    public WebElementRecordEvent peekWebElementRecordEventQueue() {
        synchronized (mSyncWebElementRecordEventQueue) {
            return mWebElementRecordEventQueue.peek();
        }
    }

    public WebElementRecordEvent pollWebElementRecordEventQueue() {
        synchronized (mSyncWebElementRecordEventQueue) {
            return mWebElementRecordEventQueue.poll();

        }
    }

    private void sleep(long time) {
        try {
            Thread.sleep(time);
        } catch (Exception ignored) {
        }
    }

    /**
     * set hook listener on WebView body
     * 
     * @param webView
     * @return
     */
    public void handleWebView(final WebView webView) {
        if (webView == null) {
            return;
        }
        print("start monitor WebView: " + webView);
        webView.post(new Runnable() {

            public void run() {
                print("webView getURL: " + webView.getUrl());
            }

        });
        this.webView = webView;
        // monitor WebView
        hookWebView(webView);
        handleWebElementRecordEventQueue();
    }

    public void handleWebElementRecordEventQueue() {
        new Thread(new Runnable() {
            public void run() {
                ArrayList<WebElementRecordEvent> events = new ArrayList<WebElementRecordEvent>();
                while (true) {
                    WebElementRecordEvent lastRecordEvent = null;
                    long endTime = System.currentTimeMillis() + TIMEOUT_NEXT_EVENT;
                    WebElementRecordEvent e = null;
                    while (true) {
                        e = null;
                        if ((e = pollWebElementRecordEventQueue()) != null) {
                            // here comes a record event
                            endTime = System.currentTimeMillis() + TIMEOUT_NEXT_EVENT;
                            if (lastRecordEvent == null) {
                                // if e is the first record event
                                events.add(e);
                                lastRecordEvent = e;
                            } else {
                                if (e.time > lastRecordEvent.time
                                        && e.familyString.equals(lastRecordEvent.familyString)) {
                                    events.add(e);
                                    lastRecordEvent = e;
                                } else {
                                    offerOutputEventQueue(events);
                                    lastRecordEvent = null;
                                    events.clear();
                                }
                            }
                        } else {
                            // wait until timeout, then offerOutputEventQueue
                            if (System.currentTimeMillis() > endTime) {
                                offerOutputEventQueue(events);
                                lastRecordEvent = null;
                                events.clear();
                                sleep(50);
                                break;
                            }
                        }
                        sleep(10);
                    }
                }
            }
        }).start();
    }

    private boolean offerOutputEventQueue(ArrayList<WebElementRecordEvent> events) {
        if (events.isEmpty()) {
            return false;
        }
        boolean eventOffered = false;
        boolean isTouchstart = false;
        boolean isTouchmove = false;
        for (WebElementRecordEvent e : events) {
            if ("touchstart".equals(e.action)) {
                isTouchstart = true;
            } else if ("touchmove".equals(e.action)) {
                if (isTouchstart) {
                    isTouchmove = true;
                } else {// touchmove without touchstart before, note as illegal
                    eventOffered = false;
                    break;
                }
            } else if ("touchcancel".equals(e.action)) {
                eventOffered = false;
                break;
            } else if ("touchend".equals(e.action)) {
                // when it comes touchstart -> touchend without touchmove in the
                // list, offer a click action
                if (isTouchstart && !isTouchmove) {
                    eventOffered = true;
                    // break;
                } else {
                    // touchstart -> touchmove -> touchend
                    eventOffered = false;
                    // break;
                }
                break;
            } else if ("click".equals(e.action)) {
                eventOffered = true;
                break;
            }
        }
        if (eventOffered) {
            WebElementRecordEvent e = events.get(0);
            OutputEvent outputEvent = new WebElementClickEvent(e.view);
            outputEvent.setCode(
                    String.format("local.recordReplay.clickOnWebElementByFamilyString(\"%s\"); // Click On [%s]",
                            e.familyString, e.tag));
            viewRecorder.offerOutputEventQueue(outputEvent);
        }
        return eventOffered;
    }

    /**
     * @param webView
     * @return
     */
    private WebViewClient getOriginalWebViewClient(final WebView webView) {
        // save old WebViewClient
        WebViewClient mWebViewClient = null;
        try {
            // print("Build.VERSION.SDK_INT : " + Build.VERSION.SDK_INT);
            if (Build.VERSION.SDK_INT > 14) {
                Object originalWebViewClassic = viewRecorder.getLocalLib().invoke(webView, "android.webkit.WebView",
                        "getWebViewProvider", new Class[] {}, new Object[] {});
                Object originalCallbackProxy = viewRecorder.getLocalLib().getField(originalWebViewClassic, null,
                        "mCallbackProxy");
                mWebViewClient = (WebViewClient) viewRecorder.getLocalLib().getField(originalCallbackProxy, null,
                        "mWebViewClient");
            } else {
                print("getClass:" + webView.getClass());
                mWebViewClient = (WebViewClient) viewRecorder.getLocalLib().invoke(webView,
                        "android.webkit.WebView", "getWebViewClient", new Class[] {}, new Object[] {});
            }
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return mWebViewClient;
    }

    /**
     * use java reflect mechanism to get original WebChromeClient object
     * 
     * @param webView
     * @return
     */
    private WebChromeClient getOriginalWebChromeClient(final WebView webView) {
        WebChromeClient mWebChromeClient = null;
        try {
            if (Build.VERSION.SDK_INT > 14) {
                Object originalWebViewClassic = viewRecorder.getLocalLib().invoke(webView, "android.webkit.WebView",
                        "getWebViewProvider", new Class[] {}, new Object[] {});
                Object originalCallbackProxy = viewRecorder.getLocalLib().getField(originalWebViewClassic, null,
                        "mCallbackProxy");
                mWebChromeClient = (WebChromeClient) viewRecorder.getLocalLib().getField(originalCallbackProxy,
                        null, "mWebChromeClient");
            } else {
                mWebChromeClient = (WebChromeClient) viewRecorder.getLocalLib().invoke(webView,
                        "android.webkit.WebView", "getWebChromeClient", new Class[] {}, new Object[] {});
            }
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        return mWebChromeClient;
    }

    public void setHookedWebChromeClient(final WebView webView) {
        webElementEventCreator.prepareForStart();
        if (webView != null) {
            webView.post(new Runnable() {
                public void run() {
                    webView.getSettings().setJavaScriptEnabled(true);
                    final WebChromeClient originalWebChromeClient = getOriginalWebChromeClient(webView);
                    if (originalWebChromeClient != null) {
                        webView.setWebChromeClient(new WebChromeClient() {
                            HashMap<String, Boolean> invoke = new HashMap<String, Boolean>();

                            /**
                             * Overrides onJsPrompt in order to create
                             * {@code WebElement} objects based on the web
                             * elements attributes prompted by the injections of
                             * JavaScript
                             */

                            @Override
                            public boolean onJsPrompt(WebView view, String url, String message, String defaultValue,
                                    JsPromptResult r) {

                                if (message != null) {
                                    if (message.endsWith("WebElementRecorder-finished")) {
                                        // Log.i("onJsPrompt : " + message);
                                        webElementEventCreator.setFinished(true);
                                    } else {
                                        webElementEventCreator.createWebElementEvent(message, view);
                                    }
                                }
                                r.confirm();
                                return true;
                            }

                            @Override
                            public Bitmap getDefaultVideoPoster() {
                                Bitmap ret = null;
                                String funcName = new Throwable().getStackTrace()[1].getMethodName();
                                if (invoke.get(funcName) == null || !invoke.get(funcName)) {
                                    invoke.put(funcName, true);
                                    ret = originalWebChromeClient.getDefaultVideoPoster();
                                    invoke.put(funcName, false);
                                }
                                return ret;
                            }

                            @Override
                            public View getVideoLoadingProgressView() {
                                View ret = null;
                                String funcName = new Throwable().getStackTrace()[1].getMethodName();
                                if (invoke.get(funcName) == null || !invoke.get(funcName)) {
                                    invoke.put(funcName, true);
                                    ret = originalWebChromeClient.getVideoLoadingProgressView();
                                    invoke.put(funcName, false);
                                }
                                return ret;
                            }

                            @Override
                            public void getVisitedHistory(ValueCallback<String[]> callback) {
                                String funcName = new Throwable().getStackTrace()[1].getMethodName();
                                if (invoke.get(funcName) == null || !invoke.get(funcName)) {
                                    invoke.put(funcName, true);
                                    originalWebChromeClient.getVisitedHistory(callback);
                                    invoke.put(funcName, false);
                                }
                            }

                            @Override
                            public void onCloseWindow(WebView window) {
                                String funcName = new Throwable().getStackTrace()[1].getMethodName();
                                if (invoke.get(funcName) == null || !invoke.get(funcName)) {
                                    invoke.put(funcName, true);
                                    originalWebChromeClient.onCloseWindow(window);
                                    invoke.put(funcName, false);
                                }
                            }

                            @Override
                            public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
                                boolean ret = false;
                                String funcName = new Throwable().getStackTrace()[1].getMethodName();
                                if (invoke.get(funcName) == null || !invoke.get(funcName)) {
                                    invoke.put(funcName, true);
                                    ret = originalWebChromeClient.onConsoleMessage(consoleMessage);
                                    invoke.put(funcName, false);
                                }
                                return ret;
                            }

                            @Override
                            public void onConsoleMessage(String message, int lineNumber, String sourceID) {
                                String funcName = new Throwable().getStackTrace()[1].getMethodName();
                                if (invoke.get(funcName) == null || !invoke.get(funcName)) {
                                    invoke.put(funcName, true);
                                    originalWebChromeClient.onConsoleMessage(message, lineNumber, sourceID);
                                    invoke.put(funcName, false);
                                }
                            }

                            @Override
                            public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture,
                                    Message resultMsg) {
                                boolean ret = false;
                                String funcName = new Throwable().getStackTrace()[1].getMethodName();
                                if (invoke.get(funcName) == null || !invoke.get(funcName)) {
                                    invoke.put(funcName, true);
                                    ret = originalWebChromeClient.onCreateWindow(view, isDialog, isUserGesture,
                                            resultMsg);
                                    invoke.put(funcName, false);
                                }
                                return ret;
                            }

                            @Override
                            public void onExceededDatabaseQuota(String url, String databaseIdentifier, long quota,
                                    long estimatedDatabaseSize, long totalQuota, QuotaUpdater quotaUpdater) {
                                String funcName = new Throwable().getStackTrace()[1].getMethodName();
                                if (invoke.get(funcName) == null || !invoke.get(funcName)) {
                                    invoke.put(funcName, true);
                                    originalWebChromeClient.onExceededDatabaseQuota(url, databaseIdentifier, quota,
                                            estimatedDatabaseSize, totalQuota, quotaUpdater);
                                    invoke.put(funcName, false);
                                }
                            }

                            @Override
                            public void onGeolocationPermissionsHidePrompt() {
                                String funcName = new Throwable().getStackTrace()[1].getMethodName();
                                if (invoke.get(funcName) == null || !invoke.get(funcName)) {
                                    invoke.put(funcName, true);
                                    originalWebChromeClient.onGeolocationPermissionsHidePrompt();
                                    invoke.put(funcName, false);
                                }
                            }

                            @Override
                            public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) {
                                String funcName = new Throwable().getStackTrace()[1].getMethodName();
                                if (invoke.get(funcName) == null || !invoke.get(funcName)) {
                                    invoke.put(funcName, true);
                                    originalWebChromeClient.onGeolocationPermissionsShowPrompt(origin, callback);
                                    invoke.put(funcName, false);
                                }
                            }

                            @Override
                            public void onHideCustomView() {
                                String funcName = new Throwable().getStackTrace()[1].getMethodName();
                                if (invoke.get(funcName) == null || !invoke.get(funcName)) {
                                    invoke.put(funcName, true);
                                    originalWebChromeClient.onHideCustomView();
                                    invoke.put(funcName, false);
                                }
                            }

                            @Override
                            public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
                                boolean ret = false;
                                String funcName = new Throwable().getStackTrace()[1].getMethodName();
                                if (invoke.get(funcName) == null || !invoke.get(funcName)) {
                                    invoke.put(funcName, true);
                                    ret = originalWebChromeClient.onJsAlert(view, url, message, result);
                                    invoke.put(funcName, false);
                                }
                                return ret;
                            }

                            @Override
                            public boolean onJsBeforeUnload(WebView view, String url, String message,
                                    JsResult result) {
                                boolean ret = false;
                                String funcName = new Throwable().getStackTrace()[1].getMethodName();
                                if (invoke.get(funcName) == null || !invoke.get(funcName)) {
                                    invoke.put(funcName, true);
                                    ret = originalWebChromeClient.onJsBeforeUnload(view, url, message, result);
                                    invoke.put(funcName, false);
                                }
                                return ret;
                            }

                            @Override
                            public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
                                boolean ret = false;
                                String funcName = new Throwable().getStackTrace()[1].getMethodName();
                                if (invoke.get(funcName) == null || !invoke.get(funcName)) {
                                    invoke.put(funcName, true);
                                    ret = originalWebChromeClient.onJsConfirm(view, url, message, result);
                                    invoke.put(funcName, false);
                                }
                                return ret;
                            }

                            @Override
                            public boolean onJsTimeout() {
                                boolean ret = false;
                                String funcName = new Throwable().getStackTrace()[1].getMethodName();
                                if (invoke.get(funcName) == null || !invoke.get(funcName)) {
                                    invoke.put(funcName, true);
                                    ret = originalWebChromeClient.onJsTimeout();
                                    invoke.put(funcName, false);
                                }
                                return ret;
                            }

                            @Override
                            public void onProgressChanged(WebView view, int newProgress) {
                                String funcName = new Throwable().getStackTrace()[1].getMethodName();
                                if (invoke.get(funcName) == null || !invoke.get(funcName)) {
                                    invoke.put(funcName, true);
                                    originalWebChromeClient.onProgressChanged(view, newProgress);
                                    invoke.put(funcName, false);
                                }
                            }

                            @Override
                            public void onReachedMaxAppCacheSize(long requiredStorage, long quota,
                                    QuotaUpdater quotaUpdater) {
                                String funcName = new Throwable().getStackTrace()[1].getMethodName();
                                if (invoke.get(funcName) == null || !invoke.get(funcName)) {
                                    invoke.put(funcName, true);
                                    originalWebChromeClient.onReachedMaxAppCacheSize(requiredStorage, quota,
                                            quotaUpdater);
                                    invoke.put(funcName, false);
                                }
                            }

                            @Override
                            public void onReceivedIcon(WebView view, Bitmap icon) {
                                String funcName = new Throwable().getStackTrace()[1].getMethodName();
                                if (invoke.get(funcName) == null || !invoke.get(funcName)) {
                                    invoke.put(funcName, true);
                                    originalWebChromeClient.onReceivedIcon(view, icon);
                                    invoke.put(funcName, false);
                                }
                            }

                            @Override
                            public void onReceivedTitle(WebView view, String title) {
                                String funcName = new Throwable().getStackTrace()[1].getMethodName();
                                if (invoke.get(funcName) == null || !invoke.get(funcName)) {
                                    invoke.put(funcName, true);
                                    originalWebChromeClient.onReceivedTitle(view, title);
                                    invoke.put(funcName, false);
                                }
                            }

                            @Override
                            public void onReceivedTouchIconUrl(WebView view, String url, boolean precomposed) {
                                String funcName = new Throwable().getStackTrace()[1].getMethodName();
                                if (invoke.get(funcName) == null || !invoke.get(funcName)) {
                                    invoke.put(funcName, true);
                                    originalWebChromeClient.onReceivedTouchIconUrl(view, url, precomposed);
                                    invoke.put(funcName, false);
                                }

                            }

                            @Override
                            public void onRequestFocus(WebView view) {
                                String funcName = new Throwable().getStackTrace()[1].getMethodName();
                                if (invoke.get(funcName) == null || !invoke.get(funcName)) {
                                    invoke.put(funcName, true);
                                    originalWebChromeClient.onRequestFocus(view);
                                    invoke.put(funcName, false);
                                }
                            }

                            @Override
                            public void onShowCustomView(View view, CustomViewCallback callback) {
                                String funcName = new Throwable().getStackTrace()[1].getMethodName();
                                if (invoke.get(funcName) == null || !invoke.get(funcName)) {
                                    invoke.put(funcName, true);
                                    originalWebChromeClient.onShowCustomView(view, callback);
                                    invoke.put(funcName, false);
                                }
                            }

                            public void onShowCustomView(View view, int requestedOrientation,
                                    CustomViewCallback callback) {
                                if (Build.VERSION.SDK_INT >= 14) {
                                    String funcName = new Throwable().getStackTrace()[1].getMethodName();
                                    if (invoke.get(funcName) == null || !invoke.get(funcName)) {
                                        invoke.put(funcName, true);
                                        originalWebChromeClient.onShowCustomView(view, requestedOrientation,
                                                callback);
                                        invoke.put(funcName, false);
                                    }
                                }
                            }
                        });
                    } else {
                        webView.setWebChromeClient(new WebChromeClient() {

                            /* (non-Javadoc)
                             * @see android.webkit.WebChromeClient#getDefaultVideoPoster()
                             */
                            @Override
                            public Bitmap getDefaultVideoPoster() {
                                // TODO Auto-generated method stub
                                return super.getDefaultVideoPoster();
                            }

                            /* (non-Javadoc)
                             * @see android.webkit.WebChromeClient#getVideoLoadingProgressView()
                             */
                            @Override
                            public View getVideoLoadingProgressView() {
                                // TODO Auto-generated method stub
                                return super.getVideoLoadingProgressView();
                            }

                            /* (non-Javadoc)
                             * @see android.webkit.WebChromeClient#getVisitedHistory(android.webkit.ValueCallback)
                             */
                            @Override
                            public void getVisitedHistory(ValueCallback<String[]> callback) {
                                // TODO Auto-generated method stub
                                super.getVisitedHistory(callback);
                            }

                            /* (non-Javadoc)
                             * @see android.webkit.WebChromeClient#onCloseWindow(android.webkit.WebView)
                             */
                            @Override
                            public void onCloseWindow(WebView window) {
                                // TODO Auto-generated method stub
                                super.onCloseWindow(window);
                            }

                            /* (non-Javadoc)
                             * @see android.webkit.WebChromeClient#onConsoleMessage(android.webkit.ConsoleMessage)
                             */
                            @Override
                            public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
                                // TODO Auto-generated method stub
                                return super.onConsoleMessage(consoleMessage);
                            }

                            /* (non-Javadoc)
                             * @see android.webkit.WebChromeClient#onConsoleMessage(java.lang.String, int, java.lang.String)
                             */
                            @Override
                            public void onConsoleMessage(String message, int lineNumber, String sourceID) {
                                // TODO Auto-generated method stub
                                super.onConsoleMessage(message, lineNumber, sourceID);
                            }

                            /* (non-Javadoc)
                             * @see android.webkit.WebChromeClient#onCreateWindow(android.webkit.WebView, boolean, boolean, android.os.Message)
                             */
                            @Override
                            public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture,
                                    Message resultMsg) {
                                // TODO Auto-generated method stub
                                return super.onCreateWindow(view, isDialog, isUserGesture, resultMsg);
                            }

                            /* (non-Javadoc)
                             * @see android.webkit.WebChromeClient#onExceededDatabaseQuota(java.lang.String, java.lang.String, long, long, long, android.webkit.WebStorage.QuotaUpdater)
                             */
                            @Override
                            public void onExceededDatabaseQuota(String url, String databaseIdentifier, long quota,
                                    long estimatedDatabaseSize, long totalQuota, QuotaUpdater quotaUpdater) {
                                // TODO Auto-generated method stub
                                super.onExceededDatabaseQuota(url, databaseIdentifier, quota, estimatedDatabaseSize,
                                        totalQuota, quotaUpdater);
                            }

                            /* (non-Javadoc)
                             * @see android.webkit.WebChromeClient#onGeolocationPermissionsHidePrompt()
                             */
                            @Override
                            public void onGeolocationPermissionsHidePrompt() {
                                // TODO Auto-generated method stub
                                super.onGeolocationPermissionsHidePrompt();
                            }

                            /* (non-Javadoc)
                             * @see android.webkit.WebChromeClient#onGeolocationPermissionsShowPrompt(java.lang.String, android.webkit.GeolocationPermissions.Callback)
                             */
                            @Override
                            public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) {
                                // TODO Auto-generated method stub
                                super.onGeolocationPermissionsShowPrompt(origin, callback);
                            }

                            /* (non-Javadoc)
                             * @see android.webkit.WebChromeClient#onHideCustomView()
                             */
                            @Override
                            public void onHideCustomView() {
                                // TODO Auto-generated method stub
                                super.onHideCustomView();
                            }

                            /* (non-Javadoc)
                             * @see android.webkit.WebChromeClient#onJsAlert(android.webkit.WebView, java.lang.String, java.lang.String, android.webkit.JsResult)
                             */
                            @Override
                            public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
                                // TODO Auto-generated method stub
                                return super.onJsAlert(view, url, message, result);
                            }

                            /* (non-Javadoc)
                             * @see android.webkit.WebChromeClient#onJsBeforeUnload(android.webkit.WebView, java.lang.String, java.lang.String, android.webkit.JsResult)
                             */
                            @Override
                            public boolean onJsBeforeUnload(WebView view, String url, String message,
                                    JsResult result) {
                                // TODO Auto-generated method stub
                                return super.onJsBeforeUnload(view, url, message, result);
                            }

                            /* (non-Javadoc)
                             * @see android.webkit.WebChromeClient#onJsConfirm(android.webkit.WebView, java.lang.String, java.lang.String, android.webkit.JsResult)
                             */
                            @Override
                            public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
                                // TODO Auto-generated method stub
                                return super.onJsConfirm(view, url, message, result);
                            }

                            /* (non-Javadoc)
                             * @see android.webkit.WebChromeClient#onJsPrompt(android.webkit.WebView, java.lang.String, java.lang.String, java.lang.String, android.webkit.JsPromptResult)
                             */
                            @Override
                            public boolean onJsPrompt(WebView view, String url, String message, String defaultValue,
                                    JsPromptResult result) {
                                if (message != null) {
                                    if (message.endsWith("WebElementRecorder-finished")) {
                                        // Log.i("onJsPrompt : " + message);
                                        webElementEventCreator.setFinished(true);
                                    } else {
                                        webElementEventCreator.createWebElementEvent(message, view);
                                    }
                                }
                                result.confirm();
                                return true;
                            }

                            /* (non-Javadoc)
                             * @see android.webkit.WebChromeClient#onJsTimeout()
                             */
                            @Override
                            public boolean onJsTimeout() {
                                // TODO Auto-generated method stub
                                return super.onJsTimeout();
                            }

                            /* (non-Javadoc)
                             * @see android.webkit.WebChromeClient#onProgressChanged(android.webkit.WebView, int)
                             */
                            @Override
                            public void onProgressChanged(WebView view, int newProgress) {
                                // TODO Auto-generated method stub
                                super.onProgressChanged(view, newProgress);
                            }

                            /* (non-Javadoc)
                             * @see android.webkit.WebChromeClient#onReachedMaxAppCacheSize(long, long, android.webkit.WebStorage.QuotaUpdater)
                             */
                            @Override
                            public void onReachedMaxAppCacheSize(long requiredStorage, long quota,
                                    QuotaUpdater quotaUpdater) {
                                // TODO Auto-generated method stub
                                super.onReachedMaxAppCacheSize(requiredStorage, quota, quotaUpdater);
                            }

                            /* (non-Javadoc)
                             * @see android.webkit.WebChromeClient#onReceivedIcon(android.webkit.WebView, android.graphics.Bitmap)
                             */
                            @Override
                            public void onReceivedIcon(WebView view, Bitmap icon) {
                                // TODO Auto-generated method stub
                                super.onReceivedIcon(view, icon);
                            }

                            /* (non-Javadoc)
                             * @see android.webkit.WebChromeClient#onReceivedTitle(android.webkit.WebView, java.lang.String)
                             */
                            @Override
                            public void onReceivedTitle(WebView view, String title) {
                                // TODO Auto-generated method stub
                                super.onReceivedTitle(view, title);
                            }

                            /* (non-Javadoc)
                             * @see android.webkit.WebChromeClient#onReceivedTouchIconUrl(android.webkit.WebView, java.lang.String, boolean)
                             */
                            @Override
                            public void onReceivedTouchIconUrl(WebView view, String url, boolean precomposed) {
                                // TODO Auto-generated method stub
                                super.onReceivedTouchIconUrl(view, url, precomposed);
                            }

                            /* (non-Javadoc)
                             * @see android.webkit.WebChromeClient#onRequestFocus(android.webkit.WebView)
                             */
                            @Override
                            public void onRequestFocus(WebView view) {
                                // TODO Auto-generated method stub
                                super.onRequestFocus(view);
                            }

                            /* (non-Javadoc)
                             * @see android.webkit.WebChromeClient#onShowCustomView(android.view.View, android.webkit.WebChromeClient.CustomViewCallback)
                             */
                            @Override
                            public void onShowCustomView(View view, CustomViewCallback callback) {
                                // TODO Auto-generated method stub
                                super.onShowCustomView(view, callback);
                            }

                            /* (non-Javadoc)
                             * @see android.webkit.WebChromeClient#onShowCustomView(android.view.View, int, android.webkit.WebChromeClient.CustomViewCallback)
                             */
                            @Override
                            public void onShowCustomView(View view, int requestedOrientation,
                                    CustomViewCallback callback) {
                                // TODO Auto-generated method stub
                                super.onShowCustomView(view, requestedOrientation, callback);
                            }

                        });
                    }
                }
            });
        }
    }

    public void setHookedWebViewClient(final WebView webView, final String javaScript) {
        webView.post(new Runnable() {
            // @Override
            public void run() {
                final WebViewClient orginalWebViewClient = getOriginalWebViewClient(webView);
                if (orginalWebViewClient != null) {
                    webView.setWebViewClient(new WebViewClient() {

                        HashMap<String, Boolean> invoke = new HashMap<String, Boolean>();

                        @Override
                        public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {
                            orginalWebViewClient.doUpdateVisitedHistory(view, url, isReload);
                        }

                        @Override
                        public void onFormResubmission(WebView view, Message dontResend, Message resend) {
                            orginalWebViewClient.onFormResubmission(view, dontResend, resend);
                        }

                        @Override
                        public void onLoadResource(WebView view, String url) {
                            String funcName = new Throwable().getStackTrace()[1].getMethodName();
                            if (invoke.get(funcName) == null || !invoke.get(funcName)) {
                                invoke.put(funcName, true);
                                orginalWebViewClient.onLoadResource(view, url);
                                invoke.put(funcName, false);
                            }
                        }

                        @Override
                        public void onPageFinished(WebView view, String url) {
                            String funcName = new Throwable().getStackTrace()[1].getMethodName();
                            if (invoke.get(funcName) == null || !invoke.get(funcName)) {
                                invoke.put(funcName, true);
                                orginalWebViewClient.onPageFinished(view, url);
                                if (url != null) {
                                    hookWebElements(view, javaScript);
                                }
                                invoke.put(funcName, false);
                            }
                        }

                        @Override
                        public void onPageStarted(WebView view, String url, Bitmap favicon) {
                            String funcName = new Throwable().getStackTrace()[1].getMethodName();
                            if (invoke.get(funcName) == null || !invoke.get(funcName)) {
                                invoke.put(funcName, true);
                                orginalWebViewClient.onPageStarted(view, url, favicon);
                                invoke.put(funcName, false);
                            }
                        }

                        @Override
                        public void onReceivedError(WebView view, int errorCode, String description,
                                String failingUrl) {
                            orginalWebViewClient.onReceivedError(view, errorCode, description, failingUrl);
                        }

                        @Override
                        public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host,
                                String realm) {
                            orginalWebViewClient.onReceivedHttpAuthRequest(view, handler, host, realm);
                        }

                        public void onReceivedLoginRequest(WebView view, String realm, String account,
                                String args) {
                            // do support onReceivedLoginRequest since the
                            // version 4.0
                            if (Build.VERSION.SDK_INT >= 14) {
                                orginalWebViewClient.onReceivedLoginRequest(view, realm, account, args);
                            }
                        }

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

                        @Override
                        public void onScaleChanged(WebView view, float oldScale, float newScale) {
                            orginalWebViewClient.onScaleChanged(view, oldScale, newScale);
                        }

                        @Override
                        public void onTooManyRedirects(WebView view, Message cancelMsg, Message continueMsg) {
                            orginalWebViewClient.onTooManyRedirects(view, cancelMsg, continueMsg);
                        }

                        @Override
                        public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
                            orginalWebViewClient.onUnhandledKeyEvent(view, event);
                        }

                        @Override
                        public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
                            if (Build.VERSION.SDK_INT >= 14) {
                                return orginalWebViewClient.shouldInterceptRequest(view, url);
                            } else {
                                return null;
                            }
                        }

                        @Override
                        public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
                            return orginalWebViewClient.shouldOverrideKeyEvent(view, event);
                        }

                        @Override
                        public boolean shouldOverrideUrlLoading(WebView view, String url) {
                            boolean ret = false;
                            String funcName = new Throwable().getStackTrace()[1].getMethodName();
                            if (invoke.get(funcName) == null || !invoke.get(funcName)) {
                                invoke.put(funcName, true);
                                ret = orginalWebViewClient.shouldOverrideUrlLoading(view, url);
                                invoke.put(funcName, false);
                            }
                            return ret;
                        }

                    });
                } else {
                    // set hook WebViewClient
                    webView.setWebViewClient(new WebViewClient() {

                        /* (non-Javadoc)
                         * @see android.webkit.WebViewClient#doUpdateVisitedHistory(android.webkit.WebView, java.lang.String, boolean)
                         */
                        @Override
                        public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {
                            // TODO Auto-generated method stub
                            super.doUpdateVisitedHistory(view, url, isReload);
                        }

                        /* (non-Javadoc)
                         * @see android.webkit.WebViewClient#onFormResubmission(android.webkit.WebView, android.os.Message, android.os.Message)
                         */
                        @Override
                        public void onFormResubmission(WebView view, Message dontResend, Message resend) {
                            // TODO Auto-generated method stub
                            super.onFormResubmission(view, dontResend, resend);
                        }

                        /* (non-Javadoc)
                         * @see android.webkit.WebViewClient#onLoadResource(android.webkit.WebView, java.lang.String)
                         */
                        @Override
                        public void onLoadResource(WebView view, String url) {
                            // TODO Auto-generated method stub
                            super.onLoadResource(view, url);
                        }

                        /* (non-Javadoc)
                         * @see android.webkit.WebViewClient#onPageStarted(android.webkit.WebView, java.lang.String, android.graphics.Bitmap)
                         */
                        @Override
                        public void onPageStarted(WebView view, String url, Bitmap favicon) {
                            // TODO Auto-generated method stub
                            super.onPageStarted(view, url, favicon);
                        }

                        /* (non-Javadoc)
                         * @see android.webkit.WebViewClient#onReceivedError(android.webkit.WebView, int, java.lang.String, java.lang.String)
                         */
                        @Override
                        public void onReceivedError(WebView view, int errorCode, String description,
                                String failingUrl) {
                            // TODO Auto-generated method stub
                            super.onReceivedError(view, errorCode, description, failingUrl);
                        }

                        /* (non-Javadoc)
                         * @see android.webkit.WebViewClient#onReceivedHttpAuthRequest(android.webkit.WebView, android.webkit.HttpAuthHandler, java.lang.String, java.lang.String)
                         */
                        @Override
                        public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host,
                                String realm) {
                            // TODO Auto-generated method stub
                            super.onReceivedHttpAuthRequest(view, handler, host, realm);
                        }

                        /* (non-Javadoc)
                         * @see android.webkit.WebViewClient#onReceivedLoginRequest(android.webkit.WebView, java.lang.String, java.lang.String, java.lang.String)
                         */
                        @Override
                        public void onReceivedLoginRequest(WebView view, String realm, String account,
                                String args) {
                            // TODO Auto-generated method stub
                            super.onReceivedLoginRequest(view, realm, account, args);
                        }

                        /* (non-Javadoc)
                         * @see android.webkit.WebViewClient#onReceivedSslError(android.webkit.WebView, android.webkit.SslErrorHandler, android.net.http.SslError)
                         */
                        @Override
                        public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
                            // TODO Auto-generated method stub
                            super.onReceivedSslError(view, handler, error);
                        }

                        /* (non-Javadoc)
                         * @see android.webkit.WebViewClient#onScaleChanged(android.webkit.WebView, float, float)
                         */
                        @Override
                        public void onScaleChanged(WebView view, float oldScale, float newScale) {
                            // TODO Auto-generated method stub
                            super.onScaleChanged(view, oldScale, newScale);
                        }

                        /* (non-Javadoc)
                         * @see android.webkit.WebViewClient#onTooManyRedirects(android.webkit.WebView, android.os.Message, android.os.Message)
                         */
                        @Override
                        public void onTooManyRedirects(WebView view, Message cancelMsg, Message continueMsg) {
                            // TODO Auto-generated method stub
                            super.onTooManyRedirects(view, cancelMsg, continueMsg);
                        }

                        /* (non-Javadoc)
                         * @see android.webkit.WebViewClient#onUnhandledKeyEvent(android.webkit.WebView, android.view.KeyEvent)
                         */
                        @Override
                        public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
                            // TODO Auto-generated method stub
                            super.onUnhandledKeyEvent(view, event);
                        }

                        /* (non-Javadoc)
                         * @see android.webkit.WebViewClient#shouldInterceptRequest(android.webkit.WebView, java.lang.String)
                         */
                        @Override
                        public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
                            // TODO Auto-generated method stub
                            return super.shouldInterceptRequest(view, url);
                        }

                        /* (non-Javadoc)
                         * @see android.webkit.WebViewClient#shouldOverrideKeyEvent(android.webkit.WebView, android.view.KeyEvent)
                         */
                        @Override
                        public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
                            // TODO Auto-generated method stub
                            return super.shouldOverrideKeyEvent(view, event);
                        }

                        /* (non-Javadoc)
                         * @see android.webkit.WebViewClient#shouldOverrideUrlLoading(android.webkit.WebView, java.lang.String)
                         */
                        @Override
                        public boolean shouldOverrideUrlLoading(WebView view, String url) {
                            // TODO Auto-generated method stub
                            return super.shouldOverrideUrlLoading(view, url);
                        }

                        @Override
                        public void onPageFinished(WebView view, String url) {
                            super.onPageFinished(webView, url);
                            // print("webView onPageFinished: " + url);
                            if (url != null) {
                                hookWebElements(view, javaScript);
                            }
                        }
                    });
                }
            }
        });
    }

    public void hookWebView(final WebView webView) {
        setHookedWebChromeClient(webView);
        final String javaScript = getJavaScriptAsString();
        hookWebElements(webView, javaScript);
        setHookedWebViewClient(webView, javaScript);
    }

    /**
     * @param webView
     * @param javaScript
     */
    public void hookWebElements(final WebView webView, final String javaScript) {
        webView.post(new Runnable() {
            public void run() {
                if (webView != null) {
                    webView.loadUrl("javascript:" + javaScript);
                }
            }
        });
        // viewRecorder.getLocalLib().getCurrentActivity().setProgress(10000);
    }

    private String getJavaScriptAsString() {
        StringBuffer javaScript = new StringBuffer();
        try {
            // InputStream fis =
            // getClass().getResourceAsStream("WebElementRecorder.js");
            InputStream fis = new FileInputStream(CafeTestCase.mTargetFilesDir + "/WebElementRecorder.js");
            BufferedReader input = new BufferedReader(new InputStreamReader(fis));
            String line = null;
            while ((line = input.readLine()) != null) {
                javaScript.append(line);
                javaScript.append("\n");
            }
            input.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return javaScript.toString();
    }

    private void print(String message) {
        if (ViewRecorder.DEBUG) {
            Log.i("ViewRecorder", message);
        } else {
            Log.i("ViewRecorder", DESEncryption.encryptStr(message));
        }
    }

    class WebElementEventCreator {
        private boolean isFinished = false;
        private ViewRecorder viewRecorder = null;

        public final static boolean DEBUG = true;

        public WebElementEventCreator(ViewRecorder viewRecorder) {
            this.viewRecorder = viewRecorder;
        }

        public void prepareForStart() {
            setFinished(false);
        }

        public void createWebElementEvent(String information, WebView webView) {
            String action = "";
            long time = 0;
            String familyString = "";
            int x = 0;
            int y = 0;
            int width = 0;
            int height = 0;
            String value = "";
            String tag = "";
            try {
                JSONObject jsonObject = new JSONObject(information);
                action = jsonObject.getString("action");
                time = Long.valueOf(jsonObject.getString("time"));
                familyString = jsonObject.getString("familyString");
                x = Math.round(Float.valueOf(jsonObject.getString("left")));
                y = Math.round(Float.valueOf(jsonObject.getString("top")));
                width = Math.round(Float.valueOf(jsonObject.getString("width")));
                height = Math.round(Float.valueOf(jsonObject.getString("height")));
                value = jsonObject.isNull("value") ? "" : jsonObject.getString("value");
                tag = jsonObject.isNull("tag") ? "" : jsonObject.getString("tag");
            } catch (Exception ignored) {
                if (DEBUG) {
                    ignored.printStackTrace();
                }
            }
            float scale = webView.getScale();
            int[] locationOfWebViewXY = new int[2];
            webView.getLocationOnScreen(locationOfWebViewXY);

            int locationX = (int) (locationOfWebViewXY[0] + (x + (Math.floor(width / 2))) * scale);
            int locationY = (int) (locationOfWebViewXY[1] + (y + (Math.floor(height / 2))) * scale);
            if (DEBUG) {
                System.out.println("[action:" + action + "] [familyString:" + familyString + "] [locationX:"
                        + locationX + "] [locationY:" + locationY + "] [time:" + time + "] [tag:" + tag + "]");
            }
            WebElementRecordEvent event = new WebElementRecordEvent(webView, familyString, action, locationX,
                    locationY, time, value, tag);
            offerWebElementRecordEventQueue(event);
        }

        public void setFinished(boolean isFinished) {
            this.isFinished = isFinished;
        }

        public boolean isFinished() {
            return isFinished;
        }

        private boolean waitForWebElementsToBeCreated() {
            final long endTime = SystemClock.uptimeMillis() + 20;
            while (SystemClock.uptimeMillis() < endTime) {
                if (isFinished) {
                    return true;
                }
                sleep(2);
            }
            return false;
        }
    }

    class WebElementRecordEvent {
        public View view;
        public String familyString;
        public String action;
        public int x;
        public int y;
        public long time;
        public String value;
        public String tag;

        public WebElementRecordEvent(View view, String familyString, String action, int x, int y, long time,
                String value, String tag) {
            this.view = view;
            this.familyString = familyString;
            this.action = action;
            this.x = x;
            this.y = y;
            this.time = time;
            this.value = value;
            this.tag = tag;
        }
    }

    class WebElementClickEvent extends OutputEvent {
        public WebElementClickEvent(View view) {
            this.view = view;
            this.priority = PRIORITY_WEBELEMENT_CLICK;
        }
    }

    class WebElementChangeEvent extends OutputEvent {
        public WebElementChangeEvent(View view) {
            this.view = view;
            this.priority = PRIORITY_WEBELEMENT_CHANGE;
        }
    }

}