blob: 81e96504171c869479d16ed80d21a8a90cc34065 [file] [log] [blame]
/*
* Copyright 2008, 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 APPLE COMPUTER, INC. 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.
*/
#include "config.h"
#include "android_graphics.h"
#include "Document.h"
#include "Element.h"
#include "Frame.h"
#include "PluginPackage.h"
#include "PluginView.h"
#include "PluginWidgetAndroid.h"
#include "ScrollView.h"
#include "SkANP.h"
#include "SkFlipPixelRef.h"
#include "WebViewCore.h"
PluginWidgetAndroid::PluginWidgetAndroid(WebCore::PluginView* view)
: m_pluginView(view) {
m_flipPixelRef = NULL;
m_core = NULL;
m_drawingModel = kBitmap_ANPDrawingModel;
m_eventFlags = 0;
m_pluginWindow = NULL;
m_requestedVisibleRectCount = 0;
m_requestedFrameRect.setEmpty();
m_visibleDocRect.setEmpty();
m_hasFocus = false;
m_zoomLevel = 0;
m_javaClassName = NULL;
m_childView = NULL;
}
PluginWidgetAndroid::~PluginWidgetAndroid() {
if (m_core) {
m_core->removePlugin(this);
if (m_childView) {
m_core->destroySurface(m_childView);
}
}
if (m_javaClassName) {
free(m_javaClassName);
}
m_flipPixelRef->safeUnref();
}
void PluginWidgetAndroid::init(android::WebViewCore* core) {
m_core = core;
m_core->addPlugin(this);
}
static SkBitmap::Config computeConfig(bool isTransparent) {
return isTransparent ? SkBitmap::kARGB_8888_Config
: SkBitmap::kRGB_565_Config;
}
/*
* Returns the name of the apk that contains this plugin. The caller is
* responsible for calling free(...) on the char* that is returned.
*/
static char* getPackageName(PluginPackage* pluginPackage) {
// get the directory where the plugin library is stored. The structure of
// the path looks like /data/app/plugin.package.name/lib.
const char* pluginDir = pluginPackage->parentDirectory().latin1().data();
// trim "/lib" off the directory name and store in tempString
int length = strlen(pluginDir) - 4; // -4 for "/lib"
char* tempString = (char*) malloc(length + 1); // +1 for null termination
strncpy(tempString, pluginDir, length);
tempString[length] = '\0';
// find the final '/' in tempString
char* result = strrchr(tempString, '/');
char* packageName = NULL;
if (result) {
// duplicate the tempString without the leading '/'
packageName = strdup(result+1);
}
// free extra memory and return the package name
free(tempString);
return packageName;
}
void PluginWidgetAndroid::setWindow(NPWindow* window, bool isTransparent) {
m_pluginWindow = window;
if (m_drawingModel == kSurface_ANPDrawingModel) {
if (!m_childView) {
IntPoint docPoint = frameToDocumentCoords(window->x, window->y);
char* packageName = getPackageName(m_pluginView->plugin());
m_childView = m_core->createSurface(packageName, m_javaClassName,
m_pluginView->instance(),
docPoint.x(), docPoint.y(),
window->width, window->height);
free(packageName);
}
} else {
m_flipPixelRef->safeUnref();
m_flipPixelRef = new SkFlipPixelRef(computeConfig(isTransparent),
window->width, window->height);
}
}
bool PluginWidgetAndroid::setPluginStubJavaClassName(const char* className) {
if (m_javaClassName) {
free(m_javaClassName);
}
// don't call strdup() if the className is to be set to NULL
if (!className) {
m_javaClassName = NULL;
return true;
}
// make a local copy of the className
m_javaClassName = strdup(className);
return (m_javaClassName != NULL);
}
bool PluginWidgetAndroid::setDrawingModel(ANPDrawingModel model) {
// disallow the surface drawing model if no java class name has been given
if (model == kSurface_ANPDrawingModel && m_javaClassName == NULL) {
return false;
}
m_drawingModel = model;
return true;
}
void PluginWidgetAndroid::localToDocumentCoords(SkIRect* rect) const {
if (m_pluginWindow) {
IntPoint pluginDocCoords = frameToDocumentCoords(m_pluginWindow->x,
m_pluginWindow->y);
rect->offset(pluginDocCoords.x(), pluginDocCoords.y());
}
}
bool PluginWidgetAndroid::isDirty(SkIRect* rect) const {
// nothing to report if we haven't had setWindow() called yet
if (NULL == m_flipPixelRef) {
return false;
}
const SkRegion& dirty = m_flipPixelRef->dirtyRgn();
if (dirty.isEmpty()) {
return false;
} else {
if (rect) {
*rect = dirty.getBounds();
}
return true;
}
}
void PluginWidgetAndroid::inval(const WebCore::IntRect& rect,
bool signalRedraw) {
// nothing to do if we haven't had setWindow() called yet. m_flipPixelRef
// will also be null if this is a Surface model.
if (NULL == m_flipPixelRef) {
return;
}
m_flipPixelRef->inval(rect);
if (signalRedraw && m_flipPixelRef->isDirty()) {
m_core->invalPlugin(this);
}
}
void PluginWidgetAndroid::draw(SkCanvas* canvas) {
if (NULL == m_flipPixelRef || !m_flipPixelRef->isDirty()) {
return;
}
SkAutoFlipUpdate update(m_flipPixelRef);
const SkBitmap& bitmap = update.bitmap();
const SkRegion& dirty = update.dirty();
ANPEvent event;
SkANP::InitEvent(&event, kDraw_ANPEventType);
event.data.draw.model = m_drawingModel;
SkANP::SetRect(&event.data.draw.clip, dirty.getBounds());
switch (m_drawingModel) {
case kBitmap_ANPDrawingModel: {
WebCore::PluginPackage* pkg = m_pluginView->plugin();
NPP instance = m_pluginView->instance();
if (SkANP::SetBitmap(&event.data.draw.data.bitmap,
bitmap) &&
pkg->pluginFuncs()->event(instance, &event)) {
if (canvas && m_pluginWindow) {
SkBitmap bm(bitmap);
bm.setPixelRef(m_flipPixelRef);
canvas->drawBitmap(bm, SkIntToScalar(m_pluginWindow->x),
SkIntToScalar(m_pluginWindow->y), NULL);
}
}
break;
}
default:
break;
}
}
bool PluginWidgetAndroid::sendEvent(const ANPEvent& evt) {
WebCore::PluginPackage* pkg = m_pluginView->plugin();
NPP instance = m_pluginView->instance();
// "missing" plugins won't have these
if (pkg && instance) {
// keep track of whether or not the plugin currently has focus
if (evt.eventType == kLifecycle_ANPEventType) {
if (evt.data.lifecycle.action == kLoseFocus_ANPLifecycleAction)
m_hasFocus = false;
else if (evt.data.lifecycle.action == kGainFocus_ANPLifecycleAction)
m_hasFocus = true;
}
// make a localCopy since the actual plugin may not respect its constness,
// and so we don't want our caller to have its param modified
ANPEvent localCopy = evt;
return pkg->pluginFuncs()->event(instance, &localCopy);
}
return false;
}
void PluginWidgetAndroid::updateEventFlags(ANPEventFlags flags) {
// if there are no differences then immediately return
if (m_eventFlags == flags) {
return;
}
Document* doc = m_pluginView->getParentFrame()->document();
if((m_eventFlags ^ flags) & kTouch_ANPEventFlag) {
if(flags & kTouch_ANPEventFlag)
doc->addTouchEventListener(m_pluginView->getElement());
else
doc->removeTouchEventListener(m_pluginView->getElement());
}
m_eventFlags = flags;
}
bool PluginWidgetAndroid::isAcceptingEvent(ANPEventFlag flag) {
return m_eventFlags & flag;
}
void PluginWidgetAndroid::setVisibleScreen(const ANPRectI& visibleDocRect, float zoom) {
// TODO update the bitmap size based on the zoom? (for kBitmap_ANPDrawingModel)
int oldScreenW = m_visibleDocRect.width();
int oldScreenH = m_visibleDocRect.height();
m_visibleDocRect.set(visibleDocRect.left, visibleDocRect.top,
visibleDocRect.right, visibleDocRect.bottom);
int newScreenW = m_visibleDocRect.width();
int newScreenH = m_visibleDocRect.height();
if (oldScreenW != newScreenW || oldScreenH != newScreenH)
computeVisibleFrameRect();
}
void PluginWidgetAndroid::setVisibleRects(const ANPRectI rects[], int32_t count) {
// ensure the count does not exceed our allocated space
if (count > MAX_REQUESTED_RECTS)
count = MAX_REQUESTED_RECTS;
// store the values in member variables
m_requestedVisibleRectCount = count;
memcpy(m_requestedVisibleRect, rects, count * sizeof(rects[0]));
computeVisibleFrameRect();
}
void PluginWidgetAndroid::computeVisibleFrameRect() {
// ensure the visibleDocRect has been set (i.e. not equal to zero)
if (m_visibleDocRect.isEmpty() || !m_pluginWindow)
return;
// create a rect that represents the plugin's bounds
SkIRect pluginBounds;
pluginBounds.set(m_pluginWindow->x, m_pluginWindow->y,
m_pluginWindow->x + m_pluginWindow->width,
m_pluginWindow->y + m_pluginWindow->height);
// create a rect that will contain as many of the rects that will fit on screen
SkIRect visibleRect;
visibleRect.setEmpty();
for (int counter = 0; counter < m_requestedVisibleRectCount; counter++) {
ANPRectI* rect = &m_requestedVisibleRect[counter];
// create skia rect for easier manipulation and convert it to frame coordinates
SkIRect pluginRect;
pluginRect.set(rect->left, rect->top, rect->right, rect->bottom);
pluginRect.offset(m_pluginWindow->x, m_pluginWindow->y);
// ensure the rect falls within the plugin's bounds
if (!pluginBounds.contains(pluginRect))
continue;
// combine this new rect with the higher priority rects
pluginRect.join(visibleRect);
// check to see if the new rect fits within the screen bounds. If this
// is the highest priority rect then attempt to center even if it doesn't
// fit on the screen.
if (counter > 0 && (m_visibleDocRect.width() < pluginRect.width() ||
m_visibleDocRect.height() < pluginRect.height()))
break;
// set the new visible rect
visibleRect = pluginRect;
}
m_requestedFrameRect = visibleRect;
scrollToVisibleFrameRect();
}
void PluginWidgetAndroid::scrollToVisibleFrameRect() {
if (!m_hasFocus || m_requestedFrameRect.isEmpty() || m_visibleDocRect.isEmpty())
return;
// if the entire rect is already visible then we don't need to scroll, which
// requires converting the m_requestedFrameRect from frame to doc coordinates
IntPoint pluginDocPoint = frameToDocumentCoords(m_requestedFrameRect.fLeft,
m_requestedFrameRect.fTop);
SkIRect requestedDocRect;
requestedDocRect.set(pluginDocPoint.x(), pluginDocPoint.y(),
pluginDocPoint.x() + m_requestedFrameRect.width(),
pluginDocPoint.y() + m_requestedFrameRect.height());
if (m_visibleDocRect.contains(requestedDocRect))
return;
// find the center of the visibleRect in document coordinates
int rectCenterX = requestedDocRect.fLeft + requestedDocRect.width()/2;
int rectCenterY = requestedDocRect.fTop + requestedDocRect.height()/2;
// find document coordinates for center of the visible screen
int screenCenterX = m_visibleDocRect.fLeft + m_visibleDocRect.width()/2;
int screenCenterY = m_visibleDocRect.fTop + m_visibleDocRect.height()/2;
//compute the delta of the two points
int deltaX = rectCenterX - screenCenterX;
int deltaY = rectCenterY - screenCenterY;
ScrollView* scrollView = m_pluginView->parent();
android::WebViewCore* core = android::WebViewCore::getWebViewCore(scrollView);
core->scrollBy(deltaX, deltaY, true);
}
IntPoint PluginWidgetAndroid::frameToDocumentCoords(int frameX, int frameY) const {
IntPoint docPoint = IntPoint(frameX, frameY);
const ScrollView* currentScrollView = m_pluginView->parent();
if (currentScrollView) {
const ScrollView* parentScrollView = currentScrollView->parent();
while (parentScrollView) {
docPoint.move(currentScrollView->x(), currentScrollView->y());
currentScrollView = parentScrollView;
parentScrollView = parentScrollView->parent();
}
}
return docPoint;
}