android_database_CursorWindow.cpp revision b37f8a8af15425ed3e65104e8556099d33a9c1ce
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
28#include <stdio.h>
29#include <string.h>
30#include <unistd.h>
31
32#include "binder/CursorWindow.h"
33#include "sqlite3_exception.h"
34#include "android_util_Binder.h"
35
36
37namespace android {
38
39static jfieldID gWindowField;
40static jfieldID gBufferField;
41static jfieldID gSizeCopiedField;
42
43#define GET_WINDOW(env, object) ((CursorWindow *)env->GetIntField(object, gWindowField))
44#define SET_WINDOW(env, object, window) (env->SetIntField(object, gWindowField, (int)window))
45#define SET_BUFFER(env, object, buf) (env->SetObjectField(object, gBufferField, buf))
46#define SET_SIZE_COPIED(env, object, size) (env->SetIntField(object, gSizeCopiedField, size))
47
48CursorWindow * get_window_from_object(JNIEnv * env, jobject javaWindow)
49{
50    return GET_WINDOW(env, javaWindow);
51}
52
53static void native_init_empty(JNIEnv * env, jobject object, jboolean localOnly)
54{
55    uint8_t * data;
56    size_t size;
57    CursorWindow * window;
58
59    window = new CursorWindow(MAX_WINDOW_SIZE);
60    if (!window) {
61        jniThrowException(env, "java/lang/RuntimeException", "No memory for native window object");
62        return;
63    }
64
65    if (!window->initBuffer(localOnly)) {
66        jniThrowException(env, "java/lang/RuntimeException",
67                "Memory couldn't be allocated for 1MB CursorWindow object.");
68        delete window;
69        return;
70    }
71
72LOG_WINDOW("native_init_empty: window = %p", window);
73    SET_WINDOW(env, object, window);
74}
75
76static void native_init_memory(JNIEnv * env, jobject object, jobject memObj)
77{
78    sp<IMemory> memory = interface_cast<IMemory>(ibinderForJavaObject(env, memObj));
79    if (memory == NULL) {
80        jniThrowException(env, "java/lang/IllegalStateException", "Couldn't get native binder");
81        return;
82    }
83
84    CursorWindow * window = new CursorWindow();
85    if (!window) {
86        jniThrowException(env, "java/lang/RuntimeException",
87                "CursorWindow of size 1MB couldn't be created. No memory?");
88        return;
89    }
90    if (!window->setMemory(memory)) {
91        jniThrowException(env, "java/lang/RuntimeException",
92                "Memory couldn't be initialized for 1MB CursorWindow object.");
93        delete window;
94        return;
95    }
96
97LOG_WINDOW("native_init_memory: numRows = %d, numColumns = %d, window = %p", window->getNumRows(), window->getNumColumns(), window);
98    SET_WINDOW(env, object, window);
99}
100
101static jobject native_getBinder(JNIEnv * env, jobject object)
102{
103    CursorWindow * window = GET_WINDOW(env, object);
104    if (window) {
105        sp<IMemory> memory = window->getMemory();
106        if (memory != NULL) {
107            sp<IBinder> binder = memory->asBinder();
108            return javaObjectForIBinder(env, binder);
109        }
110    }
111    return NULL;
112}
113
114static void native_clear(JNIEnv * env, jobject object)
115{
116    CursorWindow * window = GET_WINDOW(env, object);
117LOG_WINDOW("Clearing window %p", window);
118    if (window == NULL) {
119        jniThrowException(env, "java/lang/IllegalStateException", "clear() called after close()");
120        return;
121    }
122    window->clear();
123}
124
125static void native_close(JNIEnv * env, jobject object)
126{
127    CursorWindow * window = GET_WINDOW(env, object);
128    if (window) {
129LOG_WINDOW("Closing window %p", window);
130        delete window;
131        SET_WINDOW(env, object, 0);
132    }
133}
134
135static void throwExceptionWithRowCol(JNIEnv * env, jint row, jint column)
136{
137    char buf[200];
138    snprintf(buf, sizeof(buf), "Couldn't read row %d, col %d from CursorWindow. Make sure the Cursor is initialized correctly before accessing data from it",
139            row, column);
140    jniThrowException(env, "java/lang/IllegalStateException", buf);
141}
142
143static void throwUnknowTypeException(JNIEnv * env, jint type)
144{
145    char buf[80];
146    snprintf(buf, sizeof(buf), "UNKNOWN type %d", type);
147    jniThrowException(env, "java/lang/IllegalStateException", buf);
148}
149
150static jlong getLong_native(JNIEnv * env, jobject object, jint row, jint column)
151{
152    int32_t err;
153    CursorWindow * window = GET_WINDOW(env, object);
154LOG_WINDOW("Getting long for %d,%d from %p", row, column, window);
155
156    field_slot_t field;
157    err = window->read_field_slot(row, column, &field);
158    if (err != 0) {
159        throwExceptionWithRowCol(env, row, column);
160        return 0;
161    }
162
163    uint8_t type = field.type;
164    if (type == FIELD_TYPE_INTEGER) {
165        int64_t value;
166        if (window->getLong(row, column, &value)) {
167            return value;
168        }
169        return 0;
170    } else if (type == FIELD_TYPE_STRING) {
171        uint32_t size = field.data.buffer.size;
172        if (size > 0) {
173#if WINDOW_STORAGE_UTF8
174            return strtoll((char const *)window->offsetToPtr(field.data.buffer.offset), NULL, 0);
175#else
176            String8 ascii((char16_t *) window->offsetToPtr(field.data.buffer.offset), size / 2);
177            char const * str = ascii.string();
178            return strtoll(str, NULL, 0);
179#endif
180        } else {
181            return 0;
182        }
183    } else if (type == FIELD_TYPE_FLOAT) {
184        double value;
185        if (window->getDouble(row, column, &value)) {
186            return value;
187        }
188        return 0;
189    } else if (type == FIELD_TYPE_NULL) {
190        return 0;
191    } else if (type == FIELD_TYPE_BLOB) {
192        throw_sqlite3_exception(env, "Unable to convert BLOB to long");
193        return 0;
194    } else {
195        throwUnknowTypeException(env, type);
196        return 0;
197    }
198}
199
200static jbyteArray getBlob_native(JNIEnv* env, jobject object, jint row, jint column)
201{
202    int32_t err;
203    CursorWindow * window = GET_WINDOW(env, object);
204LOG_WINDOW("Getting blob for %d,%d from %p", row, column, window);
205
206    field_slot_t field;
207    err = window->read_field_slot(row, column, &field);
208    if (err != 0) {
209        throwExceptionWithRowCol(env, row, column);
210        return NULL;
211    }
212
213    uint8_t type = field.type;
214    if (type == FIELD_TYPE_BLOB || type == FIELD_TYPE_STRING) {
215        jbyteArray byteArray = env->NewByteArray(field.data.buffer.size);
216        LOG_ASSERT(byteArray, "Native could not create new byte[]");
217        env->SetByteArrayRegion(byteArray, 0, field.data.buffer.size,
218            (const jbyte*)window->offsetToPtr(field.data.buffer.offset));
219        return byteArray;
220    } else if (type == FIELD_TYPE_INTEGER) {
221        throw_sqlite3_exception(env, "INTEGER data in getBlob_native ");
222    } else if (type == FIELD_TYPE_FLOAT) {
223        throw_sqlite3_exception(env, "FLOAT data in getBlob_native ");
224    } else if (type == FIELD_TYPE_NULL) {
225        // do nothing
226    } else {
227        throwUnknowTypeException(env, type);
228    }
229    return NULL;
230}
231
232static jstring getString_native(JNIEnv* env, jobject object, jint row, jint column)
233{
234    int32_t err;
235    CursorWindow * window = GET_WINDOW(env, object);
236LOG_WINDOW("Getting string for %d,%d from %p", row, column, window);
237
238    field_slot_t field;
239    err = window->read_field_slot(row, column, &field);
240    if (err != 0) {
241        throwExceptionWithRowCol(env, row, column);
242        return NULL;
243    }
244
245    uint8_t type = field.type;
246    if (type == FIELD_TYPE_STRING) {
247        uint32_t size = field.data.buffer.size;
248        if (size > 0) {
249#if WINDOW_STORAGE_UTF8
250            // Pass size - 1 since the UTF8 is null terminated and we don't want a null terminator on the UTF16 string
251            String16 utf16((char const *)window->offsetToPtr(field.data.buffer.offset), size - 1);
252            return env->NewString((jchar const *)utf16.string(), utf16.size());
253#else
254            return env->NewString((jchar const *)window->offsetToPtr(field.data.buffer.offset), size / 2);
255#endif
256        } else {
257            return env->NewStringUTF("");
258        }
259    } else if (type == FIELD_TYPE_INTEGER) {
260        int64_t value;
261        if (window->getLong(row, column, &value)) {
262            char buf[32];
263            snprintf(buf, sizeof(buf), "%lld", value);
264            return env->NewStringUTF(buf);
265        }
266        return NULL;
267    } else if (type == FIELD_TYPE_FLOAT) {
268        double value;
269        if (window->getDouble(row, column, &value)) {
270            char buf[32];
271            snprintf(buf, sizeof(buf), "%g", value);
272            return env->NewStringUTF(buf);
273        }
274        return NULL;
275    } else if (type == FIELD_TYPE_NULL) {
276        return NULL;
277    } else if (type == FIELD_TYPE_BLOB) {
278        throw_sqlite3_exception(env, "Unable to convert BLOB to string");
279        return NULL;
280    } else {
281        throwUnknowTypeException(env, type);
282        return NULL;
283    }
284}
285
286/**
287 * Use this only to convert characters that are known to be within the
288 * 0-127 range for direct conversion to UTF-16
289 */
290static jint charToJchar(const char* src, jchar* dst, jint bufferSize)
291{
292    int32_t len = strlen(src);
293
294    if (bufferSize < len) {
295        len = bufferSize;
296    }
297
298    for (int i = 0; i < len; i++) {
299        *dst++ = (*src++ & 0x7F);
300    }
301    return len;
302}
303
304static jcharArray copyStringToBuffer_native(JNIEnv* env, jobject object, jint row,
305                                      jint column, jint bufferSize, jobject buf)
306{
307    int32_t err;
308    CursorWindow * window = GET_WINDOW(env, object);
309LOG_WINDOW("Copying string for %d,%d from %p", row, column, window);
310
311    field_slot_t field;
312    err = window->read_field_slot(row, column, &field);
313    if (err != 0) {
314        jniThrowException(env, "java/lang/IllegalStateException", "Unable to get field slot");
315        return NULL;
316    }
317
318    jcharArray buffer = (jcharArray)env->GetObjectField(buf, gBufferField);
319    if (buffer == NULL) {
320        jniThrowException(env, "java/lang/IllegalStateException", "buf should not be null");
321        return NULL;
322    }
323    jchar* dst = env->GetCharArrayElements(buffer, NULL);
324    uint8_t type = field.type;
325    uint32_t sizeCopied = 0;
326    jcharArray newArray = NULL;
327    if (type == FIELD_TYPE_STRING) {
328        uint32_t size = field.data.buffer.size;
329        if (size > 0) {
330#if WINDOW_STORAGE_UTF8
331            // Pass size - 1 since the UTF8 is null terminated and we don't want a null terminator on the UTF16 string
332            String16 utf16((char const *)window->offsetToPtr(field.data.buffer.offset), size - 1);
333            int32_t strSize = utf16.size();
334            if (strSize > bufferSize || dst == NULL) {
335                newArray = env->NewCharArray(strSize);
336                env->SetCharArrayRegion(newArray, 0, strSize, (jchar const *)utf16.string());
337            } else {
338                memcpy(dst, (jchar const *)utf16.string(), strSize * 2);
339            }
340            sizeCopied = strSize;
341#else
342            sizeCopied = size/2 + size % 2;
343            if (size > bufferSize * 2 || dst == NULL) {
344                newArray = env->NewCharArray(sizeCopied);
345                memcpy(newArray, (jchar const *)window->offsetToPtr(field.data.buffer.offset), size);
346            } else {
347                memcpy(dst, (jchar const *)window->offsetToPtr(field.data.buffer.offset), size);
348            }
349#endif
350        }
351    } else if (type == FIELD_TYPE_INTEGER) {
352        int64_t value;
353        if (window->getLong(row, column, &value)) {
354            char buf[32];
355            int len;
356            snprintf(buf, sizeof(buf), "%lld", value);
357            jchar* dst = env->GetCharArrayElements(buffer, NULL);
358            sizeCopied = charToJchar(buf, dst, bufferSize);
359         }
360    } else if (type == FIELD_TYPE_FLOAT) {
361        double value;
362        if (window->getDouble(row, column, &value)) {
363            char tempbuf[32];
364            snprintf(tempbuf, sizeof(tempbuf), "%g", value);
365            jchar* dst = env->GetCharArrayElements(buffer, NULL);
366            sizeCopied = charToJchar(tempbuf, dst, bufferSize);
367        }
368    } else if (type == FIELD_TYPE_NULL) {
369    } else if (type == FIELD_TYPE_BLOB) {
370        throw_sqlite3_exception(env, "Unable to convert BLOB to string");
371    } else {
372        LOGE("Unknown field type %d", type);
373        throw_sqlite3_exception(env, "UNKNOWN type in copyStringToBuffer_native()");
374    }
375    SET_SIZE_COPIED(env, buf, sizeCopied);
376    env->ReleaseCharArrayElements(buffer, dst, JNI_OK);
377    return newArray;
378}
379
380static jdouble getDouble_native(JNIEnv* env, jobject object, jint row, jint column)
381{
382    int32_t err;
383    CursorWindow * window = GET_WINDOW(env, object);
384LOG_WINDOW("Getting double for %d,%d from %p", row, column, window);
385
386    field_slot_t field;
387    err = window->read_field_slot(row, column, &field);
388    if (err != 0) {
389        throwExceptionWithRowCol(env, row, column);
390        return 0.0;
391    }
392
393    uint8_t type = field.type;
394    if (type == FIELD_TYPE_FLOAT) {
395        double value;
396        if (window->getDouble(row, column, &value)) {
397            return value;
398        }
399        return 0.0;
400    } else if (type == FIELD_TYPE_STRING) {
401        uint32_t size = field.data.buffer.size;
402        if (size > 0) {
403#if WINDOW_STORAGE_UTF8
404            return strtod((char const *)window->offsetToPtr(field.data.buffer.offset), NULL);
405#else
406            String8 ascii((char16_t *) window->offsetToPtr(field.data.buffer.offset), size / 2);
407            char const * str = ascii.string();
408            return strtod(str, NULL);
409#endif
410        } else {
411            return 0.0;
412        }
413    } else if (type == FIELD_TYPE_INTEGER) {
414        int64_t value;
415        if (window->getLong(row, column, &value)) {
416            return (double) value;
417        }
418        return 0.0;
419    } else if (type == FIELD_TYPE_NULL) {
420        return 0.0;
421    } else if (type == FIELD_TYPE_BLOB) {
422        throw_sqlite3_exception(env, "Unable to convert BLOB to double");
423        return 0.0;
424    } else {
425        throwUnknowTypeException(env, type);
426        return 0.0;
427    }
428}
429
430bool isNull_native(CursorWindow *window, jint row, jint column)
431{
432    LOG_WINDOW("Checking for NULL at %d,%d from %p", row, column, window);
433
434    bool isNull;
435    if (window->getNull(row, column, &isNull)) {
436        return isNull;
437    }
438
439    //TODO throw execption?
440    return true;
441}
442
443static jint getNumRows(JNIEnv * env, jobject object)
444{
445    CursorWindow * window = GET_WINDOW(env, object);
446    return window->getNumRows();
447}
448
449static jboolean setNumColumns(JNIEnv * env, jobject object, jint columnNum)
450{
451    CursorWindow * window = GET_WINDOW(env, object);
452    return window->setNumColumns(columnNum);
453}
454
455static jboolean allocRow(JNIEnv * env, jobject object)
456{
457    CursorWindow * window = GET_WINDOW(env, object);
458    return window->allocRow() != NULL;
459}
460
461static jboolean putBlob_native(JNIEnv * env, jobject object, jbyteArray value, jint row, jint col)
462{
463    CursorWindow * window = GET_WINDOW(env, object);
464    if (!value) {
465        LOG_WINDOW("How did a null value send to here");
466        return false;
467    }
468    field_slot_t * fieldSlot = window->getFieldSlotWithCheck(row, col);
469    if (fieldSlot == NULL) {
470        LOG_WINDOW(" getFieldSlotWithCheck error ");
471        return false;
472    }
473
474    jint len = env->GetArrayLength(value);
475    int offset = window->alloc(len);
476    if (!offset) {
477        LOG_WINDOW("Failed allocating %u bytes", len);
478        return false;
479    }
480    jbyte * bytes = env->GetByteArrayElements(value, NULL);
481    window->copyIn(offset, (uint8_t const *)bytes, len);
482
483    // This must be updated after the call to alloc(), since that
484    // may move the field around in the window
485    fieldSlot->type = FIELD_TYPE_BLOB;
486    fieldSlot->data.buffer.offset = offset;
487    fieldSlot->data.buffer.size = len;
488    env->ReleaseByteArrayElements(value, bytes, JNI_ABORT);
489    LOG_WINDOW("%d,%d is BLOB with %u bytes @ %d", row, col, len, offset);
490    return true;
491}
492
493static jboolean putString_native(JNIEnv * env, jobject object, jstring value, jint row, jint col)
494{
495    CursorWindow * window = GET_WINDOW(env, object);
496    if (!value) {
497        LOG_WINDOW("How did a null value send to here");
498        return false;
499    }
500    field_slot_t * fieldSlot = window->getFieldSlotWithCheck(row, col);
501    if (fieldSlot == NULL) {
502        LOG_WINDOW(" getFieldSlotWithCheck error ");
503        return false;
504    }
505
506#if WINDOW_STORAGE_UTF8
507    int len = env->GetStringUTFLength(value) + 1;
508    char const * valStr = env->GetStringUTFChars(value, NULL);
509#else
510    int len = env->GetStringLength(value);
511    // GetStringLength return number of chars and one char takes 2 bytes
512    len *= 2;
513    const jchar* valStr = env->GetStringChars(value, NULL);
514#endif
515    if (!valStr) {
516        LOG_WINDOW("value can't be transfer to UTFChars");
517        return false;
518    }
519
520    int offset = window->alloc(len);
521    if (!offset) {
522        LOG_WINDOW("Failed allocating %u bytes", len);
523#if WINDOW_STORAGE_UTF8
524        env->ReleaseStringUTFChars(value, valStr);
525#else
526        env->ReleaseStringChars(value, valStr);
527#endif
528        return false;
529    }
530
531    window->copyIn(offset, (uint8_t const *)valStr, len);
532
533    // This must be updated after the call to alloc(), since that
534    // may move the field around in the window
535    fieldSlot->type = FIELD_TYPE_STRING;
536    fieldSlot->data.buffer.offset = offset;
537    fieldSlot->data.buffer.size = len;
538
539    LOG_WINDOW("%d,%d is TEXT with %u bytes @ %d", row, col, len, offset);
540#if WINDOW_STORAGE_UTF8
541    env->ReleaseStringUTFChars(value, valStr);
542#else
543    env->ReleaseStringChars(value, valStr);
544#endif
545
546    return true;
547}
548
549static jboolean putLong_native(JNIEnv * env, jobject object, jlong value, jint row, jint col)
550{
551    CursorWindow * window = GET_WINDOW(env, object);
552    if (!window->putLong(row, col, value)) {
553        LOG_WINDOW(" getFieldSlotWithCheck error ");
554        return false;
555    }
556
557    LOG_WINDOW("%d,%d is INTEGER 0x%016llx", row, col, value);
558
559    return true;
560}
561
562static jboolean putDouble_native(JNIEnv * env, jobject object, jdouble value, jint row, jint col)
563{
564    CursorWindow * window = GET_WINDOW(env, object);
565    if (!window->putDouble(row, col, value)) {
566        LOG_WINDOW(" getFieldSlotWithCheck error ");
567        return false;
568    }
569
570    LOG_WINDOW("%d,%d is FLOAT %lf", row, col, value);
571
572    return true;
573}
574
575static jboolean putNull_native(JNIEnv * env, jobject object, jint row, jint col)
576{
577    CursorWindow * window = GET_WINDOW(env, object);
578    if (!window->putNull(row, col)) {
579        LOG_WINDOW(" getFieldSlotWithCheck error ");
580        return false;
581    }
582
583    LOG_WINDOW("%d,%d is NULL", row, col);
584
585    return true;
586}
587
588// free the last row
589static void freeLastRow(JNIEnv * env, jobject object) {
590    CursorWindow * window = GET_WINDOW(env, object);
591    window->freeLastRow();
592}
593
594static jint getType_native(JNIEnv* env, jobject object, jint row, jint column)
595{
596    int32_t err;
597    CursorWindow * window = GET_WINDOW(env, object);
598    LOG_WINDOW("returning column type affinity for %d,%d from %p", row, column, window);
599
600    if (isNull_native(window, row, column)) {
601      return FIELD_TYPE_NULL;
602    }
603
604    field_slot_t field;
605    err = window->read_field_slot(row, column, &field);
606    if (err != 0) {
607        throwExceptionWithRowCol(env, row, column);
608        return NULL;
609    }
610
611    return field.type;
612}
613
614static JNINativeMethod sMethods[] =
615{
616     /* name, signature, funcPtr */
617    {"native_init", "(Z)V", (void *)native_init_empty},
618    {"native_init", "(Landroid/os/IBinder;)V", (void *)native_init_memory},
619    {"native_getBinder", "()Landroid/os/IBinder;", (void *)native_getBinder},
620    {"native_clear", "()V", (void *)native_clear},
621    {"close_native", "()V", (void *)native_close},
622    {"getLong_native", "(II)J", (void *)getLong_native},
623    {"getBlob_native", "(II)[B", (void *)getBlob_native},
624    {"getString_native", "(II)Ljava/lang/String;", (void *)getString_native},
625    {"copyStringToBuffer_native", "(IIILandroid/database/CharArrayBuffer;)[C", (void *)copyStringToBuffer_native},
626    {"getDouble_native", "(II)D", (void *)getDouble_native},
627    {"getNumRows_native", "()I", (void *)getNumRows},
628    {"setNumColumns_native", "(I)Z", (void *)setNumColumns},
629    {"allocRow_native", "()Z", (void *)allocRow},
630    {"putBlob_native", "([BII)Z", (void *)putBlob_native},
631    {"putString_native", "(Ljava/lang/String;II)Z", (void *)putString_native},
632    {"putLong_native", "(JII)Z", (void *)putLong_native},
633    {"putDouble_native", "(DII)Z", (void *)putDouble_native},
634    {"freeLastRow_native", "()V", (void *)freeLastRow},
635    {"putNull_native", "(II)Z", (void *)putNull_native},
636    {"getType_native", "(II)I", (void *)getType_native},
637};
638
639int register_android_database_CursorWindow(JNIEnv * env)
640{
641    jclass clazz;
642
643    clazz = env->FindClass("android/database/CursorWindow");
644    if (clazz == NULL) {
645        LOGE("Can't find android/database/CursorWindow");
646        return -1;
647    }
648
649    gWindowField = env->GetFieldID(clazz, "nWindow", "I");
650
651    if (gWindowField == NULL) {
652        LOGE("Error locating fields");
653        return -1;
654    }
655
656    clazz =  env->FindClass("android/database/CharArrayBuffer");
657    if (clazz == NULL) {
658        LOGE("Can't find android/database/CharArrayBuffer");
659        return -1;
660    }
661
662    gBufferField = env->GetFieldID(clazz, "data", "[C");
663
664    if (gBufferField == NULL) {
665        LOGE("Error locating fields data in CharArrayBuffer");
666        return -1;
667    }
668
669    gSizeCopiedField = env->GetFieldID(clazz, "sizeCopied", "I");
670
671    if (gSizeCopiedField == NULL) {
672        LOGE("Error locating fields sizeCopied in CharArrayBuffer");
673        return -1;
674    }
675
676    return AndroidRuntime::registerNativeMethods(env, "android/database/CursorWindow",
677            sMethods, NELEM(sMethods));
678}
679
680} // namespace android
681