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