Home | History | Annotate | Download | only in graphics
      1 #define LOG_TAG "BitmapFactory"
      2 
      3 #include "BitmapFactory.h"
      4 #include "SkImageDecoder.h"
      5 #include "SkImageRef_ashmem.h"
      6 #include "SkImageRef_GlobalPool.h"
      7 #include "SkPixelRef.h"
      8 #include "SkStream.h"
      9 #include "SkTemplates.h"
     10 #include "SkUtils.h"
     11 #include "CreateJavaOutputStreamAdaptor.h"
     12 #include "AutoDecodeCancel.h"
     13 #include "Utils.h"
     14 
     15 #include <android_runtime/AndroidRuntime.h>
     16 #include <utils/Asset.h>
     17 #include <utils/ResourceTypes.h>
     18 #include <netinet/in.h>
     19 #include <sys/mman.h>
     20 #include <sys/stat.h>
     21 
     22 jclass gOptions_class;
     23 jfieldID gOptions_justBoundsFieldID;
     24 jfieldID gOptions_sampleSizeFieldID;
     25 jfieldID gOptions_configFieldID;
     26 jfieldID gOptions_ditherFieldID;
     27 jfieldID gOptions_purgeableFieldID;
     28 jfieldID gOptions_shareableFieldID;
     29 jfieldID gOptions_nativeAllocFieldID;
     30 jfieldID gOptions_widthFieldID;
     31 jfieldID gOptions_heightFieldID;
     32 jfieldID gOptions_mimeFieldID;
     33 jfieldID gOptions_mCancelID;
     34 
     35 static jclass gFileDescriptor_class;
     36 static jfieldID gFileDescriptor_descriptor;
     37 
     38 #if 0
     39     #define TRACE_BITMAP(code)  code
     40 #else
     41     #define TRACE_BITMAP(code)
     42 #endif
     43 
     44 using namespace android;
     45 
     46 class NinePatchPeeker : public SkImageDecoder::Peeker {
     47     SkImageDecoder* fHost;
     48 public:
     49     NinePatchPeeker(SkImageDecoder* host) {
     50         // the host lives longer than we do, so a raw ptr is safe
     51         fHost = host;
     52         fPatchIsValid = false;
     53     }
     54 
     55     ~NinePatchPeeker() {
     56         if (fPatchIsValid) {
     57             free(fPatch);
     58         }
     59     }
     60 
     61     bool    fPatchIsValid;
     62     Res_png_9patch*  fPatch;
     63 
     64     virtual bool peek(const char tag[], const void* data, size_t length) {
     65         if (strcmp("npTc", tag) == 0 && length >= sizeof(Res_png_9patch)) {
     66             Res_png_9patch* patch = (Res_png_9patch*) data;
     67             size_t patchSize = patch->serializedSize();
     68             assert(length == patchSize);
     69             // You have to copy the data because it is owned by the png reader
     70             Res_png_9patch* patchNew = (Res_png_9patch*) malloc(patchSize);
     71             memcpy(patchNew, patch, patchSize);
     72             // this relies on deserialization being done in place
     73             Res_png_9patch::deserialize(patchNew);
     74             patchNew->fileToDevice();
     75             if (fPatchIsValid) {
     76                 free(fPatch);
     77             }
     78             fPatch = patchNew;
     79             //printf("9patch: (%d,%d)-(%d,%d)\n",
     80             //       fPatch.sizeLeft, fPatch.sizeTop,
     81             //       fPatch.sizeRight, fPatch.sizeBottom);
     82             fPatchIsValid = true;
     83 
     84             // now update our host to force index or 32bit config
     85             // 'cause we don't want 565 predithered, since as a 9patch, we know
     86             // we will be stretched, and therefore we want to dither afterwards.
     87             static const SkBitmap::Config gNo565Pref[] = {
     88                 SkBitmap::kIndex8_Config,
     89                 SkBitmap::kIndex8_Config,
     90                 SkBitmap::kARGB_8888_Config,
     91                 SkBitmap::kARGB_8888_Config,
     92                 SkBitmap::kARGB_8888_Config,
     93                 SkBitmap::kARGB_8888_Config,
     94             };
     95             fHost->setPrefConfigTable(gNo565Pref);
     96         } else {
     97             fPatch = NULL;
     98         }
     99         return true;    // keep on decoding
    100     }
    101 };
    102 
    103 ///////////////////////////////////////////////////////////////////////////////
    104 
    105 static inline int32_t validOrNeg1(bool isValid, int32_t value) {
    106 //    return isValid ? value : -1;
    107     SkASSERT((int)isValid == 0 || (int)isValid == 1);
    108     return ((int32_t)isValid - 1) | value;
    109 }
    110 
    111 jstring getMimeTypeString(JNIEnv* env, SkImageDecoder::Format format) {
    112     static const struct {
    113         SkImageDecoder::Format fFormat;
    114         const char*            fMimeType;
    115     } gMimeTypes[] = {
    116         { SkImageDecoder::kBMP_Format,  "image/bmp" },
    117         { SkImageDecoder::kGIF_Format,  "image/gif" },
    118         { SkImageDecoder::kICO_Format,  "image/x-ico" },
    119         { SkImageDecoder::kJPEG_Format, "image/jpeg" },
    120         { SkImageDecoder::kPNG_Format,  "image/png" },
    121         { SkImageDecoder::kWBMP_Format, "image/vnd.wap.wbmp" }
    122     };
    123 
    124     const char* cstr = NULL;
    125     for (size_t i = 0; i < SK_ARRAY_COUNT(gMimeTypes); i++) {
    126         if (gMimeTypes[i].fFormat == format) {
    127             cstr = gMimeTypes[i].fMimeType;
    128             break;
    129         }
    130     }
    131 
    132     jstring jstr = 0;
    133     if (NULL != cstr) {
    134         jstr = env->NewStringUTF(cstr);
    135     }
    136     return jstr;
    137 }
    138 
    139 static bool optionsPurgeable(JNIEnv* env, jobject options) {
    140     return options != NULL &&
    141             env->GetBooleanField(options, gOptions_purgeableFieldID);
    142 }
    143 
    144 static bool optionsShareable(JNIEnv* env, jobject options) {
    145     return options != NULL &&
    146             env->GetBooleanField(options, gOptions_shareableFieldID);
    147 }
    148 
    149 static bool optionsReportSizeToVM(JNIEnv* env, jobject options) {
    150     return NULL == options ||
    151             !env->GetBooleanField(options, gOptions_nativeAllocFieldID);
    152 }
    153 
    154 
    155 static SkPixelRef* installPixelRef(SkBitmap* bitmap, SkStream* stream,
    156                                    int sampleSize, bool ditherImage) {
    157     SkImageRef* pr;
    158     // only use ashmem for large images, since mmaps come at a price
    159     if (bitmap->getSize() >= 32 * 1024) {
    160         pr = new SkImageRef_ashmem(stream, bitmap->config(), sampleSize);
    161     } else {
    162         pr = new SkImageRef_GlobalPool(stream, bitmap->config(), sampleSize);
    163     }
    164     pr->setDitherImage(ditherImage);
    165     bitmap->setPixelRef(pr)->unref();
    166     pr->isOpaque(bitmap);
    167     return pr;
    168 }
    169 
    170 // since we "may" create a purgeable imageref, we require the stream be ref'able
    171 // i.e. dynamically allocated, since its lifetime may exceed the current stack
    172 // frame.
    173 static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,
    174                         jobject options, bool allowPurgeable,
    175                         bool forcePurgeable = false) {
    176     int sampleSize = 1;
    177     SkImageDecoder::Mode mode = SkImageDecoder::kDecodePixels_Mode;
    178     SkBitmap::Config prefConfig = SkBitmap::kNo_Config;
    179     bool doDither = true;
    180     bool isPurgeable = forcePurgeable ||
    181                         (allowPurgeable && optionsPurgeable(env, options));
    182     bool reportSizeToVM = optionsReportSizeToVM(env, options);
    183 
    184     if (NULL != options) {
    185         sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
    186         if (env->GetBooleanField(options, gOptions_justBoundsFieldID)) {
    187             mode = SkImageDecoder::kDecodeBounds_Mode;
    188         }
    189         // initialize these, in case we fail later on
    190         env->SetIntField(options, gOptions_widthFieldID, -1);
    191         env->SetIntField(options, gOptions_heightFieldID, -1);
    192         env->SetObjectField(options, gOptions_mimeFieldID, 0);
    193 
    194         jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
    195         prefConfig = GraphicsJNI::getNativeBitmapConfig(env, jconfig);
    196         doDither = env->GetBooleanField(options, gOptions_ditherFieldID);
    197     }
    198 
    199     SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
    200     if (NULL == decoder) {
    201         return nullObjectReturn("SkImageDecoder::Factory returned null");
    202     }
    203 
    204     decoder->setSampleSize(sampleSize);
    205     decoder->setDitherImage(doDither);
    206 
    207     NinePatchPeeker     peeker(decoder);
    208     JavaPixelAllocator  javaAllocator(env, reportSizeToVM);
    209     SkBitmap*           bitmap = new SkBitmap;
    210     Res_png_9patch      dummy9Patch;
    211 
    212     SkAutoTDelete<SkImageDecoder>   add(decoder);
    213     SkAutoTDelete<SkBitmap>         adb(bitmap);
    214 
    215     decoder->setPeeker(&peeker);
    216     if (!isPurgeable) {
    217         decoder->setAllocator(&javaAllocator);
    218     }
    219 
    220     AutoDecoderCancel   adc(options, decoder);
    221 
    222     // To fix the race condition in case "requestCancelDecode"
    223     // happens earlier than AutoDecoderCancel object is added
    224     // to the gAutoDecoderCancelMutex linked list.
    225     if (NULL != options && env->GetBooleanField(options, gOptions_mCancelID)) {
    226         return nullObjectReturn("gOptions_mCancelID");;
    227     }
    228 
    229     SkImageDecoder::Mode decodeMode = mode;
    230     if (isPurgeable) {
    231         decodeMode = SkImageDecoder::kDecodeBounds_Mode;
    232     }
    233     if (!decoder->decode(stream, bitmap, prefConfig, decodeMode)) {
    234         return nullObjectReturn("decoder->decode returned false");
    235     }
    236 
    237     // update options (if any)
    238     if (NULL != options) {
    239         env->SetIntField(options, gOptions_widthFieldID, bitmap->width());
    240         env->SetIntField(options, gOptions_heightFieldID, bitmap->height());
    241         // TODO: set the mimeType field with the data from the codec.
    242         // but how to reuse a set of strings, rather than allocating new one
    243         // each time?
    244         env->SetObjectField(options, gOptions_mimeFieldID,
    245                             getMimeTypeString(env, decoder->getFormat()));
    246     }
    247 
    248     // if we're in justBounds mode, return now (skip the java bitmap)
    249     if (SkImageDecoder::kDecodeBounds_Mode == mode) {
    250         return NULL;
    251     }
    252 
    253     jbyteArray ninePatchChunk = NULL;
    254     if (peeker.fPatchIsValid) {
    255         size_t ninePatchArraySize = peeker.fPatch->serializedSize();
    256         ninePatchChunk = env->NewByteArray(ninePatchArraySize);
    257         if (NULL == ninePatchChunk) {
    258             return nullObjectReturn("ninePatchChunk == null");
    259         }
    260         jbyte* array = (jbyte*)env->GetPrimitiveArrayCritical(ninePatchChunk,
    261                                                               NULL);
    262         if (NULL == array) {
    263             return nullObjectReturn("primitive array == null");
    264         }
    265         peeker.fPatch->serialize(array);
    266         env->ReleasePrimitiveArrayCritical(ninePatchChunk, array, 0);
    267     }
    268 
    269     // detach bitmap from its autotdeleter, since we want to own it now
    270     adb.detach();
    271 
    272     if (padding) {
    273         if (peeker.fPatchIsValid) {
    274             GraphicsJNI::set_jrect(env, padding,
    275                                    peeker.fPatch->paddingLeft,
    276                                    peeker.fPatch->paddingTop,
    277                                    peeker.fPatch->paddingRight,
    278                                    peeker.fPatch->paddingBottom);
    279         } else {
    280             GraphicsJNI::set_jrect(env, padding, -1, -1, -1, -1);
    281         }
    282     }
    283 
    284     SkPixelRef* pr;
    285     if (isPurgeable) {
    286         pr = installPixelRef(bitmap, stream, sampleSize, doDither);
    287     } else {
    288         // if we get here, we're in kDecodePixels_Mode and will therefore
    289         // already have a pixelref installed.
    290         pr = bitmap->pixelRef();
    291     }
    292     // promise we will never change our pixels (great for sharing and pictures)
    293     pr->setImmutable();
    294     // now create the java bitmap
    295     return GraphicsJNI::createBitmap(env, bitmap, false, ninePatchChunk);
    296 }
    297 
    298 static jobject nativeDecodeStream(JNIEnv* env, jobject clazz,
    299                                   jobject is,       // InputStream
    300                                   jbyteArray storage,   // byte[]
    301                                   jobject padding,
    302                                   jobject options) {  // BitmapFactory$Options
    303     jobject bitmap = NULL;
    304     SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage, 0);
    305 
    306     if (stream) {
    307         // for now we don't allow purgeable with java inputstreams
    308         bitmap = doDecode(env, stream, padding, options, false);
    309         stream->unref();
    310     }
    311     return bitmap;
    312 }
    313 
    314 static ssize_t getFDSize(int fd) {
    315     off_t curr = ::lseek(fd, 0, SEEK_CUR);
    316     if (curr < 0) {
    317         return 0;
    318     }
    319     size_t size = ::lseek(fd, 0, SEEK_END);
    320     ::lseek(fd, curr, SEEK_SET);
    321     return size;
    322 }
    323 
    324 static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz,
    325                                           jobject fileDescriptor,
    326                                           jobject padding,
    327                                           jobject bitmapFactoryOptions) {
    328     NPE_CHECK_RETURN_ZERO(env, fileDescriptor);
    329 
    330     jint descriptor = env->GetIntField(fileDescriptor,
    331                                        gFileDescriptor_descriptor);
    332 
    333     bool isPurgeable = optionsPurgeable(env, bitmapFactoryOptions);
    334     bool isShareable = optionsShareable(env, bitmapFactoryOptions);
    335     bool weOwnTheFD = false;
    336     if (isPurgeable && isShareable) {
    337         int newFD = ::dup(descriptor);
    338         if (-1 != newFD) {
    339             weOwnTheFD = true;
    340             descriptor = newFD;
    341         }
    342     }
    343 
    344     SkFDStream* stream = new SkFDStream(descriptor, weOwnTheFD);
    345     SkAutoUnref aur(stream);
    346     if (!stream->isValid()) {
    347         return NULL;
    348     }
    349 
    350     /* Restore our offset when we leave, so we can be called more than once
    351        with the same descriptor. This is only required if we didn't dup the
    352        file descriptor, but it is OK to do it all the time.
    353     */
    354     AutoFDSeek as(descriptor);
    355 
    356     /* Allow purgeable iff we own the FD, i.e., in the puregeable and
    357        shareable case.
    358     */
    359     return doDecode(env, stream, padding, bitmapFactoryOptions, weOwnTheFD);
    360 }
    361 
    362 /*  make a deep copy of the asset, and return it as a stream, or NULL if there
    363     was an error.
    364  */
    365 static SkStream* copyAssetToStream(Asset* asset) {
    366     // if we could "ref/reopen" the asset, we may not need to copy it here
    367     off_t size = asset->seek(0, SEEK_SET);
    368     if ((off_t)-1 == size) {
    369         SkDebugf("---- copyAsset: asset rewind failed\n");
    370         return NULL;
    371     }
    372 
    373     size = asset->getLength();
    374     if (size <= 0) {
    375         SkDebugf("---- copyAsset: asset->getLength() returned %d\n", size);
    376         return NULL;
    377     }
    378 
    379     SkStream* stream = new SkMemoryStream(size);
    380     void* data = const_cast<void*>(stream->getMemoryBase());
    381     off_t len = asset->read(data, size);
    382     if (len != size) {
    383         SkDebugf("---- copyAsset: asset->read(%d) returned %d\n", size, len);
    384         delete stream;
    385         stream = NULL;
    386     }
    387     return stream;
    388 }
    389 
    390 static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz,
    391                                  jint native_asset,    // Asset
    392                                  jobject padding,       // Rect
    393                                  jobject options) { // BitmapFactory$Options
    394     SkStream* stream;
    395     Asset* asset = reinterpret_cast<Asset*>(native_asset);
    396     bool forcePurgeable = optionsPurgeable(env, options);
    397     if (forcePurgeable) {
    398         // if we could "ref/reopen" the asset, we may not need to copy it here
    399         // and we could assume optionsShareable, since assets are always RO
    400         stream = copyAssetToStream(asset);
    401         if (NULL == stream) {
    402             return NULL;
    403         }
    404     } else {
    405         // since we know we'll be done with the asset when we return, we can
    406         // just use a simple wrapper
    407         stream = new AssetStreamAdaptor(asset);
    408     }
    409     SkAutoUnref aur(stream);
    410     return doDecode(env, stream, padding, options, true, forcePurgeable);
    411 }
    412 
    413 static jobject nativeDecodeByteArray(JNIEnv* env, jobject, jbyteArray byteArray,
    414                                      int offset, int length, jobject options) {
    415     /*  If optionsShareable() we could decide to just wrap the java array and
    416         share it, but that means adding a globalref to the java array object
    417         and managing its lifetime. For now we just always copy the array's data
    418         if optionsPurgeable().
    419      */
    420     AutoJavaByteArray ar(env, byteArray);
    421     SkStream* stream = new SkMemoryStream(ar.ptr() + offset, length,
    422                                           optionsPurgeable(env, options));
    423     SkAutoUnref aur(stream);
    424     return doDecode(env, stream, NULL, options, true);
    425 }
    426 
    427 static void nativeRequestCancel(JNIEnv*, jobject joptions) {
    428     (void)AutoDecoderCancel::RequestCancel(joptions);
    429 }
    430 
    431 static jbyteArray nativeScaleNinePatch(JNIEnv* env, jobject, jbyteArray chunkObject, jfloat scale,
    432         jobject padding) {
    433 
    434     jbyte* array = env->GetByteArrayElements(chunkObject, 0);
    435     if (array != NULL) {
    436         size_t chunkSize = env->GetArrayLength(chunkObject);
    437         void* storage = alloca(chunkSize);
    438         android::Res_png_9patch* chunk = static_cast<android::Res_png_9patch*>(storage);
    439         memcpy(chunk, array, chunkSize);
    440         android::Res_png_9patch::deserialize(chunk);
    441 
    442         chunk->paddingLeft = int(chunk->paddingLeft * scale + 0.5f);
    443         chunk->paddingTop = int(chunk->paddingTop * scale + 0.5f);
    444         chunk->paddingRight = int(chunk->paddingRight * scale + 0.5f);
    445         chunk->paddingBottom = int(chunk->paddingBottom * scale + 0.5f);
    446 
    447         for (int i = 0; i < chunk->numXDivs; i++) {
    448             chunk->xDivs[i] = int(chunk->xDivs[i] * scale + 0.5f);
    449             if (i > 0 && chunk->xDivs[i] == chunk->xDivs[i - 1]) {
    450                 chunk->xDivs[i]++;
    451             }
    452         }
    453 
    454         for (int i = 0; i < chunk->numYDivs; i++) {
    455             chunk->yDivs[i] = int(chunk->yDivs[i] * scale + 0.5f);
    456             if (i > 0 && chunk->yDivs[i] == chunk->yDivs[i - 1]) {
    457                 chunk->yDivs[i]++;
    458             }
    459         }
    460 
    461         memcpy(array, chunk, chunkSize);
    462 
    463         if (padding) {
    464             GraphicsJNI::set_jrect(env, padding, chunk->paddingLeft, chunk->paddingTop,
    465                     chunk->paddingRight, chunk->paddingBottom);
    466         }
    467 
    468         env->ReleaseByteArrayElements(chunkObject, array, 0);
    469     }
    470     return chunkObject;
    471 }
    472 
    473 static void nativeSetDefaultConfig(JNIEnv* env, jobject, int nativeConfig) {
    474     SkBitmap::Config config = static_cast<SkBitmap::Config>(nativeConfig);
    475 
    476     // these are the only default configs that make sense for codecs right now
    477     static const SkBitmap::Config gValidDefConfig[] = {
    478         SkBitmap::kRGB_565_Config,
    479         SkBitmap::kARGB_8888_Config,
    480     };
    481 
    482     for (size_t i = 0; i < SK_ARRAY_COUNT(gValidDefConfig); i++) {
    483         if (config == gValidDefConfig[i]) {
    484             SkImageDecoder::SetDeviceConfig(config);
    485             break;
    486         }
    487     }
    488 }
    489 
    490 ///////////////////////////////////////////////////////////////////////////////
    491 
    492 static JNINativeMethod gMethods[] = {
    493     {   "nativeDecodeStream",
    494         "(Ljava/io/InputStream;[BLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
    495         (void*)nativeDecodeStream
    496     },
    497 
    498     {   "nativeDecodeFileDescriptor",
    499         "(Ljava/io/FileDescriptor;Landroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
    500         (void*)nativeDecodeFileDescriptor
    501     },
    502 
    503     {   "nativeDecodeAsset",
    504         "(ILandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
    505         (void*)nativeDecodeAsset
    506     },
    507 
    508     {   "nativeDecodeByteArray",
    509         "([BIILandroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
    510         (void*)nativeDecodeByteArray
    511     },
    512 
    513     {   "nativeScaleNinePatch",
    514         "([BFLandroid/graphics/Rect;)[B",
    515         (void*)nativeScaleNinePatch
    516     },
    517 
    518     {   "nativeSetDefaultConfig", "(I)V", (void*)nativeSetDefaultConfig },
    519 };
    520 
    521 static JNINativeMethod gOptionsMethods[] = {
    522     {   "requestCancel", "()V", (void*)nativeRequestCancel }
    523 };
    524 
    525 static jclass make_globalref(JNIEnv* env, const char classname[]) {
    526     jclass c = env->FindClass(classname);
    527     SkASSERT(c);
    528     return (jclass)env->NewGlobalRef(c);
    529 }
    530 
    531 static jfieldID getFieldIDCheck(JNIEnv* env, jclass clazz,
    532                                 const char fieldname[], const char type[]) {
    533     jfieldID id = env->GetFieldID(clazz, fieldname, type);
    534     SkASSERT(id);
    535     return id;
    536 }
    537 
    538 #define kClassPathName  "android/graphics/BitmapFactory"
    539 
    540 #define RETURN_ERR_IF_NULL(value) \
    541     do { if (!(value)) { assert(0); return -1; } } while (false)
    542 
    543 int register_android_graphics_BitmapFactory(JNIEnv* env);
    544 int register_android_graphics_BitmapFactory(JNIEnv* env) {
    545     gOptions_class = make_globalref(env, "android/graphics/BitmapFactory$Options");
    546     gOptions_justBoundsFieldID = getFieldIDCheck(env, gOptions_class, "inJustDecodeBounds", "Z");
    547     gOptions_sampleSizeFieldID = getFieldIDCheck(env, gOptions_class, "inSampleSize", "I");
    548     gOptions_configFieldID = getFieldIDCheck(env, gOptions_class, "inPreferredConfig",
    549             "Landroid/graphics/Bitmap$Config;");
    550     gOptions_ditherFieldID = getFieldIDCheck(env, gOptions_class, "inDither", "Z");
    551     gOptions_purgeableFieldID = getFieldIDCheck(env, gOptions_class, "inPurgeable", "Z");
    552     gOptions_shareableFieldID = getFieldIDCheck(env, gOptions_class, "inInputShareable", "Z");
    553     gOptions_nativeAllocFieldID = getFieldIDCheck(env, gOptions_class, "inNativeAlloc", "Z");
    554     gOptions_widthFieldID = getFieldIDCheck(env, gOptions_class, "outWidth", "I");
    555     gOptions_heightFieldID = getFieldIDCheck(env, gOptions_class, "outHeight", "I");
    556     gOptions_mimeFieldID = getFieldIDCheck(env, gOptions_class, "outMimeType", "Ljava/lang/String;");
    557     gOptions_mCancelID = getFieldIDCheck(env, gOptions_class, "mCancel", "Z");
    558 
    559     gFileDescriptor_class = make_globalref(env, "java/io/FileDescriptor");
    560     gFileDescriptor_descriptor = getFieldIDCheck(env, gFileDescriptor_class, "descriptor", "I");
    561 
    562     int ret = AndroidRuntime::registerNativeMethods(env,
    563                                     "android/graphics/BitmapFactory$Options",
    564                                     gOptionsMethods,
    565                                     SK_ARRAY_COUNT(gOptionsMethods));
    566     if (ret) {
    567         return ret;
    568     }
    569     return android::AndroidRuntime::registerNativeMethods(env, kClassPathName,
    570                                          gMethods, SK_ARRAY_COUNT(gMethods));
    571 }
    572