Home | History | Annotate | Download | only in background
      1 /*
      2  * Copyright 2008, The Android Open Source Project
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  *  * Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  *  * Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
     17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #include "BackgroundPlugin.h"
     27 #include "android_npapi.h"
     28 
     29 #include <stdio.h>
     30 #include <sys/time.h>
     31 #include <time.h>
     32 #include <math.h>
     33 #include <string.h>
     34 
     35 extern NPNetscapeFuncs*        browser;
     36 extern ANPBitmapInterfaceV0    gBitmapI;
     37 extern ANPCanvasInterfaceV0    gCanvasI;
     38 extern ANPLogInterfaceV0       gLogI;
     39 extern ANPPaintInterfaceV0     gPaintI;
     40 extern ANPSurfaceInterfaceV0   gSurfaceI;
     41 extern ANPSystemInterfaceV0    gSystemI;
     42 extern ANPTypefaceInterfaceV0  gTypefaceI;
     43 extern ANPWindowInterfaceV0    gWindowI;
     44 
     45 #define ARRAY_COUNT(array)      (sizeof(array) / sizeof(array[0]))
     46 
     47 static uint32_t getMSecs() {
     48     struct timeval tv;
     49     gettimeofday(&tv, NULL);
     50     return (uint32_t) (tv.tv_sec * 1000 + tv.tv_usec / 1000 ); // microseconds to milliseconds
     51 }
     52 
     53 ///////////////////////////////////////////////////////////////////////////////
     54 
     55 BackgroundPlugin::BackgroundPlugin(NPP inst) : SurfaceSubPlugin(inst) {
     56 
     57     // initialize the drawing surface
     58     m_surface = NULL;
     59 
     60     //initialize bitmap transparency variables
     61     mFinishedStageOne   = false;
     62     mFinishedStageTwo   = false;
     63     mFinishedStageThree = false;
     64 
     65     // test basic plugin functionality
     66     test_logging(); // android logging
     67     test_timers();  // plugin timers
     68     test_bitmaps(); // android bitmaps
     69     test_domAccess();
     70     test_javascript();
     71     test_loadJavaClass();
     72 
     73     //register for touch events
     74     ANPEventFlags flags = kTouch_ANPEventFlag;
     75     NPError err = browser->setvalue(inst, kAcceptEvents_ANPSetValue, &flags);
     76     if (err != NPERR_NO_ERROR) {
     77         gLogI.log(kError_ANPLogType, "Error selecting input events.");
     78     }
     79 }
     80 
     81 BackgroundPlugin::~BackgroundPlugin() {
     82     setContext(NULL);
     83     destroySurface();
     84 }
     85 
     86 jobject BackgroundPlugin::getSurface() {
     87 
     88     if (m_surface) {
     89         return m_surface;
     90     }
     91 
     92     // load the appropriate java class and instantiate it
     93     JNIEnv* env = NULL;
     94     if (gVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
     95         gLogI.log(kError_ANPLogType, " ---- getSurface: failed to get env");
     96         return NULL;
     97     }
     98 
     99     const char* className = "com.android.sampleplugin.BackgroundSurface";
    100     jclass backgroundClass = gSystemI.loadJavaClass(inst(), className);
    101 
    102     if(!backgroundClass) {
    103         gLogI.log(kError_ANPLogType, " ---- getSurface: failed to load class");
    104         return NULL;
    105     }
    106 
    107     jmethodID constructor = env->GetMethodID(backgroundClass, "<init>", "(Landroid/content/Context;)V");
    108     jobject backgroundSurface = env->NewObject(backgroundClass, constructor, m_context);
    109 
    110     if(!backgroundSurface) {
    111         gLogI.log(kError_ANPLogType, " ---- getSurface: failed to construct object");
    112         return NULL;
    113     }
    114 
    115     m_surface = env->NewGlobalRef(backgroundSurface);
    116     return m_surface;
    117 }
    118 
    119 void BackgroundPlugin::destroySurface() {
    120     JNIEnv* env = NULL;
    121     if (m_surface && gVM->GetEnv((void**) &env, JNI_VERSION_1_4) == JNI_OK) {
    122         env->DeleteGlobalRef(m_surface);
    123         m_surface = NULL;
    124     }
    125 }
    126 
    127 void BackgroundPlugin::drawPlugin(int surfaceWidth, int surfaceHeight) {
    128 
    129     // get the plugin's dimensions according to the DOM
    130     PluginObject *obj = (PluginObject*) inst()->pdata;
    131     const int W = obj->window->width;
    132     const int H = obj->window->height;
    133 
    134     // compute the current zoom level
    135     const float zoomFactorW = static_cast<float>(surfaceWidth) / W;
    136     const float zoomFactorH = static_cast<float>(surfaceHeight) / H;
    137 
    138     // check to make sure the zoom level is uniform
    139     if (zoomFactorW + .01 < zoomFactorH && zoomFactorW - .01 > zoomFactorH)
    140         gLogI.log(kError_ANPLogType, " ------ %p zoom is out of sync (%f,%f)",
    141                   inst(), zoomFactorW, zoomFactorH);
    142 
    143     // scale the variables based on the zoom level
    144     const int fontSize = (int)(zoomFactorW * 16);
    145     const int leftMargin = (int)(zoomFactorW * 10);
    146 
    147     // lock the surface
    148     ANPBitmap bitmap;
    149     JNIEnv* env = NULL;
    150     if (!m_surface || gVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK ||
    151         !gSurfaceI.lock(env, m_surface, &bitmap, NULL)) {
    152         gLogI.log(kError_ANPLogType, " ------ %p unable to lock the plugin", inst());
    153         return;
    154     }
    155 
    156     // create a canvas
    157     ANPCanvas* canvas = gCanvasI.newCanvas(&bitmap);
    158     gCanvasI.drawColor(canvas, 0xFFFFFFFF);
    159 
    160     ANPPaint* paint = gPaintI.newPaint();
    161     gPaintI.setFlags(paint, gPaintI.getFlags(paint) | kAntiAlias_ANPPaintFlag);
    162     gPaintI.setColor(paint, 0xFFFF0000);
    163     gPaintI.setTextSize(paint, fontSize);
    164 
    165     ANPTypeface* tf = gTypefaceI.createFromName("serif", kItalic_ANPTypefaceStyle);
    166     gPaintI.setTypeface(paint, tf);
    167     gTypefaceI.unref(tf);
    168 
    169     ANPFontMetrics fm;
    170     gPaintI.getFontMetrics(paint, &fm);
    171 
    172     gPaintI.setColor(paint, 0xFF0000FF);
    173     const char c[] = "This is a background plugin.";
    174     gCanvasI.drawText(canvas, c, sizeof(c)-1, leftMargin, -fm.fTop, paint);
    175 
    176     // clean up variables and unlock the surface
    177     gPaintI.deletePaint(paint);
    178     gCanvasI.deleteCanvas(canvas);
    179     gSurfaceI.unlock(env, m_surface);
    180 }
    181 
    182 int16_t BackgroundPlugin::handleEvent(const ANPEvent* evt) {
    183     switch (evt->eventType) {
    184         case kDraw_ANPEventType:
    185             gLogI.log(kError_ANPLogType, " ------ %p the plugin did not request draw events", inst());
    186             break;
    187         case kLifecycle_ANPEventType:
    188             switch (evt->data.lifecycle.action)  {
    189                 case kOnLoad_ANPLifecycleAction:
    190                     gLogI.log(kDebug_ANPLogType, " ------ %p onLoad", inst());
    191                     return 1;
    192                 case kOnScreen_ANPLifecycleAction:
    193                     gLogI.log(kDebug_ANPLogType, " ------ %p onScreen", inst());
    194                     return 1;
    195                 case kOffScreen_ANPLifecycleAction:
    196                     gLogI.log(kDebug_ANPLogType, " ------ %p offScreen", inst());
    197                     return 1;
    198             }
    199             break; // end kLifecycle_ANPEventType
    200         case kTouch_ANPEventType:
    201             if (kLongPress_ANPTouchAction == evt->data.touch.action) {
    202                 browser->geturl(inst(), "javascript:alert('Detected long press event.')", 0);
    203                 gWindowI.requestFullScreen(inst());
    204             }
    205             else if (kDoubleTap_ANPTouchAction == evt->data.touch.action)
    206                 browser->geturl(inst(), "javascript:alert('Detected double tap event.')", 0);
    207             break;
    208         case kKey_ANPEventType:
    209             gLogI.log(kError_ANPLogType, " ------ %p the plugin did not request key events", inst());
    210             break;
    211         default:
    212             break;
    213     }
    214     return 0;   // unknown or unhandled event
    215 }
    216 
    217 ///////////////////////////////////////////////////////////////////////////////
    218 // LOGGING TESTS
    219 ///////////////////////////////////////////////////////////////////////////////
    220 
    221 
    222 void BackgroundPlugin::test_logging() {
    223     NPP instance = this->inst();
    224 
    225     //LOG_ERROR(instance, " ------ %p Testing Log Error", instance);
    226     gLogI.log(kError_ANPLogType, " ------ %p Testing Log Error", instance);
    227     gLogI.log(kWarning_ANPLogType, " ------ %p Testing Log Warning", instance);
    228     gLogI.log(kDebug_ANPLogType, " ------ %p Testing Log Debug", instance);
    229 }
    230 
    231 ///////////////////////////////////////////////////////////////////////////////
    232 // TIMER TESTS
    233 ///////////////////////////////////////////////////////////////////////////////
    234 
    235 #define TIMER_INTERVAL     50
    236 static void timer_oneshot(NPP instance, uint32_t timerID);
    237 static void timer_repeat(NPP instance, uint32_t timerID);
    238 static void timer_neverfires(NPP instance, uint32_t timerID);
    239 static void timer_latency(NPP instance, uint32_t timerID);
    240 
    241 void BackgroundPlugin::test_timers() {
    242     NPP instance = this->inst();
    243 
    244     //Setup the testing counters
    245     mTimerRepeatCount = 5;
    246     mTimerLatencyCount = 5;
    247 
    248     // test for bogus timerID
    249     browser->unscheduletimer(instance, 999999);
    250     // test one-shot
    251     browser->scheduletimer(instance, 100, false, timer_oneshot);
    252     // test repeat
    253     browser->scheduletimer(instance, 50, true, timer_repeat);
    254     // test timer latency
    255     browser->scheduletimer(instance, TIMER_INTERVAL, true, timer_latency);
    256     mStartTime = mPrevTime = getMSecs();
    257     // test unschedule immediately
    258     uint32_t id = browser->scheduletimer(instance, 100, false, timer_neverfires);
    259     browser->unscheduletimer(instance, id);
    260     // test double unschedule (should be no-op)
    261     browser->unscheduletimer(instance, id);
    262 
    263 }
    264 
    265 static void timer_oneshot(NPP instance, uint32_t timerID) {
    266     gLogI.log(kDebug_ANPLogType, "-------- oneshot timer\n");
    267 }
    268 
    269 static void timer_repeat(NPP instance, uint32_t timerID) {
    270     BackgroundPlugin *obj = ((BackgroundPlugin*) ((PluginObject*) instance->pdata)->activePlugin);
    271 
    272     gLogI.log(kDebug_ANPLogType, "-------- repeat timer %d\n",
    273               obj->mTimerRepeatCount);
    274     if (--obj->mTimerRepeatCount == 0) {
    275         browser->unscheduletimer(instance, timerID);
    276     }
    277 }
    278 
    279 static void timer_neverfires(NPP instance, uint32_t timerID) {
    280     gLogI.log(kError_ANPLogType, "-------- timer_neverfires!!!\n");
    281 }
    282 
    283 static void timer_latency(NPP instance, uint32_t timerID) {
    284     BackgroundPlugin *obj = ((BackgroundPlugin*) ((PluginObject*) instance->pdata)->activePlugin);
    285 
    286     obj->mTimerLatencyCurrentCount += 1;
    287 
    288     uint32_t now = getMSecs();
    289     uint32_t interval = now - obj->mPrevTime;
    290     uint32_t dur = now - obj->mStartTime;
    291     uint32_t expectedDur = obj->mTimerLatencyCurrentCount * TIMER_INTERVAL;
    292     int32_t drift = dur - expectedDur;
    293     int32_t avgDrift = drift / obj->mTimerLatencyCurrentCount;
    294 
    295     obj->mPrevTime = now;
    296 
    297     gLogI.log(kDebug_ANPLogType,
    298               "-------- latency test: [%3d] interval %d expected %d, total %d expected %d, drift %d avg %d\n",
    299               obj->mTimerLatencyCurrentCount, interval, TIMER_INTERVAL, dur,
    300               expectedDur, drift, avgDrift);
    301 
    302     if (--obj->mTimerLatencyCount == 0) {
    303         browser->unscheduletimer(instance, timerID);
    304     }
    305 }
    306 
    307 ///////////////////////////////////////////////////////////////////////////////
    308 // BITMAP TESTS
    309 ///////////////////////////////////////////////////////////////////////////////
    310 
    311 static void test_formats(NPP instance);
    312 
    313 void BackgroundPlugin::test_bitmaps() {
    314     test_formats(this->inst());
    315 }
    316 
    317 static void test_formats(NPP instance) {
    318 
    319     // TODO pull names from enum in npapi instead of hardcoding them
    320     static const struct {
    321         ANPBitmapFormat fFormat;
    322         const char*     fName;
    323     } gRecs[] = {
    324         { kUnknown_ANPBitmapFormat,   "unknown" },
    325         { kRGBA_8888_ANPBitmapFormat, "8888" },
    326         { kRGB_565_ANPBitmapFormat,   "565" },
    327     };
    328 
    329     ANPPixelPacking packing;
    330     for (size_t i = 0; i < ARRAY_COUNT(gRecs); i++) {
    331         if (gBitmapI.getPixelPacking(gRecs[i].fFormat, &packing)) {
    332             gLogI.log(kDebug_ANPLogType,
    333                       "pixel format [%d] %s has packing ARGB [%d %d] [%d %d] [%d %d] [%d %d]\n",
    334                       gRecs[i].fFormat, gRecs[i].fName,
    335                       packing.AShift, packing.ABits,
    336                       packing.RShift, packing.RBits,
    337                       packing.GShift, packing.GBits,
    338                       packing.BShift, packing.BBits);
    339         } else {
    340             gLogI.log(kDebug_ANPLogType,
    341                       "pixel format [%d] %s has no packing\n",
    342                       gRecs[i].fFormat, gRecs[i].fName);
    343         }
    344     }
    345 }
    346 
    347 void BackgroundPlugin::test_bitmap_transparency(const ANPEvent* evt) {
    348     NPP instance = this->inst();
    349 
    350     // check default & set transparent
    351     if (!mFinishedStageOne) {
    352 
    353         gLogI.log(kDebug_ANPLogType, "BEGIN: testing bitmap transparency");
    354 
    355         //check to make sure it is not transparent
    356         if (evt->data.draw.data.bitmap.format == kRGBA_8888_ANPBitmapFormat) {
    357             gLogI.log(kError_ANPLogType, "bitmap default format is transparent");
    358         }
    359 
    360         //make it transparent (any non-null value will set it to true)
    361         bool value = true;
    362         NPError err = browser->setvalue(instance, NPPVpluginTransparentBool, &value);
    363         if (err != NPERR_NO_ERROR) {
    364             gLogI.log(kError_ANPLogType, "Error setting transparency.");
    365         }
    366 
    367         mFinishedStageOne = true;
    368         browser->invalidaterect(instance, NULL);
    369     }
    370     // check transparent & set opaque
    371     else if (!mFinishedStageTwo) {
    372 
    373         //check to make sure it is transparent
    374         if (evt->data.draw.data.bitmap.format != kRGBA_8888_ANPBitmapFormat) {
    375             gLogI.log(kError_ANPLogType, "bitmap did not change to transparent format");
    376         }
    377 
    378         //make it opaque
    379         NPError err = browser->setvalue(instance, NPPVpluginTransparentBool, NULL);
    380         if (err != NPERR_NO_ERROR) {
    381             gLogI.log(kError_ANPLogType, "Error setting transparency.");
    382         }
    383 
    384         mFinishedStageTwo = true;
    385     }
    386     // check opaque
    387     else if (!mFinishedStageThree) {
    388 
    389         //check to make sure it is not transparent
    390         if (evt->data.draw.data.bitmap.format == kRGBA_8888_ANPBitmapFormat) {
    391             gLogI.log(kError_ANPLogType, "bitmap default format is transparent");
    392         }
    393 
    394         gLogI.log(kDebug_ANPLogType, "END: testing bitmap transparency");
    395 
    396         mFinishedStageThree = true;
    397     }
    398 }
    399 
    400 ///////////////////////////////////////////////////////////////////////////////
    401 // DOM TESTS
    402 ///////////////////////////////////////////////////////////////////////////////
    403 
    404 void BackgroundPlugin::test_domAccess() {
    405     NPP instance = this->inst();
    406 
    407     gLogI.log(kDebug_ANPLogType, " ------ %p Testing DOM Access", instance);
    408 
    409     // Get the plugin's DOM object
    410     NPObject* windowObject = NULL;
    411     browser->getvalue(instance, NPNVWindowNPObject, &windowObject);
    412 
    413     if (!windowObject)
    414         gLogI.log(kError_ANPLogType, " ------ %p Unable to retrieve DOM Window", instance);
    415 
    416     // Retrieve a property from the plugin's DOM object
    417     NPIdentifier topIdentifier = browser->getstringidentifier("top");
    418     NPVariant topObjectVariant;
    419     browser->getproperty(instance, windowObject, topIdentifier, &topObjectVariant);
    420 
    421     if (topObjectVariant.type != NPVariantType_Object)
    422         gLogI.log(kError_ANPLogType, " ------ %p Invalid Variant type for DOM Property: %d,%d", instance, topObjectVariant.type, NPVariantType_Object);
    423 }
    424 
    425 
    426 ///////////////////////////////////////////////////////////////////////////////
    427 // JAVASCRIPT TESTS
    428 ///////////////////////////////////////////////////////////////////////////////
    429 
    430 
    431 void BackgroundPlugin::test_javascript() {
    432     NPP instance = this->inst();
    433 
    434     gLogI.log(kDebug_ANPLogType, " ------ %p Testing JavaScript Access", instance);
    435 
    436     // Get the plugin's DOM object
    437     NPObject* windowObject = NULL;
    438     browser->getvalue(instance, NPNVWindowNPObject, &windowObject);
    439 
    440     if (!windowObject)
    441         gLogI.log(kError_ANPLogType, " ------ %p Unable to retrieve DOM Window", instance);
    442 
    443     // create a string (JS code) that is stored in memory allocated by the browser
    444     const char* jsString = "1200 + 34";
    445     void* stringMem = browser->memalloc(strlen(jsString));
    446     memcpy(stringMem, jsString, strlen(jsString));
    447 
    448     // execute the javascript in the plugin's DOM object
    449     NPString script = { (char*)stringMem, strlen(jsString) };
    450     NPVariant scriptVariant;
    451     if (!browser->evaluate(instance, windowObject, &script, &scriptVariant))
    452         gLogI.log(kError_ANPLogType, " ------ %p Unable to eval the JS.", instance);
    453 
    454     if (scriptVariant.type == NPVariantType_Int32) {
    455         if (scriptVariant.value.intValue != 1234)
    456             gLogI.log(kError_ANPLogType, " ------ %p Invalid Value for JS Return: %d,1234", instance, scriptVariant.value.intValue);
    457     } else {
    458         gLogI.log(kError_ANPLogType, " ------ %p Invalid Variant type for JS Return: %d,%d", instance, scriptVariant.type, NPVariantType_Int32);
    459     }
    460 
    461     // free the memory allocated within the browser
    462     browser->memfree(stringMem);
    463 }
    464 
    465 ///////////////////////////////////////////////////////////////////////////////
    466 // Load Java Classes Tests
    467 ///////////////////////////////////////////////////////////////////////////////
    468 
    469 void BackgroundPlugin::test_loadJavaClass() {
    470 
    471     JNIEnv* env = NULL;
    472     if (gVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
    473         gLogI.log(kError_ANPLogType, " ---- LoadJavaTest: failed to get env");
    474         return;
    475     }
    476 
    477     const char* className = "com.android.sampleplugin.BackgroundTest";
    478     jclass backgroundClass = gSystemI.loadJavaClass(inst(), className);
    479 
    480     if(!backgroundClass) {
    481         gLogI.log(kError_ANPLogType, " ---- LoadJavaTest: failed to load class");
    482         return;
    483     }
    484 
    485     jmethodID constructor = env->GetMethodID(backgroundClass, "<init>", "()V");
    486     jmethodID addMethod = env->GetMethodID(backgroundClass, "addInt", "(II)I");
    487     jobject backgroundObject = env->NewObject(backgroundClass, constructor);
    488 
    489     if(!backgroundObject) {
    490         gLogI.log(kError_ANPLogType, " ---- LoadJavaTest: failed to construct object");
    491         return;
    492     }
    493 
    494     jint result = env->CallIntMethod(backgroundObject, addMethod, 2, 2);
    495 
    496     if (result != 4) {
    497         gLogI.log(kError_ANPLogType, " ---- LoadJavaTest: invalid result (%d != 4)", result);
    498     }
    499 }
    500