android_database_CursorWindow.cpp revision 3bc6bbc92cd2095f42039b5aadd0a14d0e5d9230
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#include <utils/Unicode.h> 28 29#include <stdio.h> 30#include <string.h> 31#include <unistd.h> 32 33#include "binder/CursorWindow.h" 34#include "sqlite3_exception.h" 35#include "android_util_Binder.h" 36 37namespace android { 38 39static struct { 40 jfieldID data; 41 jfieldID sizeCopied; 42} gCharArrayBufferClassInfo; 43 44static jstring gEmptyString; 45 46static void throwExceptionWithRowCol(JNIEnv* env, jint row, jint column) { 47 String8 msg; 48 msg.appendFormat("Couldn't read row %d, col %d from CursorWindow. " 49 "Make sure the Cursor is initialized correctly before accessing data from it.", 50 row, column); 51 jniThrowException(env, "java/lang/IllegalStateException", msg.string()); 52} 53 54static void throwUnknownTypeException(JNIEnv * env, jint type) { 55 String8 msg; 56 msg.appendFormat("UNKNOWN type %d", type); 57 jniThrowException(env, "java/lang/IllegalStateException", msg.string()); 58} 59 60static jint nativeInitializeEmpty(JNIEnv* env, jclass clazz, 61 jint cursorWindowSize, jboolean localOnly) { 62 CursorWindow* window = new CursorWindow(cursorWindowSize); 63 if (!window) { 64 return 0; 65 } 66 if (!window->initBuffer(localOnly)) { 67 delete window; 68 return 0; 69 } 70 71 LOG_WINDOW("nativeInitializeEmpty: window = %p", window); 72 return reinterpret_cast<jint>(window); 73} 74 75static jint nativeInitializeFromBinder(JNIEnv* env, jclass clazz, jobject binderObj) { 76 sp<IMemory> memory = interface_cast<IMemory>(ibinderForJavaObject(env, binderObj)); 77 if (memory == NULL) { 78 jniThrowException(env, "java/lang/IllegalStateException", "Couldn't get native binder"); 79 return 0; 80 } 81 82 CursorWindow* window = new CursorWindow(); 83 if (!window) { 84 return 0; 85 } 86 if (!window->setMemory(memory)) { 87 delete window; 88 return 0; 89 } 90 91 LOG_WINDOW("nativeInitializeFromBinder: numRows = %d, numColumns = %d, window = %p", 92 window->getNumRows(), window->getNumColumns(), window); 93 return reinterpret_cast<jint>(window); 94} 95 96static void nativeDispose(JNIEnv* env, jclass clazz, jint windowPtr) { 97 CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr); 98 if (window) { 99 LOG_WINDOW("Closing window %p", window); 100 delete window; 101 } 102} 103 104static jobject nativeGetBinder(JNIEnv * env, jclass clazz, jint windowPtr) { 105 CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr); 106 if (window) { 107 sp<IMemory> memory = window->getMemory(); 108 if (memory != NULL) { 109 sp<IBinder> binder = memory->asBinder(); 110 return javaObjectForIBinder(env, binder); 111 } 112 } 113 return NULL; 114} 115 116static void nativeClear(JNIEnv * env, jclass clazz, jint windowPtr) { 117 CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr); 118 LOG_WINDOW("Clearing window %p", window); 119 window->clear(); 120} 121 122static jint nativeGetNumRows(JNIEnv* env, jclass clazz, jint windowPtr) { 123 CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr); 124 return window->getNumRows(); 125} 126 127static jboolean nativeSetNumColumns(JNIEnv* env, jclass clazz, jint windowPtr, 128 jint columnNum) { 129 CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr); 130 return window->setNumColumns(columnNum); 131} 132 133static jboolean nativeAllocRow(JNIEnv* env, jclass clazz, jint windowPtr) { 134 CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr); 135 return window->allocRow() != NULL; 136} 137 138static void nativeFreeLastRow(JNIEnv* env, jclass clazz, jint windowPtr) { 139 CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr); 140 window->freeLastRow(); 141} 142 143static jint nativeGetType(JNIEnv* env, jclass clazz, jint windowPtr, 144 jint row, jint column) { 145 CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr); 146 LOG_WINDOW("returning column type affinity for %d,%d from %p", row, column, window); 147 148 field_slot_t* fieldSlot = window->getFieldSlotWithCheck(row, column); 149 if (!fieldSlot) { 150 throwExceptionWithRowCol(env, row, column); 151 return NULL; 152 } 153 return fieldSlot->type; 154} 155 156static jbyteArray nativeGetBlob(JNIEnv* env, jclass clazz, jint windowPtr, 157 jint row, jint column) { 158 CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr); 159 LOG_WINDOW("Getting blob for %d,%d from %p", row, column, window); 160 161 field_slot_t* fieldSlot = window->getFieldSlotWithCheck(row, column); 162 if (!fieldSlot) { 163 throwExceptionWithRowCol(env, row, column); 164 return NULL; 165 } 166 167 uint8_t type = fieldSlot->type; 168 if (type == FIELD_TYPE_BLOB || type == FIELD_TYPE_STRING) { 169 uint32_t size = fieldSlot->data.buffer.size; 170 jbyteArray byteArray = env->NewByteArray(size); 171 if (!byteArray) { 172 env->ExceptionClear(); 173 throw_sqlite3_exception(env, "Native could not create new byte[]"); 174 return NULL; 175 } 176 env->SetByteArrayRegion(byteArray, 0, size, 177 reinterpret_cast<jbyte*>(window->offsetToPtr(fieldSlot->data.buffer.offset))); 178 return byteArray; 179 } else if (type == FIELD_TYPE_INTEGER) { 180 throw_sqlite3_exception(env, "INTEGER data in nativeGetBlob "); 181 } else if (type == FIELD_TYPE_FLOAT) { 182 throw_sqlite3_exception(env, "FLOAT data in nativeGetBlob "); 183 } else if (type == FIELD_TYPE_NULL) { 184 // do nothing 185 } else { 186 throwUnknownTypeException(env, type); 187 } 188 return NULL; 189} 190 191static jstring nativeGetString(JNIEnv* env, jclass clazz, jint windowPtr, 192 jint row, jint column) { 193 CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr); 194 LOG_WINDOW("Getting string for %d,%d from %p", row, column, window); 195 196 field_slot_t* fieldSlot = window->getFieldSlotWithCheck(row, column); 197 if (!fieldSlot) { 198 throwExceptionWithRowCol(env, row, column); 199 return NULL; 200 } 201 202 uint8_t type = fieldSlot->type; 203 if (type == FIELD_TYPE_STRING) { 204 uint32_t size = fieldSlot->data.buffer.size; 205#if WINDOW_STORAGE_UTF8 206 return size > 1 ? env->NewStringUTF(window->getFieldSlotValueString(fieldSlot)) 207 : gEmptyString; 208#else 209 size_t chars = size / sizeof(char16_t); 210 return chars ? env->NewString(reinterpret_cast<jchar*>( 211 window->getFieldSlotValueString(fieldSlot)), chars) 212 : gEmptyString; 213#endif 214 } else if (type == FIELD_TYPE_INTEGER) { 215 int64_t value = window->getFieldSlotValueLong(fieldSlot); 216 char buf[32]; 217 snprintf(buf, sizeof(buf), "%lld", value); 218 return env->NewStringUTF(buf); 219 } else if (type == FIELD_TYPE_FLOAT) { 220 double value = window->getFieldSlotValueDouble(fieldSlot); 221 char buf[32]; 222 snprintf(buf, sizeof(buf), "%g", value); 223 return env->NewStringUTF(buf); 224 } else if (type == FIELD_TYPE_NULL) { 225 return NULL; 226 } else if (type == FIELD_TYPE_BLOB) { 227 throw_sqlite3_exception(env, "Unable to convert BLOB to string"); 228 return NULL; 229 } else { 230 throwUnknownTypeException(env, type); 231 return NULL; 232 } 233} 234 235static jcharArray allocCharArrayBuffer(JNIEnv* env, jobject bufferObj, size_t size) { 236 jcharArray dataObj = jcharArray(env->GetObjectField(bufferObj, 237 gCharArrayBufferClassInfo.data)); 238 if (dataObj && size) { 239 jsize capacity = env->GetArrayLength(dataObj); 240 if (size_t(capacity) < size) { 241 env->DeleteLocalRef(dataObj); 242 dataObj = NULL; 243 } 244 } 245 if (!dataObj) { 246 jsize capacity = size; 247 if (capacity < 64) { 248 capacity = 64; 249 } 250 dataObj = env->NewCharArray(capacity); // might throw OOM 251 if (dataObj) { 252 env->SetObjectField(bufferObj, gCharArrayBufferClassInfo.data, dataObj); 253 } 254 } 255 return dataObj; 256} 257 258static void fillCharArrayBufferUTF(JNIEnv* env, jobject bufferObj, 259 const char* str, size_t len) { 260 ssize_t size = utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(str), len); 261 if (size < 0) { 262 size = 0; // invalid UTF8 string 263 } 264 jcharArray dataObj = allocCharArrayBuffer(env, bufferObj, size); 265 if (dataObj) { 266 if (size) { 267 jchar* data = static_cast<jchar*>(env->GetPrimitiveArrayCritical(dataObj, NULL)); 268 utf8_to_utf16(reinterpret_cast<const uint8_t*>(str), len, 269 reinterpret_cast<char16_t*>(data)); 270 env->ReleasePrimitiveArrayCritical(dataObj, data, 0); 271 } 272 env->SetIntField(bufferObj, gCharArrayBufferClassInfo.sizeCopied, size); 273 } 274} 275 276#if !WINDOW_STORAGE_UTF8 277static void fillCharArrayBuffer(JNIEnv* env, jobject bufferObj, 278 const char16_t* str, size_t len) { 279 jcharArray dataObj = allocCharArrayBuffer(env, bufferObj, len); 280 if (dataObj) { 281 if (len) { 282 jchar* data = static_cast<jchar*>(env->GetPrimitiveArrayCritical(dataObj, NULL)); 283 memcpy(data, str, len * sizeof(jchar)); 284 env->ReleasePrimitiveArrayCritical(dataObj, data, 0); 285 } 286 env->SetIntField(bufferObj, gCharArrayBufferClassInfo.sizeCopied, len); 287 } 288} 289#endif 290 291static void clearCharArrayBuffer(JNIEnv* env, jobject bufferObj) { 292 jcharArray dataObj = allocCharArrayBuffer(env, bufferObj, 0); 293 if (dataObj) { 294 env->SetIntField(bufferObj, gCharArrayBufferClassInfo.sizeCopied, 0); 295 } 296} 297 298static void nativeCopyStringToBuffer(JNIEnv* env, jclass clazz, jint windowPtr, 299 jint row, jint column, jobject bufferObj) { 300 CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr); 301 LOG_WINDOW("Copying string for %d,%d from %p", row, column, window); 302 303 field_slot_t* fieldSlot = window->getFieldSlotWithCheck(row, column); 304 if (!fieldSlot) { 305 throwExceptionWithRowCol(env, row, column); 306 return; 307 } 308 309 uint8_t type = fieldSlot->type; 310 if (type == FIELD_TYPE_STRING) { 311 uint32_t size = fieldSlot->data.buffer.size; 312#if WINDOW_STORAGE_UTF8 313 if (size > 1) { 314 fillCharArrayBufferUTF(env, bufferObj, 315 window->getFieldSlotValueString(fieldSlot), size - 1); 316 } else { 317 clearCharArrayBuffer(env, bufferObj); 318 } 319#else 320 size_t chars = size / sizeof(char16_t); 321 if (chars) { 322 fillCharArrayBuffer(env, bufferObj, 323 window->getFieldSlotValueString(fieldSlot), chars); 324 } else { 325 clearCharArrayBuffer(env, bufferObj); 326 } 327#endif 328 } else if (type == FIELD_TYPE_INTEGER) { 329 int64_t value = window->getFieldSlotValueLong(fieldSlot); 330 char buf[32]; 331 snprintf(buf, sizeof(buf), "%lld", value); 332 fillCharArrayBufferUTF(env, bufferObj, buf, strlen(buf)); 333 } else if (type == FIELD_TYPE_FLOAT) { 334 double value = window->getFieldSlotValueDouble(fieldSlot); 335 char buf[32]; 336 snprintf(buf, sizeof(buf), "%g", value); 337 fillCharArrayBufferUTF(env, bufferObj, buf, strlen(buf)); 338 } else if (type == FIELD_TYPE_NULL) { 339 clearCharArrayBuffer(env, bufferObj); 340 } else if (type == FIELD_TYPE_BLOB) { 341 throw_sqlite3_exception(env, "Unable to convert BLOB to string"); 342 } else { 343 throwUnknownTypeException(env, type); 344 } 345} 346 347static jlong nativeGetLong(JNIEnv* env, jclass clazz, jint windowPtr, 348 jint row, jint column) { 349 CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr); 350 LOG_WINDOW("Getting long for %d,%d from %p", row, column, window); 351 352 field_slot_t* fieldSlot = window->getFieldSlotWithCheck(row, column); 353 if (!fieldSlot) { 354 throwExceptionWithRowCol(env, row, column); 355 return 0; 356 } 357 358 uint8_t type = fieldSlot->type; 359 if (type == FIELD_TYPE_INTEGER) { 360 return window->getFieldSlotValueLong(fieldSlot); 361 } else if (type == FIELD_TYPE_STRING) { 362 uint32_t size = fieldSlot->data.buffer.size; 363#if WINDOW_STORAGE_UTF8 364 return size > 1 ? strtoll(window->getFieldSlotValueString(fieldSlot), NULL, 0) : 0L; 365#else 366 size_t chars = size / sizeof(char16_t); 367 return chars ? strtoll(String8(window->getFieldSlotValueString(fieldSlot), chars) 368 .string(), NULL, 0) : 0L; 369#endif 370 } else if (type == FIELD_TYPE_FLOAT) { 371 return jlong(window->getFieldSlotValueDouble(fieldSlot)); 372 } else if (type == FIELD_TYPE_NULL) { 373 return 0; 374 } else if (type == FIELD_TYPE_BLOB) { 375 throw_sqlite3_exception(env, "Unable to convert BLOB to long"); 376 return 0; 377 } else { 378 throwUnknownTypeException(env, type); 379 return 0; 380 } 381} 382 383static jdouble nativeGetDouble(JNIEnv* env, jclass clazz, jint windowPtr, 384 jint row, jint column) { 385 CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr); 386 LOG_WINDOW("Getting double for %d,%d from %p", row, column, window); 387 388 field_slot_t* fieldSlot = window->getFieldSlotWithCheck(row, column); 389 if (!fieldSlot) { 390 throwExceptionWithRowCol(env, row, column); 391 return 0.0; 392 } 393 394 uint8_t type = fieldSlot->type; 395 if (type == FIELD_TYPE_FLOAT) { 396 return window->getFieldSlotValueDouble(fieldSlot); 397 } else if (type == FIELD_TYPE_STRING) { 398 uint32_t size = fieldSlot->data.buffer.size; 399#if WINDOW_STORAGE_UTF8 400 return size > 1 ? strtod(window->getFieldSlotValueString(fieldSlot), NULL) : 0.0; 401#else 402 size_t chars = size / sizeof(char16_t); 403 return chars ? strtod(String8(window->getFieldSlotValueString(fieldSlot), chars) 404 .string(), NULL) : 0.0; 405#endif 406 } else if (type == FIELD_TYPE_INTEGER) { 407 return jdouble(window->getFieldSlotValueLong(fieldSlot)); 408 } else if (type == FIELD_TYPE_NULL) { 409 return 0.0; 410 } else if (type == FIELD_TYPE_BLOB) { 411 throw_sqlite3_exception(env, "Unable to convert BLOB to double"); 412 return 0.0; 413 } else { 414 throwUnknownTypeException(env, type); 415 return 0.0; 416 } 417} 418 419static jboolean nativePutBlob(JNIEnv* env, jclass clazz, jint windowPtr, 420 jbyteArray valueObj, jint row, jint column) { 421 CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr); 422 field_slot_t * fieldSlot = window->getFieldSlotWithCheck(row, column); 423 if (fieldSlot == NULL) { 424 LOG_WINDOW(" getFieldSlotWithCheck error "); 425 return false; 426 } 427 428 jsize len = env->GetArrayLength(valueObj); 429 uint32_t offset = window->alloc(len); 430 if (!offset) { 431 LOG_WINDOW("Failed allocating %u bytes", len); 432 return false; 433 } 434 435 void* value = env->GetPrimitiveArrayCritical(valueObj, NULL); 436 window->copyIn(offset, static_cast<const uint8_t*>(value), len); 437 env->ReleasePrimitiveArrayCritical(valueObj, value, JNI_ABORT); 438 439 fieldSlot->type = FIELD_TYPE_BLOB; 440 fieldSlot->data.buffer.offset = offset; 441 fieldSlot->data.buffer.size = len; 442 LOG_WINDOW("%d,%d is BLOB with %u bytes @ %d", row, column, len, offset); 443 return true; 444} 445 446static jboolean nativePutString(JNIEnv* env, jclass clazz, jint windowPtr, 447 jstring valueObj, jint row, jint column) { 448 CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr); 449 field_slot_t * fieldSlot = window->getFieldSlotWithCheck(row, column); 450 if (fieldSlot == NULL) { 451 LOG_WINDOW(" getFieldSlotWithCheck error "); 452 return false; 453 } 454 455#if WINDOW_STORAGE_UTF8 456 size_t size = env->GetStringUTFLength(valueObj) + 1; 457 const char* valueStr = env->GetStringUTFChars(valueObj, NULL); 458#else 459 size_t size = env->GetStringLength(valueObj) * sizeof(jchar); 460 const jchar* valueStr = env->GetStringChars(valueObj, NULL); 461#endif 462 if (!valueStr) { 463 LOG_WINDOW("value can't be transfer to UTFChars"); 464 return false; 465 } 466 467 uint32_t offset = window->alloc(size); 468 if (!offset) { 469 LOG_WINDOW("Failed allocating %u bytes", size); 470#if WINDOW_STORAGE_UTF8 471 env->ReleaseStringUTFChars(valueObj, valueStr); 472#else 473 env->ReleaseStringChars(valueObj, valueStr); 474#endif 475 return false; 476 } 477 478 window->copyIn(offset, reinterpret_cast<const uint8_t*>(valueStr), size); 479 480#if WINDOW_STORAGE_UTF8 481 env->ReleaseStringUTFChars(valueObj, valueStr); 482#else 483 env->ReleaseStringChars(valueObj, valueStr); 484#endif 485 486 fieldSlot->type = FIELD_TYPE_STRING; 487 fieldSlot->data.buffer.offset = offset; 488 fieldSlot->data.buffer.size = size; 489 LOG_WINDOW("%d,%d is TEXT with %u bytes @ %d", row, column, size, offset); 490 return true; 491} 492 493static jboolean nativePutLong(JNIEnv* env, jclass clazz, jint windowPtr, 494 jlong value, jint row, jint column) { 495 CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr); 496 if (!window->putLong(row, column, value)) { 497 LOG_WINDOW(" getFieldSlotWithCheck error "); 498 return false; 499 } 500 501 LOG_WINDOW("%d,%d is INTEGER 0x%016llx", row, column, value); 502 return true; 503} 504 505static jboolean nativePutDouble(JNIEnv* env, jclass clazz, jint windowPtr, 506 jdouble value, jint row, jint column) { 507 CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr); 508 if (!window->putDouble(row, column, value)) { 509 LOG_WINDOW(" getFieldSlotWithCheck error "); 510 return false; 511 } 512 513 LOG_WINDOW("%d,%d is FLOAT %lf", row, column, value); 514 return true; 515} 516 517static jboolean nativePutNull(JNIEnv* env, jclass clazz, jint windowPtr, 518 jint row, jint column) { 519 CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr); 520 if (!window->putNull(row, column)) { 521 LOG_WINDOW(" getFieldSlotWithCheck error "); 522 return false; 523 } 524 525 LOG_WINDOW("%d,%d is NULL", row, column); 526 return true; 527} 528 529static JNINativeMethod sMethods[] = 530{ 531 /* name, signature, funcPtr */ 532 { "nativeInitializeEmpty", "(IZ)I", 533 (void*)nativeInitializeEmpty }, 534 { "nativeInitializeFromBinder", "(Landroid/os/IBinder;)I", 535 (void*)nativeInitializeFromBinder }, 536 { "nativeDispose", "(I)V", 537 (void*)nativeDispose }, 538 { "nativeGetBinder", "(I)Landroid/os/IBinder;", 539 (void*)nativeGetBinder }, 540 { "nativeClear", "(I)V", 541 (void*)nativeClear }, 542 { "nativeGetNumRows", "(I)I", 543 (void*)nativeGetNumRows }, 544 { "nativeSetNumColumns", "(II)Z", 545 (void*)nativeSetNumColumns }, 546 { "nativeAllocRow", "(I)Z", 547 (void*)nativeAllocRow }, 548 { "nativeFreeLastRow", "(I)V", 549 (void*)nativeFreeLastRow }, 550 { "nativeGetType", "(III)I", 551 (void*)nativeGetType }, 552 { "nativeGetBlob", "(III)[B", 553 (void*)nativeGetBlob }, 554 { "nativeGetString", "(III)Ljava/lang/String;", 555 (void*)nativeGetString }, 556 { "nativeGetLong", "(III)J", 557 (void*)nativeGetLong }, 558 { "nativeGetDouble", "(III)D", 559 (void*)nativeGetDouble }, 560 { "nativeCopyStringToBuffer", "(IIILandroid/database/CharArrayBuffer;)V", 561 (void*)nativeCopyStringToBuffer }, 562 { "nativePutBlob", "(I[BII)Z", 563 (void*)nativePutBlob }, 564 { "nativePutString", "(ILjava/lang/String;II)Z", 565 (void*)nativePutString }, 566 { "nativePutLong", "(IJII)Z", 567 (void*)nativePutLong }, 568 { "nativePutDouble", "(IDII)Z", 569 (void*)nativePutDouble }, 570 { "nativePutNull", "(III)Z", 571 (void*)nativePutNull }, 572}; 573 574#define FIND_CLASS(var, className) \ 575 var = env->FindClass(className); \ 576 LOG_FATAL_IF(! var, "Unable to find class " className); 577 578#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \ 579 var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \ 580 LOG_FATAL_IF(! var, "Unable to find field " fieldName); 581 582int register_android_database_CursorWindow(JNIEnv * env) 583{ 584 jclass clazz; 585 FIND_CLASS(clazz, "android/database/CharArrayBuffer"); 586 587 GET_FIELD_ID(gCharArrayBufferClassInfo.data, clazz, 588 "data", "[C"); 589 GET_FIELD_ID(gCharArrayBufferClassInfo.sizeCopied, clazz, 590 "sizeCopied", "I"); 591 592 gEmptyString = jstring(env->NewGlobalRef(env->NewStringUTF(""))); 593 LOG_FATAL_IF(!gEmptyString, "Unable to create empty string"); 594 595 return AndroidRuntime::registerNativeMethods(env, "android/database/CursorWindow", 596 sMethods, NELEM(sMethods)); 597} 598 599} // namespace android 600