android_database_CursorWindow.cpp revision 0a2c6cc0138ceac8164061b3cdc3758441916c18
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, 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 jstring getString_native(JNIEnv* env, jobject object, jint row, jint column) 229{ 230 int32_t err; 231 CursorWindow * window = GET_WINDOW(env, object); 232LOG_WINDOW("Getting string 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 uint8_t type = field.type; 242 if (type == FIELD_TYPE_STRING) { 243 uint32_t size = field.data.buffer.size; 244 if (size > 0) { 245#if WINDOW_STORAGE_UTF8 246 // Pass size - 1 since the UTF8 is null terminated and we don't want a null terminator on the UTF16 string 247 String16 utf16((char const *)window->offsetToPtr(field.data.buffer.offset), size - 1); 248 return env->NewString((jchar const *)utf16.string(), utf16.size()); 249#else 250 return env->NewString((jchar const *)window->offsetToPtr(field.data.buffer.offset), size / 2); 251#endif 252 } else { 253 return env->NewStringUTF(""); 254 } 255 } else if (type == FIELD_TYPE_INTEGER) { 256 int64_t value; 257 if (window->getLong(row, column, &value)) { 258 char buf[32]; 259 snprintf(buf, sizeof(buf), "%lld", value); 260 return env->NewStringUTF(buf); 261 } 262 return NULL; 263 } else if (type == FIELD_TYPE_FLOAT) { 264 double value; 265 if (window->getDouble(row, column, &value)) { 266 char buf[32]; 267 snprintf(buf, sizeof(buf), "%g", value); 268 return env->NewStringUTF(buf); 269 } 270 return NULL; 271 } else if (type == FIELD_TYPE_NULL) { 272 return NULL; 273 } else if (type == FIELD_TYPE_BLOB) { 274 throw_sqlite3_exception(env, "Unable to convert BLOB to string"); 275 return NULL; 276 } else { 277 throwUnknowTypeException(env, type); 278 return NULL; 279 } 280} 281 282/** 283 * Use this only to convert characters that are known to be within the 284 * 0-127 range for direct conversion to UTF-16 285 */ 286static jint charToJchar(const char* src, jchar* dst, jint bufferSize) 287{ 288 int32_t len = strlen(src); 289 290 if (bufferSize < len) { 291 len = bufferSize; 292 } 293 294 for (int i = 0; i < len; i++) { 295 *dst++ = (*src++ & 0x7F); 296 } 297 return len; 298} 299 300static jcharArray copyStringToBuffer_native(JNIEnv* env, jobject object, jint row, 301 jint column, jint bufferSize, jobject buf) 302{ 303 int32_t err; 304 CursorWindow * window = GET_WINDOW(env, object); 305LOG_WINDOW("Copying string for %d,%d from %p", row, column, window); 306 307 field_slot_t field; 308 err = window->read_field_slot(row, column, &field); 309 if (err != 0) { 310 jniThrowException(env, "java/lang/IllegalStateException", "Unable to get field slot"); 311 return NULL; 312 } 313 314 jcharArray buffer = (jcharArray)env->GetObjectField(buf, gBufferField); 315 if (buffer == NULL) { 316 jniThrowException(env, "java/lang/IllegalStateException", "buf should not be null"); 317 return NULL; 318 } 319 jchar* dst = env->GetCharArrayElements(buffer, NULL); 320 uint8_t type = field.type; 321 uint32_t sizeCopied = 0; 322 jcharArray newArray = NULL; 323 if (type == FIELD_TYPE_STRING) { 324 uint32_t size = field.data.buffer.size; 325 if (size > 0) { 326#if WINDOW_STORAGE_UTF8 327 // Pass size - 1 since the UTF8 is null terminated and we don't want a null terminator on the UTF16 string 328 String16 utf16((char const *)window->offsetToPtr(field.data.buffer.offset), size - 1); 329 int32_t strSize = utf16.size(); 330 if (strSize > bufferSize || dst == NULL) { 331 newArray = env->NewCharArray(strSize); 332 env->SetCharArrayRegion(newArray, 0, strSize, (jchar const *)utf16.string()); 333 } else { 334 memcpy(dst, (jchar const *)utf16.string(), strSize * 2); 335 } 336 sizeCopied = strSize; 337#else 338 sizeCopied = size/2 + size % 2; 339 if (size > bufferSize * 2 || dst == NULL) { 340 newArray = env->NewCharArray(sizeCopied); 341 memcpy(newArray, (jchar const *)window->offsetToPtr(field.data.buffer.offset), size); 342 } else { 343 memcpy(dst, (jchar const *)window->offsetToPtr(field.data.buffer.offset), size); 344 } 345#endif 346 } 347 } else if (type == FIELD_TYPE_INTEGER) { 348 int64_t value; 349 if (window->getLong(row, column, &value)) { 350 char buf[32]; 351 int len; 352 snprintf(buf, sizeof(buf), "%lld", value); 353 jchar* dst = env->GetCharArrayElements(buffer, NULL); 354 sizeCopied = charToJchar(buf, dst, bufferSize); 355 } 356 } else if (type == FIELD_TYPE_FLOAT) { 357 double value; 358 if (window->getDouble(row, column, &value)) { 359 char tempbuf[32]; 360 snprintf(tempbuf, sizeof(tempbuf), "%g", value); 361 jchar* dst = env->GetCharArrayElements(buffer, NULL); 362 sizeCopied = charToJchar(tempbuf, dst, bufferSize); 363 } 364 } else if (type == FIELD_TYPE_NULL) { 365 } else if (type == FIELD_TYPE_BLOB) { 366 throw_sqlite3_exception(env, "Unable to convert BLOB to string"); 367 } else { 368 LOGE("Unknown field type %d", type); 369 throw_sqlite3_exception(env, "UNKNOWN type in copyStringToBuffer_native()"); 370 } 371 SET_SIZE_COPIED(env, buf, sizeCopied); 372 env->ReleaseCharArrayElements(buffer, dst, JNI_OK); 373 return newArray; 374} 375 376static jdouble getDouble_native(JNIEnv* env, jobject object, jint row, jint column) 377{ 378 int32_t err; 379 CursorWindow * window = GET_WINDOW(env, object); 380LOG_WINDOW("Getting double for %d,%d from %p", row, column, window); 381 382 field_slot_t field; 383 err = window->read_field_slot(row, column, &field); 384 if (err != 0) { 385 throwExceptionWithRowCol(env, row, column); 386 return 0.0; 387 } 388 389 uint8_t type = field.type; 390 if (type == FIELD_TYPE_FLOAT) { 391 double value; 392 if (window->getDouble(row, column, &value)) { 393 return value; 394 } 395 return 0.0; 396 } else if (type == FIELD_TYPE_STRING) { 397 uint32_t size = field.data.buffer.size; 398 if (size > 0) { 399#if WINDOW_STORAGE_UTF8 400 return strtod((char const *)window->offsetToPtr(field.data.buffer.offset), NULL); 401#else 402 String8 ascii((char16_t *) window->offsetToPtr(field.data.buffer.offset), size / 2); 403 char const * str = ascii.string(); 404 return strtod(str, NULL); 405#endif 406 } else { 407 return 0.0; 408 } 409 } else if (type == FIELD_TYPE_INTEGER) { 410 int64_t value; 411 if (window->getLong(row, column, &value)) { 412 return (double) value; 413 } 414 return 0.0; 415 } else if (type == FIELD_TYPE_NULL) { 416 return 0.0; 417 } else if (type == FIELD_TYPE_BLOB) { 418 throw_sqlite3_exception(env, "Unable to convert BLOB to double"); 419 return 0.0; 420 } else { 421 throwUnknowTypeException(env, type); 422 return 0.0; 423 } 424} 425 426bool isNull_native(CursorWindow *window, jint row, jint column) 427{ 428 LOG_WINDOW("Checking for NULL at %d,%d from %p", row, column, window); 429 430 bool isNull; 431 if (window->getNull(row, column, &isNull)) { 432 return isNull; 433 } 434 435 //TODO throw execption? 436 return true; 437} 438 439static jint getNumRows(JNIEnv * env, jobject object) 440{ 441 CursorWindow * window = GET_WINDOW(env, object); 442 return window->getNumRows(); 443} 444 445static jboolean setNumColumns(JNIEnv * env, jobject object, jint columnNum) 446{ 447 CursorWindow * window = GET_WINDOW(env, object); 448 return window->setNumColumns(columnNum); 449} 450 451static jboolean allocRow(JNIEnv * env, jobject object) 452{ 453 CursorWindow * window = GET_WINDOW(env, object); 454 return window->allocRow() != NULL; 455} 456 457static jboolean putBlob_native(JNIEnv * env, jobject object, jbyteArray value, jint row, jint col) 458{ 459 CursorWindow * window = GET_WINDOW(env, object); 460 if (!value) { 461 LOG_WINDOW("How did a null value send to here"); 462 return false; 463 } 464 field_slot_t * fieldSlot = window->getFieldSlotWithCheck(row, col); 465 if (fieldSlot == NULL) { 466 LOG_WINDOW(" getFieldSlotWithCheck error "); 467 return false; 468 } 469 470 jint len = env->GetArrayLength(value); 471 int offset = window->alloc(len); 472 if (!offset) { 473 LOG_WINDOW("Failed allocating %u bytes", len); 474 return false; 475 } 476 jbyte * bytes = env->GetByteArrayElements(value, NULL); 477 window->copyIn(offset, (uint8_t const *)bytes, len); 478 479 // This must be updated after the call to alloc(), since that 480 // may move the field around in the window 481 fieldSlot->type = FIELD_TYPE_BLOB; 482 fieldSlot->data.buffer.offset = offset; 483 fieldSlot->data.buffer.size = len; 484 env->ReleaseByteArrayElements(value, bytes, JNI_ABORT); 485 LOG_WINDOW("%d,%d is BLOB with %u bytes @ %d", row, col, len, offset); 486 return true; 487} 488 489static jboolean putString_native(JNIEnv * env, jobject object, jstring value, jint row, jint col) 490{ 491 CursorWindow * window = GET_WINDOW(env, object); 492 if (!value) { 493 LOG_WINDOW("How did a null value send to here"); 494 return false; 495 } 496 field_slot_t * fieldSlot = window->getFieldSlotWithCheck(row, col); 497 if (fieldSlot == NULL) { 498 LOG_WINDOW(" getFieldSlotWithCheck error "); 499 return false; 500 } 501 502#if WINDOW_STORAGE_UTF8 503 int len = env->GetStringUTFLength(value) + 1; 504 char const * valStr = env->GetStringUTFChars(value, NULL); 505#else 506 int len = env->GetStringLength(value); 507 // GetStringLength return number of chars and one char takes 2 bytes 508 len *= 2; 509 const jchar* valStr = env->GetStringChars(value, NULL); 510#endif 511 if (!valStr) { 512 LOG_WINDOW("value can't be transfer to UTFChars"); 513 return false; 514 } 515 516 int offset = window->alloc(len); 517 if (!offset) { 518 LOG_WINDOW("Failed allocating %u bytes", len); 519#if WINDOW_STORAGE_UTF8 520 env->ReleaseStringUTFChars(value, valStr); 521#else 522 env->ReleaseStringChars(value, valStr); 523#endif 524 return false; 525 } 526 527 window->copyIn(offset, (uint8_t const *)valStr, len); 528 529 // This must be updated after the call to alloc(), since that 530 // may move the field around in the window 531 fieldSlot->type = FIELD_TYPE_STRING; 532 fieldSlot->data.buffer.offset = offset; 533 fieldSlot->data.buffer.size = len; 534 535 LOG_WINDOW("%d,%d is TEXT with %u bytes @ %d", row, col, len, offset); 536#if WINDOW_STORAGE_UTF8 537 env->ReleaseStringUTFChars(value, valStr); 538#else 539 env->ReleaseStringChars(value, valStr); 540#endif 541 542 return true; 543} 544 545static jboolean putLong_native(JNIEnv * env, jobject object, jlong value, jint row, jint col) 546{ 547 CursorWindow * window = GET_WINDOW(env, object); 548 if (!window->putLong(row, col, value)) { 549 LOG_WINDOW(" getFieldSlotWithCheck error "); 550 return false; 551 } 552 553 LOG_WINDOW("%d,%d is INTEGER 0x%016llx", row, col, value); 554 555 return true; 556} 557 558static jboolean putDouble_native(JNIEnv * env, jobject object, jdouble value, jint row, jint col) 559{ 560 CursorWindow * window = GET_WINDOW(env, object); 561 if (!window->putDouble(row, col, value)) { 562 LOG_WINDOW(" getFieldSlotWithCheck error "); 563 return false; 564 } 565 566 LOG_WINDOW("%d,%d is FLOAT %lf", row, col, value); 567 568 return true; 569} 570 571static jboolean putNull_native(JNIEnv * env, jobject object, jint row, jint col) 572{ 573 CursorWindow * window = GET_WINDOW(env, object); 574 if (!window->putNull(row, col)) { 575 LOG_WINDOW(" getFieldSlotWithCheck error "); 576 return false; 577 } 578 579 LOG_WINDOW("%d,%d is NULL", row, col); 580 581 return true; 582} 583 584// free the last row 585static void freeLastRow(JNIEnv * env, jobject object) { 586 CursorWindow * window = GET_WINDOW(env, object); 587 window->freeLastRow(); 588} 589 590static jint getType_native(JNIEnv* env, jobject object, jint row, jint column) 591{ 592 int32_t err; 593 CursorWindow * window = GET_WINDOW(env, object); 594 LOG_WINDOW("returning column type affinity for %d,%d from %p", row, column, window); 595 596 if (isNull_native(window, row, column)) { 597 return FIELD_TYPE_NULL; 598 } 599 600 field_slot_t field; 601 err = window->read_field_slot(row, column, &field); 602 if (err != 0) { 603 throwExceptionWithRowCol(env, row, column); 604 return NULL; 605 } 606 607 return field.type; 608} 609 610static JNINativeMethod sMethods[] = 611{ 612 /* name, signature, funcPtr */ 613 {"native_init", "(Z)V", (void *)native_init_empty}, 614 {"native_init", "(Landroid/os/IBinder;)V", (void *)native_init_memory}, 615 {"native_getBinder", "()Landroid/os/IBinder;", (void *)native_getBinder}, 616 {"native_clear", "()V", (void *)native_clear}, 617 {"close_native", "()V", (void *)native_close}, 618 {"getLong_native", "(II)J", (void *)getLong_native}, 619 {"getBlob_native", "(II)[B", (void *)getBlob_native}, 620 {"getString_native", "(II)Ljava/lang/String;", (void *)getString_native}, 621 {"copyStringToBuffer_native", "(IIILandroid/database/CharArrayBuffer;)[C", (void *)copyStringToBuffer_native}, 622 {"getDouble_native", "(II)D", (void *)getDouble_native}, 623 {"getNumRows_native", "()I", (void *)getNumRows}, 624 {"setNumColumns_native", "(I)Z", (void *)setNumColumns}, 625 {"allocRow_native", "()Z", (void *)allocRow}, 626 {"putBlob_native", "([BII)Z", (void *)putBlob_native}, 627 {"putString_native", "(Ljava/lang/String;II)Z", (void *)putString_native}, 628 {"putLong_native", "(JII)Z", (void *)putLong_native}, 629 {"putDouble_native", "(DII)Z", (void *)putDouble_native}, 630 {"freeLastRow_native", "()V", (void *)freeLastRow}, 631 {"putNull_native", "(II)Z", (void *)putNull_native}, 632 {"getType_native", "(II)I", (void *)getType_native}, 633}; 634 635int register_android_database_CursorWindow(JNIEnv * env) 636{ 637 jclass clazz; 638 639 clazz = env->FindClass("android/database/CursorWindow"); 640 if (clazz == NULL) { 641 LOGE("Can't find android/database/CursorWindow"); 642 return -1; 643 } 644 645 gWindowField = env->GetFieldID(clazz, "nWindow", "I"); 646 647 if (gWindowField == NULL) { 648 LOGE("Error locating fields"); 649 return -1; 650 } 651 652 clazz = env->FindClass("android/database/CharArrayBuffer"); 653 if (clazz == NULL) { 654 LOGE("Can't find android/database/CharArrayBuffer"); 655 return -1; 656 } 657 658 gBufferField = env->GetFieldID(clazz, "data", "[C"); 659 660 if (gBufferField == NULL) { 661 LOGE("Error locating fields data in CharArrayBuffer"); 662 return -1; 663 } 664 665 gSizeCopiedField = env->GetFieldID(clazz, "sizeCopied", "I"); 666 667 if (gSizeCopiedField == NULL) { 668 LOGE("Error locating fields sizeCopied in CharArrayBuffer"); 669 return -1; 670 } 671 672 return AndroidRuntime::registerNativeMethods(env, "android/database/CursorWindow", 673 sMethods, NELEM(sMethods)); 674} 675 676} // namespace android 677