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