Home | History | Annotate | Download | only in jni
      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 #define LOG_NDEBUG 0
     20 
     21 #include <inttypes.h>
     22 #include <jni.h>
     23 #include <nativehelper/JNIHelp.h>
     24 #include <android_runtime/AndroidRuntime.h>
     25 
     26 #include <utils/Log.h>
     27 #include <utils/String8.h>
     28 #include <utils/String16.h>
     29 #include <utils/Unicode.h>
     30 
     31 #include <stdio.h>
     32 #include <string.h>
     33 #include <unistd.h>
     34 #include <sys/types.h>
     35 #include <dirent.h>
     36 
     37 #undef LOG_NDEBUG
     38 #define LOG_NDEBUG 1
     39 
     40 #include <androidfw/CursorWindow.h>
     41 #include "android_os_Parcel.h"
     42 #include "android_util_Binder.h"
     43 #include "android_database_SQLiteCommon.h"
     44 
     45 #include "core_jni_helpers.h"
     46 
     47 namespace android {
     48 
     49 static struct {
     50     jfieldID data;
     51     jfieldID sizeCopied;
     52 } gCharArrayBufferClassInfo;
     53 
     54 static jstring gEmptyString;
     55 
     56 static void throwExceptionWithRowCol(JNIEnv* env, jint row, jint column) {
     57     String8 msg;
     58     msg.appendFormat("Couldn't read row %d, col %d from CursorWindow.  "
     59             "Make sure the Cursor is initialized correctly before accessing data from it.",
     60             row, column);
     61     jniThrowException(env, "java/lang/IllegalStateException", msg.string());
     62 }
     63 
     64 static void throwUnknownTypeException(JNIEnv * env, jint type) {
     65     String8 msg;
     66     msg.appendFormat("UNKNOWN type %d", type);
     67     jniThrowException(env, "java/lang/IllegalStateException", msg.string());
     68 }
     69 
     70 static int getFdCount() {
     71     char fdpath[PATH_MAX];
     72     int count = 0;
     73     snprintf(fdpath, PATH_MAX, "/proc/%d/fd", getpid());
     74     DIR *dir = opendir(fdpath);
     75     if (dir != NULL) {
     76         struct dirent *dirent;
     77         while ((dirent = readdir(dir))) {
     78             count++;
     79         }
     80         count -= 2; // discount "." and ".."
     81         closedir(dir);
     82     }
     83     return count;
     84 }
     85 
     86 static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring nameObj, jint cursorWindowSize) {
     87     String8 name;
     88     const char* nameStr = env->GetStringUTFChars(nameObj, NULL);
     89     name.setTo(nameStr);
     90     env->ReleaseStringUTFChars(nameObj, nameStr);
     91 
     92     CursorWindow* window;
     93     status_t status = CursorWindow::create(name, cursorWindowSize, &window);
     94     if (status || !window) {
     95         ALOGE("Could not allocate CursorWindow '%s' of size %d due to error %d.",
     96                 name.string(), cursorWindowSize, status);
     97         return 0;
     98     }
     99 
    100     LOG_WINDOW("nativeInitializeEmpty: window = %p", window);
    101     return reinterpret_cast<jlong>(window);
    102 }
    103 
    104 static jlong nativeCreateFromParcel(JNIEnv* env, jclass clazz, jobject parcelObj) {
    105     Parcel* parcel = parcelForJavaObject(env, parcelObj);
    106 
    107     CursorWindow* window;
    108     status_t status = CursorWindow::createFromParcel(parcel, &window);
    109     if (status || !window) {
    110         ALOGE("Could not create CursorWindow from Parcel due to error %d, process fd count=%d",
    111                 status, getFdCount());
    112         return 0;
    113     }
    114 
    115     LOG_WINDOW("nativeInitializeFromBinder: numRows = %d, numColumns = %d, window = %p",
    116             window->getNumRows(), window->getNumColumns(), window);
    117     return reinterpret_cast<jlong>(window);
    118 }
    119 
    120 static void nativeDispose(JNIEnv* env, jclass clazz, jlong windowPtr) {
    121     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    122     if (window) {
    123         LOG_WINDOW("Closing window %p", window);
    124         delete window;
    125     }
    126 }
    127 
    128 static jstring nativeGetName(JNIEnv* env, jclass clazz, jlong windowPtr) {
    129     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    130     return env->NewStringUTF(window->name().string());
    131 }
    132 
    133 static void nativeWriteToParcel(JNIEnv * env, jclass clazz, jlong windowPtr,
    134         jobject parcelObj) {
    135     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    136     Parcel* parcel = parcelForJavaObject(env, parcelObj);
    137 
    138     status_t status = window->writeToParcel(parcel);
    139     if (status) {
    140         String8 msg;
    141         msg.appendFormat("Could not write CursorWindow to Parcel due to error %d.", status);
    142         jniThrowRuntimeException(env, msg.string());
    143     }
    144 }
    145 
    146 static void nativeClear(JNIEnv * env, jclass clazz, jlong windowPtr) {
    147     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    148     LOG_WINDOW("Clearing window %p", window);
    149     status_t status = window->clear();
    150     if (status) {
    151         LOG_WINDOW("Could not clear window. error=%d", status);
    152     }
    153 }
    154 
    155 static jint nativeGetNumRows(JNIEnv* env, jclass clazz, jlong windowPtr) {
    156     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    157     return window->getNumRows();
    158 }
    159 
    160 static jboolean nativeSetNumColumns(JNIEnv* env, jclass clazz, jlong windowPtr,
    161         jint columnNum) {
    162     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    163     status_t status = window->setNumColumns(columnNum);
    164     return status == OK;
    165 }
    166 
    167 static jboolean nativeAllocRow(JNIEnv* env, jclass clazz, jlong windowPtr) {
    168     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    169     status_t status = window->allocRow();
    170     return status == OK;
    171 }
    172 
    173 static void nativeFreeLastRow(JNIEnv* env, jclass clazz, jlong windowPtr) {
    174     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    175     window->freeLastRow();
    176 }
    177 
    178 static jint nativeGetType(JNIEnv* env, jclass clazz, jlong windowPtr,
    179         jint row, jint column) {
    180     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    181     LOG_WINDOW("returning column type affinity for %d,%d from %p", row, column, window);
    182 
    183     CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
    184     if (!fieldSlot) {
    185         // FIXME: This is really broken but we have CTS tests that depend
    186         // on this legacy behavior.
    187         //throwExceptionWithRowCol(env, row, column);
    188         return CursorWindow::FIELD_TYPE_NULL;
    189     }
    190     return window->getFieldSlotType(fieldSlot);
    191 }
    192 
    193 static jbyteArray nativeGetBlob(JNIEnv* env, jclass clazz, jlong windowPtr,
    194         jint row, jint column) {
    195     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    196     LOG_WINDOW("Getting blob for %d,%d from %p", row, column, window);
    197 
    198     CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
    199     if (!fieldSlot) {
    200         throwExceptionWithRowCol(env, row, column);
    201         return NULL;
    202     }
    203 
    204     int32_t type = window->getFieldSlotType(fieldSlot);
    205     if (type == CursorWindow::FIELD_TYPE_BLOB || type == CursorWindow::FIELD_TYPE_STRING) {
    206         size_t size;
    207         const void* value = window->getFieldSlotValueBlob(fieldSlot, &size);
    208         if (!value) {
    209             throw_sqlite3_exception(env, "Native could not read blob slot");
    210             return NULL;
    211         }
    212         jbyteArray byteArray = env->NewByteArray(size);
    213         if (!byteArray) {
    214             env->ExceptionClear();
    215             throw_sqlite3_exception(env, "Native could not create new byte[]");
    216             return NULL;
    217         }
    218         env->SetByteArrayRegion(byteArray, 0, size, static_cast<const jbyte*>(value));
    219         return byteArray;
    220     } else if (type == CursorWindow::FIELD_TYPE_INTEGER) {
    221         throw_sqlite3_exception(env, "INTEGER data in nativeGetBlob ");
    222     } else if (type == CursorWindow::FIELD_TYPE_FLOAT) {
    223         throw_sqlite3_exception(env, "FLOAT data in nativeGetBlob ");
    224     } else if (type == CursorWindow::FIELD_TYPE_NULL) {
    225         // do nothing
    226     } else {
    227         throwUnknownTypeException(env, type);
    228     }
    229     return NULL;
    230 }
    231 
    232 static jstring nativeGetString(JNIEnv* env, jclass clazz, jlong windowPtr,
    233         jint row, jint column) {
    234     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    235     LOG_WINDOW("Getting string for %d,%d from %p", row, column, window);
    236 
    237     CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
    238     if (!fieldSlot) {
    239         throwExceptionWithRowCol(env, row, column);
    240         return NULL;
    241     }
    242 
    243     int32_t type = window->getFieldSlotType(fieldSlot);
    244     if (type == CursorWindow::FIELD_TYPE_STRING) {
    245         size_t sizeIncludingNull;
    246         const char* value = window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull);
    247         if (!value) {
    248             throw_sqlite3_exception(env, "Native could not read string slot");
    249             return NULL;
    250         }
    251         if (sizeIncludingNull <= 1) {
    252             return gEmptyString;
    253         }
    254         // Convert to UTF-16 here instead of calling NewStringUTF.  NewStringUTF
    255         // doesn't like UTF-8 strings with high codepoints.  It actually expects
    256         // Modified UTF-8 with encoded surrogate pairs.
    257         String16 utf16(value, sizeIncludingNull - 1);
    258         return env->NewString(reinterpret_cast<const jchar*>(utf16.string()), utf16.size());
    259     } else if (type == CursorWindow::FIELD_TYPE_INTEGER) {
    260         int64_t value = window->getFieldSlotValueLong(fieldSlot);
    261         char buf[32];
    262         snprintf(buf, sizeof(buf), "%" PRId64, value);
    263         return env->NewStringUTF(buf);
    264     } else if (type == CursorWindow::FIELD_TYPE_FLOAT) {
    265         double value = window->getFieldSlotValueDouble(fieldSlot);
    266         char buf[32];
    267         snprintf(buf, sizeof(buf), "%g", value);
    268         return env->NewStringUTF(buf);
    269     } else if (type == CursorWindow::FIELD_TYPE_NULL) {
    270         return NULL;
    271     } else if (type == CursorWindow::FIELD_TYPE_BLOB) {
    272         throw_sqlite3_exception(env, "Unable to convert BLOB to string");
    273         return NULL;
    274     } else {
    275         throwUnknownTypeException(env, type);
    276         return NULL;
    277     }
    278 }
    279 
    280 static jcharArray allocCharArrayBuffer(JNIEnv* env, jobject bufferObj, size_t size) {
    281     jcharArray dataObj = jcharArray(env->GetObjectField(bufferObj,
    282             gCharArrayBufferClassInfo.data));
    283     if (dataObj && size) {
    284         jsize capacity = env->GetArrayLength(dataObj);
    285         if (size_t(capacity) < size) {
    286             env->DeleteLocalRef(dataObj);
    287             dataObj = NULL;
    288         }
    289     }
    290     if (!dataObj) {
    291         jsize capacity = size;
    292         if (capacity < 64) {
    293             capacity = 64;
    294         }
    295         dataObj = env->NewCharArray(capacity); // might throw OOM
    296         if (dataObj) {
    297             env->SetObjectField(bufferObj, gCharArrayBufferClassInfo.data, dataObj);
    298         }
    299     }
    300     return dataObj;
    301 }
    302 
    303 static void fillCharArrayBufferUTF(JNIEnv* env, jobject bufferObj,
    304         const char* str, size_t len) {
    305     ssize_t size = utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(str), len);
    306     if (size < 0) {
    307         size = 0; // invalid UTF8 string
    308     }
    309     jcharArray dataObj = allocCharArrayBuffer(env, bufferObj, size);
    310     if (dataObj) {
    311         if (size) {
    312             jchar* data = static_cast<jchar*>(env->GetPrimitiveArrayCritical(dataObj, NULL));
    313             utf8_to_utf16_no_null_terminator(reinterpret_cast<const uint8_t*>(str), len,
    314                     reinterpret_cast<char16_t*>(data), (size_t) size);
    315             env->ReleasePrimitiveArrayCritical(dataObj, data, 0);
    316         }
    317         env->SetIntField(bufferObj, gCharArrayBufferClassInfo.sizeCopied, size);
    318     }
    319 }
    320 
    321 static void clearCharArrayBuffer(JNIEnv* env, jobject bufferObj) {
    322     jcharArray dataObj = allocCharArrayBuffer(env, bufferObj, 0);
    323     if (dataObj) {
    324         env->SetIntField(bufferObj, gCharArrayBufferClassInfo.sizeCopied, 0);
    325     }
    326 }
    327 
    328 static void nativeCopyStringToBuffer(JNIEnv* env, jclass clazz, jlong windowPtr,
    329         jint row, jint column, jobject bufferObj) {
    330     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    331     LOG_WINDOW("Copying string for %d,%d from %p", row, column, window);
    332 
    333     CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
    334     if (!fieldSlot) {
    335         throwExceptionWithRowCol(env, row, column);
    336         return;
    337     }
    338 
    339     int32_t type = window->getFieldSlotType(fieldSlot);
    340     if (type == CursorWindow::FIELD_TYPE_STRING) {
    341         size_t sizeIncludingNull;
    342         const char* value = window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull);
    343         if (sizeIncludingNull > 1) {
    344             fillCharArrayBufferUTF(env, bufferObj, value, sizeIncludingNull - 1);
    345         } else {
    346             clearCharArrayBuffer(env, bufferObj);
    347         }
    348     } else if (type == CursorWindow::FIELD_TYPE_INTEGER) {
    349         int64_t value = window->getFieldSlotValueLong(fieldSlot);
    350         char buf[32];
    351         snprintf(buf, sizeof(buf), "%" PRId64, value);
    352         fillCharArrayBufferUTF(env, bufferObj, buf, strlen(buf));
    353     } else if (type == CursorWindow::FIELD_TYPE_FLOAT) {
    354         double value = window->getFieldSlotValueDouble(fieldSlot);
    355         char buf[32];
    356         snprintf(buf, sizeof(buf), "%g", value);
    357         fillCharArrayBufferUTF(env, bufferObj, buf, strlen(buf));
    358     } else if (type == CursorWindow::FIELD_TYPE_NULL) {
    359         clearCharArrayBuffer(env, bufferObj);
    360     } else if (type == CursorWindow::FIELD_TYPE_BLOB) {
    361         throw_sqlite3_exception(env, "Unable to convert BLOB to string");
    362     } else {
    363         throwUnknownTypeException(env, type);
    364     }
    365 }
    366 
    367 static jlong nativeGetLong(JNIEnv* env, jclass clazz, jlong windowPtr,
    368         jint row, jint column) {
    369     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    370     LOG_WINDOW("Getting long for %d,%d from %p", row, column, window);
    371 
    372     CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
    373     if (!fieldSlot) {
    374         throwExceptionWithRowCol(env, row, column);
    375         return 0;
    376     }
    377 
    378     int32_t type = window->getFieldSlotType(fieldSlot);
    379     if (type == CursorWindow::FIELD_TYPE_INTEGER) {
    380         return window->getFieldSlotValueLong(fieldSlot);
    381     } else if (type == CursorWindow::FIELD_TYPE_STRING) {
    382         size_t sizeIncludingNull;
    383         const char* value = window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull);
    384         return sizeIncludingNull > 1 ? strtoll(value, NULL, 0) : 0L;
    385     } else if (type == CursorWindow::FIELD_TYPE_FLOAT) {
    386         return jlong(window->getFieldSlotValueDouble(fieldSlot));
    387     } else if (type == CursorWindow::FIELD_TYPE_NULL) {
    388         return 0;
    389     } else if (type == CursorWindow::FIELD_TYPE_BLOB) {
    390         throw_sqlite3_exception(env, "Unable to convert BLOB to long");
    391         return 0;
    392     } else {
    393         throwUnknownTypeException(env, type);
    394         return 0;
    395     }
    396 }
    397 
    398 static jdouble nativeGetDouble(JNIEnv* env, jclass clazz, jlong windowPtr,
    399         jint row, jint column) {
    400     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    401     LOG_WINDOW("Getting double for %d,%d from %p", row, column, window);
    402 
    403     CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
    404     if (!fieldSlot) {
    405         throwExceptionWithRowCol(env, row, column);
    406         return 0.0;
    407     }
    408 
    409     int32_t type = window->getFieldSlotType(fieldSlot);
    410     if (type == CursorWindow::FIELD_TYPE_FLOAT) {
    411         return window->getFieldSlotValueDouble(fieldSlot);
    412     } else if (type == CursorWindow::FIELD_TYPE_STRING) {
    413         size_t sizeIncludingNull;
    414         const char* value = window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull);
    415         return sizeIncludingNull > 1 ? strtod(value, NULL) : 0.0;
    416     } else if (type == CursorWindow::FIELD_TYPE_INTEGER) {
    417         return jdouble(window->getFieldSlotValueLong(fieldSlot));
    418     } else if (type == CursorWindow::FIELD_TYPE_NULL) {
    419         return 0.0;
    420     } else if (type == CursorWindow::FIELD_TYPE_BLOB) {
    421         throw_sqlite3_exception(env, "Unable to convert BLOB to double");
    422         return 0.0;
    423     } else {
    424         throwUnknownTypeException(env, type);
    425         return 0.0;
    426     }
    427 }
    428 
    429 static jboolean nativePutBlob(JNIEnv* env, jclass clazz, jlong windowPtr,
    430         jbyteArray valueObj, jint row, jint column) {
    431     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    432     jsize len = env->GetArrayLength(valueObj);
    433 
    434     void* value = env->GetPrimitiveArrayCritical(valueObj, NULL);
    435     status_t status = window->putBlob(row, column, value, len);
    436     env->ReleasePrimitiveArrayCritical(valueObj, value, JNI_ABORT);
    437 
    438     if (status) {
    439         LOG_WINDOW("Failed to put blob. error=%d", status);
    440         return false;
    441     }
    442 
    443     LOG_WINDOW("%d,%d is BLOB with %u bytes", row, column, len);
    444     return true;
    445 }
    446 
    447 static jboolean nativePutString(JNIEnv* env, jclass clazz, jlong windowPtr,
    448         jstring valueObj, jint row, jint column) {
    449     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    450 
    451     size_t sizeIncludingNull = env->GetStringUTFLength(valueObj) + 1;
    452     const char* valueStr = env->GetStringUTFChars(valueObj, NULL);
    453     if (!valueStr) {
    454         LOG_WINDOW("value can't be transferred to UTFChars");
    455         return false;
    456     }
    457     status_t status = window->putString(row, column, valueStr, sizeIncludingNull);
    458     env->ReleaseStringUTFChars(valueObj, valueStr);
    459 
    460     if (status) {
    461         LOG_WINDOW("Failed to put string. error=%d", status);
    462         return false;
    463     }
    464 
    465     LOG_WINDOW("%d,%d is TEXT with %u bytes", row, column, sizeIncludingNull);
    466     return true;
    467 }
    468 
    469 static jboolean nativePutLong(JNIEnv* env, jclass clazz, jlong windowPtr,
    470         jlong value, jint row, jint column) {
    471     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    472     status_t status = window->putLong(row, column, value);
    473 
    474     if (status) {
    475         LOG_WINDOW("Failed to put long. error=%d", status);
    476         return false;
    477     }
    478 
    479     LOG_WINDOW("%d,%d is INTEGER 0x%016llx", row, column, value);
    480     return true;
    481 }
    482 
    483 static jboolean nativePutDouble(JNIEnv* env, jclass clazz, jlong windowPtr,
    484         jdouble value, jint row, jint column) {
    485     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    486     status_t status = window->putDouble(row, column, value);
    487 
    488     if (status) {
    489         LOG_WINDOW("Failed to put double. error=%d", status);
    490         return false;
    491     }
    492 
    493     LOG_WINDOW("%d,%d is FLOAT %lf", row, column, value);
    494     return true;
    495 }
    496 
    497 static jboolean nativePutNull(JNIEnv* env, jclass clazz, jlong windowPtr,
    498         jint row, jint column) {
    499     CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
    500     status_t status = window->putNull(row, column);
    501 
    502     if (status) {
    503         LOG_WINDOW("Failed to put null. error=%d", status);
    504         return false;
    505     }
    506 
    507     LOG_WINDOW("%d,%d is NULL", row, column);
    508     return true;
    509 }
    510 
    511 static const JNINativeMethod sMethods[] =
    512 {
    513     /* name, signature, funcPtr */
    514     { "nativeCreate", "(Ljava/lang/String;I)J",
    515             (void*)nativeCreate },
    516     { "nativeCreateFromParcel", "(Landroid/os/Parcel;)J",
    517             (void*)nativeCreateFromParcel },
    518     { "nativeDispose", "(J)V",
    519             (void*)nativeDispose },
    520     { "nativeWriteToParcel", "(JLandroid/os/Parcel;)V",
    521             (void*)nativeWriteToParcel },
    522 
    523     { "nativeGetName", "(J)Ljava/lang/String;",
    524             (void*)nativeGetName },
    525     { "nativeGetBlob", "(JII)[B",
    526             (void*)nativeGetBlob },
    527     { "nativeGetString", "(JII)Ljava/lang/String;",
    528             (void*)nativeGetString },
    529     { "nativeCopyStringToBuffer", "(JIILandroid/database/CharArrayBuffer;)V",
    530             (void*)nativeCopyStringToBuffer },
    531     { "nativePutBlob", "(J[BII)Z",
    532             (void*)nativePutBlob },
    533     { "nativePutString", "(JLjava/lang/String;II)Z",
    534             (void*)nativePutString },
    535 
    536     // ------- @FastNative below here ----------------------
    537     { "nativeClear", "(J)V",
    538             (void*)nativeClear },
    539     { "nativeGetNumRows", "(J)I",
    540             (void*)nativeGetNumRows },
    541     { "nativeSetNumColumns", "(JI)Z",
    542             (void*)nativeSetNumColumns },
    543     { "nativeAllocRow", "(J)Z",
    544             (void*)nativeAllocRow },
    545     { "nativeFreeLastRow", "(J)V",
    546             (void*)nativeFreeLastRow },
    547     { "nativeGetType", "(JII)I",
    548             (void*)nativeGetType },
    549     { "nativeGetLong", "(JII)J",
    550             (void*)nativeGetLong },
    551     { "nativeGetDouble", "(JII)D",
    552             (void*)nativeGetDouble },
    553 
    554     { "nativePutLong", "(JJII)Z",
    555             (void*)nativePutLong },
    556     { "nativePutDouble", "(JDII)Z",
    557             (void*)nativePutDouble },
    558     { "nativePutNull", "(JII)Z",
    559             (void*)nativePutNull },
    560 };
    561 
    562 int register_android_database_CursorWindow(JNIEnv* env)
    563 {
    564     jclass clazz = FindClassOrDie(env, "android/database/CharArrayBuffer");
    565 
    566     gCharArrayBufferClassInfo.data = GetFieldIDOrDie(env, clazz, "data", "[C");
    567     gCharArrayBufferClassInfo.sizeCopied = GetFieldIDOrDie(env, clazz, "sizeCopied", "I");
    568 
    569     gEmptyString = MakeGlobalRefOrDie(env, env->NewStringUTF(""));
    570 
    571     return RegisterMethodsOrDie(env, "android/database/CursorWindow", sMethods, NELEM(sMethods));
    572 }
    573 
    574 } // namespace android
    575