/*
 * Copyright 2007, The Android Open Source Project
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#define LOG_TAG "webviewglue"

#include "config.h"

#include "AndroidAnimation.h"
#include "AndroidLog.h"
#include "BaseLayerAndroid.h"
#include "BaseRenderer.h"
#include "DrawExtra.h"
#include "Frame.h"
#include "GLWebViewState.h"
#include "GraphicsJNI.h"
#include "HTMLInputElement.h"
#include "IntPoint.h"
#include "IntRect.h"
#include "LayerAndroid.h"
#include "LayerContent.h"
#include "Node.h"
#include "utils/Functor.h"
#include "private/hwui/DrawGlInfo.h"
#include "PlatformGraphicsContext.h"
#include "PlatformString.h"
#include "ScrollableLayerAndroid.h"
#include "SelectText.h"
#include "SkCanvas.h"
#include "SkDumpCanvas.h"
#include "SkPicture.h"
#include "SkRect.h"
#include "SkTime.h"
#include "TilesManager.h"
#include "TransferQueue.h"
#include "WebCoreJni.h"
#include "WebRequestContext.h"
#include "WebViewCore.h"
#include "android_graphics.h"

#ifdef GET_NATIVE_VIEW
#undef GET_NATIVE_VIEW
#endif

#define GET_NATIVE_VIEW(env, obj) ((WebView*)env->GetIntField(obj, gWebViewField))

#include <JNIUtility.h>
#include <JNIHelp.h>
#include <jni.h>
#include <androidfw/KeycodeLabels.h>
#include <wtf/text/AtomicString.h>
#include <wtf/text/CString.h>

// Free as much as we possible can
#define TRIM_MEMORY_COMPLETE 80
// Free a lot (all textures gone)
#define TRIM_MEMORY_MODERATE 60
// More moderate free (keep bare minimum to restore quickly-ish - possibly clear all textures)
#define TRIM_MEMORY_BACKGROUND 40
// Moderate free (clear cached tiles, keep visible ones)
#define TRIM_MEMORY_UI_HIDDEN 20
// Duration to show the pressed cursor ring
#define PRESSED_STATE_DURATION 400

namespace android {

static jfieldID gWebViewField;

//-------------------------------------

static jmethodID GetJMethod(JNIEnv* env, jclass clazz, const char name[], const char signature[])
{
    jmethodID m = env->GetMethodID(clazz, name, signature);
    ALOG_ASSERT(m, "Could not find method %s", name);
    return m;
}

//-------------------------------------
// This class provides JNI for making calls into native code from the UI side
// of the multi-threaded WebView.
class WebView
{
public:
enum FrameCachePermission {
    DontAllowNewer,
    AllowNewer
};

#define DRAW_EXTRAS_SIZE 2
enum DrawExtras { // keep this in sync with WebView.java
    DrawExtrasNone = 0,
    DrawExtrasSelection = 1,
    DrawExtrasCursorRing = 2
};

struct JavaGlue {
    jweak       m_obj;
    jmethodID   m_scrollBy;
    jmethodID   m_getScaledMaxXScroll;
    jmethodID   m_getScaledMaxYScroll;
    jmethodID   m_updateRectsForGL;
    jmethodID   m_viewInvalidate;
    jmethodID   m_viewInvalidateRect;
    jmethodID   m_postInvalidateDelayed;
    jmethodID   m_pageSwapCallback;
    jfieldID    m_rectLeft;
    jfieldID    m_rectTop;
    jmethodID   m_rectWidth;
    jmethodID   m_rectHeight;
    jfieldID    m_quadFP1;
    jfieldID    m_quadFP2;
    jfieldID    m_quadFP3;
    jfieldID    m_quadFP4;
    AutoJObject object(JNIEnv* env) {
        return getRealObject(env, m_obj);
    }
} m_javaGlue;

WebView(JNIEnv* env, jobject javaWebView, int viewImpl, WTF::String drawableDir,
        bool isHighEndGfx)
    : m_isHighEndGfx(isHighEndGfx)
{
    memset(m_extras, 0, DRAW_EXTRAS_SIZE * sizeof(DrawExtra*));
    jclass clazz = env->FindClass("android/webkit/WebViewClassic");
    m_javaGlue.m_obj = env->NewWeakGlobalRef(javaWebView);
    m_javaGlue.m_scrollBy = GetJMethod(env, clazz, "setContentScrollBy", "(IIZ)Z");
    m_javaGlue.m_getScaledMaxXScroll = GetJMethod(env, clazz, "getScaledMaxXScroll", "()I");
    m_javaGlue.m_getScaledMaxYScroll = GetJMethod(env, clazz, "getScaledMaxYScroll", "()I");
    m_javaGlue.m_updateRectsForGL = GetJMethod(env, clazz, "updateRectsForGL", "()V");
    m_javaGlue.m_viewInvalidate = GetJMethod(env, clazz, "viewInvalidate", "()V");
    m_javaGlue.m_viewInvalidateRect = GetJMethod(env, clazz, "viewInvalidate", "(IIII)V");
    m_javaGlue.m_postInvalidateDelayed = GetJMethod(env, clazz,
        "viewInvalidateDelayed", "(JIIII)V");
    m_javaGlue.m_pageSwapCallback = GetJMethod(env, clazz, "pageSwapCallback", "(Z)V");
    env->DeleteLocalRef(clazz);

    jclass rectClass = env->FindClass("android/graphics/Rect");
    ALOG_ASSERT(rectClass, "Could not find Rect class");
    m_javaGlue.m_rectLeft = env->GetFieldID(rectClass, "left", "I");
    m_javaGlue.m_rectTop = env->GetFieldID(rectClass, "top", "I");
    m_javaGlue.m_rectWidth = GetJMethod(env, rectClass, "width", "()I");
    m_javaGlue.m_rectHeight = GetJMethod(env, rectClass, "height", "()I");
    env->DeleteLocalRef(rectClass);

    jclass quadFClass = env->FindClass("android/webkit/QuadF");
    ALOG_ASSERT(quadFClass, "Could not find QuadF class");
    m_javaGlue.m_quadFP1 = env->GetFieldID(quadFClass, "p1", "Landroid/graphics/PointF;");
    m_javaGlue.m_quadFP2 = env->GetFieldID(quadFClass, "p2", "Landroid/graphics/PointF;");
    m_javaGlue.m_quadFP3 = env->GetFieldID(quadFClass, "p3", "Landroid/graphics/PointF;");
    m_javaGlue.m_quadFP4 = env->GetFieldID(quadFClass, "p4", "Landroid/graphics/PointF;");
    env->DeleteLocalRef(quadFClass);

    env->SetIntField(javaWebView, gWebViewField, (jint)this);
    m_viewImpl = (WebViewCore*) viewImpl;
    m_generation = 0;
    m_heightCanMeasure = false;
    m_lastDx = 0;
    m_lastDxTime = 0;
    m_baseLayer = 0;
    m_glDrawFunctor = 0;
    m_isDrawingPaused = false;
#if USE(ACCELERATED_COMPOSITING)
    m_glWebViewState = 0;
#endif
}

~WebView()
{
    if (m_javaGlue.m_obj)
    {
        JNIEnv* env = JSC::Bindings::getJNIEnv();
        env->DeleteWeakGlobalRef(m_javaGlue.m_obj);
        m_javaGlue.m_obj = 0;
    }
#if USE(ACCELERATED_COMPOSITING)
    // We must remove the m_glWebViewState prior to deleting m_baseLayer. If we
    // do not remove it here, we risk having BaseTiles trying to paint using a
    // deallocated base layer.
    stopGL();
#endif
    SkSafeUnref(m_baseLayer);
    delete m_glDrawFunctor;
    for (int i = 0; i < DRAW_EXTRAS_SIZE; i++)
        delete m_extras[i];
}

DrawExtra* getDrawExtra(DrawExtras extras)
{
    if (extras == DrawExtrasNone)
        return 0;
    return m_extras[extras - 1];
}

void stopGL()
{
#if USE(ACCELERATED_COMPOSITING)
    delete m_glWebViewState;
    m_glWebViewState = 0;
#endif
}

WebViewCore* getWebViewCore() const {
    return m_viewImpl;
}

void scrollRectOnScreen(const IntRect& rect)
{
    if (rect.isEmpty())
        return;
    int dx = 0;
    int left = rect.x();
    int right = rect.maxX();
    if (left < m_visibleContentRect.fLeft)
        dx = left - m_visibleContentRect.fLeft;
    // Only scroll right if the entire width can fit on screen.
    else if (right > m_visibleContentRect.fRight
            && right - left < m_visibleContentRect.width())
        dx = right - m_visibleContentRect.fRight;
    int dy = 0;
    int top = rect.y();
    int bottom = rect.maxY();
    if (top < m_visibleContentRect.fTop)
        dy = top - m_visibleContentRect.fTop;
    // Only scroll down if the entire height can fit on screen
    else if (bottom > m_visibleContentRect.fBottom
            && bottom - top < m_visibleContentRect.height())
        dy = bottom - m_visibleContentRect.fBottom;
    if ((dx|dy) == 0 || !scrollBy(dx, dy))
        return;
    viewInvalidate();
}

int drawGL(WebCore::IntRect& invScreenRect, WebCore::IntRect* invalRect,
        WebCore::IntRect& screenRect, int titleBarHeight,
        WebCore::IntRect& screenClip, float scale, int extras, bool shouldDraw)
{
#if USE(ACCELERATED_COMPOSITING)
    if (!m_baseLayer)
        return 0;

    if (m_viewImpl)
        m_viewImpl->setPrerenderingEnabled(!m_isDrawingPaused);

    if (!m_glWebViewState) {
        TilesManager::instance()->setHighEndGfx(m_isHighEndGfx);
        m_glWebViewState = new GLWebViewState();
        m_glWebViewState->setBaseLayer(m_baseLayer, false, true);
    }

    DrawExtra* extra = getDrawExtra((DrawExtras) extras);

    m_glWebViewState->glExtras()->setDrawExtra(extra);

    // Make sure we have valid coordinates. We might not have valid coords
    // if the zoom manager is still initializing. We will be redrawn
    // once the correct scale is set
    if (!m_visibleContentRect.isFinite())
        return 0;
    bool treesSwapped = false;
    bool newTreeHasAnim = false;
    int ret = m_glWebViewState->drawGL(invScreenRect, m_visibleContentRect, invalRect,
                                        screenRect, titleBarHeight, screenClip, scale,
                                        &treesSwapped, &newTreeHasAnim, shouldDraw);
    if (treesSwapped) {
        ALOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!");
        JNIEnv* env = JSC::Bindings::getJNIEnv();
        AutoJObject javaObject = m_javaGlue.object(env);
        if (javaObject.get()) {
            env->CallVoidMethod(javaObject.get(), m_javaGlue.m_pageSwapCallback, newTreeHasAnim);
            checkException(env);
        }
    }
    return m_isDrawingPaused ? 0 : ret;
#endif
    return 0;
}

void draw(SkCanvas* canvas, SkColor bgColor, DrawExtras extras)
{
    if (!m_baseLayer) {
        canvas->drawColor(bgColor);
        return;
    }

    // draw the content of the base layer first
    LayerContent* content = m_baseLayer->content();
    int sc = canvas->save(SkCanvas::kClip_SaveFlag);
    if (content) {
        canvas->clipRect(SkRect::MakeLTRB(0, 0, content->width(), content->height()),
                         SkRegion::kDifference_Op);
    }
    Color c = m_baseLayer->getBackgroundColor();
    canvas->drawColor(SkColorSetARGBInline(c.alpha(), c.red(), c.green(), c.blue()));
    canvas->restoreToCount(sc);

    // call this to be sure we've adjusted for any scrolling or animations
    // before we actually draw
    m_baseLayer->updatePositionsRecursive(m_visibleContentRect);
    m_baseLayer->updatePositions();

    // We have to set the canvas' matrix on the base layer
    // (to have fixed layers work as intended)
    SkAutoCanvasRestore restore(canvas, true);
    m_baseLayer->setMatrix(canvas->getTotalMatrix());
    canvas->resetMatrix();
    m_baseLayer->draw(canvas, getDrawExtra(extras));
}

int getScaledMaxXScroll()
{
    ALOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!");
    JNIEnv* env = JSC::Bindings::getJNIEnv();
    AutoJObject javaObject = m_javaGlue.object(env);
    if (!javaObject.get())
        return 0;
    int result = env->CallIntMethod(javaObject.get(), m_javaGlue.m_getScaledMaxXScroll);
    checkException(env);
    return result;
}

int getScaledMaxYScroll()
{
    ALOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!");
    JNIEnv* env = JSC::Bindings::getJNIEnv();
    AutoJObject javaObject = m_javaGlue.object(env);
    if (!javaObject.get())
        return 0;
    int result = env->CallIntMethod(javaObject.get(), m_javaGlue.m_getScaledMaxYScroll);
    checkException(env);
    return result;
}

// Call through JNI to ask Java side to update the rectangles for GL functor.
// This is called at every draw when it is not in process mode, so we should
// keep this route as efficient as possible. Currently, its average cost on Xoom
// is about 0.1ms - 0.2ms.
// Alternatively, this can be achieved by adding more listener on Java side, but
// that will be more likely causing jank when triggering GC.
void updateRectsForGL()
{
    JNIEnv* env = JSC::Bindings::getJNIEnv();
    AutoJObject javaObject = m_javaGlue.object(env);
    if (!javaObject.get())
        return;
    env->CallVoidMethod(javaObject.get(), m_javaGlue.m_updateRectsForGL);
    checkException(env);
}

#if USE(ACCELERATED_COMPOSITING)
static const ScrollableLayerAndroid* findScrollableLayer(
    const LayerAndroid* parent, int x, int y, SkIRect* foundBounds) {
    IntRect bounds = enclosingIntRect(parent->fullContentAreaMapped());

    // Check the parent bounds first; this will clip to within a masking layer's
    // bounds.
    if (parent->masksToBounds() && !bounds.contains(x, y))
        return 0;

    int count = parent->countChildren();
    while (count--) {
        const LayerAndroid* child = parent->getChild(count);
        const ScrollableLayerAndroid* result = findScrollableLayer(child, x, y, foundBounds);
        if (result) {
            if (parent->masksToBounds()) {
                if (bounds.width() < foundBounds->width())
                    foundBounds->fRight = foundBounds->fLeft + bounds.width();
                if (bounds.height() < foundBounds->height())
                    foundBounds->fBottom = foundBounds->fTop + bounds.height();
            }
            return result;
        }
    }
    if (parent->contentIsScrollable()) {
        foundBounds->set(bounds.x(), bounds.y(), bounds.width(), bounds.height());
        return static_cast<const ScrollableLayerAndroid*>(parent);
    }
    return 0;
}
#endif

int scrollableLayer(int x, int y, SkIRect* layerRect, SkIRect* bounds)
{
#if USE(ACCELERATED_COMPOSITING)
    if (!m_baseLayer)
        return 0;
    const ScrollableLayerAndroid* result = findScrollableLayer(m_baseLayer, x, y, bounds);
    if (result) {
        result->getScrollRect(layerRect);
        return result->uniqueId();
    }
#endif
    return 0;
}

void scrollLayer(int layerId, int x, int y)
{
    if (m_glWebViewState)
        m_glWebViewState->scrollLayer(layerId, x, y);
}

void setHeightCanMeasure(bool measure)
{
    m_heightCanMeasure = measure;
}

String getSelection()
{
    SelectText* select = static_cast<SelectText*>(
            getDrawExtra(WebView::DrawExtrasSelection));
    if (select)
        return select->getText();
    return String();
}

bool scrollBy(int dx, int dy)
{
    ALOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!");

    JNIEnv* env = JSC::Bindings::getJNIEnv();
    AutoJObject javaObject = m_javaGlue.object(env);
    if (!javaObject.get())
        return false;
    bool result = env->CallBooleanMethod(javaObject.get(), m_javaGlue.m_scrollBy, dx, dy, true);
    checkException(env);
    return result;
}

void setIsScrolling(bool isScrolling)
{
#if USE(ACCELERATED_COMPOSITING)
    if (m_glWebViewState)
        m_glWebViewState->setIsScrolling(isScrolling);
#endif
}

void viewInvalidate()
{
    JNIEnv* env = JSC::Bindings::getJNIEnv();
    AutoJObject javaObject = m_javaGlue.object(env);
    if (!javaObject.get())
        return;
    env->CallVoidMethod(javaObject.get(), m_javaGlue.m_viewInvalidate);
    checkException(env);
}

void viewInvalidateRect(int l, int t, int r, int b)
{
    JNIEnv* env = JSC::Bindings::getJNIEnv();
    AutoJObject javaObject = m_javaGlue.object(env);
    if (!javaObject.get())
        return;
    env->CallVoidMethod(javaObject.get(), m_javaGlue.m_viewInvalidateRect, l, r, t, b);
    checkException(env);
}

void postInvalidateDelayed(int64_t delay, const WebCore::IntRect& bounds)
{
    JNIEnv* env = JSC::Bindings::getJNIEnv();
    AutoJObject javaObject = m_javaGlue.object(env);
    if (!javaObject.get())
        return;
    env->CallVoidMethod(javaObject.get(), m_javaGlue.m_postInvalidateDelayed,
        delay, bounds.x(), bounds.y(), bounds.maxX(), bounds.maxY());
    checkException(env);
}

#if ENABLE(ANDROID_OVERFLOW_SCROLL)
static void copyScrollPosition(const LayerAndroid* fromRoot,
                               LayerAndroid* toRoot, int layerId)
{
    if (!fromRoot || !toRoot)
        return;
    const LayerAndroid* from = fromRoot->findById(layerId);
    LayerAndroid* to = toRoot->findById(layerId);
    if (!from || !to || !from->contentIsScrollable() || !to->contentIsScrollable())
        return;
    // TODO: Support this for iframes.
    if (to->isIFrameContent() || from->isIFrameContent())
        return;
    to->setScrollOffset(from->getScrollOffset());
}
#endif

BaseLayerAndroid* getBaseLayer() const { return m_baseLayer; }

bool setBaseLayer(BaseLayerAndroid* newBaseLayer, bool showVisualIndicator,
                  bool isPictureAfterFirstLayout, int scrollingLayer)
{
    bool queueFull = false;
#if USE(ACCELERATED_COMPOSITING)
    if (m_glWebViewState)
        queueFull = m_glWebViewState->setBaseLayer(newBaseLayer, showVisualIndicator,
                                                   isPictureAfterFirstLayout);
#endif

#if ENABLE(ANDROID_OVERFLOW_SCROLL)
    copyScrollPosition(m_baseLayer, newBaseLayer, scrollingLayer);
#endif
    SkSafeUnref(m_baseLayer);
    m_baseLayer = newBaseLayer;

    return queueFull;
}

void copyBaseContentToPicture(SkPicture* picture)
{
    if (!m_baseLayer || !m_baseLayer->content())
        return;
    LayerContent* content = m_baseLayer->content();
    SkCanvas* canvas = picture->beginRecording(content->width(), content->height(),
                                              SkPicture::kUsePathBoundsForClip_RecordingFlag);

    // clear the BaseLayerAndroid's previous matrix (set at each draw)
    SkMatrix baseMatrix;
    baseMatrix.reset();
    m_baseLayer->setMatrix(baseMatrix);

    m_baseLayer->draw(canvas, 0);

    picture->endRecording();
}

bool hasContent() {
    if (!m_baseLayer || !m_baseLayer->content())
        return false;
    return !m_baseLayer->content()->isEmpty();
}

void setFunctor(Functor* functor) {
    delete m_glDrawFunctor;
    m_glDrawFunctor = functor;
}

Functor* getFunctor() {
    return m_glDrawFunctor;
}

void setVisibleContentRect(SkRect& visibleContentRect) {
    m_visibleContentRect = visibleContentRect;
}

void setDrawExtra(DrawExtra *extra, DrawExtras type)
{
    if (type == DrawExtrasNone)
        return;
    DrawExtra* old = m_extras[type - 1];
    m_extras[type - 1] = extra;
    if (old != extra) {
        delete old;
    }
}

void setTextSelection(SelectText *selection) {
    setDrawExtra(selection, DrawExtrasSelection);
}

const TransformationMatrix* getLayerTransform(int layerId) {
    if (layerId != -1 && m_baseLayer) {
        LayerAndroid* layer = m_baseLayer->findById(layerId);
        // We need to make sure the drawTransform is up to date as this is
        // called before a draw() or drawGL()
        if (layer) {
            m_baseLayer->updatePositionsRecursive(m_visibleContentRect);
            return layer->drawTransform();
        }
    }
    return 0;
}

int getHandleLayerId(SelectText::HandleId handleId, SkIPoint& cursorPoint,
        FloatQuad& textBounds) {
    SelectText* selectText = static_cast<SelectText*>(getDrawExtra(DrawExtrasSelection));
    if (!selectText || !m_baseLayer)
        return -1;
    int layerId = selectText->caretLayerId(handleId);
    IntRect cursorRect = selectText->caretRect(handleId);
    IntRect textRect = selectText->textRect(handleId);
    // Rects exclude the last pixel on right/bottom. We want only included pixels.
    cursorPoint.set(cursorRect.x(), cursorRect.maxY() - 1);
    textRect.setHeight(std::max(1, textRect.height() - 1));
    textRect.setWidth(std::max(1, textRect.width() - 1));
    textBounds = FloatQuad(textRect);

    const TransformationMatrix* transform = getLayerTransform(layerId);
    if (transform) {
        // We're overloading the concept of Rect to be just the two
        // points (bottom-left and top-right.
        cursorPoint = transform->mapPoint(cursorPoint);
        textBounds = transform->mapQuad(textBounds);
    }
    return layerId;
}

void mapLayerRect(int layerId, SkIRect& rect) {
    const TransformationMatrix* transform = getLayerTransform(layerId);
    if (transform)
        rect = transform->mapRect(rect);
}

void floatQuadToQuadF(JNIEnv* env, const FloatQuad& nativeTextQuad,
        jobject textQuad)
{
    jobject p1 = env->GetObjectField(textQuad, m_javaGlue.m_quadFP1);
    jobject p2 = env->GetObjectField(textQuad, m_javaGlue.m_quadFP2);
    jobject p3 = env->GetObjectField(textQuad, m_javaGlue.m_quadFP3);
    jobject p4 = env->GetObjectField(textQuad, m_javaGlue.m_quadFP4);
    GraphicsJNI::point_to_jpointf(nativeTextQuad.p1(), env, p1);
    GraphicsJNI::point_to_jpointf(nativeTextQuad.p2(), env, p2);
    GraphicsJNI::point_to_jpointf(nativeTextQuad.p3(), env, p3);
    GraphicsJNI::point_to_jpointf(nativeTextQuad.p4(), env, p4);
    env->DeleteLocalRef(p1);
    env->DeleteLocalRef(p2);
    env->DeleteLocalRef(p3);
    env->DeleteLocalRef(p4);
}

// This is called when WebView switches rendering modes in a more permanent fashion
// such as when the layer type is set or the view is attached/detached from the window
int setHwAccelerated(bool hwAccelerated) {
    if (!m_glWebViewState)
        return 0;
    LayerAndroid* root = m_baseLayer;
    if (root)
        return root->setHwAccelerated(hwAccelerated);
    return 0;
}

void setDrawingPaused(bool isPaused)
{
    m_isDrawingPaused = isPaused;
    if (m_viewImpl)
        m_viewImpl->setPrerenderingEnabled(!isPaused);
}

// Finds the rectangles within world to the left, right, top, and bottom
// of rect and adds them to rects. If no intersection exists, false is returned.
static bool findMaskedRects(const FloatRect& world,
        const FloatRect& rect, Vector<FloatRect>& rects) {
    if (!world.intersects(rect))
        return false; // nothing to subtract

    // left rectangle
    if (rect.x() > world.x())
        rects.append(FloatRect(world.x(), world.y(),
                rect.x() - world.x(), world.height()));
    // top rectangle
    if (rect.y() > world.y())
        rects.append(FloatRect(world.x(), world.y(),
                world.width(), rect.y() - world.y()));
    // right rectangle
    if (rect.maxX() < world.maxX())
        rects.append(FloatRect(rect.maxX(), world.y(),
                world.maxX() - rect.maxX(), world.height()));
    // bottom rectangle
    if (rect.maxY() < world.maxY())
        rects.append(FloatRect(world.x(), rect.maxY(),
                world.width(), world.maxY() - rect.maxY()));
    return true;
}

// Returns false if layerId is a fixed position layer, otherwise
// all fixed position layer rectangles are subtracted from those within
// rects. Rects will be modified to contain rectangles that don't include
// the fixed position layer rectangles.
static bool findMaskedRectsForLayer(LayerAndroid* layer,
        Vector<FloatRect>& rects, int layerId)
{
    if (layer->isPositionFixed()) {
        if (layerId == layer->uniqueId())
            return false;
        FloatRect layerRect = layer->fullContentAreaMapped();
        for (int i = rects.size() - 1; i >= 0; i--)
            if (findMaskedRects(rects[i], layerRect, rects))
                rects.remove(i);
    }

    int childIndex = 0;
    while (LayerAndroid* child = layer->getChild(childIndex++))
        if (!findMaskedRectsForLayer(child, rects, layerId))
            return false;

    return true;
}

// Finds the largest rectangle not masked by any fixed layer.
void findMaxVisibleRect(int movingLayerId, SkIRect& visibleContentRect)
{
    if (!m_baseLayer)
        return;

    FloatRect visibleContentFloatRect(visibleContentRect);
    m_baseLayer->updatePositionsRecursive(visibleContentFloatRect);
    Vector<FloatRect> rects;
    rects.append(visibleContentFloatRect);
    if (findMaskedRectsForLayer(m_baseLayer, rects, movingLayerId)) {
        float maxSize = 0.0;
        const FloatRect* largest = 0;
        for (int i = 0; i < rects.size(); i++) {
            const FloatRect& rect = rects[i];
            float size = rect.width() * rect.height();
            if (size > maxSize) {
                maxSize = size;
                largest = &rect;
            }
        }
        if (largest) {
            SkRect largeRect = *largest;
            largeRect.round(&visibleContentRect);
        }
    }
}

private: // local state for WebView
    bool m_isDrawingPaused;
    // private to getFrameCache(); other functions operate in a different thread
    WebViewCore* m_viewImpl;
    int m_generation; // associate unique ID with sent kit focus to match with ui
    // Corresponds to the same-named boolean on the java side.
    bool m_heightCanMeasure;
    int m_lastDx;
    SkMSec m_lastDxTime;
    DrawExtra* m_extras[DRAW_EXTRAS_SIZE];
    BaseLayerAndroid* m_baseLayer;
    Functor* m_glDrawFunctor;
#if USE(ACCELERATED_COMPOSITING)
    GLWebViewState* m_glWebViewState;
#endif
    SkRect m_visibleContentRect;
    bool m_isHighEndGfx;
}; // end of WebView class


/**
 * This class holds a function pointer and parameters for calling drawGL into a specific
 * viewport. The pointer to the Functor will be put on a framework display list to be called
 * when the display list is replayed.
 */
class GLDrawFunctor : Functor {
    public:
    GLDrawFunctor(WebView* _wvInstance,
            int (WebView::*_funcPtr)(WebCore::IntRect&, WebCore::IntRect*,
                    WebCore::IntRect&, int, WebCore::IntRect&, jfloat, jint, bool),
            WebCore::IntRect _invScreenRect, float _scale, int _extras) {
        wvInstance = _wvInstance;
        funcPtr = _funcPtr;
        invScreenRect = _invScreenRect;
        scale = _scale;
        extras = _extras;
    };

    status_t operator()(int messageId, void* data) {
        TRACE_METHOD();
        bool shouldDraw = (messageId == uirenderer::DrawGlInfo::kModeDraw);
        if (shouldDraw)
            wvInstance->updateRectsForGL();

        if (invScreenRect.isEmpty()) {
            // NOOP operation if viewport is empty
            return 0;
        }

        WebCore::IntRect inval;
        int titlebarHeight = screenRect.height() - invScreenRect.height();

        uirenderer::DrawGlInfo* info = reinterpret_cast<uirenderer::DrawGlInfo*>(data);
        WebCore::IntRect screenClip(info->clipLeft, info->clipTop,
                                    info->clipRight - info->clipLeft,
                                    info->clipBottom - info->clipTop);

        WebCore::IntRect localInvScreenRect = invScreenRect;
        if (info->isLayer) {
            // When webview is on a layer, we need to use the viewport relative
            // to the FBO, rather than the screen(which will use invScreenRect).
            localInvScreenRect.setX(screenClip.x());
            localInvScreenRect.setY(info->height - screenClip.y() - screenClip.height());
        }
        // Send the necessary info to the shader.
        TilesManager::instance()->shader()->setGLDrawInfo(info);

        int returnFlags = (*wvInstance.*funcPtr)(localInvScreenRect, &inval, screenRect,
                titlebarHeight, screenClip, scale, extras, shouldDraw);
        if ((returnFlags & uirenderer::DrawGlInfo::kStatusDraw) != 0) {
            IntRect finalInval;
            if (inval.isEmpty())
                finalInval = screenRect;
            else {
                finalInval.setX(screenRect.x() + inval.x());
                finalInval.setY(screenRect.y() + titlebarHeight + inval.y());
                finalInval.setWidth(inval.width());
                finalInval.setHeight(inval.height());
            }
            info->dirtyLeft = finalInval.x();
            info->dirtyTop = finalInval.y();
            info->dirtyRight = finalInval.maxX();
            info->dirtyBottom = finalInval.maxY();
        }
        // return 1 if invalidation needed, 2 to request non-drawing functor callback, 0 otherwise
        ALOGV("returnFlags are %d, shouldDraw %d", returnFlags, shouldDraw);
        return returnFlags;
    }
    void updateScreenRect(WebCore::IntRect& _screenRect) {
        screenRect = _screenRect;
    }
    void updateInvScreenRect(WebCore::IntRect& _invScreenRect) {
        invScreenRect = _invScreenRect;
    }
    void updateScale(float _scale) {
        scale = _scale;
    }
    void updateExtras(jint _extras) {
        extras = _extras;
    }
    private:
    WebView* wvInstance;
    int (WebView::*funcPtr)(WebCore::IntRect&, WebCore::IntRect*,
            WebCore::IntRect&, int, WebCore::IntRect&, float, int, bool);
    WebCore::IntRect invScreenRect;
    WebCore::IntRect screenRect;
    jfloat scale;
    jint extras;
};

/*
 * Native JNI methods
 */

static void nativeCreate(JNIEnv *env, jobject obj, int viewImpl,
                         jstring drawableDir, jboolean isHighEndGfx)
{
    WTF::String dir = jstringToWtfString(env, drawableDir);
    new WebView(env, obj, viewImpl, dir, isHighEndGfx);
    // NEED THIS OR SOMETHING LIKE IT!
    //Release(obj);
}

static WebCore::IntRect jrect_to_webrect(JNIEnv* env, jobject obj)
{
    if (obj) {
        int L, T, R, B;
        GraphicsJNI::get_jrect(env, obj, &L, &T, &R, &B);
        return WebCore::IntRect(L, T, R - L, B - T);
    } else
        return WebCore::IntRect();
}

static SkRect jrectf_to_rect(JNIEnv* env, jobject obj)
{
    SkRect rect = SkRect::MakeEmpty();
    if (obj)
        GraphicsJNI::jrectf_to_rect(env, obj, &rect);
    return rect;
}

static void nativeDraw(JNIEnv *env, jobject obj, jobject canv,
        jobject visible, jint color,
        jint extras) {
    SkCanvas* canvas = GraphicsJNI::getNativeCanvas(env, canv);
    WebView* webView = GET_NATIVE_VIEW(env, obj);
    SkRect visibleContentRect = jrectf_to_rect(env, visible);
    webView->setVisibleContentRect(visibleContentRect);
    webView->draw(canvas, color, static_cast<WebView::DrawExtras>(extras));
}

static jint nativeCreateDrawGLFunction(JNIEnv *env, jobject obj, jint nativeView,
                                       jobject jinvscreenrect, jobject jscreenrect,
                                       jobject jvisiblecontentrect,
                                       jfloat scale, jint extras) {
    WebCore::IntRect invScreenRect = jrect_to_webrect(env, jinvscreenrect);
    WebView *wvInstance = reinterpret_cast<WebView*>(nativeView);
    SkRect visibleContentRect = jrectf_to_rect(env, jvisiblecontentrect);
    wvInstance->setVisibleContentRect(visibleContentRect);

    GLDrawFunctor* functor = (GLDrawFunctor*) wvInstance->getFunctor();
    if (!functor) {
        functor = new GLDrawFunctor(wvInstance, &android::WebView::drawGL,
                                    invScreenRect, scale, extras);
        wvInstance->setFunctor((Functor*) functor);
    } else {
        functor->updateInvScreenRect(invScreenRect);
        functor->updateScale(scale);
        functor->updateExtras(extras);
    }

    WebCore::IntRect rect = jrect_to_webrect(env, jscreenrect);
    functor->updateScreenRect(rect);

    return (jint)functor;
}

static jint nativeGetDrawGLFunction(JNIEnv *env, jobject obj, jint nativeView) {
    WebView *wvInstance = reinterpret_cast<WebView*>(nativeView);
    if (!wvInstance)
        return 0;

    return (jint) wvInstance->getFunctor();
}

static void nativeUpdateDrawGLFunction(JNIEnv *env, jobject obj, jint nativeView,
                                       jobject jinvscreenrect, jobject jscreenrect,
                                       jobject jvisiblecontentrect, jfloat scale) {
    WebView *wvInstance = reinterpret_cast<WebView*>(nativeView);
    if (wvInstance) {
        GLDrawFunctor* functor = (GLDrawFunctor*) wvInstance->getFunctor();
        if (functor) {
            WebCore::IntRect invScreenRect = jrect_to_webrect(env, jinvscreenrect);
            functor->updateInvScreenRect(invScreenRect);

            SkRect visibleContentRect = jrectf_to_rect(env, jvisiblecontentrect);
            wvInstance->setVisibleContentRect(visibleContentRect);

            WebCore::IntRect screenRect = jrect_to_webrect(env, jscreenrect);
            functor->updateScreenRect(screenRect);

            functor->updateScale(scale);
        }
    }
}

static bool nativeEvaluateLayersAnimations(JNIEnv *env, jobject obj, jint nativeView)
{
    // only call in software rendering, initialize and evaluate animations
#if USE(ACCELERATED_COMPOSITING)
    BaseLayerAndroid* baseLayer = reinterpret_cast<WebView*>(nativeView)->getBaseLayer();
    if (baseLayer) {
        baseLayer->initAnimations();
        return baseLayer->evaluateAnimations();
    }
#endif
    return false;
}

static bool nativeSetBaseLayer(JNIEnv *env, jobject obj, jint nativeView, jint layer,
                               jboolean showVisualIndicator,
                               jboolean isPictureAfterFirstLayout,
                               jint scrollingLayer)
{
    BaseLayerAndroid* layerImpl = reinterpret_cast<BaseLayerAndroid*>(layer);
    return reinterpret_cast<WebView*>(nativeView)->setBaseLayer(layerImpl, showVisualIndicator,
                                                                isPictureAfterFirstLayout,
                                                                scrollingLayer);
}

static BaseLayerAndroid* nativeGetBaseLayer(JNIEnv *env, jobject obj, jint nativeView)
{
    return reinterpret_cast<WebView*>(nativeView)->getBaseLayer();
}

static void nativeCopyBaseContentToPicture(JNIEnv *env, jobject obj, jobject pict)
{
    SkPicture* picture = GraphicsJNI::getNativePicture(env, pict);
    GET_NATIVE_VIEW(env, obj)->copyBaseContentToPicture(picture);
}

static bool nativeHasContent(JNIEnv *env, jobject obj)
{
    return GET_NATIVE_VIEW(env, obj)->hasContent();
}

static void nativeSetHeightCanMeasure(JNIEnv *env, jobject obj, bool measure)
{
    WebView* view = GET_NATIVE_VIEW(env, obj);
    ALOG_ASSERT(view, "view not set in nativeSetHeightCanMeasure");
    view->setHeightCanMeasure(measure);
}

static void nativeDestroy(JNIEnv *env, jobject obj, jint ptr)
{
    WebView* view = reinterpret_cast<WebView*>(ptr);
    ALOGD("nativeDestroy view: %p", view);
    ALOG_ASSERT(view, "view not set in nativeDestroy");
    delete view;
}

static void nativeStopGL(JNIEnv *env, jobject obj, jint ptr)
{
    if (ptr)
        reinterpret_cast<WebView*>(ptr)->stopGL();
}

static jobject nativeGetSelection(JNIEnv *env, jobject obj)
{
    WebView* view = GET_NATIVE_VIEW(env, obj);
    ALOG_ASSERT(view, "view not set in %s", __FUNCTION__);
    String selection = view->getSelection();
    return wtfStringToJstring(env, selection);
}

static void nativeDiscardAllTextures(JNIEnv *env, jobject obj)
{
    //discard all textures for debugging/test purposes, but not gl backing memory
    bool allTextures = true, deleteGLTextures = false;
    TilesManager::instance()->discardTextures(allTextures, deleteGLTextures);
}

static void nativeTileProfilingStart(JNIEnv *env, jobject obj)
{
    TilesManager::instance()->getProfiler()->start();
}

static float nativeTileProfilingStop(JNIEnv *env, jobject obj)
{
    return TilesManager::instance()->getProfiler()->stop();
}

static void nativeTileProfilingClear(JNIEnv *env, jobject obj)
{
    TilesManager::instance()->getProfiler()->clear();
}

static int nativeTileProfilingNumFrames(JNIEnv *env, jobject obj)
{
    return TilesManager::instance()->getProfiler()->numFrames();
}

static int nativeTileProfilingNumTilesInFrame(JNIEnv *env, jobject obj, int frame)
{
    return TilesManager::instance()->getProfiler()->numTilesInFrame(frame);
}

static int nativeTileProfilingGetInt(JNIEnv *env, jobject obj, int frame, int tile, jstring jkey)
{
    WTF::String key = jstringToWtfString(env, jkey);
    TileProfileRecord* record = TilesManager::instance()->getProfiler()->getTile(frame, tile);

    if (key == "left")
        return record->left;
    if (key == "top")
        return record->top;
    if (key == "right")
        return record->right;
    if (key == "bottom")
        return record->bottom;
    if (key == "level")
        return record->level;
    if (key == "isReady")
        return record->isReady ? 1 : 0;
    return -1;
}

static float nativeTileProfilingGetFloat(JNIEnv *env, jobject obj, int frame, int tile, jstring jkey)
{
    TileProfileRecord* record = TilesManager::instance()->getProfiler()->getTile(frame, tile);
    return record->scale;
}

#ifdef ANDROID_DUMP_DISPLAY_TREE
static void dumpToFile(const char text[], void* file) {
    fwrite(text, 1, strlen(text), reinterpret_cast<FILE*>(file));
    fwrite("\n", 1, 1, reinterpret_cast<FILE*>(file));
}
#endif

// Return true to view invalidate WebView
static bool nativeSetProperty(JNIEnv *env, jobject obj, jstring jkey, jstring jvalue)
{
    WTF::String key = jstringToWtfString(env, jkey);
    WTF::String value = jstringToWtfString(env, jvalue);
    if (key == "inverted") {
        bool shouldInvert = (value == "true");
        TilesManager::instance()->setInvertedScreen(shouldInvert);
        return true;
    }
    else if (key == "inverted_contrast") {
        float contrast = value.toFloat();
        TilesManager::instance()->setInvertedScreenContrast(contrast);
        return true;
    }
    else if (key == "enable_cpu_upload_path") {
        TilesManager::instance()->transferQueue()->setTextureUploadType(
            value == "true" ? CpuUpload : GpuUpload);
    }
    else if (key == "use_minimal_memory") {
        TilesManager::instance()->setUseMinimalMemory(value == "true");
    }
    else if (key == "use_double_buffering") {
        TilesManager::instance()->setUseDoubleBuffering(value == "true");
    }
    else if (key == "tree_updates") {
        TilesManager::instance()->clearContentUpdates();
    }
    return false;
}

static jstring nativeGetProperty(JNIEnv *env, jobject obj, jstring jkey)
{
    WTF::String key = jstringToWtfString(env, jkey);
    if (key == "tree_updates") {
        int updates = TilesManager::instance()->getContentUpdates();
        WTF::String wtfUpdates = WTF::String::number(updates);
        return wtfStringToJstring(env, wtfUpdates);
    }
    return 0;
}

static void nativeOnTrimMemory(JNIEnv *env, jobject obj, jint level)
{
    if (TilesManager::hardwareAccelerationEnabled()) {
        // When we got TRIM_MEMORY_MODERATE or TRIM_MEMORY_COMPLETE, we should
        // make sure the transfer queue is empty and then abandon the Surface
        // Texture to avoid ANR b/c framework may destroy the EGL context.
        // Refer to WindowManagerImpl.java for conditions we followed.
        TilesManager* tilesManager = TilesManager::instance();
        if ((level >= TRIM_MEMORY_MODERATE
            && !tilesManager->highEndGfx())
            || level >= TRIM_MEMORY_COMPLETE) {
            ALOGD("OnTrimMemory with EGL Context %p", eglGetCurrentContext());
            tilesManager->cleanupGLResources();
        }

        bool freeAllTextures = (level > TRIM_MEMORY_UI_HIDDEN), glTextures = true;
        tilesManager->discardTextures(freeAllTextures, glTextures);
    }
}

static void nativeDumpDisplayTree(JNIEnv* env, jobject jwebview, jstring jurl)
{
#ifdef ANDROID_DUMP_DISPLAY_TREE
    WebView* view = GET_NATIVE_VIEW(env, jwebview);
    ALOG_ASSERT(view, "view not set in %s", __FUNCTION__);

    if (view && view->getWebViewCore()) {
        FILE* file = fopen(DISPLAY_TREE_LOG_FILE, "w");
        if (file) {
            SkFormatDumper dumper(dumpToFile, file);
            // dump the URL
            if (jurl) {
                const char* str = env->GetStringUTFChars(jurl, 0);
                SkDebugf("Dumping %s to %s\n", str, DISPLAY_TREE_LOG_FILE);
                dumpToFile(str, file);
                env->ReleaseStringUTFChars(jurl, str);
            }
            // now dump the display tree
            SkDumpCanvas canvas(&dumper);
            // this will playback the picture into the canvas, which will
            // spew its contents to the dumper
            view->draw(&canvas, 0, WebView::DrawExtrasNone);
            // we're done with the file now
            fwrite("\n", 1, 1, file);
            fclose(file);
        }
#if USE(ACCELERATED_COMPOSITING)
        const LayerAndroid* baseLayer = view->getBaseLayer();
        if (baseLayer) {
          FILE* file = fopen(LAYERS_TREE_LOG_FILE,"w");
          if (file) {
              baseLayer->dumpLayers(file, 0);
              fclose(file);
          }
        }
#endif
    }
#endif
}

static int nativeScrollableLayer(JNIEnv* env, jobject jwebview, jint nativeView,
    jint x, jint y, jobject rect, jobject bounds)
{
    WebView* webview = reinterpret_cast<WebView*>(nativeView);
    ALOG_ASSERT(webview, "webview not set in %s", __FUNCTION__);
    SkIRect nativeRect, nativeBounds;
    int id = webview->scrollableLayer(x, y, &nativeRect, &nativeBounds);
    if (rect)
        GraphicsJNI::irect_to_jrect(nativeRect, env, rect);
    if (bounds)
        GraphicsJNI::irect_to_jrect(nativeBounds, env, bounds);
    return id;
}

static bool nativeScrollLayer(JNIEnv* env, jobject obj,
    jint nativeView, jint layerId, jint x, jint y)
{
#if ENABLE(ANDROID_OVERFLOW_SCROLL)
    WebView* webview = reinterpret_cast<WebView*>(nativeView);
    webview->scrollLayer(layerId, x, y);

    //TODO: the below only needed for the SW rendering path
    LayerAndroid* baseLayer = webview->getBaseLayer();
    if (!baseLayer)
        return false;
    LayerAndroid* layer = baseLayer->findById(layerId);
    if (!layer || !layer->contentIsScrollable())
        return false;
    return static_cast<ScrollableLayerAndroid*>(layer)->scrollTo(x, y);
#endif
    return false;
}

static void nativeSetIsScrolling(JNIEnv* env, jobject jwebview, jboolean isScrolling)
{
    // TODO: Pass in the native pointer instead
    WebView* view = GET_NATIVE_VIEW(env, jwebview);
    if (view)
        view->setIsScrolling(isScrolling);
}

static void nativeUseHardwareAccelSkia(JNIEnv*, jobject, jboolean enabled)
{
    BaseRenderer::setCurrentRendererType(enabled ? BaseRenderer::Ganesh : BaseRenderer::Raster);
}

static int nativeGetBackgroundColor(JNIEnv* env, jobject obj, jint nativeView)
{
    WebView* view = reinterpret_cast<WebView*>(nativeView);
    BaseLayerAndroid* baseLayer = view->getBaseLayer();
    if (baseLayer) {
        WebCore::Color color = baseLayer->getBackgroundColor();
        if (color.isValid())
            return SkColorSetARGB(color.alpha(), color.red(),
                                  color.green(), color.blue());
    }
    return SK_ColorWHITE;
}

static void nativeSetPauseDrawing(JNIEnv *env, jobject obj, jint nativeView,
                                      jboolean pause)
{
    reinterpret_cast<WebView*>(nativeView)->setDrawingPaused(pause);
}

static void nativeSetTextSelection(JNIEnv *env, jobject obj, jint nativeView,
                                   jint selectionPtr)
{
    SelectText* selection = reinterpret_cast<SelectText*>(selectionPtr);
    reinterpret_cast<WebView*>(nativeView)->setTextSelection(selection);
}

static jint nativeGetHandleLayerId(JNIEnv *env, jobject obj, jint nativeView,
                                     jint handleIndex, jobject cursorPoint,
                                     jobject textQuad)
{
    WebView* webview = reinterpret_cast<WebView*>(nativeView);
    SkIPoint nativePoint;
    FloatQuad nativeTextQuad;
    int layerId = webview->getHandleLayerId((SelectText::HandleId) handleIndex,
            nativePoint, nativeTextQuad);
    if (cursorPoint)
        GraphicsJNI::ipoint_to_jpoint(nativePoint, env, cursorPoint);
    if (textQuad)
        webview->floatQuadToQuadF(env, nativeTextQuad, textQuad);
    return layerId;
}

static void nativeMapLayerRect(JNIEnv *env, jobject obj, jint nativeView,
        jint layerId, jobject rect)
{
    WebView* webview = reinterpret_cast<WebView*>(nativeView);
    SkIRect nativeRect;
    GraphicsJNI::jrect_to_irect(env, rect, &nativeRect);
    webview->mapLayerRect(layerId, nativeRect);
    GraphicsJNI::irect_to_jrect(nativeRect, env, rect);
}

static jint nativeSetHwAccelerated(JNIEnv *env, jobject obj, jint nativeView,
        jboolean hwAccelerated)
{
    WebView* webview = reinterpret_cast<WebView*>(nativeView);
    return webview->setHwAccelerated(hwAccelerated);
}

static void nativeFindMaxVisibleRect(JNIEnv *env, jobject obj, jint nativeView,
        jint movingLayerId, jobject visibleContentRect)
{
    WebView* webview = reinterpret_cast<WebView*>(nativeView);
    SkIRect nativeRect;
    GraphicsJNI::jrect_to_irect(env, visibleContentRect, &nativeRect);
    webview->findMaxVisibleRect(movingLayerId, nativeRect);
    GraphicsJNI::irect_to_jrect(nativeRect, env, visibleContentRect);
}

/*
 * JNI registration
 */
static JNINativeMethod gJavaWebViewMethods[] = {
    { "nativeCreate", "(ILjava/lang/String;Z)V",
        (void*) nativeCreate },
    { "nativeDestroy", "(I)V",
        (void*) nativeDestroy },
    { "nativeDraw", "(Landroid/graphics/Canvas;Landroid/graphics/RectF;II)V",
        (void*) nativeDraw },
    { "nativeCreateDrawGLFunction", "(ILandroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/RectF;FI)I",
        (void*) nativeCreateDrawGLFunction },
    { "nativeGetDrawGLFunction", "(I)I",
        (void*) nativeGetDrawGLFunction },
    { "nativeUpdateDrawGLFunction", "(ILandroid/graphics/Rect;Landroid/graphics/Rect;Landroid/graphics/RectF;F)V",
        (void*) nativeUpdateDrawGLFunction },
    { "nativeDumpDisplayTree", "(Ljava/lang/String;)V",
        (void*) nativeDumpDisplayTree },
    { "nativeEvaluateLayersAnimations", "(I)Z",
        (void*) nativeEvaluateLayersAnimations },
    { "nativeGetSelection", "()Ljava/lang/String;",
        (void*) nativeGetSelection },
    { "nativeSetHeightCanMeasure", "(Z)V",
        (void*) nativeSetHeightCanMeasure },
    { "nativeSetBaseLayer", "(IIZZI)Z",
        (void*) nativeSetBaseLayer },
    { "nativeGetBaseLayer", "(I)I",
        (void*) nativeGetBaseLayer },
    { "nativeCopyBaseContentToPicture", "(Landroid/graphics/Picture;)V",
        (void*) nativeCopyBaseContentToPicture },
    { "nativeHasContent", "()Z",
        (void*) nativeHasContent },
    { "nativeDiscardAllTextures", "()V",
        (void*) nativeDiscardAllTextures },
    { "nativeTileProfilingStart", "()V",
        (void*) nativeTileProfilingStart },
    { "nativeTileProfilingStop", "()F",
        (void*) nativeTileProfilingStop },
    { "nativeTileProfilingClear", "()V",
        (void*) nativeTileProfilingClear },
    { "nativeTileProfilingNumFrames", "()I",
        (void*) nativeTileProfilingNumFrames },
    { "nativeTileProfilingNumTilesInFrame", "(I)I",
        (void*) nativeTileProfilingNumTilesInFrame },
    { "nativeTileProfilingGetInt", "(IILjava/lang/String;)I",
        (void*) nativeTileProfilingGetInt },
    { "nativeTileProfilingGetFloat", "(IILjava/lang/String;)F",
        (void*) nativeTileProfilingGetFloat },
    { "nativeStopGL", "(I)V",
        (void*) nativeStopGL },
    { "nativeScrollableLayer", "(IIILandroid/graphics/Rect;Landroid/graphics/Rect;)I",
        (void*) nativeScrollableLayer },
    { "nativeScrollLayer", "(IIII)Z",
        (void*) nativeScrollLayer },
    { "nativeSetIsScrolling", "(Z)V",
        (void*) nativeSetIsScrolling },
    { "nativeUseHardwareAccelSkia", "(Z)V",
        (void*) nativeUseHardwareAccelSkia },
    { "nativeGetBackgroundColor", "(I)I",
        (void*) nativeGetBackgroundColor },
    { "nativeSetProperty", "(Ljava/lang/String;Ljava/lang/String;)Z",
        (void*) nativeSetProperty },
    { "nativeGetProperty", "(Ljava/lang/String;)Ljava/lang/String;",
        (void*) nativeGetProperty },
    { "nativeOnTrimMemory", "(I)V",
        (void*) nativeOnTrimMemory },
    { "nativeSetPauseDrawing", "(IZ)V",
        (void*) nativeSetPauseDrawing },
    { "nativeSetTextSelection", "(II)V",
        (void*) nativeSetTextSelection },
    { "nativeGetHandleLayerId", "(IILandroid/graphics/Point;Landroid/webkit/QuadF;)I",
        (void*) nativeGetHandleLayerId },
    { "nativeMapLayerRect", "(IILandroid/graphics/Rect;)V",
        (void*) nativeMapLayerRect },
    { "nativeSetHwAccelerated", "(IZ)I",
        (void*) nativeSetHwAccelerated },
    { "nativeFindMaxVisibleRect", "(IILandroid/graphics/Rect;)V",
        (void*) nativeFindMaxVisibleRect },
};

int registerWebView(JNIEnv* env)
{
    jclass clazz = env->FindClass("android/webkit/WebViewClassic");
    ALOG_ASSERT(clazz, "Unable to find class android/webkit/WebViewClassic");
    gWebViewField = env->GetFieldID(clazz, "mNativeClass", "I");
    ALOG_ASSERT(gWebViewField, "Unable to find android/webkit/WebViewClassic.mNativeClass");
    env->DeleteLocalRef(clazz);

    return jniRegisterNativeMethods(env, "android/webkit/WebViewClassic", gJavaWebViewMethods, NELEM(gJavaWebViewMethods));
}

} // namespace android