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 != null && name.length() != 0 ? name : "<unnamed>"; 102 mWindowPtr = nativeCreate(mName, 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, never null. 165 * @hide 166 */ 167 public String getName() { 168 return mName; 169 } 170 171 /** 172 * Clears out the existing contents of the window, making it safe to reuse 173 * for new data. 174 * <p> 175 * The start position ({@link #getStartPosition()}), number of rows ({@link #getNumRows()}), 176 * and number of columns in the cursor are all reset to zero. 177 * </p> 178 */ 179 public void clear() { 180 acquireReference(); 181 try { 182 mStartPos = 0; 183 nativeClear(mWindowPtr); 184 } finally { 185 releaseReference(); 186 } 187 } 188 189 /** 190 * Gets the start position of this cursor window. 191 * <p> 192 * The start position is the zero-based index of the first row that this window contains 193 * relative to the entire result set of the {@link Cursor}. 194 * </p> 195 * 196 * @return The zero-based start position. 197 */ 198 public int getStartPosition() { 199 return mStartPos; 200 } 201 202 /** 203 * Sets the start position of this cursor window. 204 * <p> 205 * The start position is the zero-based index of the first row that this window contains 206 * relative to the entire result set of the {@link Cursor}. 207 * </p> 208 * 209 * @param pos The new zero-based start position. 210 */ 211 public void setStartPosition(int pos) { 212 mStartPos = pos; 213 } 214 215 /** 216 * Gets the number of rows in this window. 217 * 218 * @return The number of rows in this cursor window. 219 */ 220 public int getNumRows() { 221 acquireReference(); 222 try { 223 return nativeGetNumRows(mWindowPtr); 224 } finally { 225 releaseReference(); 226 } 227 } 228 229 /** 230 * Sets the number of columns in this window. 231 * <p> 232 * This method must be called before any rows are added to the window, otherwise 233 * it will fail to set the number of columns if it differs from the current number 234 * of columns. 235 * </p> 236 * 237 * @param columnNum The new number of columns. 238 * @return True if successful. 239 */ 240 public boolean setNumColumns(int columnNum) { 241 acquireReference(); 242 try { 243 return nativeSetNumColumns(mWindowPtr, columnNum); 244 } finally { 245 releaseReference(); 246 } 247 } 248 249 /** 250 * Allocates a new row at the end of this cursor window. 251 * 252 * @return True if successful, false if the cursor window is out of memory. 253 */ 254 public boolean allocRow(){ 255 acquireReference(); 256 try { 257 return nativeAllocRow(mWindowPtr); 258 } finally { 259 releaseReference(); 260 } 261 } 262 263 /** 264 * Frees the last row in this cursor window. 265 */ 266 public void freeLastRow(){ 267 acquireReference(); 268 try { 269 nativeFreeLastRow(mWindowPtr); 270 } finally { 271 releaseReference(); 272 } 273 } 274 275 /** 276 * Returns true if the field at the specified row and column index 277 * has type {@link Cursor#FIELD_TYPE_NULL}. 278 * 279 * @param row The zero-based row index. 280 * @param column The zero-based column index. 281 * @return True if the field has type {@link Cursor#FIELD_TYPE_NULL}. 282 * @deprecated Use {@link #getType(int, int)} instead. 283 */ 284 @Deprecated 285 public boolean isNull(int row, int column) { 286 return getType(row, column) == Cursor.FIELD_TYPE_NULL; 287 } 288 289 /** 290 * Returns true if the field at the specified row and column index 291 * has type {@link Cursor#FIELD_TYPE_BLOB} or {@link Cursor#FIELD_TYPE_NULL}. 292 * 293 * @param row The zero-based row index. 294 * @param column The zero-based column index. 295 * @return True if the field has type {@link Cursor#FIELD_TYPE_BLOB} or 296 * {@link Cursor#FIELD_TYPE_NULL}. 297 * @deprecated Use {@link #getType(int, int)} instead. 298 */ 299 @Deprecated 300 public boolean isBlob(int row, int column) { 301 int type = getType(row, column); 302 return type == Cursor.FIELD_TYPE_BLOB || type == Cursor.FIELD_TYPE_NULL; 303 } 304 305 /** 306 * Returns true if the field at the specified row and column index 307 * has type {@link Cursor#FIELD_TYPE_INTEGER}. 308 * 309 * @param row The zero-based row index. 310 * @param column The zero-based column index. 311 * @return True if the field has type {@link Cursor#FIELD_TYPE_INTEGER}. 312 * @deprecated Use {@link #getType(int, int)} instead. 313 */ 314 @Deprecated 315 public boolean isLong(int row, int column) { 316 return getType(row, column) == Cursor.FIELD_TYPE_INTEGER; 317 } 318 319 /** 320 * Returns true if the field at the specified row and column index 321 * has type {@link Cursor#FIELD_TYPE_FLOAT}. 322 * 323 * @param row The zero-based row index. 324 * @param column The zero-based column index. 325 * @return True if the field has type {@link Cursor#FIELD_TYPE_FLOAT}. 326 * @deprecated Use {@link #getType(int, int)} instead. 327 */ 328 @Deprecated 329 public boolean isFloat(int row, int column) { 330 return getType(row, column) == Cursor.FIELD_TYPE_FLOAT; 331 } 332 333 /** 334 * Returns true if the field at the specified row and column index 335 * has type {@link Cursor#FIELD_TYPE_STRING} or {@link Cursor#FIELD_TYPE_NULL}. 336 * 337 * @param row The zero-based row index. 338 * @param column The zero-based column index. 339 * @return True if the field has type {@link Cursor#FIELD_TYPE_STRING} 340 * or {@link Cursor#FIELD_TYPE_NULL}. 341 * @deprecated Use {@link #getType(int, int)} instead. 342 */ 343 @Deprecated 344 public boolean isString(int row, int column) { 345 int type = getType(row, column); 346 return type == Cursor.FIELD_TYPE_STRING || type == Cursor.FIELD_TYPE_NULL; 347 } 348 349 /** 350 * Returns the type of the field at the specified row and column index. 351 * <p> 352 * The returned field types are: 353 * <ul> 354 * <li>{@link Cursor#FIELD_TYPE_NULL}</li> 355 * <li>{@link Cursor#FIELD_TYPE_INTEGER}</li> 356 * <li>{@link Cursor#FIELD_TYPE_FLOAT}</li> 357 * <li>{@link Cursor#FIELD_TYPE_STRING}</li> 358 * <li>{@link Cursor#FIELD_TYPE_BLOB}</li> 359 * </ul> 360 * </p> 361 * 362 * @param row The zero-based row index. 363 * @param column The zero-based column index. 364 * @return The field type. 365 */ 366 public int getType(int row, int column) { 367 acquireReference(); 368 try { 369 return nativeGetType(mWindowPtr, row - mStartPos, column); 370 } finally { 371 releaseReference(); 372 } 373 } 374 375 /** 376 * Gets the value of the field at the specified row and column index as a byte array. 377 * <p> 378 * The result is determined as follows: 379 * <ul> 380 * <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result 381 * is <code>null</code>.</li> 382 * <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then the result 383 * is the blob value.</li> 384 * <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result 385 * is the array of bytes that make up the internal representation of the 386 * string value.</li> 387 * <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER} or 388 * {@link Cursor#FIELD_TYPE_FLOAT}, then a {@link SQLiteException} is thrown.</li> 389 * </ul> 390 * </p> 391 * 392 * @param row The zero-based row index. 393 * @param column The zero-based column index. 394 * @return The value of the field as a byte array. 395 */ 396 public byte[] getBlob(int row, int column) { 397 acquireReference(); 398 try { 399 return nativeGetBlob(mWindowPtr, row - mStartPos, column); 400 } finally { 401 releaseReference(); 402 } 403 } 404 405 /** 406 * Gets the value of the field at the specified row and column index as a string. 407 * <p> 408 * The result is determined as follows: 409 * <ul> 410 * <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result 411 * is <code>null</code>.</li> 412 * <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result 413 * is the string value.</li> 414 * <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result 415 * is a string representation of the integer in decimal, obtained by formatting the 416 * value with the <code>printf</code> family of functions using 417 * format specifier <code>%lld</code>.</li> 418 * <li>If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result 419 * is a string representation of the floating-point value in decimal, obtained by 420 * formatting the value with the <code>printf</code> family of functions using 421 * format specifier <code>%g</code>.</li> 422 * <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a 423 * {@link SQLiteException} is thrown.</li> 424 * </ul> 425 * </p> 426 * 427 * @param row The zero-based row index. 428 * @param column The zero-based column index. 429 * @return The value of the field as a string. 430 */ 431 public String getString(int row, int column) { 432 acquireReference(); 433 try { 434 return nativeGetString(mWindowPtr, row - mStartPos, column); 435 } finally { 436 releaseReference(); 437 } 438 } 439 440 /** 441 * Copies the text of the field at the specified row and column index into 442 * a {@link CharArrayBuffer}. 443 * <p> 444 * The buffer is populated as follows: 445 * <ul> 446 * <li>If the buffer is too small for the value to be copied, then it is 447 * automatically resized.</li> 448 * <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the buffer 449 * is set to an empty string.</li> 450 * <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the buffer 451 * is set to the contents of the string.</li> 452 * <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the buffer 453 * is set to a string representation of the integer in decimal, obtained by formatting the 454 * value with the <code>printf</code> family of functions using 455 * format specifier <code>%lld</code>.</li> 456 * <li>If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the buffer is 457 * set to a string representation of the floating-point value in decimal, obtained by 458 * formatting the value with the <code>printf</code> family of functions using 459 * format specifier <code>%g</code>.</li> 460 * <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a 461 * {@link SQLiteException} is thrown.</li> 462 * </ul> 463 * </p> 464 * 465 * @param row The zero-based row index. 466 * @param column The zero-based column index. 467 * @param buffer The {@link CharArrayBuffer} to hold the string. It is automatically 468 * resized if the requested string is larger than the buffer's current capacity. 469 */ 470 public void copyStringToBuffer(int row, int column, CharArrayBuffer buffer) { 471 if (buffer == null) { 472 throw new IllegalArgumentException("CharArrayBuffer should not be null"); 473 } 474 acquireReference(); 475 try { 476 nativeCopyStringToBuffer(mWindowPtr, row - mStartPos, column, buffer); 477 } finally { 478 releaseReference(); 479 } 480 } 481 482 /** 483 * Gets the value of the field at the specified row and column index as a <code>long</code>. 484 * <p> 485 * The result is determined as follows: 486 * <ul> 487 * <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result 488 * is <code>0L</code>.</li> 489 * <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result 490 * is the value obtained by parsing the string value with <code>strtoll</code>. 491 * <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result 492 * is the <code>long</code> value.</li> 493 * <li>If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result 494 * is the floating-point value converted to a <code>long</code>.</li> 495 * <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a 496 * {@link SQLiteException} is thrown.</li> 497 * </ul> 498 * </p> 499 * 500 * @param row The zero-based row index. 501 * @param column The zero-based column index. 502 * @return The value of the field as a <code>long</code>. 503 */ 504 public long getLong(int row, int column) { 505 acquireReference(); 506 try { 507 return nativeGetLong(mWindowPtr, row - mStartPos, column); 508 } finally { 509 releaseReference(); 510 } 511 } 512 513 /** 514 * Gets the value of the field at the specified row and column index as a 515 * <code>double</code>. 516 * <p> 517 * The result is determined as follows: 518 * <ul> 519 * <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result 520 * is <code>0.0</code>.</li> 521 * <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result 522 * is the value obtained by parsing the string value with <code>strtod</code>. 523 * <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result 524 * is the integer value converted to a <code>double</code>.</li> 525 * <li>If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result 526 * is the <code>double</code> value.</li> 527 * <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a 528 * {@link SQLiteException} is thrown.</li> 529 * </ul> 530 * </p> 531 * 532 * @param row The zero-based row index. 533 * @param column The zero-based column index. 534 * @return The value of the field as a <code>double</code>. 535 */ 536 public double getDouble(int row, int column) { 537 acquireReference(); 538 try { 539 return nativeGetDouble(mWindowPtr, row - mStartPos, column); 540 } finally { 541 releaseReference(); 542 } 543 } 544 545 /** 546 * Gets the value of the field at the specified row and column index as a 547 * <code>short</code>. 548 * <p> 549 * The result is determined by invoking {@link #getLong} and converting the 550 * result to <code>short</code>. 551 * </p> 552 * 553 * @param row The zero-based row index. 554 * @param column The zero-based column index. 555 * @return The value of the field as a <code>short</code>. 556 */ 557 public short getShort(int row, int column) { 558 return (short) getLong(row, column); 559 } 560 561 /** 562 * Gets the value of the field at the specified row and column index as an 563 * <code>int</code>. 564 * <p> 565 * The result is determined by invoking {@link #getLong} and converting the 566 * result to <code>int</code>. 567 * </p> 568 * 569 * @param row The zero-based row index. 570 * @param column The zero-based column index. 571 * @return The value of the field as an <code>int</code>. 572 */ 573 public int getInt(int row, int column) { 574 return (int) getLong(row, column); 575 } 576 577 /** 578 * Gets the value of the field at the specified row and column index as a 579 * <code>float</code>. 580 * <p> 581 * The result is determined by invoking {@link #getDouble} and converting the 582 * result to <code>float</code>. 583 * </p> 584 * 585 * @param row The zero-based row index. 586 * @param column The zero-based column index. 587 * @return The value of the field as an <code>float</code>. 588 */ 589 public float getFloat(int row, int column) { 590 return (float) getDouble(row, column); 591 } 592 593 /** 594 * Copies a byte array into the field at the specified row and column index. 595 * 596 * @param value The value to store. 597 * @param row The zero-based row index. 598 * @param column The zero-based column index. 599 * @return True if successful. 600 */ 601 public boolean putBlob(byte[] value, int row, int column) { 602 acquireReference(); 603 try { 604 return nativePutBlob(mWindowPtr, value, row - mStartPos, column); 605 } finally { 606 releaseReference(); 607 } 608 } 609 610 /** 611 * Copies a string into the field at the specified row and column index. 612 * 613 * @param value The value to store. 614 * @param row The zero-based row index. 615 * @param column The zero-based column index. 616 * @return True if successful. 617 */ 618 public boolean putString(String value, int row, int column) { 619 acquireReference(); 620 try { 621 return nativePutString(mWindowPtr, value, row - mStartPos, column); 622 } finally { 623 releaseReference(); 624 } 625 } 626 627 /** 628 * Puts a long integer into the field at the specified row and column index. 629 * 630 * @param value The value to store. 631 * @param row The zero-based row index. 632 * @param column The zero-based column index. 633 * @return True if successful. 634 */ 635 public boolean putLong(long value, int row, int column) { 636 acquireReference(); 637 try { 638 return nativePutLong(mWindowPtr, value, row - mStartPos, column); 639 } finally { 640 releaseReference(); 641 } 642 } 643 644 /** 645 * Puts a double-precision floating point value into the field at the 646 * specified row and column index. 647 * 648 * @param value The value to store. 649 * @param row The zero-based row index. 650 * @param column The zero-based column index. 651 * @return True if successful. 652 */ 653 public boolean putDouble(double value, int row, int column) { 654 acquireReference(); 655 try { 656 return nativePutDouble(mWindowPtr, value, row - mStartPos, column); 657 } finally { 658 releaseReference(); 659 } 660 } 661 662 /** 663 * Puts a null value into the field at the specified row and column index. 664 * 665 * @param row The zero-based row index. 666 * @param column The zero-based column index. 667 * @return True if successful. 668 */ 669 public boolean putNull(int row, int column) { 670 acquireReference(); 671 try { 672 return nativePutNull(mWindowPtr, row - mStartPos, column); 673 } finally { 674 releaseReference(); 675 } 676 } 677 678 public static final Parcelable.Creator<CursorWindow> CREATOR 679 = new Parcelable.Creator<CursorWindow>() { 680 public CursorWindow createFromParcel(Parcel source) { 681 return new CursorWindow(source); 682 } 683 684 public CursorWindow[] newArray(int size) { 685 return new CursorWindow[size]; 686 } 687 }; 688 689 public static CursorWindow newFromParcel(Parcel p) { 690 return CREATOR.createFromParcel(p); 691 } 692 693 public int describeContents() { 694 return 0; 695 } 696 697 public void writeToParcel(Parcel dest, int flags) { 698 acquireReference(); 699 try { 700 dest.writeInt(mStartPos); 701 nativeWriteToParcel(mWindowPtr, dest); 702 } finally { 703 releaseReference(); 704 } 705 706 if ((flags & Parcelable.PARCELABLE_WRITE_RETURN_VALUE) != 0) { 707 releaseReference(); 708 } 709 } 710 711 @Override 712 protected void onAllReferencesReleased() { 713 dispose(); 714 } 715 716 private static final SparseIntArray sWindowToPidMap = new SparseIntArray(); 717 718 private void recordNewWindow(int pid, int window) { 719 synchronized (sWindowToPidMap) { 720 sWindowToPidMap.put(window, pid); 721 if (Log.isLoggable(STATS_TAG, Log.VERBOSE)) { 722 Log.i(STATS_TAG, "Created a new Cursor. " + printStats()); 723 } 724 } 725 } 726 727 private void recordClosingOfWindow(int window) { 728 synchronized (sWindowToPidMap) { 729 if (sWindowToPidMap.size() == 0) { 730 // this means we are not in the ContentProvider. 731 return; 732 } 733 sWindowToPidMap.delete(window); 734 } 735 } 736 737 private String printStats() { 738 StringBuilder buff = new StringBuilder(); 739 int myPid = Process.myPid(); 740 int total = 0; 741 SparseIntArray pidCounts = new SparseIntArray(); 742 synchronized (sWindowToPidMap) { 743 int size = sWindowToPidMap.size(); 744 if (size == 0) { 745 // this means we are not in the ContentProvider. 746 return ""; 747 } 748 for (int indx = 0; indx < size; indx++) { 749 int pid = sWindowToPidMap.valueAt(indx); 750 int value = pidCounts.get(pid); 751 pidCounts.put(pid, ++value); 752 } 753 } 754 int numPids = pidCounts.size(); 755 for (int i = 0; i < numPids;i++) { 756 buff.append(" (# cursors opened by "); 757 int pid = pidCounts.keyAt(i); 758 if (pid == myPid) { 759 buff.append("this proc="); 760 } else { 761 buff.append("pid " + pid + "="); 762 } 763 int num = pidCounts.get(pid); 764 buff.append(num + ")"); 765 total += num; 766 } 767 // limit the returned string size to 1000 768 String s = (buff.length() > 980) ? buff.substring(0, 980) : buff.toString(); 769 return "# Open Cursors=" + total + s; 770 } 771 772 @Override 773 public String toString() { 774 return getName() + " {" + Integer.toHexString(mWindowPtr) + "}"; 775 } 776} 777