/*
|
* Copyright (C) 2007 The Android Open Source Project
|
*
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
* you may not use this file except in compliance with the License.
|
* You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing, software
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* See the License for the specific language governing permissions and
|
* limitations under the License.
|
*/
|
|
#undef LOG_TAG
|
#define LOG_TAG "CursorWindow"
|
#define LOG_NDEBUG 0
|
|
#include <inttypes.h>
|
#include <jni.h>
|
#include <nativehelper/JNIHelp.h>
|
#include <android_runtime/AndroidRuntime.h>
|
|
#include <utils/Log.h>
|
#include <utils/String8.h>
|
#include <utils/String16.h>
|
#include <utils/Unicode.h>
|
|
#include <stdio.h>
|
#include <string.h>
|
#include <unistd.h>
|
#include <sys/types.h>
|
#include <dirent.h>
|
|
#undef LOG_NDEBUG
|
#define LOG_NDEBUG 1
|
|
#include <androidfw/CursorWindow.h>
|
#include "android_os_Parcel.h"
|
#include "android_util_Binder.h"
|
#include "android_database_SQLiteCommon.h"
|
|
#include "core_jni_helpers.h"
|
|
namespace android {
|
|
static struct {
|
jfieldID data;
|
jfieldID sizeCopied;
|
} gCharArrayBufferClassInfo;
|
|
static jstring gEmptyString;
|
|
static void throwExceptionWithRowCol(JNIEnv* env, jint row, jint column) {
|
String8 msg;
|
msg.appendFormat("Couldn't read row %d, col %d from CursorWindow. "
|
"Make sure the Cursor is initialized correctly before accessing data from it.",
|
row, column);
|
jniThrowException(env, "java/lang/IllegalStateException", msg.string());
|
}
|
|
static void throwUnknownTypeException(JNIEnv * env, jint type) {
|
String8 msg;
|
msg.appendFormat("UNKNOWN type %d", type);
|
jniThrowException(env, "java/lang/IllegalStateException", msg.string());
|
}
|
|
static int getFdCount() {
|
char fdpath[PATH_MAX];
|
int count = 0;
|
snprintf(fdpath, PATH_MAX, "/proc/%d/fd", getpid());
|
DIR *dir = opendir(fdpath);
|
if (dir != NULL) {
|
struct dirent *dirent;
|
while ((dirent = readdir(dir))) {
|
count++;
|
}
|
count -= 2; // discount "." and ".."
|
closedir(dir);
|
}
|
return count;
|
}
|
|
static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring nameObj, jint cursorWindowSize) {
|
String8 name;
|
const char* nameStr = env->GetStringUTFChars(nameObj, NULL);
|
name.setTo(nameStr);
|
env->ReleaseStringUTFChars(nameObj, nameStr);
|
|
CursorWindow* window;
|
status_t status = CursorWindow::create(name, cursorWindowSize, &window);
|
if (status || !window) {
|
jniThrowExceptionFmt(env,
|
"android/database/CursorWindowAllocationException",
|
"Could not allocate CursorWindow '%s' of size %d due to error %d.",
|
name.string(), cursorWindowSize, status);
|
return 0;
|
}
|
|
LOG_WINDOW("nativeInitializeEmpty: window = %p", window);
|
return reinterpret_cast<jlong>(window);
|
}
|
|
static jlong nativeCreateFromParcel(JNIEnv* env, jclass clazz, jobject parcelObj) {
|
Parcel* parcel = parcelForJavaObject(env, parcelObj);
|
|
CursorWindow* window;
|
status_t status = CursorWindow::createFromParcel(parcel, &window);
|
if (status || !window) {
|
jniThrowExceptionFmt(env,
|
"android/database/CursorWindowAllocationException",
|
"Could not create CursorWindow from Parcel due to error %d, process fd count=%d",
|
status, getFdCount());
|
return 0;
|
}
|
|
LOG_WINDOW("nativeInitializeFromBinder: numRows = %d, numColumns = %d, window = %p",
|
window->getNumRows(), window->getNumColumns(), window);
|
return reinterpret_cast<jlong>(window);
|
}
|
|
static void nativeDispose(JNIEnv* env, jclass clazz, jlong windowPtr) {
|
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
|
if (window) {
|
LOG_WINDOW("Closing window %p", window);
|
delete window;
|
}
|
}
|
|
static jstring nativeGetName(JNIEnv* env, jclass clazz, jlong windowPtr) {
|
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
|
return env->NewStringUTF(window->name().string());
|
}
|
|
static void nativeWriteToParcel(JNIEnv * env, jclass clazz, jlong windowPtr,
|
jobject parcelObj) {
|
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
|
Parcel* parcel = parcelForJavaObject(env, parcelObj);
|
|
status_t status = window->writeToParcel(parcel);
|
if (status) {
|
String8 msg;
|
msg.appendFormat("Could not write CursorWindow to Parcel due to error %d.", status);
|
jniThrowRuntimeException(env, msg.string());
|
}
|
}
|
|
static void nativeClear(JNIEnv * env, jclass clazz, jlong windowPtr) {
|
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
|
LOG_WINDOW("Clearing window %p", window);
|
status_t status = window->clear();
|
if (status) {
|
LOG_WINDOW("Could not clear window. error=%d", status);
|
}
|
}
|
|
static jint nativeGetNumRows(JNIEnv* env, jclass clazz, jlong windowPtr) {
|
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
|
return window->getNumRows();
|
}
|
|
static jboolean nativeSetNumColumns(JNIEnv* env, jclass clazz, jlong windowPtr,
|
jint columnNum) {
|
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
|
status_t status = window->setNumColumns(columnNum);
|
return status == OK;
|
}
|
|
static jboolean nativeAllocRow(JNIEnv* env, jclass clazz, jlong windowPtr) {
|
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
|
status_t status = window->allocRow();
|
return status == OK;
|
}
|
|
static void nativeFreeLastRow(JNIEnv* env, jclass clazz, jlong windowPtr) {
|
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
|
window->freeLastRow();
|
}
|
|
static jint nativeGetType(JNIEnv* env, jclass clazz, jlong windowPtr,
|
jint row, jint column) {
|
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
|
LOG_WINDOW("returning column type affinity for %d,%d from %p", row, column, window);
|
|
CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
|
if (!fieldSlot) {
|
// FIXME: This is really broken but we have CTS tests that depend
|
// on this legacy behavior.
|
//throwExceptionWithRowCol(env, row, column);
|
return CursorWindow::FIELD_TYPE_NULL;
|
}
|
return window->getFieldSlotType(fieldSlot);
|
}
|
|
static jbyteArray nativeGetBlob(JNIEnv* env, jclass clazz, jlong windowPtr,
|
jint row, jint column) {
|
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
|
LOG_WINDOW("Getting blob for %d,%d from %p", row, column, window);
|
|
CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
|
if (!fieldSlot) {
|
throwExceptionWithRowCol(env, row, column);
|
return NULL;
|
}
|
|
int32_t type = window->getFieldSlotType(fieldSlot);
|
if (type == CursorWindow::FIELD_TYPE_BLOB || type == CursorWindow::FIELD_TYPE_STRING) {
|
size_t size;
|
const void* value = window->getFieldSlotValueBlob(fieldSlot, &size);
|
if (!value) {
|
throw_sqlite3_exception(env, "Native could not read blob slot");
|
return NULL;
|
}
|
jbyteArray byteArray = env->NewByteArray(size);
|
if (!byteArray) {
|
env->ExceptionClear();
|
throw_sqlite3_exception(env, "Native could not create new byte[]");
|
return NULL;
|
}
|
env->SetByteArrayRegion(byteArray, 0, size, static_cast<const jbyte*>(value));
|
return byteArray;
|
} else if (type == CursorWindow::FIELD_TYPE_INTEGER) {
|
throw_sqlite3_exception(env, "INTEGER data in nativeGetBlob ");
|
} else if (type == CursorWindow::FIELD_TYPE_FLOAT) {
|
throw_sqlite3_exception(env, "FLOAT data in nativeGetBlob ");
|
} else if (type == CursorWindow::FIELD_TYPE_NULL) {
|
// do nothing
|
} else {
|
throwUnknownTypeException(env, type);
|
}
|
return NULL;
|
}
|
|
static jstring nativeGetString(JNIEnv* env, jclass clazz, jlong windowPtr,
|
jint row, jint column) {
|
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
|
LOG_WINDOW("Getting string for %d,%d from %p", row, column, window);
|
|
CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
|
if (!fieldSlot) {
|
throwExceptionWithRowCol(env, row, column);
|
return NULL;
|
}
|
|
int32_t type = window->getFieldSlotType(fieldSlot);
|
if (type == CursorWindow::FIELD_TYPE_STRING) {
|
size_t sizeIncludingNull;
|
const char* value = window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull);
|
if (!value) {
|
throw_sqlite3_exception(env, "Native could not read string slot");
|
return NULL;
|
}
|
if (sizeIncludingNull <= 1) {
|
return gEmptyString;
|
}
|
// Convert to UTF-16 here instead of calling NewStringUTF. NewStringUTF
|
// doesn't like UTF-8 strings with high codepoints. It actually expects
|
// Modified UTF-8 with encoded surrogate pairs.
|
String16 utf16(value, sizeIncludingNull - 1);
|
return env->NewString(reinterpret_cast<const jchar*>(utf16.string()), utf16.size());
|
} else if (type == CursorWindow::FIELD_TYPE_INTEGER) {
|
int64_t value = window->getFieldSlotValueLong(fieldSlot);
|
char buf[32];
|
snprintf(buf, sizeof(buf), "%" PRId64, value);
|
return env->NewStringUTF(buf);
|
} else if (type == CursorWindow::FIELD_TYPE_FLOAT) {
|
double value = window->getFieldSlotValueDouble(fieldSlot);
|
char buf[32];
|
snprintf(buf, sizeof(buf), "%g", value);
|
return env->NewStringUTF(buf);
|
} else if (type == CursorWindow::FIELD_TYPE_NULL) {
|
return NULL;
|
} else if (type == CursorWindow::FIELD_TYPE_BLOB) {
|
throw_sqlite3_exception(env, "Unable to convert BLOB to string");
|
return NULL;
|
} else {
|
throwUnknownTypeException(env, type);
|
return NULL;
|
}
|
}
|
|
static jcharArray allocCharArrayBuffer(JNIEnv* env, jobject bufferObj, size_t size) {
|
jcharArray dataObj = jcharArray(env->GetObjectField(bufferObj,
|
gCharArrayBufferClassInfo.data));
|
if (dataObj && size) {
|
jsize capacity = env->GetArrayLength(dataObj);
|
if (size_t(capacity) < size) {
|
env->DeleteLocalRef(dataObj);
|
dataObj = NULL;
|
}
|
}
|
if (!dataObj) {
|
jsize capacity = size;
|
if (capacity < 64) {
|
capacity = 64;
|
}
|
dataObj = env->NewCharArray(capacity); // might throw OOM
|
if (dataObj) {
|
env->SetObjectField(bufferObj, gCharArrayBufferClassInfo.data, dataObj);
|
}
|
}
|
return dataObj;
|
}
|
|
static void fillCharArrayBufferUTF(JNIEnv* env, jobject bufferObj,
|
const char* str, size_t len) {
|
ssize_t size = utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(str), len);
|
if (size < 0) {
|
size = 0; // invalid UTF8 string
|
}
|
jcharArray dataObj = allocCharArrayBuffer(env, bufferObj, size);
|
if (dataObj) {
|
if (size) {
|
jchar* data = static_cast<jchar*>(env->GetPrimitiveArrayCritical(dataObj, NULL));
|
utf8_to_utf16_no_null_terminator(reinterpret_cast<const uint8_t*>(str), len,
|
reinterpret_cast<char16_t*>(data), (size_t) size);
|
env->ReleasePrimitiveArrayCritical(dataObj, data, 0);
|
}
|
env->SetIntField(bufferObj, gCharArrayBufferClassInfo.sizeCopied, size);
|
}
|
}
|
|
static void clearCharArrayBuffer(JNIEnv* env, jobject bufferObj) {
|
jcharArray dataObj = allocCharArrayBuffer(env, bufferObj, 0);
|
if (dataObj) {
|
env->SetIntField(bufferObj, gCharArrayBufferClassInfo.sizeCopied, 0);
|
}
|
}
|
|
static void nativeCopyStringToBuffer(JNIEnv* env, jclass clazz, jlong windowPtr,
|
jint row, jint column, jobject bufferObj) {
|
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
|
LOG_WINDOW("Copying string for %d,%d from %p", row, column, window);
|
|
CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
|
if (!fieldSlot) {
|
throwExceptionWithRowCol(env, row, column);
|
return;
|
}
|
|
int32_t type = window->getFieldSlotType(fieldSlot);
|
if (type == CursorWindow::FIELD_TYPE_STRING) {
|
size_t sizeIncludingNull;
|
const char* value = window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull);
|
if (sizeIncludingNull > 1) {
|
fillCharArrayBufferUTF(env, bufferObj, value, sizeIncludingNull - 1);
|
} else {
|
clearCharArrayBuffer(env, bufferObj);
|
}
|
} else if (type == CursorWindow::FIELD_TYPE_INTEGER) {
|
int64_t value = window->getFieldSlotValueLong(fieldSlot);
|
char buf[32];
|
snprintf(buf, sizeof(buf), "%" PRId64, value);
|
fillCharArrayBufferUTF(env, bufferObj, buf, strlen(buf));
|
} else if (type == CursorWindow::FIELD_TYPE_FLOAT) {
|
double value = window->getFieldSlotValueDouble(fieldSlot);
|
char buf[32];
|
snprintf(buf, sizeof(buf), "%g", value);
|
fillCharArrayBufferUTF(env, bufferObj, buf, strlen(buf));
|
} else if (type == CursorWindow::FIELD_TYPE_NULL) {
|
clearCharArrayBuffer(env, bufferObj);
|
} else if (type == CursorWindow::FIELD_TYPE_BLOB) {
|
throw_sqlite3_exception(env, "Unable to convert BLOB to string");
|
} else {
|
throwUnknownTypeException(env, type);
|
}
|
}
|
|
static jlong nativeGetLong(JNIEnv* env, jclass clazz, jlong windowPtr,
|
jint row, jint column) {
|
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
|
LOG_WINDOW("Getting long for %d,%d from %p", row, column, window);
|
|
CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
|
if (!fieldSlot) {
|
throwExceptionWithRowCol(env, row, column);
|
return 0;
|
}
|
|
int32_t type = window->getFieldSlotType(fieldSlot);
|
if (type == CursorWindow::FIELD_TYPE_INTEGER) {
|
return window->getFieldSlotValueLong(fieldSlot);
|
} else if (type == CursorWindow::FIELD_TYPE_STRING) {
|
size_t sizeIncludingNull;
|
const char* value = window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull);
|
return sizeIncludingNull > 1 ? strtoll(value, NULL, 0) : 0L;
|
} else if (type == CursorWindow::FIELD_TYPE_FLOAT) {
|
return jlong(window->getFieldSlotValueDouble(fieldSlot));
|
} else if (type == CursorWindow::FIELD_TYPE_NULL) {
|
return 0;
|
} else if (type == CursorWindow::FIELD_TYPE_BLOB) {
|
throw_sqlite3_exception(env, "Unable to convert BLOB to long");
|
return 0;
|
} else {
|
throwUnknownTypeException(env, type);
|
return 0;
|
}
|
}
|
|
static jdouble nativeGetDouble(JNIEnv* env, jclass clazz, jlong windowPtr,
|
jint row, jint column) {
|
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
|
LOG_WINDOW("Getting double for %d,%d from %p", row, column, window);
|
|
CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
|
if (!fieldSlot) {
|
throwExceptionWithRowCol(env, row, column);
|
return 0.0;
|
}
|
|
int32_t type = window->getFieldSlotType(fieldSlot);
|
if (type == CursorWindow::FIELD_TYPE_FLOAT) {
|
return window->getFieldSlotValueDouble(fieldSlot);
|
} else if (type == CursorWindow::FIELD_TYPE_STRING) {
|
size_t sizeIncludingNull;
|
const char* value = window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull);
|
return sizeIncludingNull > 1 ? strtod(value, NULL) : 0.0;
|
} else if (type == CursorWindow::FIELD_TYPE_INTEGER) {
|
return jdouble(window->getFieldSlotValueLong(fieldSlot));
|
} else if (type == CursorWindow::FIELD_TYPE_NULL) {
|
return 0.0;
|
} else if (type == CursorWindow::FIELD_TYPE_BLOB) {
|
throw_sqlite3_exception(env, "Unable to convert BLOB to double");
|
return 0.0;
|
} else {
|
throwUnknownTypeException(env, type);
|
return 0.0;
|
}
|
}
|
|
static jboolean nativePutBlob(JNIEnv* env, jclass clazz, jlong windowPtr,
|
jbyteArray valueObj, jint row, jint column) {
|
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
|
jsize len = env->GetArrayLength(valueObj);
|
|
void* value = env->GetPrimitiveArrayCritical(valueObj, NULL);
|
status_t status = window->putBlob(row, column, value, len);
|
env->ReleasePrimitiveArrayCritical(valueObj, value, JNI_ABORT);
|
|
if (status) {
|
LOG_WINDOW("Failed to put blob. error=%d", status);
|
return false;
|
}
|
|
LOG_WINDOW("%d,%d is BLOB with %u bytes", row, column, len);
|
return true;
|
}
|
|
static jboolean nativePutString(JNIEnv* env, jclass clazz, jlong windowPtr,
|
jstring valueObj, jint row, jint column) {
|
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
|
|
size_t sizeIncludingNull = env->GetStringUTFLength(valueObj) + 1;
|
const char* valueStr = env->GetStringUTFChars(valueObj, NULL);
|
if (!valueStr) {
|
LOG_WINDOW("value can't be transferred to UTFChars");
|
return false;
|
}
|
status_t status = window->putString(row, column, valueStr, sizeIncludingNull);
|
env->ReleaseStringUTFChars(valueObj, valueStr);
|
|
if (status) {
|
LOG_WINDOW("Failed to put string. error=%d", status);
|
return false;
|
}
|
|
LOG_WINDOW("%d,%d is TEXT with %u bytes", row, column, sizeIncludingNull);
|
return true;
|
}
|
|
static jboolean nativePutLong(JNIEnv* env, jclass clazz, jlong windowPtr,
|
jlong value, jint row, jint column) {
|
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
|
status_t status = window->putLong(row, column, value);
|
|
if (status) {
|
LOG_WINDOW("Failed to put long. error=%d", status);
|
return false;
|
}
|
|
LOG_WINDOW("%d,%d is INTEGER 0x%016llx", row, column, value);
|
return true;
|
}
|
|
static jboolean nativePutDouble(JNIEnv* env, jclass clazz, jlong windowPtr,
|
jdouble value, jint row, jint column) {
|
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
|
status_t status = window->putDouble(row, column, value);
|
|
if (status) {
|
LOG_WINDOW("Failed to put double. error=%d", status);
|
return false;
|
}
|
|
LOG_WINDOW("%d,%d is FLOAT %lf", row, column, value);
|
return true;
|
}
|
|
static jboolean nativePutNull(JNIEnv* env, jclass clazz, jlong windowPtr,
|
jint row, jint column) {
|
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
|
status_t status = window->putNull(row, column);
|
|
if (status) {
|
LOG_WINDOW("Failed to put null. error=%d", status);
|
return false;
|
}
|
|
LOG_WINDOW("%d,%d is NULL", row, column);
|
return true;
|
}
|
|
static const JNINativeMethod sMethods[] =
|
{
|
/* name, signature, funcPtr */
|
{ "nativeCreate", "(Ljava/lang/String;I)J",
|
(void*)nativeCreate },
|
{ "nativeCreateFromParcel", "(Landroid/os/Parcel;)J",
|
(void*)nativeCreateFromParcel },
|
{ "nativeDispose", "(J)V",
|
(void*)nativeDispose },
|
{ "nativeWriteToParcel", "(JLandroid/os/Parcel;)V",
|
(void*)nativeWriteToParcel },
|
|
{ "nativeGetName", "(J)Ljava/lang/String;",
|
(void*)nativeGetName },
|
{ "nativeGetBlob", "(JII)[B",
|
(void*)nativeGetBlob },
|
{ "nativeGetString", "(JII)Ljava/lang/String;",
|
(void*)nativeGetString },
|
{ "nativeCopyStringToBuffer", "(JIILandroid/database/CharArrayBuffer;)V",
|
(void*)nativeCopyStringToBuffer },
|
{ "nativePutBlob", "(J[BII)Z",
|
(void*)nativePutBlob },
|
{ "nativePutString", "(JLjava/lang/String;II)Z",
|
(void*)nativePutString },
|
|
// ------- @FastNative below here ----------------------
|
{ "nativeClear", "(J)V",
|
(void*)nativeClear },
|
{ "nativeGetNumRows", "(J)I",
|
(void*)nativeGetNumRows },
|
{ "nativeSetNumColumns", "(JI)Z",
|
(void*)nativeSetNumColumns },
|
{ "nativeAllocRow", "(J)Z",
|
(void*)nativeAllocRow },
|
{ "nativeFreeLastRow", "(J)V",
|
(void*)nativeFreeLastRow },
|
{ "nativeGetType", "(JII)I",
|
(void*)nativeGetType },
|
{ "nativeGetLong", "(JII)J",
|
(void*)nativeGetLong },
|
{ "nativeGetDouble", "(JII)D",
|
(void*)nativeGetDouble },
|
|
{ "nativePutLong", "(JJII)Z",
|
(void*)nativePutLong },
|
{ "nativePutDouble", "(JDII)Z",
|
(void*)nativePutDouble },
|
{ "nativePutNull", "(JII)Z",
|
(void*)nativePutNull },
|
};
|
|
int register_android_database_CursorWindow(JNIEnv* env)
|
{
|
jclass clazz = FindClassOrDie(env, "android/database/CharArrayBuffer");
|
|
gCharArrayBufferClassInfo.data = GetFieldIDOrDie(env, clazz, "data", "[C");
|
gCharArrayBufferClassInfo.sizeCopied = GetFieldIDOrDie(env, clazz, "sizeCopied", "I");
|
|
gEmptyString = MakeGlobalRefOrDie(env, env->NewStringUTF(""));
|
|
return RegisterMethodsOrDie(env, "android/database/CursorWindow", sMethods, NELEM(sMethods));
|
}
|
|
} // namespace android
|