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