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