| /* |
| * Copyright (C) 2006 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.database; |
| |
| import android.content.res.Resources; |
| import android.database.sqlite.DatabaseObjectNotClosedException; |
| import android.database.sqlite.SQLiteClosable; |
| import android.os.Binder; |
| import android.os.IBinder; |
| import android.os.Parcel; |
| import android.os.Parcelable; |
| import android.os.Process; |
| import android.os.StrictMode; |
| import android.util.Log; |
| import android.util.SparseIntArray; |
| |
| /** |
| * A buffer containing multiple cursor rows. |
| */ |
| public class CursorWindow extends SQLiteClosable implements Parcelable { |
| private static final String STATS_TAG = "CursorWindowStats"; |
| |
| /** The cursor window size. resource xml file specifies the value in kB. |
| * convert it to bytes here by multiplying with 1024. |
| */ |
| private static final int sCursorWindowSize = |
| Resources.getSystem().getInteger( |
| com.android.internal.R.integer.config_cursorWindowSize) * 1024; |
| |
| /** The pointer to the native window class. set by the native methods in |
| * android_database_CursorWindow.cpp |
| */ |
| private int nWindow; |
| |
| private int mStartPos; |
| private final Throwable mStackTrace; |
| |
| /** |
| * Creates a new empty window. |
| * |
| * @param localWindow true if this window will be used in this process only |
| */ |
| public CursorWindow(boolean localWindow) { |
| mStartPos = 0; |
| int rslt = native_init(sCursorWindowSize, localWindow); |
| printDebugMsgIfError(rslt); |
| recordNewWindow(Binder.getCallingPid(), nWindow); |
| mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace(); |
| } |
| |
| private void printDebugMsgIfError(int rslt) { |
| if (rslt > 0) { |
| // cursor window allocation failed. either low memory or too many cursors being open. |
| // print info to help in debugging this. |
| throw new CursorWindowAllocationException("Cursor Window allocation of " + |
| sCursorWindowSize/1024 + " kb failed. " + printStats()); |
| } |
| } |
| |
| /** |
| * Returns the starting position of this window within the entire |
| * Cursor's result set. |
| * |
| * @return the starting position of this window within the entire |
| * Cursor's result set. |
| */ |
| public int getStartPosition() { |
| return mStartPos; |
| } |
| |
| /** |
| * Set the start position of cursor window |
| * @param pos |
| */ |
| public void setStartPosition(int pos) { |
| mStartPos = pos; |
| } |
| |
| /** |
| * Returns the number of rows in this window. |
| * |
| * @return the number of rows in this window. |
| */ |
| public int getNumRows() { |
| acquireReference(); |
| try { |
| return getNumRows_native(); |
| } finally { |
| releaseReference(); |
| } |
| } |
| |
| private native int getNumRows_native(); |
| /** |
| * Set number of Columns |
| * @param columnNum |
| * @return true if success |
| */ |
| public boolean setNumColumns(int columnNum) { |
| acquireReference(); |
| try { |
| return setNumColumns_native(columnNum); |
| } finally { |
| releaseReference(); |
| } |
| } |
| |
| private native boolean setNumColumns_native(int columnNum); |
| |
| /** |
| * Allocate a row in cursor window |
| * @return false if cursor window is out of memory |
| */ |
| public boolean allocRow(){ |
| acquireReference(); |
| try { |
| return allocRow_native(); |
| } finally { |
| releaseReference(); |
| } |
| } |
| |
| private native boolean allocRow_native(); |
| |
| /** |
| * Free the last row |
| */ |
| public void freeLastRow(){ |
| acquireReference(); |
| try { |
| freeLastRow_native(); |
| } finally { |
| releaseReference(); |
| } |
| } |
| |
| private native void freeLastRow_native(); |
| |
| /** |
| * copy byte array to cursor window |
| * @param value |
| * @param row |
| * @param col |
| * @return false if fail to copy |
| */ |
| public boolean putBlob(byte[] value, int row, int col) { |
| acquireReference(); |
| try { |
| return putBlob_native(value, row - mStartPos, col); |
| } finally { |
| releaseReference(); |
| } |
| } |
| |
| private native boolean putBlob_native(byte[] value, int row, int col); |
| |
| /** |
| * Copy String to cursor window |
| * @param value |
| * @param row |
| * @param col |
| * @return false if fail to copy |
| */ |
| public boolean putString(String value, int row, int col) { |
| acquireReference(); |
| try { |
| return putString_native(value, row - mStartPos, col); |
| } finally { |
| releaseReference(); |
| } |
| } |
| |
| private native boolean putString_native(String value, int row, int col); |
| |
| /** |
| * Copy integer to cursor window |
| * @param value |
| * @param row |
| * @param col |
| * @return false if fail to copy |
| */ |
| public boolean putLong(long value, int row, int col) { |
| acquireReference(); |
| try { |
| return putLong_native(value, row - mStartPos, col); |
| } finally { |
| releaseReference(); |
| } |
| } |
| |
| private native boolean putLong_native(long value, int row, int col); |
| |
| |
| /** |
| * Copy double to cursor window |
| * @param value |
| * @param row |
| * @param col |
| * @return false if fail to copy |
| */ |
| public boolean putDouble(double value, int row, int col) { |
| acquireReference(); |
| try { |
| return putDouble_native(value, row - mStartPos, col); |
| } finally { |
| releaseReference(); |
| } |
| } |
| |
| private native boolean putDouble_native(double value, int row, int col); |
| |
| /** |
| * Set the [row, col] value to NULL |
| * @param row |
| * @param col |
| * @return false if fail to copy |
| */ |
| public boolean putNull(int row, int col) { |
| acquireReference(); |
| try { |
| return putNull_native(row - mStartPos, col); |
| } finally { |
| releaseReference(); |
| } |
| } |
| |
| private native boolean putNull_native(int row, int col); |
| |
| |
| /** |
| * Returns {@code true} if given field is {@code NULL}. |
| * |
| * @param row the row to read from, row - getStartPosition() being the actual row in the window |
| * @param col the column to read from |
| * @return {@code true} if given field is {@code NULL} |
| * @deprecated use {@link #getType(int, int)} instead |
| */ |
| @Deprecated |
| public boolean isNull(int row, int col) { |
| return getType(row, col) == Cursor.FIELD_TYPE_NULL; |
| } |
| |
| /** |
| * Returns a byte array for the given field. |
| * |
| * @param row the row to read from, row - getStartPosition() being the actual row in the window |
| * @param col the column to read from |
| * @return a String value for the given field |
| */ |
| public byte[] getBlob(int row, int col) { |
| acquireReference(); |
| try { |
| return getBlob_native(row - mStartPos, col); |
| } finally { |
| releaseReference(); |
| } |
| } |
| |
| /** |
| * Returns the value at (<code>row</code>, <code>col</code>) as a <code>byte</code> array. |
| * |
| * <p>If the value is null, then <code>null</code> is returned. If the |
| * type of column <code>col</code> is a string type, then the result |
| * is the array of bytes that make up the internal representation of the |
| * string value. If the type of column <code>col</code> is integral or floating-point, |
| * then an {@link SQLiteException} is thrown. |
| */ |
| private native byte[] getBlob_native(int row, int col); |
| |
| /** |
| * Returns data type of the given column's value. |
| *<p> |
| * Returned column types are |
| * <ul> |
| * <li>{@link Cursor#FIELD_TYPE_NULL}</li> |
| * <li>{@link Cursor#FIELD_TYPE_INTEGER}</li> |
| * <li>{@link Cursor#FIELD_TYPE_FLOAT}</li> |
| * <li>{@link Cursor#FIELD_TYPE_STRING}</li> |
| * <li>{@link Cursor#FIELD_TYPE_BLOB}</li> |
| *</ul> |
| *</p> |
| * |
| * @param row the row to read from, row - getStartPosition() being the actual row in the window |
| * @param col the column to read from |
| * @return the value type |
| */ |
| public int getType(int row, int col) { |
| acquireReference(); |
| try { |
| return getType_native(row - mStartPos, col); |
| } finally { |
| releaseReference(); |
| } |
| } |
| |
| /** |
| * Checks if a field contains either a blob or is null. |
| * |
| * @param row the row to read from, row - getStartPosition() being the actual row in the window |
| * @param col the column to read from |
| * @return {@code true} if given field is {@code NULL} or a blob |
| * @deprecated use {@link #getType(int, int)} instead |
| */ |
| @Deprecated |
| public boolean isBlob(int row, int col) { |
| int type = getType(row, col); |
| return type == Cursor.FIELD_TYPE_BLOB || type == Cursor.FIELD_TYPE_NULL; |
| } |
| |
| /** |
| * Checks if a field contains a long |
| * |
| * @param row the row to read from, row - getStartPosition() being the actual row in the window |
| * @param col the column to read from |
| * @return {@code true} if given field is a long |
| * @deprecated use {@link #getType(int, int)} instead |
| */ |
| @Deprecated |
| public boolean isLong(int row, int col) { |
| return getType(row, col) == Cursor.FIELD_TYPE_INTEGER; |
| } |
| |
| /** |
| * Checks if a field contains a float. |
| * |
| * @param row the row to read from, row - getStartPosition() being the actual row in the window |
| * @param col the column to read from |
| * @return {@code true} if given field is a float |
| * @deprecated use {@link #getType(int, int)} instead |
| */ |
| @Deprecated |
| public boolean isFloat(int row, int col) { |
| return getType(row, col) == Cursor.FIELD_TYPE_FLOAT; |
| } |
| |
| /** |
| * Checks if a field contains either a String or is null. |
| * |
| * @param row the row to read from, row - getStartPosition() being the actual row in the window |
| * @param col the column to read from |
| * @return {@code true} if given field is {@code NULL} or a String |
| * @deprecated use {@link #getType(int, int)} instead |
| */ |
| @Deprecated |
| public boolean isString(int row, int col) { |
| int type = getType(row, col); |
| return type == Cursor.FIELD_TYPE_STRING || type == Cursor.FIELD_TYPE_NULL; |
| } |
| |
| private native int getType_native(int row, int col); |
| |
| /** |
| * Returns a String for the given field. |
| * |
| * @param row the row to read from, row - getStartPosition() being the actual row in the window |
| * @param col the column to read from |
| * @return a String value for the given field |
| */ |
| public String getString(int row, int col) { |
| acquireReference(); |
| try { |
| return getString_native(row - mStartPos, col); |
| } finally { |
| releaseReference(); |
| } |
| } |
| |
| /** |
| * Returns the value at (<code>row</code>, <code>col</code>) as a <code>String</code>. |
| * |
| * <p>If the value is null, then <code>null</code> is returned. If the |
| * type of column <code>col</code> is integral, then the result is the string |
| * that is obtained by formatting the integer value with the <code>printf</code> |
| * family of functions using format specifier <code>%lld</code>. If the |
| * type of column <code>col</code> is floating-point, then the result is the string |
| * that is obtained by formatting the floating-point value with the |
| * <code>printf</code> family of functions using format specifier <code>%g</code>. |
| * If the type of column <code>col</code> is a blob type, then an |
| * {@link SQLiteException} is thrown. |
| */ |
| private native String getString_native(int row, int col); |
| |
| /** |
| * copy the text for the given field in the provided char array. |
| * |
| * @param row the row to read from, row - getStartPosition() being the actual row in the window |
| * @param col the column to read from |
| * @param buffer the CharArrayBuffer to copy the text into, |
| * If the requested string is larger than the buffer |
| * a new char buffer will be created to hold the string. and assigne to |
| * CharArrayBuffer.data |
| */ |
| public void copyStringToBuffer(int row, int col, CharArrayBuffer buffer) { |
| if (buffer == null) { |
| throw new IllegalArgumentException("CharArrayBuffer should not be null"); |
| } |
| if (buffer.data == null) { |
| buffer.data = new char[64]; |
| } |
| acquireReference(); |
| try { |
| char[] newbuf = copyStringToBuffer_native( |
| row - mStartPos, col, buffer.data.length, buffer); |
| if (newbuf != null) { |
| buffer.data = newbuf; |
| } |
| } finally { |
| releaseReference(); |
| } |
| } |
| |
| private native char[] copyStringToBuffer_native( |
| int row, int col, int bufferSize, CharArrayBuffer buffer); |
| |
| /** |
| * Returns a long for the given field. |
| * row is 0 based |
| * |
| * @param row the row to read from, row - getStartPosition() being the actual row in the window |
| * @param col the column to read from |
| * @return a long value for the given field |
| */ |
| public long getLong(int row, int col) { |
| acquireReference(); |
| try { |
| return getLong_native(row - mStartPos, col); |
| } finally { |
| releaseReference(); |
| } |
| } |
| |
| /** |
| * Returns the value at (<code>row</code>, <code>col</code>) as a <code>long</code>. |
| * |
| * <p>If the value is null, then <code>0L</code> is returned. If the |
| * type of column <code>col</code> is a string type, then the result |
| * is the <code>long</code> that is obtained by parsing the string value with |
| * <code>strtoll</code>. If the type of column <code>col</code> is |
| * floating-point, then the result is the floating-point value casted to a <code>long</code>. |
| * If the type of column <code>col</code> is a blob type, then an |
| * {@link SQLiteException} is thrown. |
| */ |
| private native long getLong_native(int row, int col); |
| |
| /** |
| * Returns a double for the given field. |
| * row is 0 based |
| * |
| * @param row the row to read from, row - getStartPosition() being the actual row in the window |
| * @param col the column to read from |
| * @return a double value for the given field |
| */ |
| public double getDouble(int row, int col) { |
| acquireReference(); |
| try { |
| return getDouble_native(row - mStartPos, col); |
| } finally { |
| releaseReference(); |
| } |
| } |
| |
| /** |
| * Returns the value at (<code>row</code>, <code>col</code>) as a <code>double</code>. |
| * |
| * <p>If the value is null, then <code>0.0</code> is returned. If the |
| * type of column <code>col</code> is a string type, then the result |
| * is the <code>double</code> that is obtained by parsing the string value with |
| * <code>strtod</code>. If the type of column <code>col</code> is |
| * integral, then the result is the integer value casted to a <code>double</code>. |
| * If the type of column <code>col</code> is a blob type, then an |
| * {@link SQLiteException} is thrown. |
| */ |
| private native double getDouble_native(int row, int col); |
| |
| /** |
| * Returns a short for the given field. |
| * row is 0 based |
| * |
| * @param row the row to read from, row - getStartPosition() being the actual row in the window |
| * @param col the column to read from |
| * @return a short value for the given field |
| */ |
| public short getShort(int row, int col) { |
| acquireReference(); |
| try { |
| return (short) getLong_native(row - mStartPos, col); |
| } finally { |
| releaseReference(); |
| } |
| } |
| |
| /** |
| * Returns an int for the given field. |
| * |
| * @param row the row to read from, row - getStartPosition() being the actual row in the window |
| * @param col the column to read from |
| * @return an int value for the given field |
| */ |
| public int getInt(int row, int col) { |
| acquireReference(); |
| try { |
| return (int) getLong_native(row - mStartPos, col); |
| } finally { |
| releaseReference(); |
| } |
| } |
| |
| /** |
| * Returns a float for the given field. |
| * row is 0 based |
| * |
| * @param row the row to read from, row - getStartPosition() being the actual row in the window |
| * @param col the column to read from |
| * @return a float value for the given field |
| */ |
| public float getFloat(int row, int col) { |
| acquireReference(); |
| try { |
| return (float) getDouble_native(row - mStartPos, col); |
| } finally { |
| releaseReference(); |
| } |
| } |
| |
| /** |
| * Clears out the existing contents of the window, making it safe to reuse |
| * for new data. Note that the number of columns in the window may NOT |
| * change across a call to clear(). |
| */ |
| public void clear() { |
| acquireReference(); |
| try { |
| mStartPos = 0; |
| native_clear(); |
| } finally { |
| releaseReference(); |
| } |
| } |
| |
| /** Clears out the native side of things */ |
| private native void native_clear(); |
| |
| /** |
| * Cleans up the native resources associated with the window. |
| */ |
| public void close() { |
| releaseReference(); |
| } |
| |
| private native void close_native(); |
| |
| @Override |
| protected void finalize() { |
| if (nWindow == 0) { |
| return; |
| } |
| if (StrictMode.vmSqliteObjectLeaksEnabled()) { |
| StrictMode.onSqliteObjectLeaked( |
| "Releasing cursor in a finalizer. Please ensure " + |
| "that you explicitly call close() on your cursor: ", |
| mStackTrace); |
| } |
| recordClosingOfWindow(nWindow); |
| close_native(); |
| } |
| |
| public static final Parcelable.Creator<CursorWindow> CREATOR |
| = new Parcelable.Creator<CursorWindow>() { |
| public CursorWindow createFromParcel(Parcel source) { |
| return new CursorWindow(source); |
| } |
| |
| public CursorWindow[] newArray(int size) { |
| return new CursorWindow[size]; |
| } |
| }; |
| |
| public static CursorWindow newFromParcel(Parcel p) { |
| return CREATOR.createFromParcel(p); |
| } |
| |
| public int describeContents() { |
| return 0; |
| } |
| |
| public void writeToParcel(Parcel dest, int flags) { |
| dest.writeStrongBinder(native_getBinder()); |
| dest.writeInt(mStartPos); |
| } |
| |
| private CursorWindow(Parcel source) { |
| IBinder nativeBinder = source.readStrongBinder(); |
| mStartPos = source.readInt(); |
| int rslt = native_init(nativeBinder); |
| mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace(); |
| printDebugMsgIfError(rslt); |
| } |
| |
| /** Get the binder for the native side of the window */ |
| private native IBinder native_getBinder(); |
| |
| /** Does the native side initialization for an empty window */ |
| private native int native_init(int cursorWindowSize, boolean localOnly); |
| |
| /** Does the native side initialization with an existing binder from another process */ |
| private native int native_init(IBinder nativeBinder); |
| |
| @Override |
| protected void onAllReferencesReleased() { |
| recordClosingOfWindow(nWindow); |
| close_native(); |
| } |
| |
| private static final SparseIntArray sWindowToPidMap = new SparseIntArray(); |
| |
| private void recordNewWindow(int pid, int window) { |
| synchronized (sWindowToPidMap) { |
| sWindowToPidMap.put(window, pid); |
| if (Log.isLoggable(STATS_TAG, Log.VERBOSE)) { |
| Log.i(STATS_TAG, "Created a new Cursor. " + printStats()); |
| } |
| } |
| } |
| |
| private void recordClosingOfWindow(int window) { |
| synchronized (sWindowToPidMap) { |
| if (sWindowToPidMap.size() == 0) { |
| // this means we are not in the ContentProvider. |
| return; |
| } |
| sWindowToPidMap.delete(window); |
| } |
| } |
| private String printStats() { |
| StringBuilder buff = new StringBuilder(); |
| int myPid = Process.myPid(); |
| int total = 0; |
| SparseIntArray pidCounts = new SparseIntArray(); |
| synchronized (sWindowToPidMap) { |
| int size = sWindowToPidMap.size(); |
| if (size == 0) { |
| // this means we are not in the ContentProvider. |
| return ""; |
| } |
| for (int indx = 0; indx < size; indx++) { |
| int pid = sWindowToPidMap.valueAt(indx); |
| int value = pidCounts.get(pid); |
| pidCounts.put(pid, ++value); |
| } |
| } |
| int numPids = pidCounts.size(); |
| for (int i = 0; i < numPids;i++) { |
| buff.append(" (# cursors opened by "); |
| int pid = pidCounts.keyAt(i); |
| if (pid == myPid) { |
| buff.append("this proc="); |
| } else { |
| buff.append("pid " + pid + "="); |
| } |
| int num = pidCounts.get(pid); |
| buff.append(num + ")"); |
| total += num; |
| } |
| // limit the returned string size to 1000 |
| String s = (buff.length() > 980) ? buff.substring(0, 980) : buff.toString(); |
| return "# Open Cursors=" + total + s; |
| } |
| } |