I have this Cursor implementation (API 10):
package com.blablabla.android.helpers.db.cursor;
import android.content.ContentResolver;
import android.database.AbstractWindowedCursor;
import android.database.CharArrayBuffer;
import android.database.ContentObserver;
import android.database.CrossProcessCursor;
import android.database.CursorWindow;
import android.database.DataSetObserver;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import com.blablabla.android.helpers.classloader.ClassLoaderHelper;
import com.blablabla.android.helpers.util.BijectiveHashMap;
import com.blablabla.android.helpers.util.BijectiveMap;
/**
* Cursor for IPC. Takes a CursorWindow as data buffer and the number of columns
* that CursorWindow has.
*
* @author me@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;
public ParcelableCursor() {
}
// /////////////////
// PARCELABLE IMPLEMENTATION
// /////////////////
/** CREATOR for Parcelable */
public static final Parcelable.Creator<ParcelableCursor> CREATOR = new Parcelable.Creator<ParcelableCursor>() {
public ParcelableCursor createFromParcel(Parcel in) {
return new ParcelableCursor(in);
}
public ParcelableCursor[] newArray(int size) {
return new ParcelableCursor[size];
}
};
/** Constructor for Parcelable */
public ParcelableCursor(Parcel in) {
readFromParcel(in);
}
@Override
public int describeContents() {
// Nothing to do here
return 0;
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeParcelable(window, 0);
out.writeInt(numColumns);
out.writeParcelable((Parcelable) colNames, 0);
out.writeInt(curRow);
out.writeByte(closed ? (byte) 1 : 0);
}
/** Restoring this object from a Parcel */
public void readFromParcel(Parcel in) {
window = in.readParcelable(CursorWindow.class.getClassLoader());
numColumns = in.readInt();
colNames = in.readParcelable(ClassLoaderHelper.getClassLoader());
curRow = in.readInt();
closed = (in.readByte() == 1);
}
// ////////////////
// END PARCELABLE IMPLEMENTATION
// ////////////////
// /////////////////
// CROSS PROCESS CURSOR IMPLEMENTATION
// /////////////////
@Override
public void close() {
window.close();
closed = true;
}
@Override
public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
// TODO: what does this do?
}
@Override
public void deactivate() {
// Deprecated, does nothing
}
@Override
public byte[] getBlob(int columnIndex) {
return window.getBlob(curRow, columnIndex);
}
@Override
public int getColumnCount() {
return numColumns;
}
@Override
public int getColumnIndex(String columnName) {
int ret = -1;
Integer col = colNames.get(columnName);
if (col != null) {
ret = col;
}
return ret;
}
@Override
public int getColumnIndexOrThrow(String columnName)
throws IllegalArgumentException {
Integer col = colNames.get(columnName);
if (col == null) {
throw new IllegalArgumentException();
}
return col;
}
@Override
public String getColumnName(int columnIndex) {
return colNames.getKey(columnIndex);
}
@Override
public String[] getColumnNames() {
Log.d("PARCELCURSOR.getColumnNames()", "===GETTING COLNAMES===");
return colNames.keySet().toArray(new String[colNames.keySet().size()]);
}
@Override
public int getCount() {
return window.getNumRows();
}
@Override
public double getDouble(int columnIndex) {
return window.getDouble(curRow, columnIndex);
}
@Override
public Bundle getExtras() {
// TODO
return null;
}
@Override
public float getFloat(int columnIndex) {
return window.getFloat(curRow, columnIndex);
}
@Override
public int getInt(int columnIndex) {
return window.getInt(curRow, columnIndex);
}
@Override
public long getLong(int columnIndex) {
return window.getLong(curRow, columnIndex);
}
@Override
public int getPosition() {
return curRow;
}
@Override
public short getShort(int columnIndex) {
return window.getShort(curRow, columnIndex);
}
@Override
public String getString(int columnIndex) {
return window.getString(curRow, columnIndex);
}
@Override
public boolean getWantsAllOnMoveCalls() {
return false;
}
@Override
public boolean isAfterLast() {
return (curRow >= window.getNumRows());
}
@Override
public boolean isBeforeFirst() {
return (curRow < 0);
}
@Override
public boolean isClosed() {
return closed;
}
@Override
public boolean isFirst() {
return (curRow == 0);
}
@Override
public boolean isLast() {
return (curRow == window.getNumRows() - 1);
}
@Override
public boolean isNull(int columnIndex) {
return window.isNull(curRow, columnIndex);
}
@Override
public boolean move(int offset) {
int oldPos = curRow;
curRow += offset;
if (curRow < -1) {
curRow = -1;
return false;
} else if (curRow > window.getNumRows() - 1) {
curRow = window.getNumRows() - 1;
return false;
}
return onMove(oldPos, curRow);
}
@Override
public boolean moveToFirst() {
if (window.getNumRows() == 0) {
return false;
}
int oldPos = curRow;
curRow = 0;
return onMove(oldPos, curRow);
}
@Override
public boolean moveToLast() {
if (window.getNumRows() == 0) {
return false;
}
int oldPos = curRow;
curRow = window.getNumRows() - 1;
return onMove(oldPos, curRow);
}
@Override
public boolean moveToNext() {
int oldPos = curRow++;
if (isAfterLast()) {
curRow = window.getNumRows();
return false;
}
return onMove(oldPos, curRow);
}
@Override
public boolean moveToPosition(int position) {
if (position < -1 && position >= window.getNumRows()) {
return false;
}
int oldPos = curRow;
curRow = position;
return onMove(oldPos, curRow);
}
@Override
public boolean moveToPrevious() {
int oldPos = curRow--;
if (isBeforeFirst()) {
curRow = -1;
return false;
}
return onMove(oldPos, curRow);
}
/** Not supported */
@Override
public void registerContentObserver(ContentObserver observer) {
// Does nothing
}
/** Not supported */
@Override
public void registerDataSetObserver(DataSetObserver observer) {
// Does nothing
}
/** Deprecated, not supported */
@Override
public boolean requery() {
return false;
}
/** Not supported */
@Override
public Bundle respond(Bundle extras) {
// Does nothing
return null;
}
/** Not supported */
@Override
public void setNotificationUri(ContentResolver cr, Uri uri) {
// Does nothing
}
/** Not supported */
@Override
public void unregisterContentObserver(ContentObserver observer) {
// Does nothing
}
/** Not supported */
@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
// Does nothing
}
@Override
public void fillWindow(int position, CursorWindow window) {
CursorHelper.copyCursorWindow(position, this.window, window);
}
@Override
public CursorWindow getWindow() {
CursorWindow ret = new CursorWindow(false);
fillWindow(0, ret);
return ret;
}
@Override
public boolean onMove(int oldPosition, int newPosition) {
// Don't forget to set curRow = -1 if this method returns false
return true;
}
// /////////////////
// END CROSS PROCESS CURSOR IMPLEMENTATION
// /////////////////
/** Sets this cursor using a CursorWindow data */
public void setFromWindow(CursorWindow window) {
CursorHelper.copyCursorWindow(0, window, this.window);
numColumns = CursorHelper.getCursorWindowNumCols(window);
moveToPosition(-1);
}
/** Sets this cursor from another windowed Cursor */
public void setFromCursor(AbstractWindowedCursor cursor) {
// Set column names
String[] colNames = cursor.getColumnNames();
for (String col : colNames) {
addColumn(col);
}
// Fill window
window.setNumColumns(numColumns);
cursor.fillWindow(0, window);
moveToPosition(-1);
}
/**
* 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(String name) {
numColumns++;
curRow = -1;
colNames.put(name, numColumns - 1);
}
}
I send an instance of this Cursor
through both Messenger
(inside a Message
instance) and through ContentProvider.query()
(and friends). The problem is that isNull()
method works correctly when the Cursor
is sent through Messenger
but always returns false when sent through the ContentProvider
.
This is my ContentProvider.query()
implementation:
@Override
public Cursor query(final Uri uri, final String[] projection,
final String selection, final String[] selectionArgs,
final String sortOrder) {
// Build custom query object
final Query query = getBasicQuery(uri);
// .... More code preparing the query object
// Execute the query
dbManager.executeQuery(getSourceId(uri), query);
// This is a ParcelableCursor instance
final Cursor resultCursor = query.getResultCursor();
return resultCursor;
}
Here, when I am running at the ContentProvider
process, the isNull()
method works properly, but when the Cursor
is received at the client process, it always returns false
.
And this is the exception thrown when I do a Cursor.getString()
because Cursor.isNull()
returns (wrongly) false
:
08-13 13:17:16.480: D/SELECT on ui(1572): java.lang.IllegalStateException: UNKNOWN type 0
08-13 13:17:16.480: D/SELECT on ui(1572): at android.database.CursorWindow.getString_native(Native Method)
08-13 13:17:16.480: D/SELECT on ui(1572): at android.database.CursorWindow.getString(CursorWindow.java:329)
08-13 13:17:16.480: D/SELECT on ui(1572): at android.database.AbstractWindowedCursor.getString(AbstractWindowedCursor.java:49)
08-13 13:17:16.480: D/SELECT on ui(1572): at android.database.CursorWrapper.getString(CursorWrapper.java:135)
08-13 13:17:16.480: D/SELECT on ui(1572): at com.blablabla.android.core.test.TestDBActivity.selectDB(TestDBActivity.java:332)
08-13 13:17:16.480: D/SELECT on ui(1572): at com.blablabla.android.core.test.TestDBActivity$3.onClick(TestDBActivity.java:169)
08-13 13:17:16.480: D/SELECT on ui(1572): at android.view.View.performClick(View.java:2485)
08-13 13:17:16.480: D/SELECT on ui(1572): at android.view.View$PerformClick.run(View.java:9080)
08-13 13:17:16.480: D/SELECT on ui(1572): at android.os.Handler.handleCallback(Handler.java:587)
08-13 13:17:16.480: D/SELECT on ui(1572): at android.os.Handler.dispatchMessage(Handler.java:92)
08-13 13:17:16.480: D/SELECT on ui(1572): at android.os.Looper.loop(Looper.java:123)
08-13 13:17:16.480: D/SELECT on ui(1572): at android.app.ActivityThread.main(ActivityThread.java:3683)
08-13 13:17:16.480: D/SELECT on ui(1572): at java.lang.reflect.Method.invokeNative(Native Method)
08-13 13:17:16.480: D/SELECT on ui(1572): at java.lang.reflect.Method.invoke(Method.java:507)
08-13 13:17:16.480: D/SELECT on ui(1572): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
08-13 13:17:16.480: D/SELECT on ui(1572): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
08-13 13:17:16.480: D/SELECT on ui(1572): at dalvik.system.NativeStart.main(Native Method)
EDIT: after heavy debugging, I can see that when the value is null
, isBlob()
returns true
.
Any ideas of what might be going wrong here?