CursorWindow.java revision 650de3dcfcbc7635da3c070410ef1dc4027ae464
1/* 2 * Copyright (C) 2006 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 17package android.database; 18 19import dalvik.system.CloseGuard; 20 21import android.content.res.Resources; 22import android.database.sqlite.SQLiteClosable; 23import android.database.sqlite.SQLiteException; 24import android.os.Binder; 25import android.os.Parcel; 26import android.os.Parcelable; 27import android.os.Process; 28import android.util.Log; 29import android.util.SparseIntArray; 30 31/** 32 * A buffer containing multiple cursor rows. 33 * <p> 34 * A {@link CursorWindow} is read-write when initially created and used locally. 35 * When sent to a remote process (by writing it to a {@link Parcel}), the remote process 36 * receives a read-only view of the cursor window. Typically the cursor window 37 * will be allocated by the producer, filled with data, and then sent to the 38 * consumer for reading. 39 * </p> 40 */ 41public class CursorWindow extends SQLiteClosable implements Parcelable { 42 private static final String STATS_TAG = "CursorWindowStats"; 43 44 /** The cursor window size. resource xml file specifies the value in kB. 45 * convert it to bytes here by multiplying with 1024. 46 */ 47 private static final int sCursorWindowSize = 48 Resources.getSystem().getInteger( 49 com.android.internal.R.integer.config_cursorWindowSize) * 1024; 50 51 /** 52 * The native CursorWindow object pointer. (FOR INTERNAL USE ONLY) 53 * @hide 54 */ 55 public int mWindowPtr; 56 57 private int mStartPos; 58 private final String mName; 59 60 private final CloseGuard mCloseGuard = CloseGuard.get(); 61 62 private static native int nativeCreate(String name, int cursorWindowSize); 63 private static native int nativeCreateFromParcel(Parcel parcel); 64 private static native void nativeDispose(int windowPtr); 65 private static native void nativeWriteToParcel(int windowPtr, Parcel parcel); 66 67 private static native void nativeClear(int windowPtr); 68 69 private static native int nativeGetNumRows(int windowPtr); 70 private static native boolean nativeSetNumColumns(int windowPtr, int columnNum); 71 private static native boolean nativeAllocRow(int windowPtr); 72 private static native void nativeFreeLastRow(int windowPtr); 73 74 private static native int nativeGetType(int windowPtr, int row, int column); 75 private static native byte[] nativeGetBlob(int windowPtr, int row, int column); 76 private static native String nativeGetString(int windowPtr, int row, int column); 77 private static native long nativeGetLong(int windowPtr, int row, int column); 78 private static native double nativeGetDouble(int windowPtr, int row, int column); 79 private static native void nativeCopyStringToBuffer(int windowPtr, int row, int column, 80 CharArrayBuffer buffer); 81 82 private static native boolean nativePutBlob(int windowPtr, byte[] value, int row, int column); 83 private static native boolean nativePutString(int windowPtr, String value, int row, int column); 84 private static native boolean nativePutLong(int windowPtr, long value, int row, int column); 85 private static native boolean nativePutDouble(int windowPtr, double value, int row, int column); 86 private static native boolean nativePutNull(int windowPtr, int row, int column); 87 88 private static native String nativeGetName(int windowPtr); 89 90 /** 91 * Creates a new empty cursor window and gives it a name. 92 * <p> 93 * The cursor initially has no rows or columns. Call {@link #setNumColumns(int)} to 94 * set the number of columns before adding any rows to the cursor. 95 * </p> 96 * 97 * @param name The name of the cursor window, or null if none. 98 */ 99 public CursorWindow(String name) { 100 mStartPos = 0; 101 mName = name; 102 mWindowPtr = nativeCreate(name, sCursorWindowSize); 103 if (mWindowPtr == 0) { 104 throw new CursorWindowAllocationException("Cursor window allocation of " + 105 (sCursorWindowSize / 1024) + " kb failed. " + printStats()); 106 } 107 mCloseGuard.open("close"); 108 recordNewWindow(Binder.getCallingPid(), mWindowPtr); 109 } 110 111 /** 112 * Creates a new empty cursor window. 113 * <p> 114 * The cursor initially has no rows or columns. Call {@link #setNumColumns(int)} to 115 * set the number of columns before adding any rows to the cursor. 116 * </p> 117 * 118 * @param localWindow True if this window will be used in this process only, 119 * false if it might be sent to another processes. This argument is ignored. 120 * 121 * @deprecated There is no longer a distinction between local and remote 122 * cursor windows. Use the {@link #CursorWindow(String)} constructor instead. 123 */ 124 @Deprecated 125 public CursorWindow(boolean localWindow) { 126 this((String)null); 127 } 128 129 private CursorWindow(Parcel source) { 130 mStartPos = source.readInt(); 131 mWindowPtr = nativeCreateFromParcel(source); 132 if (mWindowPtr == 0) { 133 throw new CursorWindowAllocationException("Cursor window could not be " 134 + "created from binder."); 135 } 136 mName = nativeGetName(mWindowPtr); 137 mCloseGuard.open("close"); 138 } 139 140 @Override 141 protected void finalize() throws Throwable { 142 try { 143 if (mCloseGuard != null) { 144 mCloseGuard.warnIfOpen(); 145 } 146 dispose(); 147 } finally { 148 super.finalize(); 149 } 150 } 151 152 private void dispose() { 153 if (mCloseGuard != null) { 154 mCloseGuard.close(); 155 } 156 if (mWindowPtr != 0) { 157 recordClosingOfWindow(mWindowPtr); 158 nativeDispose(mWindowPtr); 159 mWindowPtr = 0; 160 } 161 } 162 163 /** 164 * Gets the name of this cursor window. 165 * @hide 166 */ 167 public String getName() { 168 return mName; 169 } 170 171 /** 172 * Closes the cursor window and frees its underlying resources when all other 173 * remaining references have been released. 174 */ 175 public void close() { 176 releaseReference(); 177 } 178 179 /** 180 * Clears out the existing contents of the window, making it safe to reuse 181 * for new data. 182 * <p> 183 * The start position ({@link #getStartPosition()}), number of rows ({@link #getNumRows()}), 184 * and number of columns in the cursor are all reset to zero. 185 * </p> 186 */ 187 public void clear() { 188 acquireReference(); 189 try { 190 mStartPos = 0; 191 nativeClear(mWindowPtr); 192 } finally { 193 releaseReference(); 194 } 195 } 196 197 /** 198 * Gets the start position of this cursor window. 199 * <p> 200 * The start position is the zero-based index of the first row that this window contains 201 * relative to the entire result set of the {@link Cursor}. 202 * </p> 203 * 204 * @return The zero-based start position. 205 */ 206 public int getStartPosition() { 207 return mStartPos; 208 } 209 210 /** 211 * Sets the start position of this cursor window. 212 * <p> 213 * The start position is the zero-based index of the first row that this window contains 214 * relative to the entire result set of the {@link Cursor}. 215 * </p> 216 * 217 * @param pos The new zero-based start position. 218 */ 219 public void setStartPosition(int pos) { 220 mStartPos = pos; 221 } 222 223 /** 224 * Gets the number of rows in this window. 225 * 226 * @return The number of rows in this cursor window. 227 */ 228 public int getNumRows() { 229 acquireReference(); 230 try { 231 return nativeGetNumRows(mWindowPtr); 232 } finally { 233 releaseReference(); 234 } 235 } 236 237 /** 238 * Sets the number of columns in this window. 239 * <p> 240 * This method must be called before any rows are added to the window, otherwise 241 * it will fail to set the number of columns if it differs from the current number 242 * of columns. 243 * </p> 244 * 245 * @param columnNum The new number of columns. 246 * @return True if successful. 247 */ 248 public boolean setNumColumns(int columnNum) { 249 acquireReference(); 250 try { 251 return nativeSetNumColumns(mWindowPtr, columnNum); 252 } finally { 253 releaseReference(); 254 } 255 } 256 257 /** 258 * Allocates a new row at the end of this cursor window. 259 * 260 * @return True if successful, false if the cursor window is out of memory. 261 */ 262 public boolean allocRow(){ 263 acquireReference(); 264 try { 265 return nativeAllocRow(mWindowPtr); 266 } finally { 267 releaseReference(); 268 } 269 } 270 271 /** 272 * Frees the last row in this cursor window. 273 */ 274 public void freeLastRow(){ 275 acquireReference(); 276 try { 277 nativeFreeLastRow(mWindowPtr); 278 } finally { 279 releaseReference(); 280 } 281 } 282 283 /** 284 * Returns true if the field at the specified row and column index 285 * has type {@link Cursor#FIELD_TYPE_NULL}. 286 * 287 * @param row The zero-based row index. 288 * @param column The zero-based column index. 289 * @return True if the field has type {@link Cursor#FIELD_TYPE_NULL}. 290 * @deprecated Use {@link #getType(int, int)} instead. 291 */ 292 @Deprecated 293 public boolean isNull(int row, int column) { 294 return getType(row, column) == Cursor.FIELD_TYPE_NULL; 295 } 296 297 /** 298 * Returns true if the field at the specified row and column index 299 * has type {@link Cursor#FIELD_TYPE_BLOB} or {@link Cursor#FIELD_TYPE_NULL}. 300 * 301 * @param row The zero-based row index. 302 * @param column The zero-based column index. 303 * @return True if the field has type {@link Cursor#FIELD_TYPE_BLOB} or 304 * {@link Cursor#FIELD_TYPE_NULL}. 305 * @deprecated Use {@link #getType(int, int)} instead. 306 */ 307 @Deprecated 308 public boolean isBlob(int row, int column) { 309 int type = getType(row, column); 310 return type == Cursor.FIELD_TYPE_BLOB || type == Cursor.FIELD_TYPE_NULL; 311 } 312 313 /** 314 * Returns true if the field at the specified row and column index 315 * has type {@link Cursor#FIELD_TYPE_INTEGER}. 316 * 317 * @param row The zero-based row index. 318 * @param column The zero-based column index. 319 * @return True if the field has type {@link Cursor#FIELD_TYPE_INTEGER}. 320 * @deprecated Use {@link #getType(int, int)} instead. 321 */ 322 @Deprecated 323 public boolean isLong(int row, int column) { 324 return getType(row, column) == Cursor.FIELD_TYPE_INTEGER; 325 } 326 327 /** 328 * Returns true if the field at the specified row and column index 329 * has type {@link Cursor#FIELD_TYPE_FLOAT}. 330 * 331 * @param row The zero-based row index. 332 * @param column The zero-based column index. 333 * @return True if the field has type {@link Cursor#FIELD_TYPE_FLOAT}. 334 * @deprecated Use {@link #getType(int, int)} instead. 335 */ 336 @Deprecated 337 public boolean isFloat(int row, int column) { 338 return getType(row, column) == Cursor.FIELD_TYPE_FLOAT; 339 } 340 341 /** 342 * Returns true if the field at the specified row and column index 343 * has type {@link Cursor#FIELD_TYPE_STRING} or {@link Cursor#FIELD_TYPE_NULL}. 344 * 345 * @param row The zero-based row index. 346 * @param column The zero-based column index. 347 * @return True if the field has type {@link Cursor#FIELD_TYPE_STRING} 348 * or {@link Cursor#FIELD_TYPE_NULL}. 349 * @deprecated Use {@link #getType(int, int)} instead. 350 */ 351 @Deprecated 352 public boolean isString(int row, int column) { 353 int type = getType(row, column); 354 return type == Cursor.FIELD_TYPE_STRING || type == Cursor.FIELD_TYPE_NULL; 355 } 356 357 /** 358 * Returns the type of the field at the specified row and column index. 359 * <p> 360 * The returned field types are: 361 * <ul> 362 * <li>{@link Cursor#FIELD_TYPE_NULL}</li> 363 * <li>{@link Cursor#FIELD_TYPE_INTEGER}</li> 364 * <li>{@link Cursor#FIELD_TYPE_FLOAT}</li> 365 * <li>{@link Cursor#FIELD_TYPE_STRING}</li> 366 * <li>{@link Cursor#FIELD_TYPE_BLOB}</li> 367 * </ul> 368 * </p> 369 * 370 * @param row The zero-based row index. 371 * @param column The zero-based column index. 372 * @return The field type. 373 */ 374 public int getType(int row, int column) { 375 acquireReference(); 376 try { 377 return nativeGetType(mWindowPtr, row - mStartPos, column); 378 } finally { 379 releaseReference(); 380 } 381 } 382 383 /** 384 * Gets the value of the field at the specified row and column index as a byte array. 385 * <p> 386 * The result is determined as follows: 387 * <ul> 388 * <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result 389 * is <code>null</code>.</li> 390 * <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then the result 391 * is the blob value.</li> 392 * <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result 393 * is the array of bytes that make up the internal representation of the 394 * string value.</li> 395 * <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER} or 396 * {@link Cursor#FIELD_TYPE_FLOAT}, then a {@link SQLiteException} is thrown.</li> 397 * </ul> 398 * </p> 399 * 400 * @param row The zero-based row index. 401 * @param column The zero-based column index. 402 * @return The value of the field as a byte array. 403 */ 404 public byte[] getBlob(int row, int column) { 405 acquireReference(); 406 try { 407 return nativeGetBlob(mWindowPtr, row - mStartPos, column); 408 } finally { 409 releaseReference(); 410 } 411 } 412 413 /** 414 * Gets the value of the field at the specified row and column index as a string. 415 * <p> 416 * The result is determined as follows: 417 * <ul> 418 * <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result 419 * is <code>null</code>.</li> 420 * <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result 421 * is the string value.</li> 422 * <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result 423 * is a string representation of the integer in decimal, obtained by formatting the 424 * value with the <code>printf</code> family of functions using 425 * format specifier <code>%lld</code>.</li> 426 * <li>If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result 427 * is a string representation of the floating-point value in decimal, obtained by 428 * formatting the value with the <code>printf</code> family of functions using 429 * format specifier <code>%g</code>.</li> 430 * <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a 431 * {@link SQLiteException} is thrown.</li> 432 * </ul> 433 * </p> 434 * 435 * @param row The zero-based row index. 436 * @param column The zero-based column index. 437 * @return The value of the field as a string. 438 */ 439 public String getString(int row, int column) { 440 acquireReference(); 441 try { 442 return nativeGetString(mWindowPtr, row - mStartPos, column); 443 } finally { 444 releaseReference(); 445 } 446 } 447 448 /** 449 * Copies the text of the field at the specified row and column index into 450 * a {@link CharArrayBuffer}. 451 * <p> 452 * The buffer is populated as follows: 453 * <ul> 454 * <li>If the buffer is too small for the value to be copied, then it is 455 * automatically resized.</li> 456 * <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the buffer 457 * is set to an empty string.</li> 458 * <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the buffer 459 * is set to the contents of the string.</li> 460 * <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the buffer 461 * is set to a string representation of the integer in decimal, obtained by formatting the 462 * value with the <code>printf</code> family of functions using 463 * format specifier <code>%lld</code>.</li> 464 * <li>If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the buffer is 465 * set to a string representation of the floating-point value in decimal, obtained by 466 * formatting the value with the <code>printf</code> family of functions using 467 * format specifier <code>%g</code>.</li> 468 * <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a 469 * {@link SQLiteException} is thrown.</li> 470 * </ul> 471 * </p> 472 * 473 * @param row The zero-based row index. 474 * @param column The zero-based column index. 475 * @param buffer The {@link CharArrayBuffer} to hold the string. It is automatically 476 * resized if the requested string is larger than the buffer's current capacity. 477 */ 478 public void copyStringToBuffer(int row, int column, CharArrayBuffer buffer) { 479 if (buffer == null) { 480 throw new IllegalArgumentException("CharArrayBuffer should not be null"); 481 } 482 acquireReference(); 483 try { 484 nativeCopyStringToBuffer(mWindowPtr, row - mStartPos, column, buffer); 485 } finally { 486 releaseReference(); 487 } 488 } 489 490 /** 491 * Gets the value of the field at the specified row and column index as a <code>long</code>. 492 * <p> 493 * The result is determined as follows: 494 * <ul> 495 * <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result 496 * is <code>0L</code>.</li> 497 * <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result 498 * is the value obtained by parsing the string value with <code>strtoll</code>. 499 * <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result 500 * is the <code>long</code> value.</li> 501 * <li>If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result 502 * is the floating-point value converted to a <code>long</code>.</li> 503 * <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a 504 * {@link SQLiteException} is thrown.</li> 505 * </ul> 506 * </p> 507 * 508 * @param row The zero-based row index. 509 * @param column The zero-based column index. 510 * @return The value of the field as a <code>long</code>. 511 */ 512 public long getLong(int row, int column) { 513 acquireReference(); 514 try { 515 return nativeGetLong(mWindowPtr, row - mStartPos, column); 516 } finally { 517 releaseReference(); 518 } 519 } 520 521 /** 522 * Gets the value of the field at the specified row and column index as a 523 * <code>double</code>. 524 * <p> 525 * The result is determined as follows: 526 * <ul> 527 * <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result 528 * is <code>0.0</code>.</li> 529 * <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result 530 * is the value obtained by parsing the string value with <code>strtod</code>. 531 * <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result 532 * is the integer value converted to a <code>double</code>.</li> 533 * <li>If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result 534 * is the <code>double</code> value.</li> 535 * <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a 536 * {@link SQLiteException} is thrown.</li> 537 * </ul> 538 * </p> 539 * 540 * @param row The zero-based row index. 541 * @param column The zero-based column index. 542 * @return The value of the field as a <code>double</code>. 543 */ 544 public double getDouble(int row, int column) { 545 acquireReference(); 546 try { 547 return nativeGetDouble(mWindowPtr, row - mStartPos, column); 548 } finally { 549 releaseReference(); 550 } 551 } 552 553 /** 554 * Gets the value of the field at the specified row and column index as a 555 * <code>short</code>. 556 * <p> 557 * The result is determined by invoking {@link #getLong} and converting the 558 * result to <code>short</code>. 559 * </p> 560 * 561 * @param row The zero-based row index. 562 * @param column The zero-based column index. 563 * @return The value of the field as a <code>short</code>. 564 */ 565 public short getShort(int row, int column) { 566 return (short) getLong(row, column); 567 } 568 569 /** 570 * Gets the value of the field at the specified row and column index as an 571 * <code>int</code>. 572 * <p> 573 * The result is determined by invoking {@link #getLong} and converting the 574 * result to <code>int</code>. 575 * </p> 576 * 577 * @param row The zero-based row index. 578 * @param column The zero-based column index. 579 * @return The value of the field as an <code>int</code>. 580 */ 581 public int getInt(int row, int column) { 582 return (int) getLong(row, column); 583 } 584 585 /** 586 * Gets the value of the field at the specified row and column index as a 587 * <code>float</code>. 588 * <p> 589 * The result is determined by invoking {@link #getDouble} and converting the 590 * result to <code>float</code>. 591 * </p> 592 * 593 * @param row The zero-based row index. 594 * @param column The zero-based column index. 595 * @return The value of the field as an <code>float</code>. 596 */ 597 public float getFloat(int row, int column) { 598 return (float) getDouble(row, column); 599 } 600 601 /** 602 * Copies a byte array into the field at the specified row and column index. 603 * 604 * @param value The value to store. 605 * @param row The zero-based row index. 606 * @param column The zero-based column index. 607 * @return True if successful. 608 */ 609 public boolean putBlob(byte[] value, int row, int column) { 610 acquireReference(); 611 try { 612 return nativePutBlob(mWindowPtr, value, row - mStartPos, column); 613 } finally { 614 releaseReference(); 615 } 616 } 617 618 /** 619 * Copies a string into the field at the specified row and column index. 620 * 621 * @param value The value to store. 622 * @param row The zero-based row index. 623 * @param column The zero-based column index. 624 * @return True if successful. 625 */ 626 public boolean putString(String value, int row, int column) { 627 acquireReference(); 628 try { 629 return nativePutString(mWindowPtr, value, row - mStartPos, column); 630 } finally { 631 releaseReference(); 632 } 633 } 634 635 /** 636 * Puts a long integer into the field at the specified row and column index. 637 * 638 * @param value The value to store. 639 * @param row The zero-based row index. 640 * @param column The zero-based column index. 641 * @return True if successful. 642 */ 643 public boolean putLong(long value, int row, int column) { 644 acquireReference(); 645 try { 646 return nativePutLong(mWindowPtr, value, row - mStartPos, column); 647 } finally { 648 releaseReference(); 649 } 650 } 651 652 /** 653 * Puts a double-precision floating point value into the field at the 654 * specified row and column index. 655 * 656 * @param value The value to store. 657 * @param row The zero-based row index. 658 * @param column The zero-based column index. 659 * @return True if successful. 660 */ 661 public boolean putDouble(double value, int row, int column) { 662 acquireReference(); 663 try { 664 return nativePutDouble(mWindowPtr, value, row - mStartPos, column); 665 } finally { 666 releaseReference(); 667 } 668 } 669 670 /** 671 * Puts a null value into the field at the specified row and column index. 672 * 673 * @param row The zero-based row index. 674 * @param column The zero-based column index. 675 * @return True if successful. 676 */ 677 public boolean putNull(int row, int column) { 678 acquireReference(); 679 try { 680 return nativePutNull(mWindowPtr, row - mStartPos, column); 681 } finally { 682 releaseReference(); 683 } 684 } 685 686 public static final Parcelable.Creator<CursorWindow> CREATOR 687 = new Parcelable.Creator<CursorWindow>() { 688 public CursorWindow createFromParcel(Parcel source) { 689 return new CursorWindow(source); 690 } 691 692 public CursorWindow[] newArray(int size) { 693 return new CursorWindow[size]; 694 } 695 }; 696 697 public static CursorWindow newFromParcel(Parcel p) { 698 return CREATOR.createFromParcel(p); 699 } 700 701 public int describeContents() { 702 return 0; 703 } 704 705 public void writeToParcel(Parcel dest, int flags) { 706 dest.writeInt(mStartPos); 707 nativeWriteToParcel(mWindowPtr, dest); 708 709 if ((flags & Parcelable.PARCELABLE_WRITE_RETURN_VALUE) != 0) { 710 releaseReference(); 711 } 712 } 713 714 @Override 715 protected void onAllReferencesReleased() { 716 dispose(); 717 } 718 719 private static final SparseIntArray sWindowToPidMap = new SparseIntArray(); 720 721 private void recordNewWindow(int pid, int window) { 722 synchronized (sWindowToPidMap) { 723 sWindowToPidMap.put(window, pid); 724 if (Log.isLoggable(STATS_TAG, Log.VERBOSE)) { 725 Log.i(STATS_TAG, "Created a new Cursor. " + printStats()); 726 } 727 } 728 } 729 730 private void recordClosingOfWindow(int window) { 731 synchronized (sWindowToPidMap) { 732 if (sWindowToPidMap.size() == 0) { 733 // this means we are not in the ContentProvider. 734 return; 735 } 736 sWindowToPidMap.delete(window); 737 } 738 } 739 740 private String printStats() { 741 StringBuilder buff = new StringBuilder(); 742 int myPid = Process.myPid(); 743 int total = 0; 744 SparseIntArray pidCounts = new SparseIntArray(); 745 synchronized (sWindowToPidMap) { 746 int size = sWindowToPidMap.size(); 747 if (size == 0) { 748 // this means we are not in the ContentProvider. 749 return ""; 750 } 751 for (int indx = 0; indx < size; indx++) { 752 int pid = sWindowToPidMap.valueAt(indx); 753 int value = pidCounts.get(pid); 754 pidCounts.put(pid, ++value); 755 } 756 } 757 int numPids = pidCounts.size(); 758 for (int i = 0; i < numPids;i++) { 759 buff.append(" (# cursors opened by "); 760 int pid = pidCounts.keyAt(i); 761 if (pid == myPid) { 762 buff.append("this proc="); 763 } else { 764 buff.append("pid " + pid + "="); 765 } 766 int num = pidCounts.get(pid); 767 buff.append(num + ")"); 768 total += num; 769 } 770 // limit the returned string size to 1000 771 String s = (buff.length() > 980) ? buff.substring(0, 980) : buff.toString(); 772 return "# Open Cursors=" + total + s; 773 } 774 775 @Override 776 public String toString() { 777 return getName() + " {" + Integer.toHexString(mWindowPtr) + "}"; 778 } 779} 780