1
0
mirror of https://github.com/moparisthebest/k-9 synced 2024-12-25 00:58:50 -05:00

Add a caching layer to EmailProvider

Database updates can be surprisingly slow. This lead to slow updates of
the user interface which in turn made working with K-9 Mail not as fun
as it should be. This commit hopefully changes that.
This commit is contained in:
cketti 2013-02-19 04:45:14 +01:00
parent 9287d97626
commit 50fcff76fd
6 changed files with 440 additions and 114 deletions

View File

@ -0,0 +1,179 @@
package com.fsck.k9.cache;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.support.v4.content.LocalBroadcastManager;
import com.fsck.k9.fragment.MessageListFragment;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
import com.fsck.k9.mail.store.LocalStore.LocalMessage;
import com.fsck.k9.provider.EmailProvider;
/**
* Cache to bridge the time needed to write (user-initiated) changes to the database.
*/
public class EmailProviderCache {
public static final String ACTION_CACHE_UPDATED = "EmailProviderCache.ACTION_CACHE_UPDATED";
private static Context sContext;
private static Map<String, EmailProviderCache> sInstances =
new HashMap<String, EmailProviderCache>();
public static synchronized EmailProviderCache getCache(String accountUuid, Context context) {
if (sContext == null) {
sContext = context.getApplicationContext();
}
EmailProviderCache instance = sInstances.get(accountUuid);
if (instance == null) {
instance = new EmailProviderCache(accountUuid);
sInstances.put(accountUuid, instance);
}
return instance;
}
private String mAccountUuid;
private Map<Long, Map<String, String>> mMessageCache = new HashMap<Long, Map<String, String>>();
private Map<Long, Map<String, String>> mThreadCache = new HashMap<Long, Map<String, String>>();
private Map<Long, Long> mHiddenMessageCache = new HashMap<Long, Long>();
private EmailProviderCache(String accountUuid) {
mAccountUuid = accountUuid;
}
public String getValueForMessage(Long messageId, String columnName) {
synchronized (mMessageCache) {
Map<String, String> map = mMessageCache.get(messageId);
return (map == null) ? null : map.get(columnName);
}
}
public String getValueForThread(Long threadRootId, String columnName) {
synchronized (mThreadCache) {
Map<String, String> map = mThreadCache.get(threadRootId);
return (map == null) ? null : map.get(columnName);
}
}
public void setValueForMessages(List<Long> messageIds, String columnName, String value) {
synchronized (mMessageCache) {
for (Long messageId : messageIds) {
Map<String, String> map = mMessageCache.get(messageId);
if (map == null) {
map = new HashMap<String, String>();
mMessageCache.put(messageId, map);
}
map.put(columnName, value);
}
}
notifyChange();
}
public void setValueForThreads(List<Long> threadRootIds, String columnName, String value) {
synchronized (mThreadCache) {
for (Long threadRootId : threadRootIds) {
Map<String, String> map = mThreadCache.get(threadRootId);
if (map == null) {
map = new HashMap<String, String>();
mThreadCache.put(threadRootId, map);
}
map.put(columnName, value);
}
}
notifyChange();
}
public void removeValueForMessages(List<Long> messageIds, String columnName) {
synchronized (mMessageCache) {
for (Long messageId : messageIds) {
Map<String, String> map = mMessageCache.get(messageId);
if (map != null) {
map.remove(columnName);
if (map.size() == 0) {
mMessageCache.remove(messageId);
}
}
}
}
}
public void removeValueForThreads(List<Long> threadRootIds, String columnName) {
synchronized (mThreadCache) {
for (Long threadRootId : threadRootIds) {
Map<String, String> map = mThreadCache.get(threadRootId);
if (map != null) {
map.remove(columnName);
if (map.size() == 0) {
mThreadCache.remove(threadRootId);
}
}
}
}
}
public void hideMessages(List<Message> messages) {
synchronized (mHiddenMessageCache) {
for (Message message : messages) {
LocalMessage localMessage = (LocalMessage) message;
long messageId = localMessage.getId();
long folderId = ((LocalFolder) localMessage.getFolder()).getId();
mHiddenMessageCache.put(messageId, folderId);
}
}
notifyChange();
}
public boolean isMessageHidden(Long messageId, long folderId) {
synchronized (mHiddenMessageCache) {
Long hiddenInFolder = mHiddenMessageCache.get(messageId);
return (hiddenInFolder != null && hiddenInFolder.longValue() == folderId);
}
}
public void unhideMessages(Message[] messages) {
synchronized (mHiddenMessageCache) {
for (Message message : messages) {
LocalMessage localMessage = (LocalMessage) message;
long messageId = localMessage.getId();
long folderId = ((LocalFolder) localMessage.getFolder()).getId();
Long hiddenInFolder = mHiddenMessageCache.get(messageId);
if (hiddenInFolder != null && hiddenInFolder.longValue() == folderId) {
mHiddenMessageCache.remove(messageId);
}
}
}
}
/**
* Notify all concerned parties that the message list has changed.
*
* <p><strong>Note:</strong>
* Notifying the content resolver of the change will cause the {@code CursorLoader} in
* {@link MessageListFragment} to reload the cursor. But especially with flag changes this will
* block because of the DB write operation to update the flags. So additionally we use
* {@link LocalBroadcastManager} to send a {@link #ACTION_CACHE_UPDATED} broadcast. This way
* {@code MessageListFragment} can update the view without reloading the cursor.
* </p>
*/
private void notifyChange() {
LocalBroadcastManager.getInstance(sContext).sendBroadcast(new Intent(ACTION_CACHE_UPDATED));
Uri uri = Uri.withAppendedPath(EmailProvider.CONTENT_URI, "account/" + mAccountUuid +
"/messages");
sContext.getContentResolver().notifyChange(uri, null);
}
}

View File

@ -0,0 +1,129 @@
package com.fsck.k9.cache;
import java.util.ArrayList;
import java.util.List;
import com.fsck.k9.provider.EmailProvider.MessageColumns;
import com.fsck.k9.provider.EmailProvider.ThreadColumns;
import android.content.Context;
import android.database.Cursor;
import android.database.CursorWrapper;
/**
* A {@link CursorWrapper} that utilizes {@link EmailProviderCache}.
*/
public class EmailProviderCacheCursor extends CursorWrapper {
private EmailProviderCache mCache;
private List<Integer> mHiddenRows = new ArrayList<Integer>();
private int mMessageIdColumn;
private int mFolderIdColumn;
private int mThreadRootColumn;
private int mPosition;
public EmailProviderCacheCursor(String accountUuid, Cursor cursor, Context context) {
super(cursor);
mCache = EmailProviderCache.getCache(accountUuid, context);
mMessageIdColumn = cursor.getColumnIndex(MessageColumns.ID);
mFolderIdColumn = cursor.getColumnIndex(MessageColumns.FOLDER_ID);
mThreadRootColumn = cursor.getColumnIndex(ThreadColumns.ROOT);
if (mMessageIdColumn == -1 || mFolderIdColumn == -1 || mThreadRootColumn == -1) {
throw new IllegalArgumentException("The supplied cursor needs to contain the " +
"following columns: " + MessageColumns.ID + ", " + MessageColumns.FOLDER_ID +
", " + ThreadColumns.ROOT);
}
while (cursor.moveToNext()) {
long messageId = cursor.getLong(mMessageIdColumn);
long folderId = cursor.getLong(mFolderIdColumn);
if (mCache.isMessageHidden(messageId, folderId)) {
mHiddenRows.add(cursor.getPosition());
}
}
// Reset the cursor position
cursor.moveToFirst();
cursor.moveToPrevious();
}
@Override
public int getInt(int columnIndex) {
long messageId = getLong(mMessageIdColumn);
long threadRootId = getLong(mThreadRootColumn);
String columnName = getColumnName(columnIndex);
String value = mCache.getValueForMessage(messageId, columnName);
if (value != null) {
return Integer.valueOf(value);
}
value = mCache.getValueForThread(threadRootId, columnName);
if (value != null) {
return Integer.valueOf(value);
}
return super.getInt(columnIndex);
}
@Override
public int getCount() {
return super.getCount() - mHiddenRows.size();
}
@Override
public boolean moveToFirst() {
return moveToPosition(0);
}
@Override
public boolean moveToLast() {
return moveToPosition(getCount());
}
@Override
public boolean moveToNext() {
return moveToPosition(getPosition() + 1);
}
@Override
public boolean moveToPrevious() {
return moveToPosition(getPosition() - 1);
}
@Override
public boolean move(int offset) {
return moveToPosition(getPosition() + offset);
}
@Override
public boolean moveToPosition(int position) {
if (mHiddenRows.size() == 0) {
return super.moveToPosition(position);
}
mPosition = position;
int newPosition = position;
for (int hiddenRow : mHiddenRows) {
if (hiddenRow > newPosition) {
break;
}
newPosition++;
}
return super.moveToPosition(newPosition);
}
@Override
public int getPosition() {
if (mHiddenRows.size() == 0) {
return super.getPosition();
}
return mPosition;
}
}

View File

@ -53,6 +53,7 @@ import com.fsck.k9.activity.MessageReference;
import com.fsck.k9.activity.NotificationDeleteConfirmation;
import com.fsck.k9.activity.setup.AccountSetupIncoming;
import com.fsck.k9.activity.setup.AccountSetupOutgoing;
import com.fsck.k9.cache.EmailProviderCache;
import com.fsck.k9.helper.Contacts;
import com.fsck.k9.helper.power.TracingPowerManager;
import com.fsck.k9.helper.power.TracingPowerManager.TracingWakeLock;
@ -199,9 +200,6 @@ public class MessagingController implements Runnable {
*/
private Application mApplication;
// Key is accountUuid:folderName:messageUid , value is unimportant
private ConcurrentHashMap<String, String> deletedUids = new ConcurrentHashMap<String, String>();
/**
* A holder class for pending notification data
*
@ -320,45 +318,69 @@ public class MessagingController implements Runnable {
private static final Flag[] SYNC_FLAGS = new Flag[] { Flag.SEEN, Flag.FLAGGED, Flag.ANSWERED, Flag.FORWARDED };
private String createMessageKey(Account account, String folder, Message message) {
return createMessageKey(account, folder, message.getUid());
private void suppressMessages(Account account, List<Message> messages) {
EmailProviderCache cache = EmailProviderCache.getCache(account.getUuid(),
mApplication.getApplicationContext());
cache.hideMessages(messages);
}
private String createMessageKey(Account account, String folder, String uid) {
return account.getUuid() + ":" + folder + ":" + uid;
private void unsuppressMessages(Account account, Message[] messages) {
EmailProviderCache cache = EmailProviderCache.getCache(account.getUuid(),
mApplication.getApplicationContext());
cache.unhideMessages(messages);
}
private void suppressMessage(Account account, String folder, Message message) {
private boolean isMessageSuppressed(Account account, Message message) {
LocalMessage localMessage = (LocalMessage) message;
String accountUuid = account.getUuid();
long messageId = localMessage.getId();
long folderId = ((LocalFolder) localMessage.getFolder()).getId();
if (account == null || folder == null || message == null) {
return;
}
String messKey = createMessageKey(account, folder, message);
deletedUids.put(messKey, "true");
EmailProviderCache cache = EmailProviderCache.getCache(accountUuid,
mApplication.getApplicationContext());
return cache.isMessageHidden(messageId, folderId);
}
private void unsuppressMessage(Account account, String folder, String uid) {
if (account == null || folder == null || uid == null) {
return;
}
String messKey = createMessageKey(account, folder, uid);
deletedUids.remove(messKey);
private void setFlagInCache(final Account account, final List<Long> messageIds,
final Flag flag, final boolean newState) {
EmailProviderCache cache = EmailProviderCache.getCache(account.getUuid(),
mApplication.getApplicationContext());
String columnName = LocalStore.getColumnNameForFlag(flag);
String value = Integer.toString((newState) ? 1 : 0);
cache.setValueForMessages(messageIds, columnName, value);
}
private void removeFlagFromCache(final Account account, final List<Long> messageIds,
final Flag flag) {
private boolean isMessageSuppressed(Account account, String folder, Message message) {
if (account == null || folder == null || message == null) {
return false;
}
String messKey = createMessageKey(account, folder, message);
if (deletedUids.containsKey(messKey)) {
return true;
}
return false;
EmailProviderCache cache = EmailProviderCache.getCache(account.getUuid(),
mApplication.getApplicationContext());
String columnName = LocalStore.getColumnNameForFlag(flag);
cache.removeValueForMessages(messageIds, columnName);
}
private void setFlagForThreadsInCache(final Account account, final List<Long> threadRootIds,
final Flag flag, final boolean newState) {
EmailProviderCache cache = EmailProviderCache.getCache(account.getUuid(),
mApplication.getApplicationContext());
String columnName = LocalStore.getColumnNameForFlag(flag);
String value = Integer.toString((newState) ? 1 : 0);
cache.setValueForThreads(threadRootIds, columnName, value);
}
private void removeFlagForThreadsFromCache(final Account account, final List<Long> messageIds,
final Flag flag) {
EmailProviderCache cache = EmailProviderCache.getCache(account.getUuid(),
mApplication.getApplicationContext());
String columnName = LocalStore.getColumnNameForFlag(flag);
cache.removeValueForThreads(messageIds, columnName);
}
/**
* @param application {@link K9}
*/
@ -677,7 +699,7 @@ public class MessagingController implements Runnable {
public void messagesFinished(int number) {}
@Override
public void messageFinished(Message message, int number, int ofTotal) {
if (!isMessageSuppressed(message.getFolder().getAccount(), message.getFolder().getName(), message)) {
if (!isMessageSuppressed(message.getFolder().getAccount(), message)) {
List<Message> messages = new ArrayList<Message>();
messages.add(message);
@ -1386,8 +1408,6 @@ public class MessagingController implements Runnable {
if (message.isSet(Flag.DELETED)) {
syncFlagMessages.add(message);
return;
} else if (isMessageSuppressed(account, folder, message)) {
return;
}
Message localMessage = localFolder.getMessage(message.getUid());
@ -1498,14 +1518,13 @@ public class MessagingController implements Runnable {
* (POP) may not be able to give us headers for
* ENVELOPE, only size.
*/
if (!isMessageSuppressed(account, folder, message)) {
// keep message for delayed storing
chunk.add(message);
if (chunk.size() >= UNSYNC_CHUNK_SIZE) {
writeUnsyncedMessages(chunk, localFolder, account, folder);
chunk.clear();
}
// keep message for delayed storing
chunk.add(message);
if (chunk.size() >= UNSYNC_CHUNK_SIZE) {
writeUnsyncedMessages(chunk, localFolder, account, folder);
chunk.clear();
}
}
} catch (Exception e) {
@ -1567,15 +1586,6 @@ public class MessagingController implements Runnable {
private boolean shouldImportMessage(final Account account, final String folder, final Message message, final AtomicInteger progress, final Date earliestDate) {
if (isMessageSuppressed(account, folder, message)) {
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Message " + message.getUid() + " was suppressed " +
"but just downloaded. " +
"The race condition means we wasted some bandwidth. Oh well.");
}
return false;
}
if (account.isSearchByDateCapable() && message.olderThan(earliestDate)) {
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Message " + message.getUid() + " is older than "
@ -1819,7 +1829,7 @@ public class MessagingController implements Runnable {
boolean messageChanged = syncFlags(localMessage, remoteMessage);
if (messageChanged) {
boolean shouldBeNotifiedOf = false;
if (localMessage.isSet(Flag.DELETED) || isMessageSuppressed(account, folder, localMessage)) {
if (localMessage.isSet(Flag.DELETED) || isMessageSuppressed(account, localMessage)) {
for (MessagingListener l : getListeners()) {
l.synchronizeMailboxRemovedMessage(account, folder, localMessage);
}
@ -2748,6 +2758,8 @@ public class MessagingController implements Runnable {
public void setFlag(final Account account, final List<Long> messageIds, final Flag flag,
final boolean newState) {
setFlagInCache(account, messageIds, flag, newState);
threadPool.execute(new Runnable() {
@Override
public void run() {
@ -2759,6 +2771,8 @@ public class MessagingController implements Runnable {
public void setFlagForThreads(final Account account, final List<Long> threadRootIds,
final Flag flag, final boolean newState) {
setFlagForThreadsInCache(account, threadRootIds, flag, newState);
threadPool.execute(new Runnable() {
@Override
public void run() {
@ -2783,8 +2797,10 @@ public class MessagingController implements Runnable {
try {
if (threadedList) {
localStore.setFlagForThreads(ids, flag, newState);
removeFlagFromCache(account, ids, flag);
} else {
localStore.setFlag(ids, flag, newState);
removeFlagForThreadsFromCache(account, ids, flag);
}
} catch (MessagingException e) {
Log.e(K9.LOG_TAG, "Couldn't set flags in local database", e);
@ -3763,9 +3779,7 @@ public class MessagingController implements Runnable {
final List<Message> messages, final String destFolder,
final MessagingListener listener) {
for (Message message : messages) {
suppressMessage(account, srcFolder, message);
}
suppressMessages(account, messages);
putBackground("moveMessages", null, new Runnable() {
@Override
@ -3779,9 +3793,7 @@ public class MessagingController implements Runnable {
public void moveMessagesInThread(final Account account, final String srcFolder,
final List<Message> messages, final String destFolder) {
for (Message message : messages) {
suppressMessage(account, srcFolder, message);
}
suppressMessages(account, messages);
putBackground("moveMessagesInThread", null, new Runnable() {
@Override
@ -3905,8 +3917,8 @@ public class MessagingController implements Runnable {
for (MessagingListener l : getListeners()) {
l.messageUidChanged(account, srcFolder, origUid, message.getUid());
}
unsuppressMessage(account, srcFolder, origUid);
}
unsuppressMessages(account, messages);
if (unreadCountAffected) {
// If this move operation changes the unread count, notify the listeners
@ -3970,9 +3982,7 @@ public class MessagingController implements Runnable {
public void act(final Account account, final Folder folder,
final List<Message> accountMessages) {
for (Message message : accountMessages) {
suppressMessage(account, folder.getName(), message);
}
suppressMessages(account, messages);
putBackground("deleteThreads", null, new Runnable() {
@Override
@ -4021,9 +4031,7 @@ public class MessagingController implements Runnable {
@Override
public void act(final Account account, final Folder folder,
final List<Message> accountMessages) {
for (Message message : accountMessages) {
suppressMessage(account, folder.getName(), message);
}
suppressMessages(account, messages);
putBackground("deleteMessages", null, new Runnable() {
@Override
@ -4111,9 +4119,8 @@ public class MessagingController implements Runnable {
if (K9.DEBUG)
Log.d(K9.LOG_TAG, "Delete policy " + account.getDeletePolicy() + " prevents delete from server");
}
for (String uid : uids) {
unsuppressMessage(account, folder, uid);
}
unsuppressMessages(account, messages);
} catch (UnavailableStorageException e) {
Log.i(K9.LOG_TAG, "Failed to delete message because storage is not available - trying again later.");
throw new UnavailableAccountException(e);

View File

@ -13,8 +13,10 @@ import java.util.Set;
import java.util.concurrent.Future;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences.Editor;
import android.database.Cursor;
import android.graphics.Color;
@ -32,6 +34,7 @@ import android.support.v4.app.LoaderManager;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.widget.CursorAdapter;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
@ -75,19 +78,19 @@ import com.fsck.k9.activity.ChooseFolder;
import com.fsck.k9.activity.FolderInfoHolder;
import com.fsck.k9.activity.MessageReference;
import com.fsck.k9.activity.misc.ContactPictureLoader;
import com.fsck.k9.cache.EmailProviderCache;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.fragment.ConfirmationDialogFragment;
import com.fsck.k9.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener;
import com.fsck.k9.helper.MessageHelper;
import com.fsck.k9.helper.MergeCursorWithUniqueId;
import com.fsck.k9.helper.MessageHelper;
import com.fsck.k9.helper.StringUtils;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Folder.OpenMode;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Folder.OpenMode;
import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
import com.fsck.k9.provider.EmailProvider;
@ -428,6 +431,11 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
private ContactPictureLoader mContactsPictureLoader;
private LocalBroadcastManager mLocalBroadcastManager;
private BroadcastReceiver mCacheBroadcastReceiver;
private IntentFilter mCacheIntentFilter;
/**
* This class is used to run operations that modify UI elements in the UI thread.
*
@ -728,7 +736,9 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPreferences = Preferences.getPreferences(getActivity().getApplicationContext());
Context appContext = getActivity().getApplicationContext();
mPreferences = Preferences.getPreferences(appContext);
mController = MessagingController.getInstance(getActivity().getApplication());
mPreviewLines = K9.messageListPreviewLines();
@ -742,6 +752,8 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
restoreInstanceState(savedInstanceState);
decodeArguments();
createCacheBroadcastReceiver(appContext);
mInitialized = true;
}
@ -916,6 +928,19 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
mListView.setAdapter(mAdapter);
}
private void createCacheBroadcastReceiver(Context appContext) {
mLocalBroadcastManager = LocalBroadcastManager.getInstance(appContext);
mCacheBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
mAdapter.notifyDataSetChanged();
}
};
mCacheIntentFilter = new IntentFilter(EmailProviderCache.ACTION_CACHE_UPDATED);
}
private FolderInfoHolder getFolder(String folder, Account account) {
LocalFolder local_folder = null;
try {
@ -960,6 +985,8 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
@Override
public void onPause() {
super.onPause();
mLocalBroadcastManager.unregisterReceiver(mCacheBroadcastReceiver);
mListener.onPause(getActivity());
mController.removeListener(mListener);
}
@ -1001,6 +1028,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
}
}
mLocalBroadcastManager.registerReceiver(mCacheBroadcastReceiver, mCacheIntentFilter);
mListener.onResume(getActivity());
mController.addListener(mListener);

View File

@ -72,6 +72,7 @@ import com.fsck.k9.mail.store.LockableDatabase.WrappedException;
import com.fsck.k9.mail.store.StorageManager.StorageProvider;
import com.fsck.k9.provider.AttachmentProvider;
import com.fsck.k9.provider.EmailProvider;
import com.fsck.k9.provider.EmailProvider.MessageColumns;
import com.fsck.k9.search.LocalSearch;
import com.fsck.k9.search.SearchSpecification.Attribute;
import com.fsck.k9.search.SearchSpecification.Searchfield;
@ -127,6 +128,28 @@ public class LocalStore extends Store implements Serializable {
public static final int DB_VERSION = 47;
public static String getColumnNameForFlag(Flag flag) {
switch (flag) {
case SEEN: {
return MessageColumns.READ;
}
case FLAGGED: {
return MessageColumns.FLAGGED;
}
case ANSWERED: {
return MessageColumns.ANSWERED;
}
case FORWARDED: {
return MessageColumns.FORWARDED;
}
default: {
throw new IllegalArgumentException("Flag must be a special column flag");
}
}
}
protected String uUid = null;
private final Application mApplication;
@ -4104,28 +4127,7 @@ public class LocalStore extends Store implements Serializable {
throws MessagingException {
final ContentValues cv = new ContentValues();
switch (flag) {
case SEEN: {
cv.put("read", newState);
break;
}
case FLAGGED: {
cv.put("flagged", newState);
break;
}
case ANSWERED: {
cv.put("answered", newState);
break;
}
case FORWARDED: {
cv.put("forwarded", newState);
break;
}
default: {
throw new IllegalArgumentException("Flag must be a special column flag");
}
}
cv.put(getColumnNameForFlag(flag), newState);
doBatchSetSelection(new BatchSetSelection() {
@ -4173,28 +4175,7 @@ public class LocalStore extends Store implements Serializable {
public void setFlagForThreads(final List<Long> threadRootIds, Flag flag, final boolean newState)
throws MessagingException {
final String flagColumn;
switch (flag) {
case SEEN: {
flagColumn = "read";
break;
}
case FLAGGED: {
flagColumn = "flagged";
break;
}
case ANSWERED: {
flagColumn = "answered";
break;
}
case FORWARDED: {
flagColumn = "forwarded";
break;
}
default: {
throw new IllegalArgumentException("Flag must be a special column flag");
}
}
final String flagColumn = getColumnNameForFlag(flag);
doBatchSetSelection(new BatchSetSelection() {

View File

@ -7,6 +7,7 @@ import java.util.Map;
import com.fsck.k9.Account;
import com.fsck.k9.Preferences;
import com.fsck.k9.cache.EmailProviderCacheCursor;
import com.fsck.k9.helper.StringUtils;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.MessagingException;
@ -271,6 +272,7 @@ public class EmailProvider extends ContentProvider {
cursor = new SpecialColumnsCursor(new IdTrickeryCursor(cursor), projection,
specialColumns);
cursor = new EmailProviderCacheCursor(accountUuid, cursor, getContext());
break;
}
case STATS: {