Android Open Source - Cafe Web Element Recorder






From Project

Back to project page Cafe.

License

The source code is released under:

Apache License

If you think the Android project Cafe listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
 * Copyright (C) 2013 Baidu.com Inc/*  w w  w .  ja v  a  2 s  .  com*/
 *
 * 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;
    }
  }

}




Java Source Code List

com.baidu.cafe.CafeExceptionHandler.java
com.baidu.cafe.CafeListener.java
com.baidu.cafe.CafeServiceTestCase.java
com.baidu.cafe.CafeTestCase.java
com.baidu.cafe.CafeTestRunner.java
com.baidu.cafe.TearDownHelper.java
com.baidu.cafe.local.DESEncryption.java
com.baidu.cafe.local.FPSTracer.java
com.baidu.cafe.local.FileUtils.java
com.baidu.cafe.local.LocalLib.java
com.baidu.cafe.local.Log.java
com.baidu.cafe.local.NetworkUtils.java
com.baidu.cafe.local.SnapshotHelper.java
com.baidu.cafe.local.record.CafeWebViewClient.java
com.baidu.cafe.local.record.OutputEvent.java
com.baidu.cafe.local.record.ViewRecorderSDK.java
com.baidu.cafe.local.record.ViewRecorder.java
com.baidu.cafe.local.record.WebElementRecorder.java
com.baidu.cafe.local.traveler.APPTraveler.java
com.baidu.cafe.local.traveler.Logger.java
com.baidu.cafe.local.traveler.Operation.java
com.baidu.cafe.local.traveler.Util.java
com.baidu.cafe.local.traveler.ViewHelper.java
com.baidu.cafe.remote.ArmsBinder.java
com.baidu.cafe.remote.ArmsBootupReceiver.java
com.baidu.cafe.remote.Arms.java
com.baidu.cafe.remote.Armser.java
com.baidu.cafe.remote.BatteryState.java
com.baidu.cafe.remote.LockActivity.java
com.baidu.cafe.remote.Log.java
com.baidu.cafe.remote.MonkeyNetwork.java
com.baidu.cafe.remote.MyIntent.java
com.baidu.cafe.remote.SystemLib.java
com.baidu.cafe.remote.UILib.java
com.baidu.cafe.remote.ViewPropertyProvider.java
com.baidu.cafe.utils.CommandResult.java
com.baidu.cafe.utils.ReflectHelper.java
com.baidu.cafe.utils.ShellExecute.java
com.baidu.cafe.utils.Strings.java
com.baidu.cafe.utils.TreeNode.java