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