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