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