package com.fsck.k9.provider; import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.UriMatcher; import android.database.CharArrayBuffer; import android.database.ContentObserver; import android.database.CrossProcessCursor; import android.database.Cursor; import android.database.CursorWindow; import android.database.DataSetObserver; import android.database.MatrixCursor; import android.net.Uri; import android.os.Bundle; import android.provider.BaseColumns; import android.util.Log; import com.fsck.k9.Account; import com.fsck.k9.AccountStats; import com.fsck.k9.K9; import com.fsck.k9.Preferences; import com.fsck.k9.SearchAccount; import com.fsck.k9.activity.FolderInfoHolder; import com.fsck.k9.activity.MessageInfoHolder; import com.fsck.k9.activity.MessageList; import com.fsck.k9.controller.MessagingController; import com.fsck.k9.controller.MessagingListener; import com.fsck.k9.helper.MessageHelper; import com.fsck.k9.mail.Folder; import com.fsck.k9.mail.Message; import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.store.LocalStore; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.Semaphore; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; public class MessageProvider extends ContentProvider { public static interface MessageColumns extends BaseColumns { /** * The number of milliseconds since Jan. 1, 1970, midnight GMT. * *
Type: INTEGER (long)
*/ String SEND_DATE = "date"; /** *Type: TEXT
*/ String SENDER = "sender"; /** *Type: TEXT
*/ String SENDER_ADDRESS = "senderAddress"; /** *Type: TEXT
*/ String SUBJECT = "subject"; /** *Type: TEXT
*/ String PREVIEW = "preview"; /** *Type: BOOLEAN
*/ String UNREAD = "unread"; String ACCOUNT = "account"; String URI = "uri"; String DELETE_URI = "delUri"; /** * @deprecated the field value is misnamed/misleading - present for compatibility purpose only. To be removed. */ @Deprecated String INCREMENT = "id"; } protected static interface QueryHandler { /** * The path this instance is able to respond to. * * @return Nevernull
.
*/
String getPath();
/**
* @param uri
* @param projection
* @param selection
* @param selectionArgs
* @param sortOrder
* @return
* @throws Exception
* @see {@link ContentProvider#query(Uri, String[], String, String[], String)}
*/
Cursor query(Uri uri, String[] projection,
String selection, String[] selectionArgs, String sortOrder) throws Exception;
}
/**
* Extracts a value from an object.
*
* @param null
, use the default
* projection.
* @return Never null
.
* @throws InterruptedException
*/
protected MatrixCursor getMessages(final String[] projection) throws InterruptedException {
final BlockingQueuenull
.
* @param semaphore
* The semaphore to release on close. Never
* null
.
*/
protected MonitoredCursor(final CrossProcessCursor cursor, final Semaphore semaphore) {
this.mCursor = cursor;
this.mSemaphore = semaphore;
}
/* (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();
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);
}
@Override
public int getType(int columnIndex) {
checkClosed();
return mCursor.getType(columnIndex);
}
}
protected class ThrottlingQueryHandler implements QueryHandler {
private QueryHandler mDelegate;
public ThrottlingQueryHandler(final QueryHandler delegate) {
mDelegate = delegate;
}
@Override
public String getPath() {
return mDelegate.getPath();
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) throws Exception {
mSemaphore.acquire();
final Cursor cursor;
cursor = mDelegate.query(uri, projection, selection, selectionArgs, sortOrder);
/* Android content resolvers can only process CrossProcessCursor instances */
if (!(cursor instanceof CrossProcessCursor)) {
Log.w(K9.LOG_TAG, "Unsupported cursor, returning null: " + cursor);
return null;
}
final MonitoredCursor wrapped = new MonitoredCursor((CrossProcessCursor) cursor, mSemaphore);
/* use a weak reference not to actively prevent garbage collection */
final WeakReferencenull
. The synchronized channel to use
* to retrieve {@link MessageInfoHolder}s.
*/
public MesssageInfoHolderRetrieverListener(final BlockingQueuenull
.
*/
protected void registerQueryHandler(final QueryHandler handler) {
if (mQueryHandlers.contains(handler)) {
return;
}
mQueryHandlers.add(handler);
// use the index inside the list as the UriMatcher code for that handler
final int code = mQueryHandlers.indexOf(handler);
mUriMatcher.addURI(AUTHORITY, handler.getPath(), code);
}
}