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