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