android_database_CursorWindow.cpp revision 3bc6bbc92cd2095f42039b5aadd0a14d0e5d9230
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        throwExceptionWithRowCol(env, row, column);
151        return NULL;
152    }
153    return fieldSlot->type;
154}
155
156static jbyteArray nativeGetBlob(JNIEnv* env, jclass clazz, jint windowPtr,
157        jint row, jint column) {
158    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
159    LOG_WINDOW("Getting blob for %d,%d from %p", row, column, window);
160
161    field_slot_t* fieldSlot = window->getFieldSlotWithCheck(row, column);
162    if (!fieldSlot) {
163        throwExceptionWithRowCol(env, row, column);
164        return NULL;
165    }
166
167    uint8_t type = fieldSlot->type;
168    if (type == FIELD_TYPE_BLOB || type == FIELD_TYPE_STRING) {
169        uint32_t size = fieldSlot->data.buffer.size;
170        jbyteArray byteArray = env->NewByteArray(size);
171        if (!byteArray) {
172            env->ExceptionClear();
173            throw_sqlite3_exception(env, "Native could not create new byte[]");
174            return NULL;
175        }
176        env->SetByteArrayRegion(byteArray, 0, size,
177                reinterpret_cast<jbyte*>(window->offsetToPtr(fieldSlot->data.buffer.offset)));
178        return byteArray;
179    } else if (type == FIELD_TYPE_INTEGER) {
180        throw_sqlite3_exception(env, "INTEGER data in nativeGetBlob ");
181    } else if (type == FIELD_TYPE_FLOAT) {
182        throw_sqlite3_exception(env, "FLOAT data in nativeGetBlob ");
183    } else if (type == FIELD_TYPE_NULL) {
184        // do nothing
185    } else {
186        throwUnknownTypeException(env, type);
187    }
188    return NULL;
189}
190
191static jstring nativeGetString(JNIEnv* env, jclass clazz, jint windowPtr,
192        jint row, jint column) {
193    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
194    LOG_WINDOW("Getting string for %d,%d from %p", row, column, window);
195
196    field_slot_t* fieldSlot = window->getFieldSlotWithCheck(row, column);
197    if (!fieldSlot) {
198        throwExceptionWithRowCol(env, row, column);
199        return NULL;
200    }
201
202    uint8_t type = fieldSlot->type;
203    if (type == FIELD_TYPE_STRING) {
204        uint32_t size = fieldSlot->data.buffer.size;
205#if WINDOW_STORAGE_UTF8
206        return size > 1 ? env->NewStringUTF(window->getFieldSlotValueString(fieldSlot))
207                : gEmptyString;
208#else
209        size_t chars = size / sizeof(char16_t);
210        return chars ? env->NewString(reinterpret_cast<jchar*>(
211                window->getFieldSlotValueString(fieldSlot)), chars)
212                : gEmptyString;
213#endif
214    } else if (type == FIELD_TYPE_INTEGER) {
215        int64_t value = window->getFieldSlotValueLong(fieldSlot);
216        char buf[32];
217        snprintf(buf, sizeof(buf), "%lld", value);
218        return env->NewStringUTF(buf);
219    } else if (type == FIELD_TYPE_FLOAT) {
220        double value = window->getFieldSlotValueDouble(fieldSlot);
221        char buf[32];
222        snprintf(buf, sizeof(buf), "%g", value);
223        return env->NewStringUTF(buf);
224    } else if (type == FIELD_TYPE_NULL) {
225        return NULL;
226    } else if (type == FIELD_TYPE_BLOB) {
227        throw_sqlite3_exception(env, "Unable to convert BLOB to string");
228        return NULL;
229    } else {
230        throwUnknownTypeException(env, type);
231        return NULL;
232    }
233}
234
235static jcharArray allocCharArrayBuffer(JNIEnv* env, jobject bufferObj, size_t size) {
236    jcharArray dataObj = jcharArray(env->GetObjectField(bufferObj,
237            gCharArrayBufferClassInfo.data));
238    if (dataObj && size) {
239        jsize capacity = env->GetArrayLength(dataObj);
240        if (size_t(capacity) < size) {
241            env->DeleteLocalRef(dataObj);
242            dataObj = NULL;
243        }
244    }
245    if (!dataObj) {
246        jsize capacity = size;
247        if (capacity < 64) {
248            capacity = 64;
249        }
250        dataObj = env->NewCharArray(capacity); // might throw OOM
251        if (dataObj) {
252            env->SetObjectField(bufferObj, gCharArrayBufferClassInfo.data, dataObj);
253        }
254    }
255    return dataObj;
256}
257
258static void fillCharArrayBufferUTF(JNIEnv* env, jobject bufferObj,
259        const char* str, size_t len) {
260    ssize_t size = utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(str), len);
261    if (size < 0) {
262        size = 0; // invalid UTF8 string
263    }
264    jcharArray dataObj = allocCharArrayBuffer(env, bufferObj, size);
265    if (dataObj) {
266        if (size) {
267            jchar* data = static_cast<jchar*>(env->GetPrimitiveArrayCritical(dataObj, NULL));
268            utf8_to_utf16(reinterpret_cast<const uint8_t*>(str), len,
269                    reinterpret_cast<char16_t*>(data));
270            env->ReleasePrimitiveArrayCritical(dataObj, data, 0);
271        }
272        env->SetIntField(bufferObj, gCharArrayBufferClassInfo.sizeCopied, size);
273    }
274}
275
276#if !WINDOW_STORAGE_UTF8
277static void fillCharArrayBuffer(JNIEnv* env, jobject bufferObj,
278        const char16_t* str, size_t len) {
279    jcharArray dataObj = allocCharArrayBuffer(env, bufferObj, len);
280    if (dataObj) {
281        if (len) {
282            jchar* data = static_cast<jchar*>(env->GetPrimitiveArrayCritical(dataObj, NULL));
283            memcpy(data, str, len * sizeof(jchar));
284            env->ReleasePrimitiveArrayCritical(dataObj, data, 0);
285        }
286        env->SetIntField(bufferObj, gCharArrayBufferClassInfo.sizeCopied, len);
287    }
288}
289#endif
290
291static void clearCharArrayBuffer(JNIEnv* env, jobject bufferObj) {
292    jcharArray dataObj = allocCharArrayBuffer(env, bufferObj, 0);
293    if (dataObj) {
294        env->SetIntField(bufferObj, gCharArrayBufferClassInfo.sizeCopied, 0);
295    }
296}
297
298static void nativeCopyStringToBuffer(JNIEnv* env, jclass clazz, jint windowPtr,
299        jint row, jint column, jobject bufferObj) {
300    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
301    LOG_WINDOW("Copying string for %d,%d from %p", row, column, window);
302
303    field_slot_t* fieldSlot = window->getFieldSlotWithCheck(row, column);
304    if (!fieldSlot) {
305        throwExceptionWithRowCol(env, row, column);
306        return;
307    }
308
309    uint8_t type = fieldSlot->type;
310    if (type == FIELD_TYPE_STRING) {
311        uint32_t size = fieldSlot->data.buffer.size;
312#if WINDOW_STORAGE_UTF8
313        if (size > 1) {
314            fillCharArrayBufferUTF(env, bufferObj,
315                    window->getFieldSlotValueString(fieldSlot), size - 1);
316        } else {
317            clearCharArrayBuffer(env, bufferObj);
318        }
319#else
320        size_t chars = size / sizeof(char16_t);
321        if (chars) {
322            fillCharArrayBuffer(env, bufferObj,
323                    window->getFieldSlotValueString(fieldSlot), chars);
324        } else {
325            clearCharArrayBuffer(env, bufferObj);
326        }
327#endif
328    } else if (type == FIELD_TYPE_INTEGER) {
329        int64_t value = window->getFieldSlotValueLong(fieldSlot);
330        char buf[32];
331        snprintf(buf, sizeof(buf), "%lld", value);
332        fillCharArrayBufferUTF(env, bufferObj, buf, strlen(buf));
333    } else if (type == FIELD_TYPE_FLOAT) {
334        double value = window->getFieldSlotValueDouble(fieldSlot);
335        char buf[32];
336        snprintf(buf, sizeof(buf), "%g", value);
337        fillCharArrayBufferUTF(env, bufferObj, buf, strlen(buf));
338    } else if (type == FIELD_TYPE_NULL) {
339        clearCharArrayBuffer(env, bufferObj);
340    } else if (type == FIELD_TYPE_BLOB) {
341        throw_sqlite3_exception(env, "Unable to convert BLOB to string");
342    } else {
343        throwUnknownTypeException(env, type);
344    }
345}
346
347static jlong nativeGetLong(JNIEnv* env, jclass clazz, jint windowPtr,
348        jint row, jint column) {
349    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
350    LOG_WINDOW("Getting long for %d,%d from %p", row, column, window);
351
352    field_slot_t* fieldSlot = window->getFieldSlotWithCheck(row, column);
353    if (!fieldSlot) {
354        throwExceptionWithRowCol(env, row, column);
355        return 0;
356    }
357
358    uint8_t type = fieldSlot->type;
359    if (type == FIELD_TYPE_INTEGER) {
360        return window->getFieldSlotValueLong(fieldSlot);
361    } else if (type == FIELD_TYPE_STRING) {
362        uint32_t size = fieldSlot->data.buffer.size;
363#if WINDOW_STORAGE_UTF8
364        return size > 1 ? strtoll(window->getFieldSlotValueString(fieldSlot), NULL, 0) : 0L;
365#else
366        size_t chars = size / sizeof(char16_t);
367        return chars ? strtoll(String8(window->getFieldSlotValueString(fieldSlot), chars)
368                .string(), NULL, 0) : 0L;
369#endif
370    } else if (type == FIELD_TYPE_FLOAT) {
371        return jlong(window->getFieldSlotValueDouble(fieldSlot));
372    } else if (type == FIELD_TYPE_NULL) {
373        return 0;
374    } else if (type == FIELD_TYPE_BLOB) {
375        throw_sqlite3_exception(env, "Unable to convert BLOB to long");
376        return 0;
377    } else {
378        throwUnknownTypeException(env, type);
379        return 0;
380    }
381}
382
383static jdouble nativeGetDouble(JNIEnv* env, jclass clazz, jint windowPtr,
384        jint row, jint column) {
385    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
386    LOG_WINDOW("Getting double for %d,%d from %p", row, column, window);
387
388    field_slot_t* fieldSlot = window->getFieldSlotWithCheck(row, column);
389    if (!fieldSlot) {
390        throwExceptionWithRowCol(env, row, column);
391        return 0.0;
392    }
393
394    uint8_t type = fieldSlot->type;
395    if (type == FIELD_TYPE_FLOAT) {
396        return window->getFieldSlotValueDouble(fieldSlot);
397    } else if (type == FIELD_TYPE_STRING) {
398        uint32_t size = fieldSlot->data.buffer.size;
399#if WINDOW_STORAGE_UTF8
400        return size > 1 ? strtod(window->getFieldSlotValueString(fieldSlot), NULL) : 0.0;
401#else
402        size_t chars = size / sizeof(char16_t);
403        return chars ? strtod(String8(window->getFieldSlotValueString(fieldSlot), chars)
404                .string(), NULL) : 0.0;
405#endif
406    } else if (type == FIELD_TYPE_INTEGER) {
407        return jdouble(window->getFieldSlotValueLong(fieldSlot));
408    } else if (type == FIELD_TYPE_NULL) {
409        return 0.0;
410    } else if (type == FIELD_TYPE_BLOB) {
411        throw_sqlite3_exception(env, "Unable to convert BLOB to double");
412        return 0.0;
413    } else {
414        throwUnknownTypeException(env, type);
415        return 0.0;
416    }
417}
418
419static jboolean nativePutBlob(JNIEnv* env, jclass clazz, jint windowPtr,
420        jbyteArray valueObj, jint row, jint column) {
421    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
422    field_slot_t * fieldSlot = window->getFieldSlotWithCheck(row, column);
423    if (fieldSlot == NULL) {
424        LOG_WINDOW(" getFieldSlotWithCheck error ");
425        return false;
426    }
427
428    jsize len = env->GetArrayLength(valueObj);
429    uint32_t offset = window->alloc(len);
430    if (!offset) {
431        LOG_WINDOW("Failed allocating %u bytes", len);
432        return false;
433    }
434
435    void* value = env->GetPrimitiveArrayCritical(valueObj, NULL);
436    window->copyIn(offset, static_cast<const uint8_t*>(value), len);
437    env->ReleasePrimitiveArrayCritical(valueObj, value, JNI_ABORT);
438
439    fieldSlot->type = FIELD_TYPE_BLOB;
440    fieldSlot->data.buffer.offset = offset;
441    fieldSlot->data.buffer.size = len;
442    LOG_WINDOW("%d,%d is BLOB with %u bytes @ %d", row, column, len, offset);
443    return true;
444}
445
446static jboolean nativePutString(JNIEnv* env, jclass clazz, jint windowPtr,
447        jstring valueObj, jint row, jint column) {
448    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
449    field_slot_t * fieldSlot = window->getFieldSlotWithCheck(row, column);
450    if (fieldSlot == NULL) {
451        LOG_WINDOW(" getFieldSlotWithCheck error ");
452        return false;
453    }
454
455#if WINDOW_STORAGE_UTF8
456    size_t size = env->GetStringUTFLength(valueObj) + 1;
457    const char* valueStr = env->GetStringUTFChars(valueObj, NULL);
458#else
459    size_t size = env->GetStringLength(valueObj) * sizeof(jchar);
460    const jchar* valueStr = env->GetStringChars(valueObj, NULL);
461#endif
462    if (!valueStr) {
463        LOG_WINDOW("value can't be transfer to UTFChars");
464        return false;
465    }
466
467    uint32_t offset = window->alloc(size);
468    if (!offset) {
469        LOG_WINDOW("Failed allocating %u bytes", size);
470#if WINDOW_STORAGE_UTF8
471        env->ReleaseStringUTFChars(valueObj, valueStr);
472#else
473        env->ReleaseStringChars(valueObj, valueStr);
474#endif
475        return false;
476    }
477
478    window->copyIn(offset, reinterpret_cast<const uint8_t*>(valueStr), size);
479
480#if WINDOW_STORAGE_UTF8
481    env->ReleaseStringUTFChars(valueObj, valueStr);
482#else
483    env->ReleaseStringChars(valueObj, valueStr);
484#endif
485
486    fieldSlot->type = FIELD_TYPE_STRING;
487    fieldSlot->data.buffer.offset = offset;
488    fieldSlot->data.buffer.size = size;
489    LOG_WINDOW("%d,%d is TEXT with %u bytes @ %d", row, column, size, offset);
490    return true;
491}
492
493static jboolean nativePutLong(JNIEnv* env, jclass clazz, jint windowPtr,
494        jlong value, jint row, jint column) {
495    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
496    if (!window->putLong(row, column, value)) {
497        LOG_WINDOW(" getFieldSlotWithCheck error ");
498        return false;
499    }
500
501    LOG_WINDOW("%d,%d is INTEGER 0x%016llx", row, column, value);
502    return true;
503}
504
505static jboolean nativePutDouble(JNIEnv* env, jclass clazz, jint windowPtr,
506        jdouble value, jint row, jint column) {
507    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
508    if (!window->putDouble(row, column, value)) {
509        LOG_WINDOW(" getFieldSlotWithCheck error ");
510        return false;
511    }
512
513    LOG_WINDOW("%d,%d is FLOAT %lf", row, column, value);
514    return true;
515}
516
517static jboolean nativePutNull(JNIEnv* env, jclass clazz, jint windowPtr,
518        jint row, jint column) {
519    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
520    if (!window->putNull(row, column)) {
521        LOG_WINDOW(" getFieldSlotWithCheck error ");
522        return false;
523    }
524
525    LOG_WINDOW("%d,%d is NULL", row, column);
526    return true;
527}
528
529static JNINativeMethod sMethods[] =
530{
531    /* name, signature, funcPtr */
532    { "nativeInitializeEmpty", "(IZ)I",
533            (void*)nativeInitializeEmpty },
534    { "nativeInitializeFromBinder", "(Landroid/os/IBinder;)I",
535            (void*)nativeInitializeFromBinder },
536    { "nativeDispose", "(I)V",
537            (void*)nativeDispose },
538    { "nativeGetBinder", "(I)Landroid/os/IBinder;",
539            (void*)nativeGetBinder },
540    { "nativeClear", "(I)V",
541            (void*)nativeClear },
542    { "nativeGetNumRows", "(I)I",
543            (void*)nativeGetNumRows },
544    { "nativeSetNumColumns", "(II)Z",
545            (void*)nativeSetNumColumns },
546    { "nativeAllocRow", "(I)Z",
547            (void*)nativeAllocRow },
548    { "nativeFreeLastRow", "(I)V",
549            (void*)nativeFreeLastRow },
550    { "nativeGetType", "(III)I",
551            (void*)nativeGetType },
552    { "nativeGetBlob", "(III)[B",
553            (void*)nativeGetBlob },
554    { "nativeGetString", "(III)Ljava/lang/String;",
555            (void*)nativeGetString },
556    { "nativeGetLong", "(III)J",
557            (void*)nativeGetLong },
558    { "nativeGetDouble", "(III)D",
559            (void*)nativeGetDouble },
560    { "nativeCopyStringToBuffer", "(IIILandroid/database/CharArrayBuffer;)V",
561            (void*)nativeCopyStringToBuffer },
562    { "nativePutBlob", "(I[BII)Z",
563            (void*)nativePutBlob },
564    { "nativePutString", "(ILjava/lang/String;II)Z",
565            (void*)nativePutString },
566    { "nativePutLong", "(IJII)Z",
567            (void*)nativePutLong },
568    { "nativePutDouble", "(IDII)Z",
569            (void*)nativePutDouble },
570    { "nativePutNull", "(III)Z",
571            (void*)nativePutNull },
572};
573
574#define FIND_CLASS(var, className) \
575        var = env->FindClass(className); \
576        LOG_FATAL_IF(! var, "Unable to find class " className);
577
578#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
579        var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
580        LOG_FATAL_IF(! var, "Unable to find field " fieldName);
581
582int register_android_database_CursorWindow(JNIEnv * env)
583{
584    jclass clazz;
585    FIND_CLASS(clazz, "android/database/CharArrayBuffer");
586
587    GET_FIELD_ID(gCharArrayBufferClassInfo.data, clazz,
588            "data", "[C");
589    GET_FIELD_ID(gCharArrayBufferClassInfo.sizeCopied, clazz,
590            "sizeCopied", "I");
591
592    gEmptyString = jstring(env->NewGlobalRef(env->NewStringUTF("")));
593    LOG_FATAL_IF(!gEmptyString, "Unable to create empty string");
594
595    return AndroidRuntime::registerNativeMethods(env, "android/database/CursorWindow",
596            sMethods, NELEM(sMethods));
597}
598
599} // namespace android
600