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