android_database_CursorWindow.cpp revision d0ff68da6a606602235fb8749473999e3d1bde53
1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#undef LOG_TAG
18#define LOG_TAG "CursorWindow"
19
20#include <jni.h>
21#include <JNIHelp.h>
22#include <android_runtime/AndroidRuntime.h>
23
24#include <utils/Log.h>
25#include <utils/String8.h>
26#include <utils/String16.h>
27#include <utils/Unicode.h>
28
29#include <stdio.h>
30#include <string.h>
31#include <unistd.h>
32
33#include "binder/CursorWindow.h"
34#include "sqlite3_exception.h"
35#include "android_util_Binder.h"
36
37namespace android {
38
39static struct {
40    jfieldID data;
41    jfieldID sizeCopied;
42} gCharArrayBufferClassInfo;
43
44static jstring gEmptyString;
45
46static void throwExceptionWithRowCol(JNIEnv* env, jint row, jint column) {
47    String8 msg;
48    msg.appendFormat("Couldn't read row %d, col %d from CursorWindow.  "
49            "Make sure the Cursor is initialized correctly before accessing data from it.",
50            row, column);
51    jniThrowException(env, "java/lang/IllegalStateException", msg.string());
52}
53
54static void throwUnknownTypeException(JNIEnv * env, jint type) {
55    String8 msg;
56    msg.appendFormat("UNKNOWN type %d", type);
57    jniThrowException(env, "java/lang/IllegalStateException", msg.string());
58}
59
60static jint nativeInitializeEmpty(JNIEnv* env, jclass clazz,
61        jint cursorWindowSize, jboolean localOnly) {
62    CursorWindow* window = new CursorWindow(cursorWindowSize);
63    if (!window) {
64        return 0;
65    }
66    if (!window->initBuffer(localOnly)) {
67        delete window;
68        return 0;
69    }
70
71    LOG_WINDOW("nativeInitializeEmpty: window = %p", window);
72    return reinterpret_cast<jint>(window);
73}
74
75static jint nativeInitializeFromBinder(JNIEnv* env, jclass clazz, jobject binderObj) {
76    sp<IMemory> memory = interface_cast<IMemory>(ibinderForJavaObject(env, binderObj));
77    if (memory == NULL) {
78        jniThrowException(env, "java/lang/IllegalStateException", "Couldn't get native binder");
79        return 0;
80    }
81
82    CursorWindow* window = new CursorWindow();
83    if (!window) {
84        return 0;
85    }
86    if (!window->setMemory(memory)) {
87        delete window;
88        return 0;
89    }
90
91    LOG_WINDOW("nativeInitializeFromBinder: numRows = %d, numColumns = %d, window = %p",
92            window->getNumRows(), window->getNumColumns(), window);
93    return reinterpret_cast<jint>(window);
94}
95
96static void nativeDispose(JNIEnv* env, jclass clazz, jint windowPtr) {
97    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
98    if (window) {
99        LOG_WINDOW("Closing window %p", window);
100        delete window;
101    }
102}
103
104static jobject nativeGetBinder(JNIEnv * env, jclass clazz, jint windowPtr) {
105    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
106    if (window) {
107        sp<IMemory> memory = window->getMemory();
108        if (memory != NULL) {
109            sp<IBinder> binder = memory->asBinder();
110            return javaObjectForIBinder(env, binder);
111        }
112    }
113    return NULL;
114}
115
116static void nativeClear(JNIEnv * env, jclass clazz, jint windowPtr) {
117    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
118    LOG_WINDOW("Clearing window %p", window);
119    window->clear();
120}
121
122static jint nativeGetNumRows(JNIEnv* env, jclass clazz, jint windowPtr) {
123    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
124    return window->getNumRows();
125}
126
127static jboolean nativeSetNumColumns(JNIEnv* env, jclass clazz, jint windowPtr,
128        jint columnNum) {
129    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
130    return window->setNumColumns(columnNum);
131}
132
133static jboolean nativeAllocRow(JNIEnv* env, jclass clazz, jint windowPtr) {
134    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
135    return window->allocRow() != NULL;
136}
137
138static void nativeFreeLastRow(JNIEnv* env, jclass clazz, jint windowPtr) {
139    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
140    window->freeLastRow();
141}
142
143static jint nativeGetType(JNIEnv* env, jclass clazz, jint windowPtr,
144        jint row, jint column) {
145    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
146    LOG_WINDOW("returning column type affinity for %d,%d from %p", row, column, window);
147
148    field_slot_t* fieldSlot = window->getFieldSlotWithCheck(row, column);
149    if (!fieldSlot) {
150        // FIXME: This is really broken but we have CTS tests that depend
151        // on this legacy behavior.
152        //throwExceptionWithRowCol(env, row, column);
153        return FIELD_TYPE_NULL;
154    }
155    return fieldSlot->type;
156}
157
158static jbyteArray nativeGetBlob(JNIEnv* env, jclass clazz, jint windowPtr,
159        jint row, jint column) {
160    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
161    LOG_WINDOW("Getting blob for %d,%d from %p", row, column, window);
162
163    field_slot_t* fieldSlot = window->getFieldSlotWithCheck(row, column);
164    if (!fieldSlot) {
165        throwExceptionWithRowCol(env, row, column);
166        return NULL;
167    }
168
169    uint8_t type = fieldSlot->type;
170    if (type == FIELD_TYPE_BLOB || type == FIELD_TYPE_STRING) {
171        uint32_t size = fieldSlot->data.buffer.size;
172        jbyteArray byteArray = env->NewByteArray(size);
173        if (!byteArray) {
174            env->ExceptionClear();
175            throw_sqlite3_exception(env, "Native could not create new byte[]");
176            return NULL;
177        }
178        env->SetByteArrayRegion(byteArray, 0, size,
179                reinterpret_cast<jbyte*>(window->offsetToPtr(fieldSlot->data.buffer.offset)));
180        return byteArray;
181    } else if (type == FIELD_TYPE_INTEGER) {
182        throw_sqlite3_exception(env, "INTEGER data in nativeGetBlob ");
183    } else if (type == FIELD_TYPE_FLOAT) {
184        throw_sqlite3_exception(env, "FLOAT data in nativeGetBlob ");
185    } else if (type == FIELD_TYPE_NULL) {
186        // do nothing
187    } else {
188        throwUnknownTypeException(env, type);
189    }
190    return NULL;
191}
192
193static jstring nativeGetString(JNIEnv* env, jclass clazz, jint windowPtr,
194        jint row, jint column) {
195    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
196    LOG_WINDOW("Getting string for %d,%d from %p", row, column, window);
197
198    field_slot_t* fieldSlot = window->getFieldSlotWithCheck(row, column);
199    if (!fieldSlot) {
200        throwExceptionWithRowCol(env, row, column);
201        return NULL;
202    }
203
204    uint8_t type = fieldSlot->type;
205    if (type == FIELD_TYPE_STRING) {
206        uint32_t size = fieldSlot->data.buffer.size;
207#if WINDOW_STORAGE_UTF8
208        return size > 1 ? env->NewStringUTF(window->getFieldSlotValueString(fieldSlot))
209                : gEmptyString;
210#else
211        size_t chars = size / sizeof(char16_t);
212        return chars ? env->NewString(reinterpret_cast<jchar*>(
213                window->getFieldSlotValueString(fieldSlot)), chars)
214                : gEmptyString;
215#endif
216    } else if (type == FIELD_TYPE_INTEGER) {
217        int64_t value = window->getFieldSlotValueLong(fieldSlot);
218        char buf[32];
219        snprintf(buf, sizeof(buf), "%lld", value);
220        return env->NewStringUTF(buf);
221    } else if (type == FIELD_TYPE_FLOAT) {
222        double value = window->getFieldSlotValueDouble(fieldSlot);
223        char buf[32];
224        snprintf(buf, sizeof(buf), "%g", value);
225        return env->NewStringUTF(buf);
226    } else if (type == FIELD_TYPE_NULL) {
227        return NULL;
228    } else if (type == FIELD_TYPE_BLOB) {
229        throw_sqlite3_exception(env, "Unable to convert BLOB to string");
230        return NULL;
231    } else {
232        throwUnknownTypeException(env, type);
233        return NULL;
234    }
235}
236
237static jcharArray allocCharArrayBuffer(JNIEnv* env, jobject bufferObj, size_t size) {
238    jcharArray dataObj = jcharArray(env->GetObjectField(bufferObj,
239            gCharArrayBufferClassInfo.data));
240    if (dataObj && size) {
241        jsize capacity = env->GetArrayLength(dataObj);
242        if (size_t(capacity) < size) {
243            env->DeleteLocalRef(dataObj);
244            dataObj = NULL;
245        }
246    }
247    if (!dataObj) {
248        jsize capacity = size;
249        if (capacity < 64) {
250            capacity = 64;
251        }
252        dataObj = env->NewCharArray(capacity); // might throw OOM
253        if (dataObj) {
254            env->SetObjectField(bufferObj, gCharArrayBufferClassInfo.data, dataObj);
255        }
256    }
257    return dataObj;
258}
259
260static void fillCharArrayBufferUTF(JNIEnv* env, jobject bufferObj,
261        const char* str, size_t len) {
262    ssize_t size = utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(str), len);
263    if (size < 0) {
264        size = 0; // invalid UTF8 string
265    }
266    jcharArray dataObj = allocCharArrayBuffer(env, bufferObj, size);
267    if (dataObj) {
268        if (size) {
269            jchar* data = static_cast<jchar*>(env->GetPrimitiveArrayCritical(dataObj, NULL));
270            utf8_to_utf16_no_null_terminator(reinterpret_cast<const uint8_t*>(str), len,
271                    reinterpret_cast<char16_t*>(data));
272            env->ReleasePrimitiveArrayCritical(dataObj, data, 0);
273        }
274        env->SetIntField(bufferObj, gCharArrayBufferClassInfo.sizeCopied, size);
275    }
276}
277
278#if !WINDOW_STORAGE_UTF8
279static void fillCharArrayBuffer(JNIEnv* env, jobject bufferObj,
280        const char16_t* str, size_t len) {
281    jcharArray dataObj = allocCharArrayBuffer(env, bufferObj, len);
282    if (dataObj) {
283        if (len) {
284            jchar* data = static_cast<jchar*>(env->GetPrimitiveArrayCritical(dataObj, NULL));
285            memcpy(data, str, len * sizeof(jchar));
286            env->ReleasePrimitiveArrayCritical(dataObj, data, 0);
287        }
288        env->SetIntField(bufferObj, gCharArrayBufferClassInfo.sizeCopied, len);
289    }
290}
291#endif
292
293static void clearCharArrayBuffer(JNIEnv* env, jobject bufferObj) {
294    jcharArray dataObj = allocCharArrayBuffer(env, bufferObj, 0);
295    if (dataObj) {
296        env->SetIntField(bufferObj, gCharArrayBufferClassInfo.sizeCopied, 0);
297    }
298}
299
300static void nativeCopyStringToBuffer(JNIEnv* env, jclass clazz, jint windowPtr,
301        jint row, jint column, jobject bufferObj) {
302    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
303    LOG_WINDOW("Copying string for %d,%d from %p", row, column, window);
304
305    field_slot_t* fieldSlot = window->getFieldSlotWithCheck(row, column);
306    if (!fieldSlot) {
307        throwExceptionWithRowCol(env, row, column);
308        return;
309    }
310
311    uint8_t type = fieldSlot->type;
312    if (type == FIELD_TYPE_STRING) {
313        uint32_t size = fieldSlot->data.buffer.size;
314#if WINDOW_STORAGE_UTF8
315        if (size > 1) {
316            fillCharArrayBufferUTF(env, bufferObj,
317                    window->getFieldSlotValueString(fieldSlot), size - 1);
318        } else {
319            clearCharArrayBuffer(env, bufferObj);
320        }
321#else
322        size_t chars = size / sizeof(char16_t);
323        if (chars) {
324            fillCharArrayBuffer(env, bufferObj,
325                    window->getFieldSlotValueString(fieldSlot), chars);
326        } else {
327            clearCharArrayBuffer(env, bufferObj);
328        }
329#endif
330    } else if (type == FIELD_TYPE_INTEGER) {
331        int64_t value = window->getFieldSlotValueLong(fieldSlot);
332        char buf[32];
333        snprintf(buf, sizeof(buf), "%lld", value);
334        fillCharArrayBufferUTF(env, bufferObj, buf, strlen(buf));
335    } else if (type == FIELD_TYPE_FLOAT) {
336        double value = window->getFieldSlotValueDouble(fieldSlot);
337        char buf[32];
338        snprintf(buf, sizeof(buf), "%g", value);
339        fillCharArrayBufferUTF(env, bufferObj, buf, strlen(buf));
340    } else if (type == FIELD_TYPE_NULL) {
341        clearCharArrayBuffer(env, bufferObj);
342    } else if (type == FIELD_TYPE_BLOB) {
343        throw_sqlite3_exception(env, "Unable to convert BLOB to string");
344    } else {
345        throwUnknownTypeException(env, type);
346    }
347}
348
349static jlong nativeGetLong(JNIEnv* env, jclass clazz, jint windowPtr,
350        jint row, jint column) {
351    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
352    LOG_WINDOW("Getting long for %d,%d from %p", row, column, window);
353
354    field_slot_t* fieldSlot = window->getFieldSlotWithCheck(row, column);
355    if (!fieldSlot) {
356        throwExceptionWithRowCol(env, row, column);
357        return 0;
358    }
359
360    uint8_t type = fieldSlot->type;
361    if (type == FIELD_TYPE_INTEGER) {
362        return window->getFieldSlotValueLong(fieldSlot);
363    } else if (type == FIELD_TYPE_STRING) {
364        uint32_t size = fieldSlot->data.buffer.size;
365#if WINDOW_STORAGE_UTF8
366        return size > 1 ? strtoll(window->getFieldSlotValueString(fieldSlot), NULL, 0) : 0L;
367#else
368        size_t chars = size / sizeof(char16_t);
369        return chars ? strtoll(String8(window->getFieldSlotValueString(fieldSlot), chars)
370                .string(), NULL, 0) : 0L;
371#endif
372    } else if (type == FIELD_TYPE_FLOAT) {
373        return jlong(window->getFieldSlotValueDouble(fieldSlot));
374    } else if (type == FIELD_TYPE_NULL) {
375        return 0;
376    } else if (type == FIELD_TYPE_BLOB) {
377        throw_sqlite3_exception(env, "Unable to convert BLOB to long");
378        return 0;
379    } else {
380        throwUnknownTypeException(env, type);
381        return 0;
382    }
383}
384
385static jdouble nativeGetDouble(JNIEnv* env, jclass clazz, jint windowPtr,
386        jint row, jint column) {
387    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
388    LOG_WINDOW("Getting double for %d,%d from %p", row, column, window);
389
390    field_slot_t* fieldSlot = window->getFieldSlotWithCheck(row, column);
391    if (!fieldSlot) {
392        throwExceptionWithRowCol(env, row, column);
393        return 0.0;
394    }
395
396    uint8_t type = fieldSlot->type;
397    if (type == FIELD_TYPE_FLOAT) {
398        return window->getFieldSlotValueDouble(fieldSlot);
399    } else if (type == FIELD_TYPE_STRING) {
400        uint32_t size = fieldSlot->data.buffer.size;
401#if WINDOW_STORAGE_UTF8
402        return size > 1 ? strtod(window->getFieldSlotValueString(fieldSlot), NULL) : 0.0;
403#else
404        size_t chars = size / sizeof(char16_t);
405        return chars ? strtod(String8(window->getFieldSlotValueString(fieldSlot), chars)
406                .string(), NULL) : 0.0;
407#endif
408    } else if (type == FIELD_TYPE_INTEGER) {
409        return jdouble(window->getFieldSlotValueLong(fieldSlot));
410    } else if (type == FIELD_TYPE_NULL) {
411        return 0.0;
412    } else if (type == FIELD_TYPE_BLOB) {
413        throw_sqlite3_exception(env, "Unable to convert BLOB to double");
414        return 0.0;
415    } else {
416        throwUnknownTypeException(env, type);
417        return 0.0;
418    }
419}
420
421static jboolean nativePutBlob(JNIEnv* env, jclass clazz, jint windowPtr,
422        jbyteArray valueObj, jint row, jint column) {
423    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
424    field_slot_t * fieldSlot = window->getFieldSlotWithCheck(row, column);
425    if (fieldSlot == NULL) {
426        LOG_WINDOW(" getFieldSlotWithCheck error ");
427        return false;
428    }
429
430    jsize len = env->GetArrayLength(valueObj);
431    uint32_t offset = window->alloc(len);
432    if (!offset) {
433        LOG_WINDOW("Failed allocating %u bytes", len);
434        return false;
435    }
436
437    void* value = env->GetPrimitiveArrayCritical(valueObj, NULL);
438    window->copyIn(offset, static_cast<const uint8_t*>(value), len);
439    env->ReleasePrimitiveArrayCritical(valueObj, value, JNI_ABORT);
440
441    fieldSlot->type = FIELD_TYPE_BLOB;
442    fieldSlot->data.buffer.offset = offset;
443    fieldSlot->data.buffer.size = len;
444    LOG_WINDOW("%d,%d is BLOB with %u bytes @ %d", row, column, len, offset);
445    return true;
446}
447
448static jboolean nativePutString(JNIEnv* env, jclass clazz, jint windowPtr,
449        jstring valueObj, jint row, jint column) {
450    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
451    field_slot_t * fieldSlot = window->getFieldSlotWithCheck(row, column);
452    if (fieldSlot == NULL) {
453        LOG_WINDOW(" getFieldSlotWithCheck error ");
454        return false;
455    }
456
457#if WINDOW_STORAGE_UTF8
458    size_t size = env->GetStringUTFLength(valueObj) + 1;
459    const char* valueStr = env->GetStringUTFChars(valueObj, NULL);
460#else
461    size_t size = env->GetStringLength(valueObj) * sizeof(jchar);
462    const jchar* valueStr = env->GetStringChars(valueObj, NULL);
463#endif
464    if (!valueStr) {
465        LOG_WINDOW("value can't be transfer to UTFChars");
466        return false;
467    }
468
469    uint32_t offset = window->alloc(size);
470    if (!offset) {
471        LOG_WINDOW("Failed allocating %u bytes", size);
472#if WINDOW_STORAGE_UTF8
473        env->ReleaseStringUTFChars(valueObj, valueStr);
474#else
475        env->ReleaseStringChars(valueObj, valueStr);
476#endif
477        return false;
478    }
479
480    window->copyIn(offset, reinterpret_cast<const uint8_t*>(valueStr), size);
481
482#if WINDOW_STORAGE_UTF8
483    env->ReleaseStringUTFChars(valueObj, valueStr);
484#else
485    env->ReleaseStringChars(valueObj, valueStr);
486#endif
487
488    fieldSlot->type = FIELD_TYPE_STRING;
489    fieldSlot->data.buffer.offset = offset;
490    fieldSlot->data.buffer.size = size;
491    LOG_WINDOW("%d,%d is TEXT with %u bytes @ %d", row, column, size, offset);
492    return true;
493}
494
495static jboolean nativePutLong(JNIEnv* env, jclass clazz, jint windowPtr,
496        jlong value, jint row, jint column) {
497    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
498    if (!window->putLong(row, column, value)) {
499        LOG_WINDOW(" getFieldSlotWithCheck error ");
500        return false;
501    }
502
503    LOG_WINDOW("%d,%d is INTEGER 0x%016llx", row, column, value);
504    return true;
505}
506
507static jboolean nativePutDouble(JNIEnv* env, jclass clazz, jint windowPtr,
508        jdouble value, jint row, jint column) {
509    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
510    if (!window->putDouble(row, column, value)) {
511        LOG_WINDOW(" getFieldSlotWithCheck error ");
512        return false;
513    }
514
515    LOG_WINDOW("%d,%d is FLOAT %lf", row, column, value);
516    return true;
517}
518
519static jboolean nativePutNull(JNIEnv* env, jclass clazz, jint windowPtr,
520        jint row, jint column) {
521    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
522    if (!window->putNull(row, column)) {
523        LOG_WINDOW(" getFieldSlotWithCheck error ");
524        return false;
525    }
526
527    LOG_WINDOW("%d,%d is NULL", row, column);
528    return true;
529}
530
531static JNINativeMethod sMethods[] =
532{
533    /* name, signature, funcPtr */
534    { "nativeInitializeEmpty", "(IZ)I",
535            (void*)nativeInitializeEmpty },
536    { "nativeInitializeFromBinder", "(Landroid/os/IBinder;)I",
537            (void*)nativeInitializeFromBinder },
538    { "nativeDispose", "(I)V",
539            (void*)nativeDispose },
540    { "nativeGetBinder", "(I)Landroid/os/IBinder;",
541            (void*)nativeGetBinder },
542    { "nativeClear", "(I)V",
543            (void*)nativeClear },
544    { "nativeGetNumRows", "(I)I",
545            (void*)nativeGetNumRows },
546    { "nativeSetNumColumns", "(II)Z",
547            (void*)nativeSetNumColumns },
548    { "nativeAllocRow", "(I)Z",
549            (void*)nativeAllocRow },
550    { "nativeFreeLastRow", "(I)V",
551            (void*)nativeFreeLastRow },
552    { "nativeGetType", "(III)I",
553            (void*)nativeGetType },
554    { "nativeGetBlob", "(III)[B",
555            (void*)nativeGetBlob },
556    { "nativeGetString", "(III)Ljava/lang/String;",
557            (void*)nativeGetString },
558    { "nativeGetLong", "(III)J",
559            (void*)nativeGetLong },
560    { "nativeGetDouble", "(III)D",
561            (void*)nativeGetDouble },
562    { "nativeCopyStringToBuffer", "(IIILandroid/database/CharArrayBuffer;)V",
563            (void*)nativeCopyStringToBuffer },
564    { "nativePutBlob", "(I[BII)Z",
565            (void*)nativePutBlob },
566    { "nativePutString", "(ILjava/lang/String;II)Z",
567            (void*)nativePutString },
568    { "nativePutLong", "(IJII)Z",
569            (void*)nativePutLong },
570    { "nativePutDouble", "(IDII)Z",
571            (void*)nativePutDouble },
572    { "nativePutNull", "(III)Z",
573            (void*)nativePutNull },
574};
575
576#define FIND_CLASS(var, className) \
577        var = env->FindClass(className); \
578        LOG_FATAL_IF(! var, "Unable to find class " className);
579
580#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
581        var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
582        LOG_FATAL_IF(! var, "Unable to find field " fieldName);
583
584int register_android_database_CursorWindow(JNIEnv * env)
585{
586    jclass clazz;
587    FIND_CLASS(clazz, "android/database/CharArrayBuffer");
588
589    GET_FIELD_ID(gCharArrayBufferClassInfo.data, clazz,
590            "data", "[C");
591    GET_FIELD_ID(gCharArrayBufferClassInfo.sizeCopied, clazz,
592            "sizeCopied", "I");
593
594    gEmptyString = jstring(env->NewGlobalRef(env->NewStringUTF("")));
595    LOG_FATAL_IF(!gEmptyString, "Unable to create empty string");
596
597    return AndroidRuntime::registerNativeMethods(env, "android/database/CursorWindow",
598            sMethods, NELEM(sMethods));
599}
600
601} // namespace android
602