I need to pass a Cursor
(SQLiteCursor
) from a service to an application on API 10, and having hard time finding a decent (and fast) solution.
I've seen the CursorWindow
class. This is Parcelable
but I can't instantiate this class on API 10 to use SQLiteCursor.fillWindow()
because it has no valid constructors. CursorWindow(boolean)
is deprecated.
And even if I got a CursorWindow
instance with data from a SQLiteCursor
, how do I copy this window into a new Cursor
? What Cursor
implementation should I use for this? I see no usable Cursor
that extends AbstractWindowedCursor
.
Thanks for your time!
I implemented a ParcelableCursor
class that implements CrossProcessCursor
and Parcelable
interfaces. I'll post it if anyone is interested. Some operations are not supported/implemented yet, as well as using a custom BijectiveMap
(which is quite easy to implement).
/**
* Prefer ParcelableCursorForIntent instead.<br/>
* Cursor for IPC. Takes a CursorWindow as data buffer and the number of columns
* that CursorWindow has.<br/>
* <br/>
* <b>NOTE: this Cursor cannot be parceled when sending by intents due to <a
* href="http://code.google.com/p/android/issues/detail?id=4470">an Android
* bug</a>. Please use ParcelableCursorForIntent instead.</b>
*
* @author m0skit0@blablabla.eu
*
*/
public class ParcelableCursor implements Parcelable, CrossProcessCursor {
/** Cursor data window */
protected CursorWindow window = CursorHelper.getCursorWindowInstance();
/** How many columns we have */
protected int numColumns = 0;
/** Column names */
protected BijectiveMap<String, Integer> colNames = new BijectiveHashMap<String, Integer>();
/** Current row */
protected int curRow = -1;
/** Is this cursor closed? */
protected boolean closed = false;
/** CREATOR for Parcelable */
public static final Parcelable.Creator<ParcelableCursor> CREATOR = new Parcelable.Creator<ParcelableCursor>() { // NOPMD
// AM
@Override
public ParcelableCursor createFromParcel(final Parcel in) {
return new ParcelableCursor(in);
}
@Override
public ParcelableCursor[] newArray(final int size) {
return new ParcelableCursor[size];
}
};
/**
* Creates an empty ParcelableCursor. Please consider to use
* {@link #setFromCursor(AbstractWindowedCursor)} or
* {@link #setFromWindow(CursorWindow)} to initialize it.
*/
public ParcelableCursor() {
// Empty ParcelableCursor, don't forget to use #setFromCursor
}
/** Constructor for Parcelable */
public ParcelableCursor(final Parcel in) {
readFromParcel(in); // NOPMD by yasin on 12/7/12 11:55 AM - Android's
// Parceleble
}
/**
* Adds a new column at the end and assigns it this name. This will make
* this cursor to lose all its data, so you have to add all the columns
* before adding any row.
*/
private void addColumn(final String name) {
this.numColumns++;
this.curRow = -1;
this.colNames.put(name, this.numColumns - 1);
}
@Override
public void close() {
this.window.close();
this.closed = true;
}
@Override
public void copyStringToBuffer(final int columnIndex,
final CharArrayBuffer buffer) {
// TODO: what does this do?
}
@Override
public void deactivate() {
// Deprecated, does nothing
}
@Override
public int describeContents() {
// Nothing to do here
return 0;
}
@Override
public void fillWindow(final int position, final CursorWindow window) {
CursorHelper.copyCursorWindow(position, this.window, window);
}
@Override
public byte[] getBlob(final int columnIndex) {
return this.window.getBlob(this.curRow, columnIndex);
}
@Override
public int getColumnCount() {
return this.numColumns;
}
@Override
public int getColumnIndex(final String columnName) {
int ret = -1;
final Integer col = this.colNames.get(columnName);
if (col != null) {
ret = col;
}
return ret;
}
@Override
public int getColumnIndexOrThrow(final String columnName)
throws IllegalArgumentException {
final Integer col = this.colNames.get(columnName);
if (col == null) {
throw new IllegalArgumentException();
}
return col;
}
@Override
public String getColumnName(final int columnIndex) {
return this.colNames.getKey(columnIndex);
}
@Override
public String[] getColumnNames() {
if (DebugConfig.DEBUG) {
Log.d("PARCELCURSOR.getColumnNames()---", "===GETTING COLNAMES===");
}
final Set<Entry<String, Integer>> set = this.colNames.entrySet();
final String[] colArray = new String[set.size()];
for (final String colName : this.colNames.keySet()) {
if (DebugConfig.DEBUG) {
Log.d("-------------PARCELCURSOR.getColumnNames()", colName);
}
final int pos = this.colNames.get(colName);
colArray[pos] = colName;
}
return colArray;
}
@Override
public int getCount() {
return this.window.getNumRows();
}
@Override
public double getDouble(final int columnIndex) {
return this.window.getDouble(this.curRow, columnIndex);
}
@Override
public Bundle getExtras() {
// Does not support Extras
return null;
}
@Override
public float getFloat(final int columnIndex) {
return this.window.getFloat(this.curRow, columnIndex);
}
@Override
public int getInt(final int columnIndex) {
return this.window.getInt(this.curRow, columnIndex);
}
@Override
public long getLong(final int columnIndex) {
return this.window.getLong(this.curRow, columnIndex);
}
@Override
public int getPosition() {
return this.curRow;
}
@Override
public short getShort(final int columnIndex) { // NOPMD by yasin on 12/7/12
// 11:57 AM - Override
return this.window.getShort(this.curRow, columnIndex);
}
@Override
public String getString(final int columnIndex) {
return this.window.getString(this.curRow, columnIndex);
}
@SuppressLint("NewApi")
@Override
public int getType(final int columnIndex) {
final int currentapiVersion = android.os.Build.VERSION.SDK_INT;
int result = 0;
if (currentapiVersion >= android.os.Build.VERSION_CODES.HONEYCOMB) {
result = this.window.getType(this.curRow, columnIndex);
} else {
if (this.window.isNull(this.curRow, columnIndex)) {
result = 0; // FIELD_TYPE_NULL;
} else if (this.window.isFloat(this.curRow, columnIndex)) {
result = 2; // FIELD_TYPE_FLOAT;
} else if (this.window.isLong(this.curRow, columnIndex)) {
result = 1; // FIELD_TYPE_INTEGER;
} else if (this.window.isString(this.curRow, columnIndex)) {
result = 3; // FIELD_TYPE_STRING;
} else if (this.window.isBlob(this.curRow, columnIndex)) {
result = 4; // FIELD_TYPE_BLOB;
}
}
return result;
}
@Override
public boolean getWantsAllOnMoveCalls() {
return false;
}
@Override
public CursorWindow getWindow() {
final CursorWindow ret = CursorHelper.getCursorWindowInstance();
fillWindow(0, ret);
return ret;
}
@Override
public boolean isAfterLast() {
return (this.curRow >= this.window.getNumRows());
}
@Override
public boolean isBeforeFirst() {
return (this.curRow < 0);
}
@Override
public boolean isClosed() {
return this.closed;
}
@Override
public boolean isFirst() {
return (this.curRow == 0);
}
@Override
public boolean isLast() {
return (this.curRow == this.window.getNumRows() - 1);
}
@Override
public boolean isNull(final int columnIndex) {
return this.getType(columnIndex) == FIELD_TYPE_NULL;
}
@Override
public boolean move(final int offset) {
final int oldPos = this.curRow;
this.curRow += offset;
if (this.curRow < -1) {
this.curRow = -1;
return false;
} else if (this.curRow > this.window.getNumRows() - 1) {
this.curRow = this.window.getNumRows() - 1;
return false;
}
return onMove(oldPos, this.curRow);
}
@Override
public boolean moveToFirst() {
if (this.window.getNumRows() == 0) {
return false;
}
final int oldPos = this.curRow;
this.curRow = 0;
return onMove(oldPos, this.curRow);
}
@Override
public boolean moveToLast() {
if (this.window.getNumRows() == 0) {
return false;
}
final int oldPos = this.curRow;
this.curRow = this.window.getNumRows() - 1;
return onMove(oldPos, this.curRow);
}
@Override
public boolean moveToNext() {
final int oldPos = this.curRow++;
if (isAfterLast()) {
this.curRow = this.window.getNumRows();
return false;
}
return onMove(oldPos, this.curRow);
}
@Override
public boolean moveToPosition(final int position) {
if (position < -1 && position >= this.window.getNumRows()) {
return false;
}
final int oldPos = this.curRow;
this.curRow = position;
return onMove(oldPos, this.curRow);
}
@Override
public boolean moveToPrevious() {
final int oldPos = this.curRow--;
if (isBeforeFirst()) {
this.curRow = -1;
return false;
}
return onMove(oldPos, this.curRow);
}
@Override
public boolean onMove(final int oldPosition, final int newPosition) {
// Don't forget to set curRow = -1 if this method returns false
return true;
}
/** Restoring this object from a Parcel */
public void readFromParcel(final Parcel in) {
this.numColumns = in.readInt();
this.colNames = in.readParcelable(ClassLoaderHelper.getClassLoader());
this.curRow = in.readInt();
this.closed = (in.readByte() == 1);
// Closes the cursor before create a new cursor.
if (window != null) {
window.close();
}
this.window = CursorWindow.newFromParcel(in);
}
/** Not supported */
@Override
public void registerContentObserver(final ContentObserver observer) {
// Does nothing
}
/** Not supported */
@Override
public void registerDataSetObserver(final DataSetObserver observer) {
// Does nothing
}
/** Deprecated, not supported */
@Override
public boolean requery() {
return false;
}
/** Not supported */
@Override
public Bundle respond(final Bundle extras) {
// Does nothing
return null;
}
/** Sets this cursor from another windowed Cursor */
public void setFromCursor(final AbstractWindowedCursor cursor) throws CursorIndexOutOfBoundsException, IllegalStateException {
// Reset number of columns
this.numColumns = 0;
// Set column names
final String[] colNames = cursor.getColumnNames();
if (colNames != null) {
for (final String col : colNames) {
addColumn(col);
}
}
// Fill window
this.window.clear();
this.window.setNumColumns(this.numColumns);
cursor.fillWindow(0, this.window);
moveToPosition(-1);
}
/** Sets this cursor from another windowed Cursor */
public void setFromCursor(final MatrixCursor cursor) throws CursorIndexOutOfBoundsException ,IllegalStateException{
// Reset number of columns
this.numColumns = 0;
// Set column names
final String[] colNames = cursor.getColumnNames();
if (colNames != null) {
for (final String col : colNames) {
addColumn(col);
}
}
// Fill window
this.window.clear();
this.window.setNumColumns(this.numColumns);
cursor.fillWindow(0, this.window);
moveToPosition(-1);
}
/** Sets this cursor using a CursorWindow data */
public void setFromWindow(final CursorWindow window) {
CursorHelper.copyCursorWindow(0, window, this.window);
this.numColumns = CursorHelper.getCursorWindowNumCols(window);
moveToPosition(-1);
}
/** Not supported */
@Override
public void setNotificationUri(final ContentResolver cr, final Uri uri) {
// Does nothing
}
/** Not supported */
@Override
public void unregisterContentObserver(final ContentObserver observer) {
// Does nothing
}
/** Not supported */
@Override
public void unregisterDataSetObserver(final DataSetObserver observer) {
// Does nothing
}
@Override
public void writeToParcel(final Parcel out, final int flags) {
out.writeInt(this.numColumns);
out.writeParcelable((Parcelable) this.colNames, 0);
out.writeInt(this.curRow);
out.writeByte(this.closed ? (byte) 1 : 0);
this.window.writeToParcel(out, flags);
}
}
Still looking for a more standard way to do this. Any information would be appreciated greatly!
EDIT: this passed very few tests, so test it before using it.
EDIT2: in fact, it's full of bugs... I'll update with a less buggy version soon.
EDIT3: updated with working cursor we are using since one year.
Use Content Provider to store your data. You can access it from service as well as from application. tutorial
To solve a similar problem which i had, i made my own custom class which implements a Parcelable Interface. and inside i just implement HashMap object. So i don't jave to worry anymore about amount of rows, i just map the cursor to my own ParcelableRow object. here is mi code:
public class ParcelableRow implements Parcelable {
private HashMap<String, String> colsMap;
public static final Parcelable.Creator<ParcelableRow> CREATOR
= new Parcelable.Creator<ParcelableRow>() {
@Override
public ParcelableRow createFromParcel(Parcel source) {
return new ParcelableRow(source);
}
@Override
public ParcelableRow[] newArray(int size) {
return new ParcelableRow[size];
}
};
public ParcelableRow(Parcel in) {
colsMap = new HashMap<String, String>();
readFromParcel(in);
}
public ParcelableRow() {
colsMap = new HashMap<String, String>();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
for(String key: colsMap.keySet()){
dest.writeString(key);
dest.writeString(colsMap.get(key));
}
}
public void readFromParcel(Parcel parcel) {
int limit = parcel.dataSize();
parcel.setDataPosition(0);
for(int i = 0; i < limit; i++){
colsMap.put(parcel.readString(), parcel.readString());
}
}
public void addNewCol(String colName, String colValue){
colsMap.put(colName, colValue);
}
public String getColumnValue(String colName){
return colsMap.get(colName);
}
}
I hope this can be useful to someone or you @m0skit0, i've just spend some days trying to find something that could satisfy my needs. here are some code examples which i've used. advises are welcome.