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