package us.costan.chrome;

import java.util.Map;

import org.chromium.android_webview.AwBrowserContext;
import org.chromium.android_webview.AwContents;
import org.chromium.content.browser.ContentViewCore;
import org.chromium.content.browser.ContentViewStatics;
import org.chromium.content.browser.LoadUrlParams;
import org.chromium.content.browser.ContentViewCore.JavaScriptCallback;

import us.costan.chrome.impl.ChromeAwContentsClientProxy;
import us.costan.chrome.impl.ChromeInitializer;
import us.costan.chrome.impl.ChromeSettingsProxy;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Picture;
import android.graphics.Rect;
import android.net.http.SslCertificate;
import android.os.Build;
import android.os.Bundle;
import android.os.Message;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.webkit.DownloadListener;
import android.webkit.ValueCallback;
import android.webkit.WebSettings;
import android.webkit.WebView.FindListener;
import android.widget.FrameLayout;

/** WebView-like layer. */
public class ChromeView extends FrameLayout {
  /** The closest thing to a WebView that Chromium has to offer. */
  private AwContents awContents_;

  /** Implements some of AwContents. */
  private ContentViewCore contentViewCore_;

  /** Glue that passes calls from the Chromium view to a WebChromeClient. */
  private ChromeAwContentsClientProxy awContentsClient_;

  /** Everything pertaining to the user's browsing session. */
  private AwBrowserContext browserContext_;

  /** Glue that passes calls from the Chromium view to its parent (us).  */
  private ChromeInternalAcccessAdapter internalAccessAdapter_;

  public ChromeView(Context context) {
    this(context, null);
  }

  /** Constructor for inflating via XML. */
  public ChromeView(Context context, AttributeSet attrs) {
    super(context, attrs, android.R.attr.webViewStyle);

    if (isInEditMode()) {
      return;  // Chromium isn't loaded in edit mode.
    }
    
    try {
      Activity activity = (Activity)context;
      activity.getWindow().setFlags(
          WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
          WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
            
    } catch(ClassCastException e) {
      // Hope that hardware acceleration is enabled.
    }

    SharedPreferences sharedPreferences = context.getSharedPreferences(
        "chromeview", Context.MODE_PRIVATE);
    // TODO(pwnall): is there a better way to get an AwBrowserContext?
    browserContext_ = new AwBrowserContext(sharedPreferences);

    internalAccessAdapter_ = new ChromeInternalAcccessAdapter();
    awContentsClient_ = new ChromeAwContentsClientProxy(this);
    awContents_ = new AwContents(browserContext_, this, internalAccessAdapter_,
        awContentsClient_, true);
    contentViewCore_ = awContents_.getContentViewCore();
  }

  //// Non-WebView extensions

  /**
   * Injects the passed Javascript code in the current page and evaluates it.
   * If a result is required, pass in a callback.
   * Used in automation tests.
   *
   * @param script The Javascript to execute.
   * @param message The callback to be fired off when a result is ready. The script's
   *                result will be json encoded and passed as the parameter, and the call
   *                will be made on the main thread.
   *                If no result is required, pass null.
   * @throws IllegalStateException If the ContentView has been destroyed.
   */
  public void evaluateJavaScript(String script, JavaScriptCallback callback) {
    if (awContents_ != null) {
      contentViewCore_.evaluateJavaScript(script, callback);
    }
  }

  //// WebView methods

  /**
    * Return the first substring consisting of the address of a physical
    * location. Currently, only addresses in the United States are detected,
    * and consist of:
    * - a house number
    * - a street name
    * - a street type (Road, Circle, etc), either spelled out or abbreviated
    * - a city name
    * - a state or territory, either spelled out or two-letter abbr.
    * - an optional 5 digit or 9 digit zip code.
    *
    * All names must be correctly capitalized, and the zip code, if present,
    * must be valid for the state. The street type must be a standard USPS
    * spelling or abbreviation. The state or territory must also be spelled
    * or abbreviated using USPS standards. The house number may not exceed
    * five digits.
    * @param addr The string to search for addresses.
    *
    * @return the address, or if no address is found, return null.
    */
  public static String findAddress(String addr) {
    return ContentViewStatics.findAddress(addr);
  }

  /**
   * Sets the ChromeViewClient associated with this view.
   *
   * @param chromeViewClient the new handler; replaces the old handler
   * @see android.webkit.WebView#setWebViewClient(android.webkit.WebViewClient)
   * @see android.webkit.WebViewClient
   */
  public void setChromeViewClient(ChromeViewClient chromeViewClient) {
    awContentsClient_.setChromeViewClient(chromeViewClient);
  }

  /**
   * Sets the ChromeWebClient associated with this view.
   *
   * @param chromeWebClient the new handler; replaces the old handler
   * @see android.webkit.WebView#setWebChromeClient(android.webkit.WebChromeClient)
   * @see android.webkit.WebChromeClient
   */
  public void setChromeWebClient(ChromeWebClient chromeWebClient) {
    awContentsClient_.setChromeWebClient(chromeWebClient);
  }

  /**
   * Sets the DownloadListener associated with this view.
   *
   * @param downloadListener the new listener; replaces the old listener
   * @see android.webkit.WebView#setDownloadListener(DownloadListener)
   */
  public void setDownloadListener(DownloadListener downloadListener) {
    awContentsClient_.setDownloadListener(downloadListener);
  }

  /**
   * Sets the FindListener associated with this view.
   *
   * @param findListener the new listener; replaces the old listener
   * @see android.webkit.WebView#setFindListener(FindListener)
   */
 public void setFindListener(FindListener findListener) {
   awContentsClient_.setFindListener(findListener);
  }

  /**
   * Gets the SSL certificate for the main top-level page or null if there is
   * no certificate (the site is not secure).
   *
   * @return the SSL certificate for the main top-level page
   */
  public SslCertificate getCertificate() {
    return awContents_.getCertificate();
  }

  /**
   * Stores HTTP authentication credentials for a given host and realm. This
   * method is intended to be used with
   * {@link ChromeViewClient#onReceivedHttpAuthRequest(ChromeView, ChromeHttpAuthHandler, String, String)}
   *
   * @param host the host to which the credentials apply
   * @param realm the realm to which the credentials apply
   * @param username the username
   * @param password the password
   * @see getHttpAuthUsernamePassword
   * @see android.webkit.WebViewDatabase#hasHttpAuthUsernamePassword()
   * @see android.webkit.WebViewDatabase#clearHttpAuthUsernamePassword()
   */
  public void setHttpAuthUsernamePassword(String host, String realm,
      String username, String password) {
    awContents_.setHttpAuthUsernamePassword(host, realm, username, password);
  }

  /**
   * Retrieves HTTP authentication credentials for a given host and realm.
   * This method is intended to be used with
   * {@link ChromeViewClient#onReceivedHttpAuthRequest(ChromeView, ChromeHttpAuthHandler, String, String)}.
   *
   * @param host the host to which the credentials apply
   * @param realm the realm to which the credentials apply
   * @return the credentials as a String array, if found. The first element
   *    is the username and the second element is the password. Null if no
   *    credentials are found.
   * @see setHttpAuthUsernamePassword
   * @see android.webkit.WebViewDatabase#hasHttpAuthUsernamePassword()
   * @see android.webkit.WebViewDatabase#clearHttpAuthUsernamePassword()
   */
  public String[] getHttpAuthUsernamePassword(String host, String realm) {
      return awContents_.getHttpAuthUsernamePassword(host, realm);
  }

  /**
   * Saves the state of this WebView used in
   * {@link Activity#onSaveInstanceState}. Please note that this
   * method no longer stores the display data for this WebView. The previous
   * behavior could potentially leak files if {@link #restoreState} was never
   * called.
   *
   * @param outState the Bundle to store this WebView's state
   * @return false if saveState fails
   */
  public boolean saveState(Bundle outState) {
    return awContents_.saveState(outState);
  }

  /**
   * Restores the state of this WebView from the given Bundle. This method is
   * intended for use in {@link Activity#onRestoreInstanceState}
   * and should be called to restore the state of this WebView. If
   * it is called after this WebView has had a chance to build state (load
   * pages, create a back/forward list, etc.) there may be undesirable
   * side-effects. Please note that this method no longer restores the
   * display data for this WebView.
   *
   * @param inState the incoming Bundle of state
   * @return false if restoreState failed
   */
  public boolean restoreState(Bundle inState) {
      return awContents_.restoreState(inState);
  }

  /**
   * Loads the given URL with the specified additional HTTP headers.
   *
   * @param url the URL of the resource to load
   * @param additionalHttpHeaders the additional headers to be used in the
   *            HTTP request for this URL, specified as a map from name to
   *            value. Note that if this map contains any of the headers
   *            that are set by default by this WebView, such as those
   *            controlling caching, accept types or the User-Agent, their
   *            values may be overriden by this WebView's defaults.
   */
  public void loadUrl(String url, Map<String, String> additionalHttpHeaders) {
    LoadUrlParams loadUrlParams = new LoadUrlParams(url);
    loadUrlParams.setExtraHeaders(additionalHttpHeaders);
    awContents_.loadUrl(loadUrlParams);
  }

  /**
   * Loads the given URL.
   *
   * @param url the URL of the resource to load
   */
  public void loadUrl(String url) {
    awContents_.loadUrl(new LoadUrlParams(url));
  }

  /**
   * Loads the URL with postData using "POST" method into this WebView. If url
   * is not a network URL, it will be loaded with {link
   * {@link #loadUrl(String)} instead.
   *
   * @param url the URL of the resource to load
   * @param postData the data will be passed to "POST" request
   */
  public void postUrl(String url, byte[] postData) {
    awContents_.loadUrl(LoadUrlParams.createLoadHttpPostParams(url, postData));
  }

  /**
   * Loads the given data into this WebView using a 'data' scheme URL.
   * <p>
   * Note that JavaScript's same origin policy means that script running in a
   * page loaded using this method will be unable to access content loaded
   * using any scheme other than 'data', including 'http(s)'. To avoid this
   * restriction, use {@link
   * #loadDataWithBaseURL(String,String,String,String,String)
   * loadDataWithBaseURL()} with an appropriate base URL.
   * <p>
   * The encoding parameter specifies whether the data is base64 or URL
   * encoded. If the data is base64 encoded, the value of the encoding
   * parameter must be 'base64'. For all other values of the parameter,
   * including null, it is assumed that the data uses ASCII encoding for
   * octets inside the range of safe URL characters and use the standard %xx
   * hex encoding of URLs for octets outside that range. For example, '#',
   * '%', '\', '?' should be replaced by %23, %25, %27, %3f respectively.
   * <p>
   * The 'data' scheme URL formed by this method uses the default US-ASCII
   * charset. If you need need to set a different charset, you should form a
   * 'data' scheme URL which explicitly specifies a charset parameter in the
   * mediatype portion of the URL and call {@link #loadUrl(String)} instead.
   * Note that the charset obtained from the mediatype portion of a data URL
   * always overrides that specified in the HTML or XML document itself.
   *
   * @param data a String of data in the given encoding
   * @param mimeType the MIME type of the data, e.g. 'text/html'
   * @param encoding the encoding of the data
   */
  public void loadData(String data, String mimeType, String encoding) {
    LoadUrlParams loadUrlParams = LoadUrlParams.createLoadDataParams(data,
        mimeType, encoding.equals("base64"));
    awContents_.loadUrl(loadUrlParams);
  }

  /**
   * Loads the given data into this WebView, using baseUrl as the base URL for
   * the content. The base URL is used both to resolve relative URLs and when
   * applying JavaScript's same origin policy. The historyUrl is used for the
   * history entry.
   * <p>
   * Note that content specified in this way can access local device files
   * (via 'file' scheme URLs) only if baseUrl specifies a scheme other than
   * 'http', 'https', 'ftp', 'ftps', 'about' or 'javascript'.
   * <p>
   * If the base URL uses the data scheme, this method is equivalent to
   * calling {@link #loadData(String,String,String) loadData()} and the
   * historyUrl is ignored.
   *
   * @param baseUrl the URL to use as the page's base URL. If null defaults to
   *                'about:blank'.
   * @param data a String of data in the given encoding
   * @param mimeType the MIMEType of the data, e.g. 'text/html'. If null,
   *                 defaults to 'text/html'.
   * @param encoding the encoding of the data
   * @param historyUrl the URL to use as the history entry. If null defaults
   *                   to 'about:blank'.
   */
  public void loadDataWithBaseURL(String baseUrl, String data,
          String mimeType, String encoding, String historyUrl) {
      LoadUrlParams loadUrlParams =
          LoadUrlParams.createLoadDataParamsWithBaseUrl(data, mimeType,
          encoding.equals("base64"), baseUrl, historyUrl);
      awContents_.loadUrl(loadUrlParams);
  }

  /**
   * Saves the current view as a web archive.
   *
   * @param filename the filename where the archive should be placed
   */
  public void saveWebArchive(String filename) {
    ValueCallback<String> callback = new ValueCallback<String>() {
      @Override
      public void onReceiveValue(String value) { }
    };
    awContents_.saveWebArchive(filename, false, callback);
  }

  /**
   * Saves the current view as a web archive.
   *
   * @param basename the filename where the archive should be placed
   * @param autoname if false, takes basename to be a file. If true, basename
   *                 is assumed to be a directory in which a filename will be
   *                 chosen according to the URL of the current page.
   * @param callback called after the web archive has been saved. The
   *                 parameter for onReceiveValue will either be the filename
   *                 under which the file was saved, or null if saving the
   *                 file failed.
   */
  public void saveWebArchive(String basename, boolean autoname,
      ValueCallback<String> callback) {
    awContents_.saveWebArchive(basename, autoname, callback);
  }

  /**
   * Stops the current load.
   */
  public void stopLoading() {
    awContents_.stopLoading();
  }

  /**
   * Reloads the current URL.
   */
  public void reload() {
    awContents_.reload();
  }

  /**
   * Gets whether this WebView has a back history item.
   *
   * @return true iff this WebView has a back history item
   */
  public boolean canGoBack() {
    return awContents_.canGoBack();
  }

  /**
   * Goes back in the history of this WebView.
   */
  public void goBack() {
    awContents_.goBack();
  }

  /**
   * Gets whether this WebView has a forward history item.
   *
   * @return true iff this Webview has a forward history item
   */
  public boolean canGoForward() {
    return awContents_.canGoForward();
  }

  /**
   * Goes forward in the history of this WebView.
   */
  public void goForward() {
    awContents_.goForward();
  }

  /**
   * Gets whether the page can go back or forward the given
   * number of steps.
   *
   * @param steps the negative or positive number of steps to move the
   *              history
   */
  public boolean canGoBackOrForward(int steps) {
    return awContents_.canGoBackOrForward(steps);
  }

  /**
   * Goes to the history item that is the number of steps away from
   * the current item. Steps is negative if backward and positive
   * if forward.
   *
   * @param steps the number of steps to take back or forward in the back
   *              forward list
   */
  public void goBackOrForward(int steps) {
      awContents_.goBackOrForward(steps);
  }

  /**
   * Gets whether private browsing is enabled in this WebView.
   */
  public boolean isPrivateBrowsingEnabled() {
    return false;
  }

  /**
   * Scrolls the contents of this WebView up by half the view size.
   *
   * @param top true to jump to the top of the page
   * @return true if the page was scrolled
   */
  public boolean pageUp(boolean top) {
    return awContents_.pageUp(top);
  }

  /**
   * Scrolls the contents of this WebView down by half the page size.
   *
   * @param bottom true to jump to bottom of page
   * @return true if the page was scrolled
   */
  public boolean pageDown(boolean bottom) {
    return awContents_.pageDown(bottom);
  }

  /**
   * Gets a new picture that captures the current contents of this WebView.
   * The picture is of the entire document being displayed, and is not
   * limited to the area currently displayed by this WebView. Also, the
   * picture is a static copy and is unaffected by later changes to the
   * content being displayed.
   * <p>
   * Note that due to internal changes, for API levels between
   * {@link Build.VERSION_CODES#HONEYCOMB} and
   * {@link Build.VERSION_CODES#ICE_CREAM_SANDWICH} inclusive, the
   * picture does not include fixed position elements or scrollable divs.
   *
   * @return a picture that captures the current contents of this WebView
   */
  public Picture capturePicture() {
    return awContents_.capturePicture();
  }

  /**
   * Sets the initial scale for this WebView. 0 means default. If
   * {@link WebSettings#getUseWideViewPort()} is true, it zooms out all the
   * way. Otherwise it starts with 100%. If initial scale is greater than 0,
   * WebView starts with this value as initial scale.
   * Please note that unlike the scale properties in the viewport meta tag,
   * this method doesn't take the screen density into account.
   *
   * @param scaleInPercent the initial scale in percent
   */
  public void setInitialScale(int scaleInPercent) {
    // TODO(pwnall): seems useful
  }

  /**
   * Invokes the graphical zoom picker widget for this WebView. This will
   * result in the zoom widget appearing on the screen to control the zoom
   * level of this WebView.
   */
  public void invokeZoomPicker() {
    awContents_.invokeZoomPicker();
  }

  /**
   * Requests the anchor or image element URL at the last tapped point.
   * If hrefMsg is null, this method returns immediately and does not
   * dispatch hrefMsg to its target. If the tapped point hits an image,
   * an anchor, or an image in an anchor, the message associates
   * strings in named keys in its data. The value paired with the key
   * may be an empty string.
   *
   * @param hrefMsg the message to be dispatched with the result of the
   *                request. The message data contains three keys. "url"
   *                returns the anchor's href attribute. "title" returns the
   *                anchor's text. "src" returns the image's src attribute.
   */
  public void requestFocusNodeHref(Message hrefMsg) {
    awContents_.requestFocusNodeHref(hrefMsg);
  }

  /**
   * Requests the URL of the image last touched by the user. msg will be sent
   * to its target with a String representing the URL as its object.
   *
   * @param msg the message to be dispatched with the result of the request
   *            as the data member with "url" as key. The result can be null.
   */
  public void requestImageRef(Message msg) {
    awContents_.requestImageRef(msg);
  }

  /**
   * Gets the URL for the current page. This is not always the same as the URL
   * passed to WebViewClient.onPageStarted because although the load for
   * that URL has begun, the current page may not have changed.
   *
   * @return the URL for the current page
   */
  public String getUrl() {
    return awContents_.getUrl();
  }

  /**
   * Gets the original URL for the current page. This is not always the same
   * as the URL passed to WebViewClient.onPageStarted because although the
   * load for that URL has begun, the current page may not have changed.
   * Also, there may have been redirects resulting in a different URL to that
   * originally requested.
   *
   * @return the URL that was originally requested for the current page
   */
  public String getOriginalUrl() {
    return awContents_.getOriginalUrl();
  }

  /**
   * Gets the title for the current page. This is the title of the current page
   * until WebViewClient.onReceivedTitle is called.
   *
   * @return the title for the current page
   */
  public String getTitle() {
    return awContents_.getTitle();
  }

  /**
   * Gets the favicon for the current page. This is the favicon of the current
   * page until WebViewClient.onReceivedIcon is called.
   *
   * @return the favicon for the current page
   */
  public Bitmap getFavicon() {
    return awContents_.getFavicon();
  }

  /**
   * Gets the height of the HTML content.
   *
   * @return the height of the HTML content
   */
  public int getContentHeight() {
    return awContents_.getContentHeightCss();
  }

  /**
   * Gets the width of the HTML content.
   *
   * @return the width of the HTML content
   * @hide
   */
  public int getContentWidth() {
    return awContents_.getContentWidthCss();
  }

  /**
   * Pauses all layout, parsing, and JavaScript timers for all WebViews. This
   * is a global requests, not restricted to just this WebView. This can be
   * useful if the application has been paused.
   */
  public void pauseTimers() {
    awContents_.pauseTimers();
  }

  /**
   * Resumes all layout, parsing, and JavaScript timers for all WebViews.
   * This will resume dispatching all timers.
   */
  public void resumeTimers() {
    awContents_.resumeTimers();
  }

  /**
   * Pauses any extra processing associated with this WebView and its
   * associated DOM, plugins, JavaScript etc. For example, if this WebView is
   * taken offscreen, this could be called to reduce unnecessary CPU or
   * network traffic. When this WebView is again "active", call onResume().
   * Note that this differs from pauseTimers(), which affects all WebViews.
   */
  public void onPause() {
    awContents_.onPause();
  }

  /**
   * Resumes a WebView after a previous call to onPause().
   */
  public void onResume() {
    awContents_.onResume();
  }

  /**
   * Gets whether this WebView is paused, meaning onPause() was called.
   * Calling onResume() sets the paused state back to false.
   *
   * @hide
   */
  public boolean isPaused() {
    return awContents_.isPaused();
  }

  /**
   * Clears the resource cache. Note that the cache is per-application, so
   * this will clear the cache for all WebViews used.
   *
   * @param includeDiskFiles if false, only the RAM cache is cleared
   */
  public void clearCache(boolean includeDiskFiles) {
    awContents_.clearCache(includeDiskFiles);
  }

  /**
   * Tells this WebView to clear its internal back/forward list.
   */
  public void clearHistory() {
    awContents_.clearHistory();
  }

  /**
   * Clears the SSL preferences table stored in response to proceeding with
   * SSL certificate errors.
   */
  public void clearSslPreferences() {
    awContents_.clearSslPreferences();
  }

  /**
   * Highlights and scrolls to the next match found by
   * {@link #findAllAsync}, wrapping around page boundaries as necessary.
   * Notifies any registered {@link FindListener}. If {@link #findAllAsync(String)}
   * has not been called yet, or if {@link #clearMatches} has been called since the
   * last find operation, this function does nothing.
   *
   * @param forward the direction to search
   * @see #setFindListener
   */
  public void findNext(boolean forward) {
    awContents_.findNext(forward);
  }

  /**
   * Finds all instances of find on the page and highlights them,
   * asynchronously. Notifies any registered {@link FindListener}.
   * Successive calls to this will cancel any pending searches.
   *
   * @param find the string to find.
   * @see #setFindListener
   */
  public void findAllAsync(String find) {
    awContents_.findAllAsync(find);
  }

  /**
   * Starts an ActionMode for finding text in this WebView.  Only works if this
   * WebView is attached to the view system.
   *
   * @param text if non-null, will be the initial text to search for.
   *             Otherwise, the last String searched for in this WebView will
   *             be used to start.
   * @param showIme if true, show the IME, assuming the user will begin typing.
   *                If false and text is non-null, perform a find all.
   * @return true if the find dialog is shown, false otherwise
   */
  public boolean showFindDialog(String text, boolean showIme) {
    // TODO(pwnall): seems useful
    return false;
  }

  /**
   * Clears the highlighting surrounding text matches created by
   * {@link #findAllAsync}.
   */
  public void clearMatches() {
    awContents_.clearMatches();
  }

  /**
   * Queries the document to see if it contains any image references. The
   * message object will be dispatched with arg1 being set to 1 if images
   * were found and 0 if the document does not reference any images.
   *
   * @param response the message that will be dispatched with the result
   */
  public void documentHasImages(Message response) {
    awContents_.documentHasImages(response);
  }

  /**
   * Injects the supplied Java object into this WebView. The object is
   * injected into the JavaScript context of the main frame, using the
   * supplied name. This allows the Java object's methods to be
   * accessed from JavaScript. Only public methods that are annotated with
   * {@link us.costan.chrome.ChromeJavascriptInterface} can be accessed from
   * JavaScript.
   * <p> Note that injected objects will not
   * appear in JavaScript until the page is next (re)loaded. For example:
   * <pre>
   * class JsObject {
   *    {@literal @}ChromeJavascriptInterface
   *    public String toString() { return "injectedObject"; }
   * }
   * webView.addJavascriptInterface(new JsObject(), "injectedObject");
   * webView.loadData("<!DOCTYPE html><title></title>", "text/html", null);
   * webView.loadUrl("javascript:alert(injectedObject.toString())");</pre>
   * <p>
   * <strong>IMPORTANT:</strong>
   * <ul>
   * <li> This method can be used to allow JavaScript to control the host
   * application. Use of this method in a WebView
   * containing untrusted content could allow an attacker to manipulate the
   * host application in unintended ways, executing Java code with the
   * permissions of the host application. Use extreme care when using this
   * method in a WebView which could contain untrusted content.</li>
   * <li> JavaScript interacts with Java object on a private, background
   * thread of this WebView. Care is therefore required to maintain thread
   * safety.</li>
   * <li> The Java object's fields are not accessible.</li>
   * </ul>
   *
   * @param object the Java object to inject into this WebView's JavaScript
   *               context. Null values are ignored.
   * @param name the name used to expose the object in JavaScript
   */
  public void addJavascriptInterface(Object object, String name) {
    awContents_.addPossiblyUnsafeJavascriptInterface(object, name,
        ChromeJavascriptInterface.class);
  }

  /**
   * Removes a previously injected Java object from this WebView. Note that
   * the removal will not be reflected in JavaScript until the page is next
   * (re)loaded. See {@link #addJavascriptInterface}.
   *
   * @param name the name used to expose the object in JavaScript
   */
  public void removeJavascriptInterface(String name) {
    awContents_.removeJavascriptInterface(name);
  }

  /**
   * Gets the ChromeSettings object used to control the settings for this
   * ChromeView.
   *
   * @return a ChromeSettings object that can be used to control this
   *      ChromeView's settings
   */
  public ChromeSettings getSettings() {
    return new ChromeSettingsProxy(awContents_);
  }

  public void flingScroll(int vx, int vy) {
    awContents_.flingScroll(vx, vy);
  }

  /**
   * Performs zoom in in this WebView.
   *
   * @return true if zoom in succeeds, false if no zoom changes
   */
  public boolean zoomIn() {
    return awContents_.zoomIn();
  }

  /**
   * Performs zoom out in this WebView.
   *
   * @return true if zoom out succeeds, false if no zoom changes
   */
  public boolean zoomOut() {
    return awContents_.zoomOut();
  }


  //// Methods outside WebView.

  /**
   * Sets up the Chromium libraries backing ChromeView.
   *
   * This should be called from {@link android.app.Application#onCreate()}.
   *
   * @param context Android context for the application using ChromeView
   */
  public static void initialize(Context context) {
    ChromeInitializer.initialize(context);
  }

  /**
   * The object that implements the WebView API.
   */
  public ContentViewCore getContentViewCore() {
    return contentViewCore_;
  }

  //// Forward a bunch of calls to the Chromium view.
  //// Lifted from chromium/src/android_webview/test/shell/src/org/chromium/android_webview/test/AwTestContainerView

  public void destroy() {
    awContents_.destroy();
  }

  @SuppressLint("WrongCall")
  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    if (awContents_ != null) {
      awContents_.onMeasure(widthMeasureSpec, heightMeasureSpec);
    } else {
    }
  }
  @Override
  protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    if (awContents_ != null) {
      awContents_.onSizeChanged(w, h, oldw, oldh);
    }
  }
  @Override
  protected void onAttachedToWindow() {
    super.onAttachedToWindow();
    if (awContents_ != null) {
      awContents_.onAttachedToWindow();
    }
  }
  @Override
  protected void onConfigurationChanged(Configuration newConfig) {
    if (awContents_ != null) {
      awContents_.onConfigurationChanged(newConfig);
    } else {
      super.onConfigurationChanged(newConfig);
    }
  }
  @Override
  protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    if (awContents_ != null) {
      awContents_.onDetachedFromWindow();
    }
  }
  @SuppressLint("WrongCall")
  @Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if (awContents_ != null) {
      awContents_.onDraw(canvas);
    }
  }
  @Override
  protected void onFocusChanged(boolean gainFocus, int direction,
      Rect previouslyFocusedRect) {
    super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
    if (awContents_ != null) {
      awContents_.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
    }
  }
  @Override
  public boolean onTouchEvent(MotionEvent event) {
    if (awContents_ != null) {
      return awContents_.onTouchEvent(event);
    } else {
      return super.onTouchEvent(event);
    }
  }
  @Override
  protected void onVisibilityChanged(View changedView, int visibility) {
    super.onVisibilityChanged(changedView, visibility);
    if (awContents_ != null) {
      awContents_.onVisibilityChanged(changedView, visibility);
    }
  }
  @Override
  protected void onWindowVisibilityChanged(int visibility) {
    super.onWindowVisibilityChanged(visibility);
    if (awContents_ != null) {
      awContents_.onWindowVisibilityChanged(visibility);
    }
  }

  //// Forward a bunch more calls to the Chromium view.
  //// Lifted from
  ////     platform/frameworks/base ./core/java/android/webkit/WebKit.java

  @Override
  protected int computeHorizontalScrollRange() {
    if (awContents_ != null) {
      return awContents_.computeHorizontalScrollRange();
    } else {
      return super.computeHorizontalScrollRange();
    }
  }
  @Override
  protected int computeHorizontalScrollOffset() {
    if (awContents_ != null) {
      return awContents_.computeHorizontalScrollOffset();
    } else {
      return super.computeHorizontalScrollOffset();
    }
  }
  @Override
  protected int computeVerticalScrollRange() {
    if (awContents_ != null) {
      return awContents_.computeVerticalScrollRange();
    } else {
      return super.computeVerticalScrollRange();
    }
  }
  @Override
  protected int computeVerticalScrollOffset() {
    if (awContents_ != null) {
      return awContents_.computeVerticalScrollOffset();
    } else {
      return super.computeVerticalScrollOffset();
    }
  }
  @Override
  protected int computeVerticalScrollExtent() {
    if (awContents_ != null) {
      return awContents_.computeVerticalScrollExtent();
    } else {
      return super.computeVerticalScrollExtent();
    }
  }
  @Override
  public boolean onHoverEvent(MotionEvent event) {
    if (awContents_ != null) {
      return awContents_.onHoverEvent(event);
    } else {
      return super.onHoverEvent(event);
    }
  }
  @Override
  public boolean onGenericMotionEvent(MotionEvent event) {
    if (awContents_ != null) {
      return awContents_.onGenericMotionEvent(event);
    } else {
      return super.onGenericMotionEvent(event);
    }
  }
  @Override
  public boolean onKeyUp(int keyCode, KeyEvent event) {
    if (awContents_ != null) {
      return awContents_.onKeyUp(keyCode, event);
    } else {
      return super.onKeyUp(keyCode, event);
    }
  }
  @Override
  public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
    super.onInitializeAccessibilityNodeInfo(info);
    if (awContents_ != null) {
      info.setClassName(ChromeView.class.getName());
      awContents_.onInitializeAccessibilityNodeInfo(info);
    }
  }
  @Override
  public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
    super.onInitializeAccessibilityEvent(event);
    if (awContents_ != null) {
      event.setClassName(ChromeView.class.getName());
      awContents_.onInitializeAccessibilityEvent(event);
    }
  }
  @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
  @Override
  public boolean performAccessibilityAction(int action, Bundle arguments) {
    if (awContents_ != null) {
      // Lifted from content.browser.JellyBeanContentView
      if (contentViewCore_.supportsAccessibilityAction(action)) {
        return awContents_.performAccessibilityAction(action, arguments);
      }
    }
    return super.performAccessibilityAction(action, arguments);
  }
  @Override
  public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
    if (awContents_ != null) {
      return awContents_.onCreateInputConnection(outAttrs);
    } else {
      return super.onCreateInputConnection(outAttrs);
    }
  }
  @Override
  public void onWindowFocusChanged(boolean hasWindowFocus) {
    if (awContents_ != null) {
      awContents_.onWindowFocusChanged(hasWindowFocus);
    }
    super.onWindowFocusChanged(hasWindowFocus);
  }
  @Override
  public boolean dispatchKeyEvent(KeyEvent event) {
    if (awContents_ != null) {
      return awContents_.dispatchKeyEvent(event);
    } else {
      return super.dispatchKeyEvent(event);
    }
  }
  @Override
  public boolean dispatchKeyEventPreIme(KeyEvent event) {
    if (awContents_ != null) {
      return contentViewCore_.dispatchKeyEventPreIme(event);
    } else {
      return super.dispatchKeyEventPreIme(event);
    }
  }

  //// Forward calls that ContentViewCore responds to.

  @Override
  public boolean onCheckIsTextEditor() {
    if (awContents_ != null) {
      return contentViewCore_.onCheckIsTextEditor();
    } else {
      return super.onCheckIsTextEditor();
    }
  }
  @Override
  public void scrollTo(int x, int y) {
    super.scrollTo(x, y);
    if (awContents_ != null) {
      contentViewCore_.scrollTo(x, y);
    }
  }
  @Override
  public void scrollBy(int x, int y) {
    super.scrollBy(x, y);
    if (awContents_ != null) {
      contentViewCore_.scrollBy(x, y);
    }
  }
  @Override
  protected boolean awakenScrollBars(int startDelay, boolean invalidate) {
    if (awContents_ != null) {
      return contentViewCore_.awakenScrollBars(startDelay, invalidate);
    } else {
      return super.awakenScrollBars(startDelay, invalidate);
    }
  }

  /** Glue that passes calls from the Chromium view to its container (us). */
  private class ChromeInternalAcccessAdapter implements AwContents.InternalAccessDelegate {
    //// Lifted from chromium/src/android_webview/test/shell/src/org/chromium/android_webview/test/AwTestContainerView
    @Override
    public boolean drawChild(Canvas canvas, View child, long drawingTime) {
      return ChromeView.this.drawChild(canvas, child, drawingTime);
    }
    @Override
    public boolean super_onKeyUp(int keyCode, KeyEvent event) {
      return ChromeView.super.onKeyUp(keyCode, event);
    }
    @Override
    public boolean super_dispatchKeyEventPreIme(KeyEvent event) {
      return ChromeView.super.dispatchKeyEventPreIme(event);
    }
    @Override
    public boolean super_dispatchKeyEvent(KeyEvent event) {
      return ChromeView.super.dispatchKeyEvent(event);
    }
    @Override
    public boolean super_onGenericMotionEvent(MotionEvent event) {
      return ChromeView.super.onGenericMotionEvent(event);
    }
    @Override
    public void super_onConfigurationChanged(Configuration newConfig) {
      ChromeView.super.onConfigurationChanged(newConfig);
    }
    @Override
    public void onScrollChanged(int lPix, int tPix, int oldlPix, int oldtPix) {
      ChromeView.this.onScrollChanged(lPix, tPix, oldlPix, oldtPix);
    }
    @Override
    public boolean awakenScrollBars() {
      return ChromeView.this.awakenScrollBars();
    }
    @Override
    public boolean super_awakenScrollBars(int startDelay, boolean invalidate) {
      return ChromeView.super.awakenScrollBars(startDelay, invalidate);
    }
    @Override
    public void setMeasuredDimension(int measuredWidth, int measuredHeight) {
      ChromeView.this.setMeasuredDimension(measuredWidth, measuredHeight);
    }
    @Override
    public boolean requestDrawGL(Canvas canvas) {
      if (canvas != null) {
        if (canvas.isHardwareAccelerated()) {
          // TODO(pwnall): figure out what AwContents wants from us, and do it;
          //               most likely something to do with
          //               AwContents.getAwDrawGLFunction()
          return false;
        } else {
          return false;
        }
      } else {
        if (ChromeView.this.isHardwareAccelerated()) {
          // TODO(pwnall): figure out what AwContents wants from us, and do it;
          //               most likely something to do with
          //               AwContents.getAwDrawGLFunction()
          return false;
        } else {
          return false;
        }
      }
    }
  }
}
