5

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!

General Grievance
  • 4,555
  • 31
  • 31
  • 45
m0skit0
  • 25,268
  • 11
  • 79
  • 127
  • 1
    Why not use an `ArrayList` of `Bundle` instead? – CommonsWare Aug 03 '12 at 13:11
  • I want to provide a `Cursor` to keep it homogeneous with how `ContentProvider` works. Passing a `List` or a `Map` is also an alternative solution. – m0skit0 Aug 03 '12 at 14:27
  • "deprecated" does not mean "doesn't work". it just means the value of that boolean parameter is ignored in later versions of the platform. just make sure to test it on both old and new versions to verify proper behavior. – j__m May 08 '14 at 21:29

3 Answers3

2

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.

m0skit0
  • 25,268
  • 11
  • 79
  • 127
  • i would not like to be disrespectful, but i think is not an elegant solution because there isn't compatiblity with API under level 15. seems it needs a minimum API level 15. so there should be a better soution. peace – AXSM Mar 05 '13 at 16:22
  • @AlexSanchez did you even read the question? *"I need to pass a Cursor (SQLiteCursor) from a service to an application on **API 10**"* – m0skit0 Mar 06 '13 at 08:43
  • mmmmm. I see. Maybe you can help me. i got a similar problem. @m0squit0. – AXSM Mar 06 '13 at 15:50
  • @AlexSanchez post your question on SO and maybe I can help you :) – m0skit0 Mar 06 '13 at 19:32
  • [here](http://stackoverflow.com/questions/15257568/send-a-cursor-to-an-activity-from-a-service) is my question. – AXSM Mar 06 '13 at 20:45
  • What's the problem with this implementation? – m0skit0 Mar 06 '13 at 21:01
  • http://stackoverflow.com/questions/15257568/send-a-cursor-to-an-activity-from-a-service – AXSM Mar 06 '13 at 21:03
  • I've read your question already on previous link. So what's the problem with this `ParcelableCursor`? `/** Sets this cursor from another windowed Cursor */ public void setFromCursor(AbstractWindowedCursor cursor) {` – m0skit0 Mar 06 '13 at 21:07
  • ok. one, i cant use a WindowCursor object because mi minimum API level is 9 and the minimun required is 15, and that's not negotiable, and two is dont really know how to implement a BijectiveMap. – AXSM Mar 06 '13 at 21:44
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/25727/discussion-between-m0skit0-and-alexsanchez) – m0skit0 Mar 06 '13 at 21:49
  • @m0skit0.. I am also trying to pass the cursor over IPC, may I know, what this CursorHelper class is..??? – Swapnil Kale Feb 03 '14 at 05:43
  • @SwapnilKale A helper class of our own that provides some useful methods to manipulate Cursors. – m0skit0 Feb 03 '14 at 09:40
1

Use Content Provider to store your data. You can access it from service as well as from application. tutorial

amukhachov
  • 5,822
  • 1
  • 41
  • 60
  • I'm already using a Content Provider in my service API, but I want to provide advanced queries to the application side as well. Content Provider does not support table joins for example, or simply raw SQLs. – m0skit0 Aug 03 '12 at 09:52
  • 1
    A ContentProvider acts as an interface to a data store. It can be used to support table joins, you just have to write the code to do it. As far as running raw SQL, I don't think that is always a good idea. Allowing client code to run SQL against a data store also allows them to potentially compromise the integrity of the data. The point of a content provider is to give a structured API to that data. – adstro Apr 07 '16 at 15:32
1

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.

Community
  • 1
  • 1
AXSM
  • 1,142
  • 1
  • 12
  • 27
  • 1
    Sorry, I didn't comment on this before: for my case this is no use because this only allows one row. I can have tables with thousands of rows, and it's very slow to parcel one row each time. Also this is slow in the fact that you need to convert your DB values to String to later convert back to whatever value it was originally. – m0skit0 May 08 '14 at 21:46