diff --git a/src/com/fsck/k9/provider/MessageProvider.java b/src/com/fsck/k9/provider/MessageProvider.java index c342d5e3d..7ef4ea934 100644 --- a/src/com/fsck/k9/provider/MessageProvider.java +++ b/src/com/fsck/k9/provider/MessageProvider.java @@ -84,7 +84,7 @@ public class MessageProvider extends ContentProvider String INCREMENT = "id"; } - protected interface QueryHandler + protected static interface QueryHandler { /** * The path this instance is able to respond to. @@ -451,272 +451,359 @@ public class MessageProvider extends ContentProvider } } - protected class ThrottlingQueryHandler implements QueryHandler + /** + * Cursor wrapper that release a semaphore on close. Close is also triggered + * on {@link #finalize()}. + */ + protected static class MonitoredCursor implements CrossProcessCursor { + /** + * The underlying cursor implementation that handles regular + * requests + */ + private CrossProcessCursor mCursor; - protected final class MonitoredCursor implements CrossProcessCursor + /** + * Whether {@link #close()} was invoked + */ + private AtomicBoolean mClosed = new AtomicBoolean(false); + + private Semaphore mSemaphore; + + /** + * @param cursor + * Never null. + * @param semaphore + * The semaphore to release on close. Never + * null. + */ + protected MonitoredCursor(final CrossProcessCursor cursor, final Semaphore semaphore) { - private CrossProcessCursor mCursor; + this.mCursor = cursor; + this.mSemaphore = semaphore; + } - @Override - public void close() + /* (non-Javadoc) + * + * Close the underlying cursor and dereference it. + * + * @see android.database.Cursor#close() + */ + @Override + public void close() + { + if (mClosed.compareAndSet(false, true)) { mCursor.close(); - if (mClosed.compareAndSet(false, true)) - { - Log.d(K9.LOG_TAG, "Cursor closed, releasing semaphore"); - mSemaphore.release(); - } - } - - @Override - public void fillWindow(int pos, CursorWindow winow) - { - mCursor.fillWindow(pos, winow); - } - - @Override - public CursorWindow getWindow() - { - return mCursor.getWindow(); - } - - @Override - public boolean onMove(int oldPosition, int newPosition) - { - return mCursor.onMove(oldPosition, newPosition); - } - - private AtomicBoolean mClosed = new AtomicBoolean(false); - - - protected MonitoredCursor(final CrossProcessCursor cursor) - { - this.mCursor = cursor; - } - - @Override - public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) - { - mCursor.copyStringToBuffer(columnIndex, buffer); - } - - @Override - public void deactivate() - { - mCursor.deactivate(); - } - - @Override - public byte[] getBlob(int columnIndex) - { - return mCursor.getBlob(columnIndex); - } - - @Override - public int getColumnCount() - { - return mCursor.getColumnCount(); - } - - @Override - public int getColumnIndex(String columnName) - { - return mCursor.getColumnIndex(columnName); - } - - @Override - public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException - { - return mCursor.getColumnIndexOrThrow(columnName); - } - - @Override - public String getColumnName(int columnIndex) - { - return mCursor.getColumnName(columnIndex); - } - - @Override - public String[] getColumnNames() - { - return mCursor.getColumnNames(); - } - - @Override - public int getCount() - { - return mCursor.getCount(); - } - - @Override - public double getDouble(int columnIndex) - { - return mCursor.getDouble(columnIndex); - } - - @Override - public Bundle getExtras() - { - return mCursor.getExtras(); - } - - @Override - public float getFloat(int columnIndex) - { - return mCursor.getFloat(columnIndex); - } - - @Override - public int getInt(int columnIndex) - { - return mCursor.getInt(columnIndex); - } - - @Override - public long getLong(int columnIndex) - { - return mCursor.getLong(columnIndex); - } - - @Override - public int getPosition() - { - return mCursor.getPosition(); - } - - @Override - public short getShort(int columnIndex) - { - return mCursor.getShort(columnIndex); - } - - @Override - public String getString(int columnIndex) - { - return mCursor.getString(columnIndex); - } - - @Override - public boolean getWantsAllOnMoveCalls() - { - return mCursor.getWantsAllOnMoveCalls(); - } - - @Override - public boolean isAfterLast() - { - return mCursor.isAfterLast(); - } - - @Override - public boolean isBeforeFirst() - { - return mCursor.isBeforeFirst(); - } - - @Override - public boolean isClosed() - { - return mCursor.isClosed(); - } - - @Override - public boolean isFirst() - { - return mCursor.isFirst(); - } - - public boolean isLast() - { - return mCursor.isLast(); - } - - @Override - public boolean isNull(int columnIndex) - { - return mCursor.isNull(columnIndex); - } - - @Override - public boolean move(int offset) - { - return mCursor.move(offset); - } - - @Override - public boolean moveToFirst() - { - return mCursor.moveToFirst(); - } - - @Override - public boolean moveToLast() - { - return mCursor.moveToLast(); - } - - @Override - public boolean moveToNext() - { - return mCursor.moveToNext(); - } - - @Override - public boolean moveToPosition(int position) - { - return mCursor.moveToPosition(position); - } - - @Override - public boolean moveToPrevious() - { - return mCursor.moveToPrevious(); - } - - @Override - public void registerContentObserver(ContentObserver observer) - { - mCursor.registerContentObserver(observer); - } - - @Override - public void registerDataSetObserver(DataSetObserver observer) - { - mCursor.registerDataSetObserver(observer); - } - - @Override - public boolean requery() - { - return mCursor.requery(); - } - - @Override - public Bundle respond(Bundle extras) - { - return mCursor.respond(extras); - } - - @Override - public void setNotificationUri(ContentResolver cr, Uri uri) - { - mCursor.setNotificationUri(cr, uri); - } - - @Override - public void unregisterContentObserver(ContentObserver observer) - { - mCursor.unregisterContentObserver(observer); - } - - @Override - public void unregisterDataSetObserver(DataSetObserver observer) - { - mCursor.unregisterDataSetObserver(observer); + Log.d(K9.LOG_TAG, "Cursor closed, null'ing & releasing semaphore"); + mCursor = null; + mSemaphore.release(); } } + @Override + public boolean isClosed() + { + return mClosed.get() || mCursor.isClosed(); + } + + /* (non-Javadoc) + * + * Making sure cursor gets closed on garbage collection + * + * @see java.lang.Object#finalize() + */ + @Override + protected void finalize() throws Throwable + { + close(); + super.finalize(); + } + + protected void checkClosed() throws IllegalStateException + { + if (mClosed.get()) + { + throw new IllegalStateException("Cursor was closed"); + } + } + + @Override + public void fillWindow(int pos, CursorWindow winow) + { + checkClosed(); + mCursor.fillWindow(pos, winow); + } + + @Override + public CursorWindow getWindow() + { + checkClosed(); + return mCursor.getWindow(); + } + + @Override + public boolean onMove(int oldPosition, int newPosition) + { + checkClosed(); + return mCursor.onMove(oldPosition, newPosition); + } + + @Override + public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) + { + checkClosed(); + mCursor.copyStringToBuffer(columnIndex, buffer); + } + + @Override + public void deactivate() + { + checkClosed(); + mCursor.deactivate(); + } + + @Override + public byte[] getBlob(int columnIndex) + { + checkClosed(); + return mCursor.getBlob(columnIndex); + } + + @Override + public int getColumnCount() + { + checkClosed(); + return mCursor.getColumnCount(); + } + + @Override + public int getColumnIndex(String columnName) + { + checkClosed(); + return mCursor.getColumnIndex(columnName); + } + + @Override + public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException + { + checkClosed(); + return mCursor.getColumnIndexOrThrow(columnName); + } + + @Override + public String getColumnName(int columnIndex) + { + checkClosed(); + return mCursor.getColumnName(columnIndex); + } + + @Override + public String[] getColumnNames() + { + checkClosed(); + return mCursor.getColumnNames(); + } + + @Override + public int getCount() + { + checkClosed(); + return mCursor.getCount(); + } + + @Override + public double getDouble(int columnIndex) + { + checkClosed(); + return mCursor.getDouble(columnIndex); + } + + @Override + public Bundle getExtras() + { + checkClosed(); + return mCursor.getExtras(); + } + + @Override + public float getFloat(int columnIndex) + { + checkClosed(); + return mCursor.getFloat(columnIndex); + } + + @Override + public int getInt(int columnIndex) + { + checkClosed(); + return mCursor.getInt(columnIndex); + } + + @Override + public long getLong(int columnIndex) + { + checkClosed(); + return mCursor.getLong(columnIndex); + } + + @Override + public int getPosition() + { + checkClosed(); + return mCursor.getPosition(); + } + + @Override + public short getShort(int columnIndex) + { + checkClosed(); + return mCursor.getShort(columnIndex); + } + + @Override + public String getString(int columnIndex) + { + checkClosed(); + return mCursor.getString(columnIndex); + } + + @Override + public boolean getWantsAllOnMoveCalls() + { + checkClosed(); + return mCursor.getWantsAllOnMoveCalls(); + } + + @Override + public boolean isAfterLast() + { + checkClosed(); + return mCursor.isAfterLast(); + } + + @Override + public boolean isBeforeFirst() + { + checkClosed(); + return mCursor.isBeforeFirst(); + } + + @Override + public boolean isFirst() + { + checkClosed(); + return mCursor.isFirst(); + } + + public boolean isLast() + { + checkClosed(); + return mCursor.isLast(); + } + + @Override + public boolean isNull(int columnIndex) + { + checkClosed(); + return mCursor.isNull(columnIndex); + } + + @Override + public boolean move(int offset) + { + checkClosed(); + return mCursor.move(offset); + } + + @Override + public boolean moveToFirst() + { + checkClosed(); + return mCursor.moveToFirst(); + } + + @Override + public boolean moveToLast() + { + checkClosed(); + return mCursor.moveToLast(); + } + + @Override + public boolean moveToNext() + { + checkClosed(); + return mCursor.moveToNext(); + } + + @Override + public boolean moveToPosition(int position) + { + checkClosed(); + return mCursor.moveToPosition(position); + } + + @Override + public boolean moveToPrevious() + { + checkClosed(); + return mCursor.moveToPrevious(); + } + + @Override + public void registerContentObserver(ContentObserver observer) + { + checkClosed(); + mCursor.registerContentObserver(observer); + } + + @Override + public void registerDataSetObserver(DataSetObserver observer) + { + checkClosed(); + mCursor.registerDataSetObserver(observer); + } + + @Override + public boolean requery() + { + checkClosed(); + return mCursor.requery(); + } + + @Override + public Bundle respond(Bundle extras) + { + checkClosed(); + return mCursor.respond(extras); + } + + @Override + public void setNotificationUri(ContentResolver cr, Uri uri) + { + checkClosed(); + mCursor.setNotificationUri(cr, uri); + } + + @Override + public void unregisterContentObserver(ContentObserver observer) + { + checkClosed(); + mCursor.unregisterContentObserver(observer); + } + + @Override + public void unregisterDataSetObserver(DataSetObserver observer) + { + checkClosed(); + mCursor.unregisterDataSetObserver(observer); + } + } + + protected class ThrottlingQueryHandler implements QueryHandler + { + private QueryHandler mDelegate; public ThrottlingQueryHandler(final QueryHandler delegate) @@ -740,12 +827,13 @@ public class MessageProvider extends ContentProvider cursor = mDelegate.query(uri, projection, selection, selectionArgs, sortOrder); /* Android content resolvers can only process CrossProcessCursor instances */ - if (cursor == null || !(cursor instanceof CrossProcessCursor)) + if (!(cursor instanceof CrossProcessCursor)) { + Log.w(K9.LOG_TAG, "Unsupported cursor, returning null: " + cursor); return null; } - final MonitoredCursor wrapped = new MonitoredCursor((CrossProcessCursor) cursor); + final MonitoredCursor wrapped = new MonitoredCursor((CrossProcessCursor) cursor, mSemaphore); /* use a weak reference not to actively prevent garbage collection */ final WeakReference weakReference = new WeakReference(wrapped); @@ -758,7 +846,7 @@ public class MessageProvider extends ContentProvider public void run() { final MonitoredCursor monitored = weakReference.get(); - if (monitored != null && monitored.mClosed.compareAndSet(false, true)) + if (monitored != null && !monitored.isClosed()) { Log.w(K9.LOG_TAG, "Forcibly closing remotely exposed cursor"); try @@ -865,6 +953,9 @@ public class MessageProvider extends ContentProvider private MessageHelper mMessageHelper; + /** + * How many simultaneous cursors we can affort to expose at once + */ /* package */ Semaphore mSemaphore = new Semaphore(1);