From 63b6b497a06fddf57601c06f9be5bab3f8e49daa Mon Sep 17 00:00:00 2001
From: cketti
Date: Mon, 8 Oct 2012 22:51:29 +0200
Subject: [PATCH 01/74] Early version of message threading
Missing:
- UI support for threading when polling
- code to upgrade existing installations
- UI elements to switch from/to threaded display mode
- threading of messages with same subject
---
res/layout/message_list_item.xml | 25 +-
src/com/fsck/k9/activity/MessageList.java | 8 +
.../k9/controller/MessagingController.java | 25 +-
.../fsck/k9/fragment/MessageListFragment.java | 42 +-
src/com/fsck/k9/helper/Utility.java | 42 ++
src/com/fsck/k9/mail/store/ImapStore.java | 2 +-
src/com/fsck/k9/mail/store/LocalStore.java | 597 ++++++++++++++++--
7 files changed, 684 insertions(+), 57 deletions(-)
diff --git a/res/layout/message_list_item.xml b/res/layout/message_list_item.xml
index 89c7bf619..14ce2996e 100644
--- a/res/layout/message_list_item.xml
+++ b/res/layout/message_list_item.xml
@@ -45,14 +45,32 @@
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/chip_wrapper"
android:layout_below="@+id/subject"
+ android:layout_toLeftOf="@+id/thread_count"
android:layout_marginLeft="1dip"
android:layout_marginBottom="3dip"
- android:layout_marginRight="16dip"
- android:layout_alignParentRight="true"
+ android:layout_marginRight="3dip"
android:bufferType="spannable"
android:singleLine="false"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorPrimary" />
+
+
+
+
-
-
-
diff --git a/src/com/fsck/k9/activity/MessageList.java b/src/com/fsck/k9/activity/MessageList.java
index 8862314e9..eb7c91ee4 100644
--- a/src/com/fsck/k9/activity/MessageList.java
+++ b/src/com/fsck/k9/activity/MessageList.java
@@ -748,4 +748,12 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
return true;
}
+
+ @Override
+ public void showThread(Account account, String folderName, long threadRootId) {
+ MessageListFragment fragment = MessageListFragment.newInstance(account, folderName,
+ threadRootId);
+
+ addMessageListFragment(fragment);
+ }
}
diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java
index d5860a4c3..e4cf48734 100644
--- a/src/com/fsck/k9/controller/MessagingController.java
+++ b/src/com/fsck/k9/controller/MessagingController.java
@@ -517,13 +517,16 @@ public class MessagingController implements Runnable {
* @param account
* @param folder
* @param listener
+ * @param threaded
+ * @param threadId
* @throws MessagingException
*/
- public void listLocalMessages(final Account account, final String folder, final MessagingListener listener) {
+ public void listLocalMessages(final Account account, final String folder,
+ final MessagingListener listener, final boolean threaded, final long threadId) {
threadPool.execute(new Runnable() {
@Override
public void run() {
- listLocalMessagesSynchronous(account, folder, listener);
+ listLocalMessagesSynchronous(account, folder, listener, threaded, threadId);
}
});
}
@@ -535,9 +538,12 @@ public class MessagingController implements Runnable {
* @param account
* @param folder
* @param listener
+ * @param threaded
+ * @param threadId
* @throws MessagingException
*/
- public void listLocalMessagesSynchronous(final Account account, final String folder, final MessagingListener listener) {
+ public void listLocalMessagesSynchronous(final Account account, final String folder,
+ final MessagingListener listener, boolean threaded, long threadId) {
for (MessagingListener l : getListeners(listener)) {
l.listLocalMessagesStarted(account, folder);
@@ -588,10 +594,15 @@ public class MessagingController implements Runnable {
//Purging followed by getting requires 2 DB queries.
//TODO: Fix getMessages to allow auto-pruning at visible limit?
localFolder.purgeToVisibleLimit(null);
- localFolder.getMessages(
- retrievalListener,
- false // Skip deleted messages
- );
+
+ if (threadId != -1) {
+ localFolder.getMessagesInThread(threadId, retrievalListener);
+ } else if (threaded) {
+ localFolder.getThreadedMessages(retrievalListener);
+ } else {
+ localFolder.getMessages(retrievalListener, false);
+ }
+
if (K9.DEBUG)
Log.v(K9.LOG_TAG, "Got ack that callbackRunner finished");
diff --git a/src/com/fsck/k9/fragment/MessageListFragment.java b/src/com/fsck/k9/fragment/MessageListFragment.java
index df8d85b33..87510e5a2 100644
--- a/src/com/fsck/k9/fragment/MessageListFragment.java
+++ b/src/com/fsck/k9/fragment/MessageListFragment.java
@@ -76,6 +76,7 @@ import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
+import com.fsck.k9.mail.store.LocalStore.LocalMessage;
import com.handmark.pulltorefresh.library.PullToRefreshBase;
import com.handmark.pulltorefresh.library.PullToRefreshListView;
@@ -94,6 +95,19 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
return fragment;
}
+ public static MessageListFragment newInstance(Account account, String folderName,
+ long threadRootId) {
+ MessageListFragment fragment = new MessageListFragment();
+
+ Bundle args = new Bundle();
+ args.putString(ARG_ACCOUNT, account.getUuid());
+ args.putString(ARG_FOLDER, folderName);
+ args.putLong(ARG_THREAD_ID, threadRootId);
+ fragment.setArguments(args);
+
+ return fragment;
+ }
+
public static MessageListFragment newInstance(String title, String[] accountUuids,
String[] folderNames, String queryString, Flag[] flags,
Flag[] forbiddenFlags, boolean integrate) {
@@ -290,6 +304,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
private static final String ARG_ACCOUNT_UUIDS = "accountUuids";
private static final String ARG_FOLDER_NAMES = "folderNames";
private static final String ARG_TITLE = "title";
+ private static final String ARG_THREAD_ID = "thread_id";
private static final String STATE_LIST_POSITION = "listPosition";
@@ -389,6 +404,11 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
private DateFormat mTimeFormat;
+ //TODO: make this a setting
+ private boolean mThreadViewEnabled = true;
+
+ private long mThreadId;
+
/**
* This class is used to run operations that modify UI elements in the UI thread.
@@ -667,6 +687,10 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
final MessageInfoHolder message = (MessageInfoHolder) parent.getItemAtPosition(position);
if (mSelectedCount > 0) {
toggleMessageSelect(message);
+ } else if (((LocalMessage) message.message).getThreadCount() > 1) {
+ Folder folder = message.message.getFolder();
+ long rootId = ((LocalMessage) message.message).getRootId();
+ mFragmentListener.showThread(folder.getAccount(), folder.getName(), rootId);
} else {
onOpenMessage(message);
}
@@ -730,6 +754,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
mRemoteSearch = args.getBoolean(ARG_REMOTE_SEARCH, false);
mSearchAccount = args.getString(ARG_SEARCH_ACCOUNT);
mSearchFolder = args.getString(ARG_SEARCH_FOLDER);
+ mThreadId = args.getLong(ARG_THREAD_ID, -1);
String accountUuid = args.getString(ARG_ACCOUNT);
@@ -892,7 +917,8 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
//TODO: Support flag based search
mRemoteSearchFuture = mController.searchRemoteMessages(mSearchAccount, mSearchFolder, mQueryString, null, null, mAdapter.mListener);
} else if (mFolderName != null) {
- mController.listLocalMessages(mAccount, mFolderName, mAdapter.mListener);
+ mController.listLocalMessages(mAccount, mFolderName, mAdapter.mListener, mThreadViewEnabled, mThreadId);
+
// Hide the archive button if we don't have an archive folder.
if (!mAccount.hasArchiveFolder()) {
// mBatchArchiveButton.setVisibility(View.GONE);
@@ -914,7 +940,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
@Override
public void run() {
if (mFolderName != null) {
- mController.listLocalMessagesSynchronous(mAccount, mFolderName, mAdapter.mListener);
+ mController.listLocalMessagesSynchronous(mAccount, mFolderName, mAdapter.mListener, mThreadViewEnabled, mThreadId);
} else if (mQueryString != null) {
mController.searchLocalMessagesSynchronous(mAccountUuids, mFolderNames, null, mQueryString, mIntegrate, mQueryFlags, mForbiddenFlags, mAdapter.mListener);
}
@@ -1929,6 +1955,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
holder.preview.setLines(mPreviewLines);
holder.preview.setTextSize(TypedValue.COMPLEX_UNIT_SP, mFontSizes.getMessageListPreview());
+ holder.threadCount = (TextView) view.findViewById(R.id.thread_count);
view.setTag(holder);
}
@@ -2029,6 +2056,15 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
subject = message.message.getSubject();
}
+ int threadCount = ((LocalMessage) message.message).getThreadCount();
+ if (threadCount > 1) {
+ holder.threadCount.setText(Integer.toString(threadCount));
+ holder.threadCount.setVisibility(View.VISIBLE);
+ } else {
+ holder.threadCount.setVisibility(View.GONE);
+ }
+
+
// We'll get badge support soon --jrv
// if (holder.badge != null) {
// String email = message.counterpartyAddress;
@@ -2133,6 +2169,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
public TextView date;
public View chip;
public int position = -1;
+ public TextView threadCount;
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
@@ -2902,6 +2939,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
public interface MessageListFragmentListener {
void setMessageListProgress(int level);
+ void showThread(Account account, String folderName, long rootId);
void remoteSearch(String searchAccount, String searchFolder, String queryString);
void showMoreFromSameSender(String senderAddress);
void onResendMessage(Message message);
diff --git a/src/com/fsck/k9/helper/Utility.java b/src/com/fsck/k9/helper/Utility.java
index 113032d34..5a66a0ab9 100644
--- a/src/com/fsck/k9/helper/Utility.java
+++ b/src/com/fsck/k9/helper/Utility.java
@@ -18,7 +18,9 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
import java.util.Date;
+import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -666,4 +668,44 @@ public class Utility {
return false;
}
}
+
+ private static final Pattern MESSAGE_ID = Pattern.compile("<" +
+ "(?:" +
+ "[a-zA-Z0-9!#$%&'*+\\-/=?^_`{|}~]+" +
+ "(?:\\.[a-zA-Z0-9!#$%&'*+\\-/=?^_`{|}~]+)*" +
+ "|" +
+ "\"(?:[^\\\\\"]|\\\\.)*\"" +
+ ")" +
+ "@" +
+ "(?:" +
+ "[a-zA-Z0-9!#$%&'*+\\-/=?^_`{|}~]+" +
+ "(?:\\.[a-zA-Z0-9!#$%&'*+\\-/=?^_`{|}~]+)*" +
+ "|" +
+ "\\[(?:[^\\\\\\]]|\\\\.)*\\]" +
+ ")" +
+ ">");
+
+ public static List extractMessageIds(final String text) {
+ List messageIds = new ArrayList();
+ Matcher matcher = MESSAGE_ID.matcher(text);
+
+ int start = 0;
+ while (matcher.find(start)) {
+ String messageId = text.substring(matcher.start(), matcher.end());
+ messageIds.add(messageId);
+ start = matcher.end();
+ }
+
+ return messageIds;
+ }
+
+ public static String extractMessageId(final String text) {
+ Matcher matcher = MESSAGE_ID.matcher(text);
+
+ if (matcher.find()) {
+ return text.substring(matcher.start(), matcher.end());
+ }
+
+ return null;
+ }
}
diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java
index 906cf8b04..39fab2478 100644
--- a/src/com/fsck/k9/mail/store/ImapStore.java
+++ b/src/com/fsck/k9/mail/store/ImapStore.java
@@ -1478,7 +1478,7 @@ public class ImapStore extends Store {
fetchFields.add("INTERNALDATE");
fetchFields.add("RFC822.SIZE");
fetchFields.add("BODY.PEEK[HEADER.FIELDS (date subject from content-type to cc " +
- "reply-to message-id " + K9.IDENTITY_HEADER + ")]");
+ "reply-to message-id references " + K9.IDENTITY_HEADER + ")]");
}
if (fp.contains(FetchProfile.Item.STRUCTURE)) {
fetchFields.add("BODYSTRUCTURE");
diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java
index 6e16c1cc7..fe69d8af3 100644
--- a/src/com/fsck/k9/mail/store/LocalStore.java
+++ b/src/com/fsck/k9/mail/store/LocalStore.java
@@ -13,10 +13,8 @@ import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
@@ -82,11 +80,8 @@ public class LocalStore extends Store implements Serializable {
private static final long serialVersionUID = -5142141896809423072L;
private static final Message[] EMPTY_MESSAGE_ARRAY = new Message[0];
-
- /**
- * Immutable empty {@link String} array
- */
private static final String[] EMPTY_STRING_ARRAY = new String[0];
+ private static final Flag[] EMPTY_FLAG_ARRAY = new Flag[0];
private static final Flag[] PERMANENT_FLAGS = { Flag.DELETED, Flag.X_DESTROYED, Flag.SEEN, Flag.FLAGGED };
@@ -96,13 +91,13 @@ public class LocalStore extends Store implements Serializable {
*/
static private String GET_MESSAGES_COLS =
"subject, sender_list, date, uid, flags, id, to_list, cc_list, "
- + "bcc_list, reply_to_list, attachment_count, internal_date, message_id, folder_id, preview ";
+ + "bcc_list, reply_to_list, attachment_count, internal_date, message_id, folder_id, preview, thread_root, thread_parent, empty ";
static private String GET_FOLDER_COLS = "id, name, unread_count, visible_limit, last_updated, status, push_state, last_pushed, flagged_count, integrate, top_group, poll_class, push_class, display_class";
- protected static final int DB_VERSION = 43;
+ protected static final int DB_VERSION = 44;
protected String uUid = null;
@@ -166,10 +161,31 @@ public class LocalStore extends Store implements Serializable {
db.execSQL("CREATE INDEX IF NOT EXISTS folder_name ON folders (name)");
db.execSQL("DROP TABLE IF EXISTS messages");
- db.execSQL("CREATE TABLE messages (id INTEGER PRIMARY KEY, deleted INTEGER default 0, folder_id INTEGER, uid TEXT, subject TEXT, "
- + "date INTEGER, flags TEXT, sender_list TEXT, to_list TEXT, cc_list TEXT, bcc_list TEXT, reply_to_list TEXT, "
- + "html_content TEXT, text_content TEXT, attachment_count INTEGER, internal_date INTEGER, message_id TEXT, preview TEXT, "
- + "mime_type TEXT)");
+ db.execSQL("CREATE TABLE messages (" +
+ "id INTEGER PRIMARY KEY, " +
+ "deleted INTEGER default 0, " +
+ "folder_id INTEGER, " +
+ "uid TEXT, " +
+ "subject TEXT, " +
+ "date INTEGER, " +
+ "flags TEXT, " +
+ "sender_list TEXT, " +
+ "to_list TEXT, " +
+ "cc_list TEXT, " +
+ "bcc_list TEXT, " +
+ "reply_to_list TEXT, " +
+ "html_content TEXT, " +
+ "text_content TEXT, " +
+ "attachment_count INTEGER, " +
+ "internal_date INTEGER, " +
+ "message_id TEXT, " +
+ "preview TEXT, " +
+ "mime_type TEXT, "+
+ "thread_root INTEGER, " +
+ "thread_parent INTEGER, " +
+ "normalized_subject_hash INTEGER, " +
+ "empty INTEGER" +
+ ")");
db.execSQL("DROP TABLE IF EXISTS headers");
db.execSQL("CREATE TABLE headers (id INTEGER PRIMARY KEY, message_id INTEGER, name TEXT, value TEXT)");
@@ -179,6 +195,9 @@ public class LocalStore extends Store implements Serializable {
db.execSQL("DROP INDEX IF EXISTS msg_folder_id");
db.execSQL("DROP INDEX IF EXISTS msg_folder_id_date");
db.execSQL("CREATE INDEX IF NOT EXISTS msg_folder_id_deleted_date ON messages (folder_id,deleted,internal_date)");
+
+ //TODO: add indices for the thread columns
+
db.execSQL("DROP TABLE IF EXISTS attachments");
db.execSQL("CREATE TABLE attachments (id INTEGER PRIMARY KEY, message_id INTEGER,"
+ "store_data TEXT, content_uri TEXT, size INTEGER, name TEXT,"
@@ -364,6 +383,18 @@ public class LocalStore extends Store implements Serializable {
Log.e(K9.LOG_TAG, "Error trying to fix the outbox folders", e);
}
}
+ if (db.getVersion() < 44) {
+ try {
+ db.execSQL("ALTER TABLE messages ADD thread_root INTEGER");
+ db.execSQL("ALTER TABLE messages ADD thread_parent INTEGER");
+ db.execSQL("ALTER TABLE messages ADD normalized_subject_hash INTEGER");
+ db.execSQL("ALTER TABLE messages ADD empty INTEGER");
+ } catch (SQLiteException e) {
+ if (! e.getMessage().startsWith("duplicate column name:")) {
+ throw e;
+ }
+ }
+ }
}
} catch (SQLiteException e) {
Log.e(K9.LOG_TAG, "Exception while upgrading database. Resetting the DB to v0");
@@ -942,7 +973,7 @@ public class LocalStore extends Store implements Serializable {
null,
"SELECT "
+ GET_MESSAGES_COLS
- + "FROM messages WHERE deleted = 0 " + whereClause.toString() + " ORDER BY date DESC"
+ + "FROM messages WHERE (empty IS NULL OR empty != 1) AND deleted = 0 " + whereClause.toString() + " ORDER BY date DESC"
, args.toArray(EMPTY_STRING_ARRAY)
);
}
@@ -1285,7 +1316,7 @@ public class LocalStore extends Store implements Serializable {
}
Cursor cursor = null;
try {
- cursor = db.rawQuery("SELECT COUNT(*) FROM messages WHERE deleted = 0 and folder_id = ?",
+ cursor = db.rawQuery("SELECT COUNT(*) FROM messages WHERE (empty IS NULL OR empty != 1) AND deleted = 0 and folder_id = ?",
new String[] {
Long.toString(mFolderId)
});
@@ -1901,7 +1932,7 @@ public class LocalStore extends Store implements Serializable {
listener,
LocalFolder.this,
"SELECT " + GET_MESSAGES_COLS
- + "FROM messages WHERE "
+ + "FROM messages WHERE (empty IS NULL OR empty != 1) AND "
+ (includeDeleted ? "" : "deleted = 0 AND ")
+ " folder_id = ? ORDER BY date DESC"
, new String[] {
@@ -1918,6 +1949,75 @@ public class LocalStore extends Store implements Serializable {
}
}
+ public Message[] getThreadedMessages(final MessageRetrievalListener listener)
+ throws MessagingException {
+ try {
+ return database.execute(false, new DbCallback() {
+ @Override
+ public Message[] doDbWork(final SQLiteDatabase db) throws WrappedException,
+ UnavailableStorageException {
+ try {
+ open(OpenMode.READ_WRITE);
+
+ return LocalStore.this.getMessages(
+ listener,
+ LocalFolder.this,
+ "SELECT " +
+ "m.subject, m.sender_list, MAX(m.date), m.uid, m.flags, " +
+ "m.id, m.to_list, m.cc_list, m.bcc_list, m.reply_to_list, " +
+ "m.attachment_count, m.internal_date, m.message_id, " +
+ "m.folder_id, m.preview, m.thread_root, m.thread_parent, " +
+ "m.empty, COUNT(h.id) " +
+ "FROM messages h JOIN messages m " +
+ "ON (h.id = m.thread_root OR h.id = m.id) " +
+ "WHERE h.deleted = 0 AND m.deleted = 0 AND " +
+ "(m.empty IS NULL OR m.empty != 1) AND " +
+ "h.thread_root IS NULL AND h.folder_id = ? " +
+ "GROUP BY h.id",
+ new String[] { Long.toString(mFolderId) });
+ } catch (MessagingException e) {
+ throw new WrappedException(e);
+ }
+ }
+ });
+ } catch (WrappedException e) {
+ throw(MessagingException) e.getCause();
+ }
+ }
+
+ public Message[] getMessagesInThread(final long threadId,
+ final MessageRetrievalListener listener) throws MessagingException {
+ try {
+ return database.execute(false, new DbCallback() {
+ @Override
+ public Message[] doDbWork(final SQLiteDatabase db) throws WrappedException,
+ UnavailableStorageException {
+ try {
+ open(OpenMode.READ_WRITE);
+
+ String threadIdString = Long.toString(threadId);
+
+ return LocalStore.this.getMessages(
+ listener,
+ LocalFolder.this,
+ "SELECT " + GET_MESSAGES_COLS + " " +
+ "FROM messages " +
+ "WHERE deleted = 0 AND (empty IS NULL OR empty != 1) AND " +
+ "(thread_root = ? OR id = ?) AND folder_id = ?",
+ new String[] {
+ threadIdString,
+ threadIdString,
+ Long.toString(mFolderId)
+ });
+ } catch (MessagingException e) {
+ throw new WrappedException(e);
+ }
+ }
+ });
+ } catch (WrappedException e) {
+ throw(MessagingException) e.getCause();
+ }
+ }
@Override
public Message[] getMessages(String[] uids, MessageRetrievalListener listener)
@@ -1975,30 +2075,118 @@ public class LocalStore extends Store implements Serializable {
String oldUID = message.getUid();
- if (K9.DEBUG)
+ if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Updating folder_id to " + lDestFolder.getId() + " for message with UID "
+ message.getUid() + ", id " + lMessage.getId() + " currently in folder " + getName());
+ }
String newUid = K9.LOCAL_UID_PREFIX + UUID.randomUUID().toString();
message.setUid(newUid);
uidMap.put(oldUID, newUid);
- db.execSQL("UPDATE messages " + "SET folder_id = ?, uid = ? " + "WHERE id = ?", new Object[] {
- lDestFolder.getId(),
- message.getUid(),
- lMessage.getId()
- });
+ // Message threading in the target folder
+ ThreadInfo threadInfo = lDestFolder.doMessageThreading(db, message);
+
+ /*
+ * "Move" the message into the new folder and thread structure
+ */
+ long id = lMessage.getId();
+ String[] idArg = new String[] { Long.toString(id) };
+
+ ContentValues cv = new ContentValues();
+ cv.put("folder_id", lDestFolder.getId());
+ cv.put("uid", newUid);
+
+ if (threadInfo.rootId != -1) {
+ cv.put("thread_root", threadInfo.rootId);
+ }
+
+ if (threadInfo.parentId != -1) {
+ cv.put("thread_parent", threadInfo.parentId);
+ }
+
+ db.update("messages", cv, "id = ?", idArg);
+
+ if (threadInfo.id != -1) {
+ String[] oldIdArg =
+ new String[] { Long.toString(threadInfo.id) };
+
+ cv.clear();
+ cv.put("thread_root", id);
+ int x = db.update("messages", cv, "thread_root = ?", oldIdArg);
+
+ cv.clear();
+ cv.put("thread_parent", id);
+ x = db.update("messages", cv, "thread_parent = ?", oldIdArg);
+ }
/*
* Add a placeholder message so we won't download the original
* message again if we synchronize before the remote move is
* complete.
*/
- LocalMessage placeHolder = new LocalMessage(oldUID, LocalFolder.this);
- placeHolder.setFlagInternal(Flag.DELETED, true);
- placeHolder.setFlagInternal(Flag.SEEN, true);
- appendMessages(new Message[] { placeHolder });
+
+ // We need to open this folder to get the folder id
+ open(OpenMode.READ_WRITE);
+
+ cv.clear();
+ cv.put("uid", oldUID);
+ cv.put("flags", serializeFlags(new Flag[] { Flag.DELETED, Flag.SEEN }));
+ cv.put("deleted", 1);
+ cv.put("folder_id", mFolderId);
+
+ String messageId = message.getMessageId();
+ if (messageId != null) {
+ cv.put("message_id", messageId);
+ cv.put("empty", 1);
+
+ long rootId = lMessage.getRootId();
+ if (rootId != -1) {
+ cv.put("thread_root", rootId);
+ }
+
+ long parentId = lMessage.getParentId();
+ if (parentId != -1) {
+ cv.put("thread_parent", parentId);
+ }
+ }
+
+ final long newId;
+ if (threadInfo.id != -1) {
+ // There already existed an empty message in the target folder.
+ // Let's use it as placeholder.
+
+ newId = threadInfo.id;
+
+ db.update("messages", cv, "id = ?",
+ new String[] { Long.toString(newId) });
+ } else {
+ newId = db.insert("messages", null, cv);
+ }
+
+ /*
+ * Replace all "thread links" to the original message with links to
+ * the placeholder message.
+ */
+
+ String[] whereArgs = new String[] {
+ Long.toString(mFolderId),
+ Long.toString(id) };
+
+ // Note: If there was an empty message in the target folder we
+ // already reconnected some messages to point to 'id'. We don't
+ // want to change those links again, so we limit the update
+ // statements below to the source folder.
+ cv.clear();
+ cv.put("thread_root", newId);
+ db.update("messages", cv, "folder_id = ? AND thread_root = ?",
+ whereArgs);
+
+ cv.clear();
+ cv.put("thread_parent", newId);
+ db.update("messages", cv, "folder_id = ? AND thread_parent = ?",
+ whereArgs);
}
} catch (MessagingException e) {
throw new WrappedException(e);
@@ -2076,6 +2264,70 @@ public class LocalStore extends Store implements Serializable {
}
}
+ /**
+ * Get the (database) ID of the message with the specified message ID.
+ *
+ * @param db
+ * A {@link SQLiteDatabase} instance to access the database.
+ * @param messageId
+ * The message ID to search for.
+ * @param onlyEmptyMessages
+ * {@code true} if only "empty messages" (placeholders for threading) should be
+ * searched
+ *
+ * @return If exactly one message with the specified message ID was found, the database ID
+ * of this message; {@code -1} otherwise.
+ */
+ private long getDatabaseIdByMessageId(SQLiteDatabase db, String messageId,
+ boolean onlyEmptyMessages) {
+ long id = -1;
+
+ Cursor cursor = db.query("messages",
+ new String[] { "id" },
+ (onlyEmptyMessages) ?
+ "empty=1 AND folder_id=? AND message_id=?" :
+ "folder_id=? AND message_id=?",
+ new String[] { Long.toString(mFolderId), messageId },
+ null, null, null);
+
+ if (cursor != null) {
+ try {
+ if (cursor.getCount() == 1) {
+ cursor.moveToFirst();
+ id = cursor.getLong(0);
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+ return id;
+ }
+
+ private ThreadInfo getThreadInfo(SQLiteDatabase db, String messageId) {
+ Cursor cursor = db.query("messages",
+ new String[] { "id", "thread_root", "thread_parent" },
+ "folder_id=? AND message_id=?",
+ new String[] { Long.toString(mFolderId), messageId },
+ null, null, null);
+
+ if (cursor != null) {
+ try {
+ if (cursor.getCount() == 1) {
+ cursor.moveToFirst();
+ long id = cursor.getLong(0);
+ long rootId = (cursor.isNull(1)) ? -1 : cursor.getLong(1);
+ long parentId = (cursor.isNull(2)) ? -1 : cursor.getLong(2);
+
+ return new ThreadInfo(id, messageId, rootId, parentId);
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+ return null;
+ }
/**
* The method differs slightly from the contract; If an incoming message already has a uid
@@ -2143,6 +2395,17 @@ public class LocalStore extends Store implements Serializable {
deleteAttachments(message.getUid());
}
+ long rootId = -1;
+ long parentId = -1;
+
+ if (oldMessageId == -1) {
+ // This is a new message. Do the message threading.
+ ThreadInfo threadInfo = doMessageThreading(db, message);
+ oldMessageId = threadInfo.id;
+ rootId = threadInfo.rootId;
+ parentId = threadInfo.parentId;
+ }
+
boolean isDraft = (message.getHeader(K9.IDENTITY_HEADER) != null);
List attachments;
@@ -2176,7 +2439,7 @@ public class LocalStore extends Store implements Serializable {
cv.put("sender_list", Address.pack(message.getFrom()));
cv.put("date", message.getSentDate() == null
? System.currentTimeMillis() : message.getSentDate().getTime());
- cv.put("flags", Utility.combine(message.getFlags(), ',').toUpperCase(Locale.US));
+ cv.put("flags", serializeFlags(message.getFlags()));
cv.put("deleted", message.isSet(Flag.DELETED) ? 1 : 0);
cv.put("folder_id", mFolderId);
cv.put("to_list", Address.pack(message.getRecipients(RecipientType.TO)));
@@ -2190,11 +2453,20 @@ public class LocalStore extends Store implements Serializable {
cv.put("internal_date", message.getInternalDate() == null
? System.currentTimeMillis() : message.getInternalDate().getTime());
cv.put("mime_type", message.getMimeType());
+ cv.put("empty", 0);
String messageId = message.getMessageId();
if (messageId != null) {
cv.put("message_id", messageId);
}
+
+ if (rootId != -1) {
+ cv.put("thread_root", rootId);
+ }
+ if (parentId != -1) {
+ cv.put("thread_parent", parentId);
+ }
+
long messageUid;
if (oldMessageId == -1) {
@@ -2270,7 +2542,7 @@ public class LocalStore extends Store implements Serializable {
message.getSentDate() == null ? System
.currentTimeMillis() : message.getSentDate()
.getTime(),
- Utility.combine(message.getFlags(), ',').toUpperCase(Locale.US),
+ serializeFlags(message.getFlags()),
mFolderId,
Address.pack(message
.getRecipients(RecipientType.TO)),
@@ -2337,7 +2609,8 @@ public class LocalStore extends Store implements Serializable {
db.execSQL("UPDATE messages " + "SET flags = ? " + " WHERE id = ?",
new Object[]
- { Utility.combine(appendedFlags.toArray(), ',').toUpperCase(Locale.US), id });
+ { serializeFlags(appendedFlags.toArray(EMPTY_FLAG_ARRAY)), id });
+
return null;
}
});
@@ -2581,7 +2854,7 @@ public class LocalStore extends Store implements Serializable {
Message[] messages = LocalStore.this.getMessages(
null,
this,
- "SELECT " + GET_MESSAGES_COLS + "FROM messages WHERE " + whereClause,
+ "SELECT " + GET_MESSAGES_COLS + "FROM messages WHERE (empty IS NULL OR empty != 1) AND (" + whereClause + ")",
params);
for (Message message : messages) {
@@ -2883,6 +3156,95 @@ public class LocalStore extends Store implements Serializable {
});
}
+ private String serializeFlags(Flag[] flags) {
+ return Utility.combine(flags, ',').toUpperCase(Locale.US);
+ }
+
+ private ThreadInfo doMessageThreading(SQLiteDatabase db, Message message)
+ throws MessagingException {
+ long rootId = -1;
+ long parentId = -1;
+
+ String messageId = message.getMessageId();
+
+ // If there's already an empty message in the database, update that
+ long id = getDatabaseIdByMessageId(db, messageId, true);
+
+ // Get the message IDs from the "References" header line
+ String[] referencesArray = message.getHeader("References");
+ List messageIds = null;
+ if (referencesArray != null && referencesArray.length > 0) {
+ messageIds = Utility.extractMessageIds(referencesArray[0]);
+ }
+
+ // Append the first message ID from the "In-Reply-To" header line
+ String[] inReplyToArray = message.getHeader("In-Reply-To");
+ String inReplyTo = null;
+ if (inReplyToArray != null && inReplyToArray.length > 0) {
+ inReplyTo = Utility.extractMessageId(inReplyToArray[0]);
+ if (inReplyTo != null) {
+ if (messageIds == null) {
+ messageIds = new ArrayList(1);
+ messageIds.add(inReplyTo);
+ } else if (!messageIds.contains(inReplyTo)) {
+ messageIds.add(inReplyTo);
+ }
+ }
+ }
+
+ if (messageIds == null) {
+ // This is not a reply, nothing to do for us.
+ return new ThreadInfo(id, messageId, -1, -1);
+ }
+
+ for (String reference : messageIds) {
+ ThreadInfo threadInfo = getThreadInfo(db, reference);
+
+ if (threadInfo == null) {
+ // Create placeholder message
+ ContentValues cv = new ContentValues();
+ cv.put("message_id", reference);
+ cv.put("folder_id", mFolderId);
+ cv.put("empty", 1);
+
+ if (rootId != -1) {
+ cv.put("thread_root", rootId);
+ }
+ if (parentId != -1) {
+ cv.put("thread_parent", parentId);
+ }
+
+ parentId = db.insert("messages", null, cv);
+ if (rootId == -1) {
+ rootId = parentId;
+ }
+ } else {
+ if (rootId != -1 && threadInfo.rootId == -1 && rootId != threadInfo.id) {
+ // We found an existing root container that is not
+ // the root of our current path (References).
+ // Connect it to the current parent.
+
+ // Let all children know who's the new root
+ ContentValues cv = new ContentValues();
+ cv.put("thread_root", rootId);
+ db.update("messages", cv, "thread_root=?",
+ new String[] { Long.toString(threadInfo.id) });
+
+ // Connect the message to the current parent
+ cv.put("thread_parent", parentId);
+ db.update("messages", cv, "id=?",
+ new String[] { Long.toString(threadInfo.id) });
+ } else {
+ rootId = (threadInfo.rootId == -1) ? threadInfo.id : threadInfo.rootId;
+ }
+ parentId = threadInfo.id;
+ }
+ }
+
+ //TODO: set in-reply-to "link" even if one already exists
+
+ return new ThreadInfo(id, messageId, rootId, parentId);
+ }
}
public static class LocalTextBody extends TextBody {
@@ -2930,6 +3292,11 @@ public class LocalStore extends Store implements Serializable {
private boolean mHeadersLoaded = false;
private boolean mMessageDirty = false;
+ private long mRootId;
+ private long mParentId;
+ private boolean mEmpty;
+ private int mThreadCount = 0;
+
public LocalMessage() {
}
@@ -2983,6 +3350,15 @@ public class LocalStore extends Store implements Serializable {
f.open(LocalFolder.OpenMode.READ_WRITE);
this.mFolder = f;
}
+
+ mRootId = (cursor.isNull(15)) ? -1 : cursor.getLong(15);
+ mParentId = (cursor.isNull(16)) ? -1 : cursor.getLong(16);
+
+ mEmpty = (cursor.isNull(17)) ? false : (cursor.getInt(17) == 1);
+
+ if (cursor.getColumnCount() > 18) {
+ mThreadCount = cursor.getInt(18);
+ }
}
/**
@@ -3236,13 +3612,26 @@ public class LocalStore extends Store implements Serializable {
try {
database.execute(true, new DbCallback() {
@Override
- public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
- db.execSQL("UPDATE messages SET " + "deleted = 1," + "subject = NULL, "
- + "sender_list = NULL, " + "date = NULL, " + "to_list = NULL, "
- + "cc_list = NULL, " + "bcc_list = NULL, " + "preview = NULL, "
- + "html_content = NULL, " + "text_content = NULL, "
- + "reply_to_list = NULL " + "WHERE id = ?", new Object[]
- { mId });
+ public Void doDbWork(final SQLiteDatabase db) throws WrappedException,
+ UnavailableStorageException {
+ String[] idArg = new String[] { Long.toString(mId) };
+
+ ContentValues cv = new ContentValues();
+ cv.put("deleted", 1);
+ cv.put("empty", 1);
+ cv.putNull("subject");
+ cv.putNull("sender_list");
+ cv.putNull("date");
+ cv.putNull("to_list");
+ cv.putNull("cc_list");
+ cv.putNull("bcc_list");
+ cv.putNull("preview");
+ cv.putNull("html_content");
+ cv.putNull("text_content");
+ cv.putNull("reply_to_list");
+
+ db.update("messages", cv, "id = ?", idArg);
+
/*
* Delete all of the message's attachments to save space.
* We do this explicit deletion here because we're not deleting the record
@@ -3253,8 +3642,8 @@ public class LocalStore extends Store implements Serializable {
} catch (MessagingException e) {
throw new WrappedException(e);
}
- db.execSQL("DELETE FROM attachments WHERE message_id = ?", new Object[]
- { mId });
+
+ db.delete("attachments", "message_id = ?", idArg);
return null;
}
});
@@ -3262,12 +3651,12 @@ public class LocalStore extends Store implements Serializable {
throw(MessagingException) e.getCause();
}
((LocalFolder)mFolder).deleteHeaders(mId);
-
-
}
/*
* Completely remove a message from the local database
+ *
+ * TODO: document how this updates the thread structure
*/
@Override
public void destroy() throws MessagingException {
@@ -3277,9 +3666,102 @@ public class LocalStore extends Store implements Serializable {
public Void doDbWork(final SQLiteDatabase db) throws WrappedException,
UnavailableStorageException {
try {
+ LocalFolder localFolder = (LocalFolder) mFolder;
+
updateFolderCountsOnFlag(Flag.X_DESTROYED, true);
- ((LocalFolder) mFolder).deleteAttachments(mId);
- db.execSQL("DELETE FROM messages WHERE id = ?", new Object[] { mId });
+ localFolder.deleteAttachments(mId);
+
+ String id = Long.toString(mId);
+
+ // Check if this message has children in the thread hierarchy
+ Cursor cursor = db.query("messages", new String[] { "COUNT(id)" },
+ "thread_root = ? OR thread_parent = ?",
+ new String[] {id, id},
+ null, null, null);
+
+ try {
+ if (cursor.moveToFirst() && cursor.getLong(0) > 0) {
+ // Make the message an empty message
+ ContentValues cv = new ContentValues();
+ cv.put("id", mId);
+ cv.put("folder_id", localFolder.getId());
+ cv.put("deleted", 0);
+ cv.put("message_id", getMessageId());
+ cv.put("empty", 1);
+
+ if (getRootId() != -1) {
+ cv.put("thread_root", getRootId());
+ }
+
+ if (getParentId() != -1) {
+ cv.put("thread_parent", getParentId());
+ }
+
+ db.replace("messages", null, cv);
+
+ // Nothing else to do
+ return null;
+ }
+ } finally {
+ cursor.close();
+ }
+
+ long parentId = getParentId();
+
+ // Check if 'parentId' is empty
+ cursor = db.query("messages", new String[] { "id" },
+ "id = ? AND empty = 1",
+ new String[] { Long.toString(parentId) },
+ null, null, null);
+
+ try {
+ if (cursor.getCount() == 0) {
+ // If the message isn't empty we skip the loop below
+ parentId = -1;
+ }
+ } finally {
+ cursor.close();
+ }
+
+ while (parentId != -1) {
+ String parentIdString = Long.toString(parentId);
+
+ // Get the parent of the message 'parentId'
+ cursor = db.query("messages", new String[] { "thread_parent" },
+ "id = ? AND empty = 1",
+ new String[] { parentIdString },
+ null, null, null);
+ try {
+ if (cursor.moveToFirst() && !cursor.isNull(0)) {
+ parentId = cursor.getLong(0);
+ } else {
+ parentId = -1;
+ }
+ } finally {
+ cursor.close();
+ }
+
+ // Check if (the old) 'parentId' has any children
+ cursor = db.query("messages", new String[] { "COUNT(id)" },
+ "thread_parent = ? AND id != ?",
+ new String[] { parentIdString, id },
+ null, null, null);
+
+ try {
+ if (cursor.moveToFirst() && cursor.getLong(0) == 0) {
+ // If it has no children we can remove it
+ db.delete("messages", "id = ?",
+ new String[] { parentIdString });
+ } else {
+ break;
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+ // Remove the placeholder message
+ db.delete("messages", "id = ?", new String[] { id });
} catch (MessagingException e) {
throw new WrappedException(e);
}
@@ -3385,6 +3867,22 @@ public class LocalStore extends Store implements Serializable {
return message;
}
+
+ public long getRootId() {
+ return mRootId;
+ }
+
+ public long getParentId() {
+ return mParentId;
+ }
+
+ public boolean isEmpty() {
+ return mEmpty;
+ }
+
+ public int getThreadCount() {
+ return mThreadCount;
+ }
}
public static class LocalAttachmentBodyPart extends MimeBodyPart {
@@ -3449,4 +3947,19 @@ public class LocalStore extends Store implements Serializable {
return mUri;
}
}
+
+ static class ThreadInfo {
+ public final long id;
+ public final String messageId;
+ public final long rootId;
+ public final long parentId;
+
+ public ThreadInfo(long id, String messageId, long rootId, long parentId) {
+ this.id = id;
+ this.messageId = messageId;
+ this.rootId = rootId;
+ this.parentId = parentId;
+ }
+ }
+
}
From 16d23260339be29794ab8b353e81926bba6ad2ff Mon Sep 17 00:00:00 2001
From: cketti
Date: Wed, 10 Oct 2012 03:02:36 +0200
Subject: [PATCH 02/74] Nasty hack to retain threads in MessageListFragment
when polling
---
.../fsck/k9/activity/MessageInfoHolder.java | 1 +
.../fsck/k9/fragment/MessageListFragment.java | 46 +++++++++++++++++--
src/com/fsck/k9/helper/MessageHelper.java | 3 ++
src/com/fsck/k9/mail/store/LocalStore.java | 22 +++++++--
4 files changed, 65 insertions(+), 7 deletions(-)
diff --git a/src/com/fsck/k9/activity/MessageInfoHolder.java b/src/com/fsck/k9/activity/MessageInfoHolder.java
index 4669daec6..56db97ced 100644
--- a/src/com/fsck/k9/activity/MessageInfoHolder.java
+++ b/src/com/fsck/k9/activity/MessageInfoHolder.java
@@ -24,6 +24,7 @@ public class MessageInfoHolder {
public boolean selected;
public String account;
public String uri;
+ public int threadCount;
// Empty constructor for comparison
public MessageInfoHolder() {
diff --git a/src/com/fsck/k9/fragment/MessageListFragment.java b/src/com/fsck/k9/fragment/MessageListFragment.java
index 87510e5a2..4bd5ba88f 100644
--- a/src/com/fsck/k9/fragment/MessageListFragment.java
+++ b/src/com/fsck/k9/fragment/MessageListFragment.java
@@ -687,7 +687,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
final MessageInfoHolder message = (MessageInfoHolder) parent.getItemAtPosition(position);
if (mSelectedCount > 0) {
toggleMessageSelect(message);
- } else if (((LocalMessage) message.message).getThreadCount() > 1) {
+ } else if (message.threadCount > 1) {
Folder folder = message.message.getFolder();
long rootId = ((LocalMessage) message.message).getRootId();
mFragmentListener.showThread(folder.getAccount(), folder.getName(), rootId);
@@ -1767,7 +1767,36 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
FolderInfoHolder folderInfoHolder = new FolderInfoHolder(
getActivity(), messageFolder, messageAccount);
messageHelper.populate(m, message, folderInfoHolder, messageAccount);
- messagesToAdd.add(m);
+
+ if (verifyAgainstSearch) {
+ LocalMessage localMessage = (LocalMessage) message;
+
+ if (mThreadId != -1) {
+ if (localMessage.getRootId() == mThreadId ||
+ localMessage.getId() == mThreadId) {
+ messagesToAdd.add(m);
+ }
+ } else if (mThreadViewEnabled) {
+ long threadId = localMessage.getRootId();
+ if (threadId == -1) {
+ threadId = localMessage.getId();
+ }
+
+ MessageInfoHolder threadPlaceHolder = getThread(threadId);
+ if (threadPlaceHolder == null) {
+ messagesToAdd.add(m);
+ } else if (m.compareDate.after(threadPlaceHolder.compareDate)) {
+ messagesToRemove.add(threadPlaceHolder);
+ messagesToAdd.add(m);
+ } else {
+ threadPlaceHolder.threadCount = m.threadCount;
+ }
+ } else {
+ messagesToAdd.add(m);
+ }
+ } else {
+ messagesToAdd.add(m);
+ }
} else {
if (mQueryString != null) {
if (verifyAgainstSearch) {
@@ -1876,6 +1905,17 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
return null;
}
+ private MessageInfoHolder getThread(long threadId) {
+ for (MessageInfoHolder holder : mMessages) {
+ LocalMessage localMessage = (LocalMessage) holder.message;
+ if (localMessage.getId() == threadId || localMessage.getRootId() == threadId) {
+ return holder;
+ }
+ }
+
+ return null;
+ }
+
public FolderInfoHolder getFolder(String folder, Account account) {
LocalFolder local_folder = null;
try {
@@ -2056,7 +2096,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
subject = message.message.getSubject();
}
- int threadCount = ((LocalMessage) message.message).getThreadCount();
+ int threadCount = message.threadCount;
if (threadCount > 1) {
holder.threadCount.setText(Integer.toString(threadCount));
holder.threadCount.setVisibility(View.VISIBLE);
diff --git a/src/com/fsck/k9/helper/MessageHelper.java b/src/com/fsck/k9/helper/MessageHelper.java
index 9c2abb491..ae708f224 100644
--- a/src/com/fsck/k9/helper/MessageHelper.java
+++ b/src/com/fsck/k9/helper/MessageHelper.java
@@ -17,6 +17,7 @@ import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Message.RecipientType;
+import com.fsck.k9.mail.store.LocalStore.LocalMessage;
import com.fsck.k9.helper.DateFormatter;
public class MessageHelper {
@@ -86,6 +87,8 @@ public class MessageHelper {
target.account = account.getUuid();
target.uri = "email://messages/" + account.getAccountNumber() + "/" + message.getFolder().getName() + "/" + message.getUid();
+ target.threadCount = ((LocalMessage) message).getThreadCount();
+
} catch (MessagingException me) {
Log.w(K9.LOG_TAG, "Unable to load message info", me);
}
diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java
index fe69d8af3..8c42b6f48 100644
--- a/src/com/fsck/k9/mail/store/LocalStore.java
+++ b/src/com/fsck/k9/mail/store/LocalStore.java
@@ -93,6 +93,10 @@ public class LocalStore extends Store implements Serializable {
"subject, sender_list, date, uid, flags, id, to_list, cc_list, "
+ "bcc_list, reply_to_list, attachment_count, internal_date, message_id, folder_id, preview, thread_root, thread_parent, empty ";
+ static private String GET_MESSAGES_COLS_PREFIX =
+ "m.subject, m.sender_list, m.date, m.uid, m.flags, m.id, m.to_list, m.cc_list, " +
+ "m.bcc_list, m.reply_to_list, m.attachment_count, m.internal_date, m.message_id, " +
+ "m.folder_id, m.preview, m.thread_root, m.thread_parent, m.empty ";
static private String GET_FOLDER_COLS = "id, name, unread_count, visible_limit, last_updated, status, push_state, last_pushed, flagged_count, integrate, top_group, poll_class, push_class, display_class";
@@ -1891,13 +1895,23 @@ public class LocalStore extends Store implements Serializable {
try {
cursor = db.rawQuery(
- "SELECT "
- + GET_MESSAGES_COLS
- + "FROM messages WHERE uid = ? AND folder_id = ?",
+ "SELECT " +
+ GET_MESSAGES_COLS_PREFIX + ", COUNT(c.id) " +
+ "FROM messages m LEFT JOIN messages c " +
+ "ON (" +
+ "(" +
+ "m.thread_root IN (c.thread_root, c.id)" +
+ " OR " +
+ "(m.thread_root IS NULL AND m.id IN (c.thread_root, c.id))" +
+ ") AND c.empty != 1) " +
+ "WHERE m.uid = ? AND m.folder_id = ?",
new String[] {
message.getUid(), Long.toString(mFolderId)
});
- if (!cursor.moveToNext()) {
+
+ // Note: Because of the COUNT(c.id) we will always get one result
+ // row even if nothing was found. So we check if 'id' is NULL
+ if (!cursor.moveToNext() || cursor.isNull(0)) {
return null;
}
message.populateFromGetMessageCursor(cursor);
From 3413cbebf54dd4c5f2b8738174dc9c81294d014a Mon Sep 17 00:00:00 2001
From: cketti
Date: Wed, 10 Oct 2012 04:30:19 +0200
Subject: [PATCH 03/74] Worked around NPEs when the activity has been detached
---
.../fsck/k9/fragment/MessageListFragment.java | 31 +++++++++++--------
1 file changed, 18 insertions(+), 13 deletions(-)
diff --git a/src/com/fsck/k9/fragment/MessageListFragment.java b/src/com/fsck/k9/fragment/MessageListFragment.java
index 4bd5ba88f..d035460fd 100644
--- a/src/com/fsck/k9/fragment/MessageListFragment.java
+++ b/src/com/fsck/k9/fragment/MessageListFragment.java
@@ -410,6 +410,9 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
private long mThreadId;
+ private Context mContext;
+
+
/**
* This class is used to run operations that modify UI elements in the UI thread.
*
@@ -634,7 +637,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
}
private void setupFormats() {
- mTimeFormat = android.text.format.DateFormat.getTimeFormat(getActivity());
+ mTimeFormat = android.text.format.DateFormat.getTimeFormat(mContext);
}
private DateFormat getTimeFormat() {
@@ -700,6 +703,8 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
public void onAttach(Activity activity) {
super.onAttach(activity);
+ mContext = activity.getApplicationContext();
+
try {
mFragmentListener = (MessageListFragmentListener) activity;
} catch (ClassCastException e) {
@@ -1449,7 +1454,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
@Override
public void remoteSearchStarted(Account acct, String folder) {
mHandler.progress(true);
- mHandler.updateFooter(getString(R.string.remote_search_sending_query), true);
+ mHandler.updateFooter(mContext.getString(R.string.remote_search_sending_query), true);
}
@@ -1458,7 +1463,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
mHandler.progress(false);
if (extraResults != null && extraResults.size() > 0) {
mExtraSearchResults = extraResults;
- mHandler.updateFooter(String.format(getString(R.string.load_more_messages_fmt), acct.getRemoteSearchNumResults()), false);
+ mHandler.updateFooter(String.format(mContext.getString(R.string.load_more_messages_fmt), acct.getRemoteSearchNumResults()), false);
} else {
mHandler.updateFooter("", false);
}
@@ -1470,9 +1475,9 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
public void remoteSearchServerQueryComplete(Account account, String folderName, int numResults) {
mHandler.progress(true);
if (account != null && account.getRemoteSearchNumResults() != 0 && numResults > account.getRemoteSearchNumResults()) {
- mHandler.updateFooter(getString(R.string.remote_search_downloading_limited, account.getRemoteSearchNumResults(), numResults), true);
+ mHandler.updateFooter(mContext.getString(R.string.remote_search_downloading_limited, account.getRemoteSearchNumResults(), numResults), true);
} else {
- mHandler.updateFooter(getString(R.string.remote_search_downloading, numResults), true);
+ mHandler.updateFooter(mContext.getString(R.string.remote_search_downloading, numResults), true);
}
mFragmentListener.setMessageListProgress(Window.PROGRESS_START);
}
@@ -1765,7 +1770,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
if (updateForMe(account, folderName)) {
m = new MessageInfoHolder();
FolderInfoHolder folderInfoHolder = new FolderInfoHolder(
- getActivity(), messageFolder, messageAccount);
+ mContext, messageFolder, messageAccount);
messageHelper.populate(m, message, folderInfoHolder, messageAccount);
if (verifyAgainstSearch) {
@@ -1804,7 +1809,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
} else {
m = new MessageInfoHolder();
FolderInfoHolder folderInfoHolder = new FolderInfoHolder(
- getActivity(), messageFolder, messageAccount);
+ mContext, messageFolder, messageAccount);
messageHelper.populate(m, message, folderInfoHolder,
messageAccount);
messagesToAdd.add(m);
@@ -1813,7 +1818,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
}
} else {
m.dirty = false; // as we reload the message, unset its dirty flag
- FolderInfoHolder folderInfoHolder = new FolderInfoHolder(getActivity(),
+ FolderInfoHolder folderInfoHolder = new FolderInfoHolder(mContext,
messageFolder, account);
messageHelper.populate(m, message, folderInfoHolder, account);
needsSort = true;
@@ -1921,7 +1926,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
try {
LocalStore localStore = account.getLocalStore();
local_folder = localStore.getFolder(folder);
- return new FolderInfoHolder(getActivity(), local_folder, account);
+ return new FolderInfoHolder(mContext, local_folder, account);
} catch (Exception e) {
Log.e(K9.LOG_TAG, "getFolder(" + folder + ") goes boom: ", e);
return null;
@@ -2241,17 +2246,17 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
if (mCurrentFolder != null && mAccount != null) {
if (mCurrentFolder.loading) {
final boolean showProgress = true;
- updateFooter(getString(R.string.status_loading_more), showProgress);
+ updateFooter(mContext.getString(R.string.status_loading_more), showProgress);
} else {
String message;
if (!mCurrentFolder.lastCheckFailed) {
if (mAccount.getDisplayCount() == 0) {
- message = getString(R.string.message_list_load_more_messages_action);
+ message = mContext.getString(R.string.message_list_load_more_messages_action);
} else {
- message = String.format(getString(R.string.load_more_messages_fmt), mAccount.getDisplayCount());
+ message = String.format(mContext.getString(R.string.load_more_messages_fmt), mAccount.getDisplayCount());
}
} else {
- message = getString(R.string.status_loading_more_failed);
+ message = mContext.getString(R.string.status_loading_more_failed);
}
final boolean showProgress = false;
updateFooter(message, showProgress);
From c2bb45171212048d867548be20c09dc6854d2ab7 Mon Sep 17 00:00:00 2001
From: cketti
Date: Wed, 10 Oct 2012 04:32:48 +0200
Subject: [PATCH 04/74] Don't display thread count in thread view
---
src/com/fsck/k9/fragment/MessageListFragment.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/com/fsck/k9/fragment/MessageListFragment.java b/src/com/fsck/k9/fragment/MessageListFragment.java
index d035460fd..1fab56b86 100644
--- a/src/com/fsck/k9/fragment/MessageListFragment.java
+++ b/src/com/fsck/k9/fragment/MessageListFragment.java
@@ -2102,7 +2102,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
}
int threadCount = message.threadCount;
- if (threadCount > 1) {
+ if (mThreadId == -1 && threadCount > 1) {
holder.threadCount.setText(Integer.toString(threadCount));
holder.threadCount.setVisibility(View.VISIBLE);
} else {
From 611bae3fb4e8e572303b16c98a897493753a6381 Mon Sep 17 00:00:00 2001
From: Sander Bogaert
Date: Fri, 12 Oct 2012 12:30:26 +0200
Subject: [PATCH 05/74] Created search package to hold the framework and moved
over the SearchSpecification class ( refactor ).
---
src/com/fsck/k9/K9.java | 4 ++--
src/com/fsck/k9/SearchAccount.java | 1 +
src/com/fsck/k9/activity/Accounts.java | 2 +-
src/com/fsck/k9/activity/FolderList.java | 2 +-
src/com/fsck/k9/activity/LauncherShortcuts.java | 2 +-
src/com/fsck/k9/activity/MessageList.java | 2 +-
src/com/fsck/k9/controller/MessagingController.java | 2 +-
src/com/fsck/k9/preferences/GlobalSettings.java | 4 ++--
src/com/fsck/k9/{ => search}/SearchSpecification.java | 2 +-
9 files changed, 11 insertions(+), 10 deletions(-)
rename src/com/fsck/k9/{ => search}/SearchSpecification.java (91%)
diff --git a/src/com/fsck/k9/K9.java b/src/com/fsck/k9/K9.java
index e6caa157e..ef0f21fb0 100644
--- a/src/com/fsck/k9/K9.java
+++ b/src/com/fsck/k9/K9.java
@@ -579,8 +579,8 @@ public class K9 extends Application {
public static void loadPrefs(Preferences prefs) {
SharedPreferences sprefs = prefs.getPreferences();
- DEBUG = sprefs.getBoolean("enableDebugLogging", false);
- DEBUG_SENSITIVE = sprefs.getBoolean("enableSensitiveLogging", false);
+ DEBUG = sprefs.getBoolean("enableDebugLogging", true);
+ DEBUG_SENSITIVE = sprefs.getBoolean("enableSensitiveLogging", true);
mAnimations = sprefs.getBoolean("animations", true);
mGesturesEnabled = sprefs.getBoolean("gesturesEnabled", false);
mUseVolumeKeysForNavigation = sprefs.getBoolean("useVolumeKeysForNavigation", false);
diff --git a/src/com/fsck/k9/SearchAccount.java b/src/com/fsck/k9/SearchAccount.java
index bf2f25570..97e2092ee 100644
--- a/src/com/fsck/k9/SearchAccount.java
+++ b/src/com/fsck/k9/SearchAccount.java
@@ -6,6 +6,7 @@ import java.util.UUID;
import android.content.Context;
import com.fsck.k9.mail.Flag;
+import com.fsck.k9.search.SearchSpecification;
/**
* This is a meta-Account that represents one or more accounts with filters on them. The filter specification
diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java
index f45fe52e5..cbaaaa8ef 100644
--- a/src/com/fsck/k9/activity/Accounts.java
+++ b/src/com/fsck/k9/activity/Accounts.java
@@ -63,7 +63,6 @@ import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
import com.fsck.k9.SearchAccount;
-import com.fsck.k9.SearchSpecification;
import com.fsck.k9.activity.misc.ExtendedAsyncTask;
import com.fsck.k9.activity.misc.NonConfigurationInstance;
import com.fsck.k9.activity.setup.AccountSettings;
@@ -80,6 +79,7 @@ import com.fsck.k9.mail.Transport;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.mail.store.WebDavStore;
+import com.fsck.k9.search.SearchSpecification;
import com.fsck.k9.view.ColorChip;
import com.fsck.k9.preferences.SettingsExporter;
import com.fsck.k9.preferences.SettingsImportExportException;
diff --git a/src/com/fsck/k9/activity/FolderList.java b/src/com/fsck/k9/activity/FolderList.java
index fa2deb5c9..dc034c4ba 100644
--- a/src/com/fsck/k9/activity/FolderList.java
+++ b/src/com/fsck/k9/activity/FolderList.java
@@ -52,7 +52,6 @@ import com.fsck.k9.FontSizes;
import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
-import com.fsck.k9.SearchSpecification;
import com.fsck.k9.activity.FolderList.FolderListAdapter.FolderListFilter;
import com.fsck.k9.activity.misc.ActionBarNavigationSpinner;
import com.fsck.k9.activity.setup.AccountSettings;
@@ -68,6 +67,7 @@ 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.LocalFolder;
+import com.fsck.k9.search.SearchSpecification;
import com.fsck.k9.service.MailService;
/**
diff --git a/src/com/fsck/k9/activity/LauncherShortcuts.java b/src/com/fsck/k9/activity/LauncherShortcuts.java
index 415689057..add1d837a 100644
--- a/src/com/fsck/k9/activity/LauncherShortcuts.java
+++ b/src/com/fsck/k9/activity/LauncherShortcuts.java
@@ -7,7 +7,7 @@ import android.os.Parcelable;
import com.fsck.k9.Account;
import com.fsck.k9.BaseAccount;
import com.fsck.k9.R;
-import com.fsck.k9.SearchSpecification;
+import com.fsck.k9.search.SearchSpecification;
public class LauncherShortcuts extends AccountList {
@Override
diff --git a/src/com/fsck/k9/activity/MessageList.java b/src/com/fsck/k9/activity/MessageList.java
index eb7c91ee4..d984efdbe 100644
--- a/src/com/fsck/k9/activity/MessageList.java
+++ b/src/com/fsck/k9/activity/MessageList.java
@@ -24,7 +24,6 @@ import com.fsck.k9.Account.SortType;
import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
-import com.fsck.k9.SearchSpecification;
import com.fsck.k9.activity.misc.SwipeGestureDetector.OnSwipeGestureListener;
import com.fsck.k9.activity.setup.AccountSettings;
import com.fsck.k9.activity.setup.FolderSettings;
@@ -35,6 +34,7 @@ import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.store.StorageManager;
+import com.fsck.k9.search.SearchSpecification;
/**
diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java
index e4cf48734..bc67559ae 100644
--- a/src/com/fsck/k9/controller/MessagingController.java
+++ b/src/com/fsck/k9/controller/MessagingController.java
@@ -40,7 +40,6 @@ import com.fsck.k9.K9.Intents;
import com.fsck.k9.NotificationSetting;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
-import com.fsck.k9.SearchSpecification;
import com.fsck.k9.activity.FolderList;
import com.fsck.k9.activity.MessageList;
import com.fsck.k9.helper.NotificationBuilder;
@@ -70,6 +69,7 @@ import com.fsck.k9.mail.store.LocalStore.LocalMessage;
import com.fsck.k9.mail.store.LocalStore.PendingCommand;
import com.fsck.k9.mail.store.UnavailableAccountException;
import com.fsck.k9.mail.store.UnavailableStorageException;
+import com.fsck.k9.search.SearchSpecification;
/**
diff --git a/src/com/fsck/k9/preferences/GlobalSettings.java b/src/com/fsck/k9/preferences/GlobalSettings.java
index e49face93..019391003 100644
--- a/src/com/fsck/k9/preferences/GlobalSettings.java
+++ b/src/com/fsck/k9/preferences/GlobalSettings.java
@@ -64,10 +64,10 @@ public class GlobalSettings {
new V(1, new DateFormatSetting(DateFormatter.DEFAULT_FORMAT))
));
s.put("enableDebugLogging", Settings.versions(
- new V(1, new BooleanSetting(false))
+ new V(1, new BooleanSetting(true))
));
s.put("enableSensitiveLogging", Settings.versions(
- new V(1, new BooleanSetting(false))
+ new V(1, new BooleanSetting(true))
));
s.put("fontSizeAccountDescription", Settings.versions(
new V(1, new FontSizeSetting(FontSizes.SMALL))
diff --git a/src/com/fsck/k9/SearchSpecification.java b/src/com/fsck/k9/search/SearchSpecification.java
similarity index 91%
rename from src/com/fsck/k9/SearchSpecification.java
rename to src/com/fsck/k9/search/SearchSpecification.java
index a1f1de0a8..e08032ff5 100644
--- a/src/com/fsck/k9/SearchSpecification.java
+++ b/src/com/fsck/k9/search/SearchSpecification.java
@@ -1,5 +1,5 @@
-package com.fsck.k9;
+package com.fsck.k9.search;
import com.fsck.k9.mail.Flag;
From 5c6552cbf3462c3f57b7f092dd4198bda4b5ca52 Mon Sep 17 00:00:00 2001
From: Sander Bogaert
Date: Fri, 12 Oct 2012 13:22:54 +0200
Subject: [PATCH 06/74] Adding the 3 core classes for the search framework.
ConditionsTreeNode, LocalSearch and SearchSpecification.
---
.../fsck/k9/search/ConditionsTreeNode.java | 365 +++++++++++++++++
src/com/fsck/k9/search/LocalSearch.java | 374 ++++++++++++++++++
.../fsck/k9/search/SearchSpecification.java | 191 ++++++++-
3 files changed, 917 insertions(+), 13 deletions(-)
create mode 100644 src/com/fsck/k9/search/ConditionsTreeNode.java
create mode 100644 src/com/fsck/k9/search/LocalSearch.java
diff --git a/src/com/fsck/k9/search/ConditionsTreeNode.java b/src/com/fsck/k9/search/ConditionsTreeNode.java
new file mode 100644
index 000000000..fbb31a7b9
--- /dev/null
+++ b/src/com/fsck/k9/search/ConditionsTreeNode.java
@@ -0,0 +1,365 @@
+package com.fsck.k9.search;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Stack;
+
+import android.database.Cursor;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.fsck.k9.search.SearchSpecification.ATTRIBUTE;
+import com.fsck.k9.search.SearchSpecification.SEARCHFIELD;
+import com.fsck.k9.search.SearchSpecification.SearchCondition;
+
+/**
+ * This class stores search conditions. It's basically a boolean expression binary tree.
+ * The output will be SQL queries ( obtained by traversing inorder ).
+ *
+ * TODO removing conditions from the tree
+ * TODO implement NOT as a node again
+ *
+ * @author dzan
+ */
+public class ConditionsTreeNode implements Parcelable{
+
+ public enum OPERATOR {
+ AND, OR, CONDITION;
+ }
+
+ public ConditionsTreeNode mLeft;
+ public ConditionsTreeNode mRight;
+ public ConditionsTreeNode mParent;
+
+ /*
+ * If mValue isn't CONDITION then mCondition contains a real
+ * condition, otherwise it's null.
+ */
+ public OPERATOR mValue;
+ public SearchCondition mCondition;
+
+ /*
+ * Used for storing and retrieving the tree to/from the database.
+ * The algorithm is called "modified preorder tree traversal".
+ */
+ public int mLeftMPTTMarker;
+ public int mRightMPTTMarker;
+
+
+ ///////////////////////////////////////////////////////////////
+ // Static Helpers to restore a tree from a database cursor
+ ///////////////////////////////////////////////////////////////
+ /**
+ * Builds a condition tree starting from a database cursor. The cursor
+ * should point to rows representing the nodes of the tree.
+ *
+ * @param cursor Cursor pointing to the first of a bunch or rows. Each rows
+ * should contains 1 tree node.
+ * @return A condition tree.
+ */
+ public static ConditionsTreeNode buildTreeFromDB(Cursor cursor) {
+ Stack stack = new Stack();
+ ConditionsTreeNode tmp = null;
+
+ // root node
+ if (cursor.moveToFirst()) {
+ tmp = buildNodeFromRow(cursor);
+ stack.push(tmp);
+ }
+
+ // other nodes
+ while (cursor.moveToNext()) {
+ tmp = buildNodeFromRow(cursor);
+ if (tmp.mRightMPTTMarker < stack.peek().mRightMPTTMarker ){
+ stack.peek().mLeft = tmp;
+ stack.push(tmp);
+ } else {
+ while (stack.peek().mRightMPTTMarker < tmp.mRightMPTTMarker) {
+ stack.pop();
+ }
+ stack.peek().mRight = tmp;
+ }
+ }
+ return tmp;
+ }
+
+ /**
+ * Converts a single database row to a single condition node.
+ *
+ * @param cursor Cursor pointing to the row we want to convert.
+ * @return A single ConditionsTreeNode
+ */
+ private static ConditionsTreeNode buildNodeFromRow(Cursor cursor) {
+ ConditionsTreeNode result = null;
+ SearchCondition condition = null;
+
+ OPERATOR tmpValue = ConditionsTreeNode.OPERATOR.valueOf(cursor.getString(5));
+
+ if (tmpValue == OPERATOR.CONDITION) {
+ condition = new SearchCondition(SEARCHFIELD.valueOf(cursor.getString(0)),
+ ATTRIBUTE.valueOf(cursor.getString(2)), cursor.getString(1));
+ }
+
+ result = new ConditionsTreeNode(condition);
+ result.mValue = tmpValue;
+ result.mLeftMPTTMarker = cursor.getInt(3);
+ result.mRightMPTTMarker = cursor.getInt(4);
+
+ return result;
+ }
+
+
+ ///////////////////////////////////////////////////////////////
+ // Constructors
+ ///////////////////////////////////////////////////////////////
+ public ConditionsTreeNode(SearchCondition condition) {
+ mParent = null;
+ mCondition = condition;
+ mValue = OPERATOR.CONDITION;
+ }
+
+ public ConditionsTreeNode(ConditionsTreeNode parent, OPERATOR op) {
+ mParent = parent;
+ mValue = op;
+ mCondition = null;
+ }
+
+
+ ///////////////////////////////////////////////////////////////
+ // Public modifiers
+ ///////////////////////////////////////////////////////////////
+ /**
+ * Adds the expression as the second argument of an AND
+ * clause to this node.
+ *
+ * @param expr Expression to 'AND' with.
+ * @return New top AND node.
+ * @throws Exception
+ */
+ public ConditionsTreeNode and(ConditionsTreeNode expr) throws Exception {
+ return add(expr, OPERATOR.AND);
+ }
+
+ /**
+ * Adds the expression as the second argument of an OR
+ * clause to this node.
+ *
+ * @param expr Expression to 'OR' with.
+ * @return New top OR node.
+ * @throws Exception
+ */
+ public ConditionsTreeNode or(ConditionsTreeNode expr) throws Exception {
+ return add(expr, OPERATOR.OR);
+ }
+
+ /**
+ * This applies the MPTT labeling to the subtree of which this node
+ * is the root node.
+ *
+ * For a description on MPTT see:
+ * http://www.sitepoint.com/hierarchical-data-database-2/
+ */
+ public void applyMPTTLabel() {
+ applyMPTTLabel(1);
+ }
+
+
+ ///////////////////////////////////////////////////////////////
+ // Public accessors
+ ///////////////////////////////////////////////////////////////
+ /**
+ * Returns the condition stored in this node.
+ * @return Condition stored in the node.
+ */
+ public SearchCondition getCondition() {
+ return mCondition;
+ }
+
+
+ /**
+ * This will traverse the tree inorder and call toString recursively resulting
+ * in a valid SQL where clause.
+ */
+ @Override
+ public String toString() {
+ return (mLeft == null ? "" : "(" + mLeft + ")")
+ + " " + ( mCondition == null ? mValue.name() : mCondition ) + " "
+ + (mRight == null ? "" : "(" + mRight + ")") ;
+ }
+
+ /**
+ * Get a set of all the leaves in the tree.
+ * @return Set of all the leaves.
+ */
+ public HashSet getLeafSet() {
+ HashSet leafSet = new HashSet();
+ return getLeafSet(leafSet);
+ }
+
+ /**
+ * Returns a list of all the nodes in the subtree of which this node
+ * is the root. The list contains the nodes in a pre traversal order.
+ *
+ * @return List of all nodes in subtree in preorder.
+ */
+ public List preorder() {
+ ArrayList result = new ArrayList();
+ Stack stack = new Stack();
+ stack.push(this);
+
+ while(!stack.isEmpty()) {
+ ConditionsTreeNode current = stack.pop( );
+ if( current.mLeft != null ) stack.push( current.mLeft );
+ if( current.mRight != null ) stack.push( current.mRight );
+ result.add(current);
+ }
+
+ return result;
+ }
+
+
+ ///////////////////////////////////////////////////////////////
+ // Private class logic
+ ///////////////////////////////////////////////////////////////
+ /**
+ * Adds two new ConditionTreeNodes, one for the operator and one for the
+ * new condition. The current node will end up on the same level as the
+ * one provided in the arguments, they will be siblings. Their common
+ * parent node will be one containing the operator provided in the arguments.
+ * The method will update all the required references so the tree ends up in
+ * a valid state.
+ *
+ * This method only supports node arguments with a null parent node.
+ *
+ * @param Node to add.
+ * @param Operator that will connect the new node with this one.
+ * @return New parent node, containing the operator.
+ * @throws Exception Throws when the provided new node does not have a null parent.
+ */
+ private ConditionsTreeNode add(ConditionsTreeNode node, OPERATOR op) throws Exception{
+ if (node.mParent != null) {
+ throw new Exception("Can only add new expressions from root node down.");
+ }
+
+ ConditionsTreeNode tmpNode = new ConditionsTreeNode(mParent, op);
+ tmpNode.mLeft = this;
+ tmpNode.mRight = node;
+
+ if (mParent != null) {
+ mParent.updateChild(this, tmpNode);
+ }
+ this.mParent = tmpNode;
+
+ node.mParent = tmpNode;
+
+ return tmpNode;
+ }
+
+ /**
+ * Helper method that replaces a child of the current node with a new node.
+ * If the provided old child node was the left one, left will be replaced with
+ * the new one. Same goes for the right one.
+ *
+ * @param oldChild Old child node to be replaced.
+ * @param newChild New child node.
+ */
+ private void updateChild(ConditionsTreeNode oldChild, ConditionsTreeNode newChild) {
+ // we can compare objects id's because this is the desired behaviour in this case
+ if (mLeft == oldChild) {
+ mLeft = newChild;
+ } else if (mRight == oldChild) {
+ mRight = newChild;
+ }
+ }
+
+ /**
+ * Recursive function to gather all the leaves in the subtree of which
+ * this node is the root.
+ *
+ * @param leafSet Leafset that's being built.
+ * @return Set of leaves being completed.
+ */
+ private HashSet getLeafSet(HashSet leafSet) {
+ // if we ended up in a leaf, add ourself and return
+ if (mLeft == null && mRight == null) {
+ leafSet.add(this);
+ return leafSet;
+ // we didn't end up in a leaf
+ } else {
+ if (mLeft != null) {
+ mLeft.getLeafSet(leafSet);
+ }
+
+ if (mRight != null) {
+ mRight.getLeafSet(leafSet);
+ }
+ return leafSet;
+ }
+ }
+
+ /**
+ * This applies the MPTT labeling to the subtree of which this node
+ * is the root node.
+ *
+ * For a description on MPTT see:
+ * http://www.sitepoint.com/hierarchical-data-database-2/
+ */
+ private int applyMPTTLabel(int label) {
+ mLeftMPTTMarker = label;
+ if (mLeft != null){
+ label = mLeft.applyMPTTLabel(label += 1);
+ }
+ if (mRight != null){
+ label = mRight.applyMPTTLabel(label += 1);
+ }
+ ++label;
+ mRightMPTTMarker = label;
+ return label;
+ }
+
+
+ ///////////////////////////////////////////////////////////////
+ // Parcelable
+ //
+ // This whole class has to be parcelable because it's passed
+ // on through intents.
+ ///////////////////////////////////////////////////////////////
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mValue.ordinal());
+ dest.writeParcelable(mCondition, flags);
+ dest.writeParcelable(mLeft, flags);
+ dest.writeParcelable(mRight, flags);
+ }
+
+ public static final Parcelable.Creator CREATOR
+ = new Parcelable.Creator() {
+ public ConditionsTreeNode createFromParcel(Parcel in) {
+ return new ConditionsTreeNode(in);
+ }
+
+ public ConditionsTreeNode[] newArray(int size) {
+ return new ConditionsTreeNode[size];
+ }
+ };
+
+ private ConditionsTreeNode(Parcel in) {
+ mValue = OPERATOR.values()[in.readInt()];
+ mCondition = in.readParcelable(ConditionsTreeNode.class.getClassLoader());
+ mLeft = in.readParcelable(ConditionsTreeNode.class.getClassLoader());
+ mRight = in.readParcelable(ConditionsTreeNode.class.getClassLoader());
+ mParent = null;
+ if (mLeft != null) {
+ mLeft.mParent = this;
+ }
+ if (mRight != null) {
+ mRight.mParent = this;
+ }
+ }
+}
diff --git a/src/com/fsck/k9/search/LocalSearch.java b/src/com/fsck/k9/search/LocalSearch.java
new file mode 100644
index 000000000..48ca3ca7c
--- /dev/null
+++ b/src/com/fsck/k9/search/LocalSearch.java
@@ -0,0 +1,374 @@
+package com.fsck.k9.search;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.fsck.k9.mail.Flag;
+
+/**
+ * This class represents a local search.
+
+ * Removing conditions could be done through matching there unique id in the leafset and then
+ * removing them from the tree.
+ *
+ * @author dzan
+ *
+ * TODO implement a complete addAllowedFolder method
+ * TODO conflicting conditions check on add
+ * TODO duplicate condition checking?
+ * TODO assign each node a unique id that's used to retrieve it from the leaveset and remove.
+ *
+ */
+
+public class LocalSearch implements SearchSpecification {
+
+ private String mName;
+ private boolean mPredefined;
+
+ // since the uuid isn't in the message table it's not in the tree neither
+ private HashSet mAccountUuids = new HashSet();
+ private ConditionsTreeNode mConditions = null;
+ private HashSet mLeafSet = new HashSet();
+
+
+ ///////////////////////////////////////////////////////////////
+ // Constructors
+ ///////////////////////////////////////////////////////////////
+ /**
+ * Use this only if the search won't be saved. Saved searches need
+ * a name!
+ */
+ public LocalSearch(){}
+
+ /**
+ *
+ * @param name
+ */
+ public LocalSearch(String name) {
+ this.mName = name;
+ }
+
+ /**
+ * Use this constructor when you know what you'r doing. Normally it's only used
+ * when restoring these search objects from the database.
+ *
+ * @param name Name of the search
+ * @param searchConditions SearchConditions, may contains flags and folders
+ * @param accounts Relative Account's uuid's
+ * @param predefined Is this a predefined search or a user created one?
+ */
+ protected LocalSearch(String name, ConditionsTreeNode searchConditions,
+ String accounts, boolean predefined) {
+ this(name);
+ mConditions = searchConditions;
+ mPredefined = predefined;
+ mLeafSet = new HashSet();
+ if (mConditions != null) {
+ mLeafSet.addAll(mConditions.getLeafSet());
+ }
+
+ // initialize accounts
+ if (accounts != null) {
+ for (String account : accounts.split(",")) {
+ mAccountUuids.add(account);
+ }
+ } else {
+ // impossible but still not unrecoverable
+ }
+ }
+
+
+ ///////////////////////////////////////////////////////////////
+ // Public manipulation methods
+ ///////////////////////////////////////////////////////////////
+ /**
+ * Sets the name of the saved search. If one existed it will
+ * be overwritten.
+ *
+ * @param name Name to be set.
+ */
+ public void setName(String name) {
+ this.mName = name;
+ }
+
+ /**
+ * Add a new account to the search. When no accounts are
+ * added manually we search all accounts on the device.
+ *
+ * @param uuid Uuid of the account to be added.
+ */
+ public void addAccountUuid(String uuid) {
+ if (uuid.equals(ALL_ACCOUNTS)) {
+ mAccountUuids.clear();
+ }
+ mAccountUuids.add(uuid);
+ }
+
+ /**
+ * Adds all the account uuids in the provided array to
+ * be matched by the seach.
+ *
+ * @param accountUuids
+ */
+ public void addAccountUuids(String[] accountUuids) {
+ for (String acc : accountUuids) {
+ addAccountUuid(acc);
+ }
+ }
+
+ /**
+ * Removes an account UUID from the current search.
+ *
+ * @param uuid Account UUID to remove.
+ * @return True if removed, false otherwise.
+ */
+ public boolean removeAccountUuid(String uuid) {
+ return mAccountUuids.remove(uuid);
+ }
+
+ /**
+ * Adds the provided node as the second argument of an AND
+ * clause to this node.
+ *
+ * @param field Message table field to match against.
+ * @param string Value to look for.
+ * @param contains Attribute to use when matching.
+ *
+ * @throws IllegalConditionException
+ */
+ public void and(SEARCHFIELD field, String value, ATTRIBUTE attribute) {
+ and(new SearchCondition(field, attribute, value));
+ }
+
+ /**
+ * Adds the provided condition as the second argument of an AND
+ * clause to this node.
+ *
+ * @param condition Condition to 'AND' with.
+ * @return New top AND node, new root.
+ */
+ public ConditionsTreeNode and(SearchCondition condition) {
+ ConditionsTreeNode tmp = new ConditionsTreeNode(condition);
+ return and(tmp);
+ }
+
+ /**
+ * Adds the provided node as the second argument of an AND
+ * clause to this node.
+ *
+ * @param node Node to 'AND' with.
+ * @return New top AND node, new root.
+ */
+ public ConditionsTreeNode and(ConditionsTreeNode node) {
+ try {
+ mLeafSet.add(node);
+
+ if (mConditions == null) {
+ mConditions = node;
+ return node;
+ }
+
+ mConditions = mConditions.and(node);
+ return mConditions;
+ } catch (Exception e) {
+ // IMPOSSIBLE!
+ return null;
+ }
+ }
+
+ /**
+ * Adds the provided condition as the second argument of an OR
+ * clause to this node.
+ *
+ * @param condition Condition to 'OR' with.
+ * @return New top OR node, new root.
+ */
+ public ConditionsTreeNode or(SearchCondition condition) {
+ ConditionsTreeNode tmp = new ConditionsTreeNode(condition);
+ return or(tmp);
+ }
+
+ /**
+ * Adds the provided node as the second argument of an OR
+ * clause to this node.
+ *
+ * @param node Node to 'OR' with.
+ * @return New top OR node, new root.
+ */
+ public ConditionsTreeNode or(ConditionsTreeNode node) {
+ try {
+ mLeafSet.add(node);
+
+ if (mConditions == null) {
+ mConditions = node;
+ return node;
+ }
+
+ mConditions = mConditions.or(node);
+ return mConditions;
+ } catch (Exception e) {
+ // IMPOSSIBLE!
+ return null;
+ }
+ }
+
+ /**
+ * Add all the flags to this node as required flags. The
+ * provided flags will be combined using AND with the root.
+ *
+ * @param requiredFlags Array of required flags.
+ */
+ public void allRequiredFlags(Flag[] requiredFlags) {
+ if (requiredFlags != null) {
+ for (Flag f : requiredFlags) {
+ and(new SearchCondition(SEARCHFIELD.FLAG, ATTRIBUTE.CONTAINS, f.name()));
+ }
+ }
+ }
+
+ /**
+ * Add all the flags to this node as forbidden flags. The
+ * provided flags will be combined using AND with the root.
+ *
+ * @param forbiddenFlags Array of forbidden flags.
+ */
+ public void allForbiddenFlags(Flag[] forbiddenFlags) {
+ if (forbiddenFlags != null) {
+ for (Flag f : forbiddenFlags) {
+ and(new SearchCondition(SEARCHFIELD.FLAG, ATTRIBUTE.NOT_CONTAINS, f.name()));
+ }
+ }
+ }
+
+ /**
+ * TODO
+ * FOR NOW: And the folder with the root.
+ *
+ * Add the folder as another folder to search in. The folder
+ * will be added AND to the root if no 'folder subtree' was found.
+ * Otherwise the folder will be added OR to that tree.
+ *
+ * @param name Name of the folder to add.
+ */
+ public void addAllowedFolder(String name) {
+ /*
+ * TODO find folder sub-tree
+ * - do and on root of it & rest of search
+ * - do or between folder nodes
+ */
+ and(new SearchCondition(SEARCHFIELD.FOLDER, ATTRIBUTE.EQUALS, name));
+ }
+
+ /*
+ * TODO make this more advanced!
+ * This is a temporarely solution that does NOT WORK for
+ * real searches because of possible extra conditions to a folder requirement.
+ */
+ public List getFolderNames() {
+ ArrayList results = new ArrayList();
+ for (ConditionsTreeNode node : mLeafSet) {
+ if (node.mCondition.field == SEARCHFIELD.FOLDER
+ && node.mCondition.attribute == ATTRIBUTE.EQUALS) {
+ results.add(node.mCondition.value);
+ }
+ }
+ return results;
+ }
+
+ /**
+ * Gets the leafset of the related condition tree.
+ *
+ * @return All the leaf conditions as a set.
+ */
+ public Set getLeafSet() {
+ return mLeafSet;
+ }
+
+ ///////////////////////////////////////////////////////////////
+ // Public accesor methods
+ ///////////////////////////////////////////////////////////////
+ /**
+ * Returns the name of the saved search.
+ *
+ * @return Name of the search.
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Checks if this search was hard coded and shipped with K-9
+ *
+ * @return True is search was shipped with K-9
+ */
+ public boolean isPredefined() {
+ return mPredefined;
+ }
+
+ /**
+ * Returns all the account uuids that this search will try to
+ * match against.
+ *
+ * @return Array of account uuids.
+ */
+ @Override
+ public String[] getAccountUuids() {
+ if (mAccountUuids.size() == 0) {
+ return new String[] {SearchSpecification.ALL_ACCOUNTS};
+ }
+
+ String[] tmp = new String[mAccountUuids.size()];
+ mAccountUuids.toArray(tmp);
+ return tmp;
+ }
+
+ /**
+ * Get the condition tree.
+ *
+ * @return The root node of the related conditions tree.
+ */
+ @Override
+ public ConditionsTreeNode getConditions() {
+ return mConditions;
+ }
+
+ ///////////////////////////////////////////////////////////////
+ // Parcelable
+ ///////////////////////////////////////////////////////////////
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mName);
+ dest.writeByte((byte) (mPredefined ? 1 : 0));
+ dest.writeStringList(new ArrayList(mAccountUuids));
+ dest.writeParcelable(mConditions, flags);
+ }
+
+ public static final Parcelable.Creator CREATOR
+ = new Parcelable.Creator() {
+ public LocalSearch createFromParcel(Parcel in) {
+ return new LocalSearch(in);
+ }
+
+ public LocalSearch[] newArray(int size) {
+ return new LocalSearch[size];
+ }
+ };
+
+ public LocalSearch(Parcel in) {
+ mName = in.readString();
+ mPredefined = in.readByte() == 1;
+ mAccountUuids.addAll(in.createStringArrayList());
+ mConditions = in.readParcelable(LocalSearch.class.getClassLoader());
+ mLeafSet = mConditions.getLeafSet();
+ }
+}
\ No newline at end of file
diff --git a/src/com/fsck/k9/search/SearchSpecification.java b/src/com/fsck/k9/search/SearchSpecification.java
index e08032ff5..3c422c64e 100644
--- a/src/com/fsck/k9/search/SearchSpecification.java
+++ b/src/com/fsck/k9/search/SearchSpecification.java
@@ -1,19 +1,184 @@
-
package com.fsck.k9.search;
-import com.fsck.k9.mail.Flag;
-
-public interface SearchSpecification {
-
- public Flag[] getRequiredFlags();
-
- public Flag[] getForbiddenFlags();
-
- public boolean isIntegrate();
-
- public String getQuery();
+import android.os.Parcel;
+import android.os.Parcelable;
+public interface SearchSpecification extends Parcelable {
+
+ /**
+ * Get all the uuids of accounts this search acts on.
+ * @return Array of uuids.
+ */
public String[] getAccountUuids();
+
+ /**
+ * Returns the search's name if it was named.
+ * @return Name of the search.
+ */
+ public String getName();
+
+ /**
+ * Returns the root node of the condition tree accompanying
+ * the search.
+ *
+ * @return Root node of conditions tree.
+ */
+ public ConditionsTreeNode getConditions();
+
+ /*
+ * Some meta names for certain conditions.
+ */
+ public static final String ALL_ACCOUNTS = "allAccounts";
+ public static final String GENERIC_INBOX_NAME = "genericInboxName";
+
+ ///////////////////////////////////////////////////////////////
+ // ATTRIBUTE enum
+ ///////////////////////////////////////////////////////////////
+ public enum ATTRIBUTE {
+ CONTAINS(false), EQUALS(false), STARTSWITH(false), ENDSWITH(false),
+ NOT_CONTAINS(true), NOT_EQUALS(true), NOT_STARTSWITH(true), NOT_ENDSWITH(true);
+
+ private boolean mNegation;
+
+ private ATTRIBUTE(boolean negation) {
+ this.mNegation = negation;
+ }
+
+ public String formQuery(String value) {
+ String queryPart = "";
+
+ switch (this) {
+ case NOT_CONTAINS:
+ case CONTAINS:
+ queryPart = "'%"+value+"%'";
+ break;
+ case NOT_EQUALS:
+ case EQUALS:
+ queryPart = "'"+value+"'";
+ break;
+ case NOT_STARTSWITH:
+ case STARTSWITH:
+ queryPart = "'%"+value+"'";
+ break;
+ case NOT_ENDSWITH:
+ case ENDSWITH:
+ queryPart = "'"+value+"%'";
+ break;
+ default: queryPart = "'"+value+"'";
+ }
+
+ return (mNegation ? " NOT LIKE " : " LIKE ") + queryPart;
+ }
+ };
+
+ ///////////////////////////////////////////////////////////////
+ // SEARCHFIELD enum
+ ///////////////////////////////////////////////////////////////
+ /*
+ * Using an enum in order to have more robust code. Users ( & coders )
+ * are prevented from passing illegal fields. No database overhead
+ * when invalid fields passed.
+ *
+ * By result, only the fields in here are searchable.
+ *
+ * Fields not in here at this moment ( and by effect not searchable ):
+ * id, html_content, internal_date, message_id,
+ * preview, mime_type
+ *
+ */
+ public enum SEARCHFIELD {
+ SUBJECT("subject"), DATE("date"), UID("uid"), FLAG("flags"),
+ SENDER("sender_list"), TO("to_list"), CC("cc_list"), FOLDER("folder_id"),
+ BCC("bcc_list"), REPLY_TO("reply_to_list"), MESSAGE("text_content"),
+ ATTACHMENT_COUNT("attachment_count"), DELETED("deleted");
- public String[] getFolderNames();
+ private String dbName;
+
+ private SEARCHFIELD(String dbName) {
+ this.dbName = dbName;
+ }
+
+ public String getDatabaseName() {
+ return dbName;
+ }
+ }
+
+
+ ///////////////////////////////////////////////////////////////
+ // SearchCondition class
+ ///////////////////////////////////////////////////////////////
+ /**
+ * This class represents 1 value for a certain search field. One
+ * value consists of three things:
+ * an attribute: equals, starts with, contains,...
+ * a searchfield: date, flags, sender, subject,...
+ * a value: "apple", "jesse",..
+ *
+ * @author dzan
+ */
+ public class SearchCondition implements Parcelable{
+ public String value;
+ public ATTRIBUTE attribute;
+ public SEARCHFIELD field;
+
+ public SearchCondition(SEARCHFIELD field, ATTRIBUTE attribute, String value) {
+ this.value = value;
+ this.attribute = attribute;
+ this.field = field;
+ }
+
+ private SearchCondition(Parcel in) {
+ this.value = in.readString();
+ this.attribute = ATTRIBUTE.values()[in.readInt()];
+ this.field = SEARCHFIELD.values()[in.readInt()];
+ }
+
+ public String toHumanString() {
+ return field.toString() + attribute.toString();
+ }
+
+ @Override
+ public String toString() {
+ return field.getDatabaseName() + attribute.formQuery(value);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof SearchCondition) {
+ SearchCondition tmp = (SearchCondition) o;
+ if (tmp.attribute == attribute
+ && tmp.value.equals(value)
+ && tmp.field == field) {
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(value);
+ dest.writeInt(attribute.ordinal());
+ dest.writeInt(field.ordinal());
+ }
+
+ public static final Parcelable.Creator CREATOR
+ = new Parcelable.Creator() {
+ public SearchCondition createFromParcel(Parcel in) {
+ return new SearchCondition(in);
+ }
+
+ public SearchCondition[] newArray(int size) {
+ return new SearchCondition[size];
+ }
+ };
+ }
}
\ No newline at end of file
From d27f909600219f5442f1650eb2b74700ab1af292 Mon Sep 17 00:00:00 2001
From: Sander Bogaert
Date: Sat, 13 Oct 2012 08:53:00 -0400
Subject: [PATCH 07/74] Add new search logic to the MessagingController and
LocalStore classes.
---
.../k9/controller/MessagingController.java | 160 ++++--------------
src/com/fsck/k9/mail/store/LocalStore.java | 155 ++++++++---------
2 files changed, 98 insertions(+), 217 deletions(-)
diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java
index bc67559ae..dfb638828 100644
--- a/src/com/fsck/k9/controller/MessagingController.java
+++ b/src/com/fsck/k9/controller/MessagingController.java
@@ -69,6 +69,7 @@ import com.fsck.k9.mail.store.LocalStore.LocalMessage;
import com.fsck.k9.mail.store.LocalStore.PendingCommand;
import com.fsck.k9.mail.store.UnavailableAccountException;
import com.fsck.k9.mail.store.UnavailableStorageException;
+import com.fsck.k9.search.LocalSearch;
import com.fsck.k9.search.SearchSpecification;
@@ -619,136 +620,39 @@ public class MessagingController implements Runnable {
}
}
- public void searchLocalMessages(SearchSpecification searchSpecification, final Message[] messages, final MessagingListener listener) {
- searchLocalMessages(searchSpecification.getAccountUuids(), searchSpecification.getFolderNames(), messages,
- searchSpecification.getQuery(), searchSpecification.isIntegrate(), searchSpecification.getRequiredFlags(), searchSpecification.getForbiddenFlags(), listener);
- }
-
-
/**
* Find all messages in any local account which match the query 'query'
* @throws MessagingException
*/
- public void searchLocalMessages(final String[] accountUuids, final String[] folderNames, final Message[] messages, final String query, final boolean integrate,
- final Flag[] requiredFlags, final Flag[] forbiddenFlags, final MessagingListener listener) {
- if (K9.DEBUG) {
- Log.i(K9.LOG_TAG, "searchLocalMessages ("
- + "accountUuids=" + Utility.combine(accountUuids, ',')
- + ", folderNames = " + Utility.combine(folderNames, ',')
- + ", messages.size() = " + (messages != null ? messages.length : -1)
- + ", query = " + query
- + ", integrate = " + integrate
- + ", requiredFlags = " + Utility.combine(requiredFlags, ',')
- + ", forbiddenFlags = " + Utility.combine(forbiddenFlags, ',')
- + ")");
- }
-
+ public void searchLocalMessages(final LocalSearch search, final MessagingListener listener) {
threadPool.execute(new Runnable() {
@Override
public void run() {
- searchLocalMessagesSynchronous(accountUuids, folderNames, messages, query, integrate, requiredFlags, forbiddenFlags, listener);
+ searchLocalMessagesSynchronous(search, listener);
}
});
}
- public void searchLocalMessagesSynchronous(final String[] accountUuids, final String[] folderNames, final Message[] messages, final String query, final boolean integrate, final Flag[] requiredFlags, final Flag[] forbiddenFlags, final MessagingListener listener) {
-
+
+ public void searchLocalMessagesSynchronous(final LocalSearch search, final MessagingListener listener) {
final AccountStats stats = new AccountStats();
- final Set accountUuidsSet = new HashSet();
- if (accountUuids != null) {
- accountUuidsSet.addAll(Arrays.asList(accountUuids));
- }
- final Preferences prefs = Preferences.getPreferences(mApplication.getApplicationContext());
- List foldersToSearch = null;
- boolean displayableOnly = false;
- boolean noSpecialFolders = true;
- for (final Account account : prefs.getAvailableAccounts()) {
- if (accountUuids != null && !accountUuidsSet.contains(account.getUuid())) {
- continue;
- }
-
- if (accountUuids != null && accountUuidsSet.contains(account.getUuid())) {
- displayableOnly = true;
- noSpecialFolders = true;
- } else if (!integrate && folderNames == null) {
- Account.Searchable searchableFolders = account.getSearchableFolders();
- switch (searchableFolders) {
- case NONE:
- continue;
- case DISPLAYABLE:
- displayableOnly = true;
- break;
-
- }
- }
- List messagesToSearch = null;
- if (messages != null) {
- messagesToSearch = new LinkedList();
- for (Message message : messages) {
- if (message.getFolder().getAccount().getUuid().equals(account.getUuid())) {
- messagesToSearch.add(message);
- }
- }
- if (messagesToSearch.isEmpty()) {
- continue;
- }
- }
- if (listener != null) {
- listener.listLocalMessagesStarted(account, null);
- }
-
- if (integrate || displayableOnly || folderNames != null || noSpecialFolders) {
- List tmpFoldersToSearch = new LinkedList();
- try {
- LocalStore store = account.getLocalStore();
- List extends Folder > folders = store.getPersonalNamespaces(false);
- Set folderNameSet = null;
- if (folderNames != null) {
- folderNameSet = new HashSet();
- folderNameSet.addAll(Arrays.asList(folderNames));
- }
- for (Folder folder : folders) {
- LocalFolder localFolder = (LocalFolder)folder;
- boolean include = true;
- folder.refresh(prefs);
- String localFolderName = localFolder.getName();
- if (integrate) {
- include = localFolder.isIntegrate();
- } else {
- if (folderNameSet != null) {
- if (!folderNameSet.contains(localFolderName))
-
- {
- include = false;
- }
- }
- // Never exclude the INBOX (see issue 1817)
- else if (noSpecialFolders && !localFolderName.equalsIgnoreCase(account.getInboxFolderName()) &&
- !localFolderName.equals(account.getArchiveFolderName()) && account.isSpecialFolder(localFolderName)) {
- include = false;
- } else if (displayableOnly && modeMismatch(account.getFolderDisplayMode(), folder.getDisplayClass())) {
- include = false;
- }
- }
-
- if (include) {
- tmpFoldersToSearch.add(localFolder);
- }
- }
- if (tmpFoldersToSearch.size() < 1) {
- continue;
- }
- foldersToSearch = tmpFoldersToSearch;
- } catch (MessagingException me) {
- Log.e(K9.LOG_TAG, "Unable to restrict search folders in Account " + account.getDescription() + ", searching all", me);
- addErrorMessage(account, null, me);
- }
-
- }
-
+ final HashSet uuidSet = new HashSet(Arrays.asList(search.getAccountUuids()));
+ Account[] accounts = Preferences.getPreferences(mApplication.getApplicationContext()).getAccounts();
+ boolean allAccounts = uuidSet.contains(SearchSpecification.ALL_ACCOUNTS);
+
+ // for every account we want to search do the query in the localstore
+ for (final Account account : accounts) {
+
+ if (!allAccounts && !uuidSet.contains(account.getUuid())) {
+ continue;
+ }
+
+ // Collecting statistics of the search result
MessageRetrievalListener retrievalListener = new MessageRetrievalListener() {
@Override
public void messageStarted(String message, int number, int ofTotal) {}
@Override
+ public void messagesFinished(int number) {}
+ @Override
public void messageFinished(Message message, int number, int ofTotal) {
if (!isMessageSuppressed(message.getFolder().getAccount(), message.getFolder().getName(), message)) {
List messages = new ArrayList();
@@ -760,22 +664,18 @@ public class MessagingController implements Runnable {
listener.listLocalMessagesAddMessages(account, null, messages);
}
}
-
- }
- @Override
- public void messagesFinished(int number) {
-
}
};
-
+
+ // alert everyone the search has started
+ if (listener != null) {
+ listener.listLocalMessagesStarted(account, null);
+ }
+
+ // build and do the query in the localstore
try {
- String[] queryFields = {"html_content", "subject", "sender_list"};
- LocalStore localStore = account.getLocalStore();
- localStore.searchForMessages(retrievalListener, queryFields
- , query, foldersToSearch,
- messagesToSearch == null ? null : messagesToSearch.toArray(EMPTY_MESSAGE_ARRAY),
- requiredFlags, forbiddenFlags);
-
+ LocalStore localStore = account.getLocalStore();
+ localStore.searchForMessages(retrievalListener, search);
} catch (Exception e) {
if (listener != null) {
listener.listLocalMessagesFailed(account, null, e.getMessage());
@@ -786,7 +686,9 @@ public class MessagingController implements Runnable {
listener.listLocalMessagesFinished(account, null);
}
}
- }
+ }
+
+ // publish the total search statistics
if (listener != null) {
listener.searchStats(stats);
}
diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java
index 8c42b6f48..40893169e 100644
--- a/src/com/fsck/k9/mail/store/LocalStore.java
+++ b/src/com/fsck/k9/mail/store/LocalStore.java
@@ -69,6 +69,9 @@ import com.fsck.k9.mail.store.LockableDatabase.DbCallback;
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.search.ConditionsTreeNode;
+import com.fsck.k9.search.LocalSearch;
+import com.fsck.k9.search.SearchSpecification.SEARCHFIELD;
/**
*
@@ -658,6 +661,22 @@ public class LocalStore extends Store implements Serializable {
return new LocalFolder(name);
}
+ private long getFolderId(final String name) throws MessagingException {
+ return database.execute(false, new DbCallback() {
+ @Override
+ public Long doDbWork(final SQLiteDatabase db) {
+ Cursor cursor = null;
+ try {
+ cursor = db.rawQuery("SELECT id FROM folders WHERE name = '" + name + "'", null);
+ cursor.moveToFirst();
+ return cursor.getLong(0);
+ } finally {
+ Utility.closeQuietly(cursor);
+ }
+ }
+ });
+ }
+
// TODO this takes about 260-300ms, seems slow.
@Override
public List extends Folder > getPersonalNamespaces(boolean forceListAll) throws MessagingException {
@@ -890,97 +909,57 @@ public class LocalStore extends Store implements Serializable {
return true;
}
- public Message[] searchForMessages(MessageRetrievalListener listener, String[] queryFields, String queryString,
- List folders, Message[] messages, final Flag[] requiredFlags, final Flag[] forbiddenFlags) throws MessagingException {
- List args = new LinkedList();
-
- StringBuilder whereClause = new StringBuilder();
- if (queryString != null && queryString.length() > 0) {
- boolean anyAdded = false;
- String likeString = "%" + queryString + "%";
- whereClause.append(" AND (");
- for (String queryField : queryFields) {
-
- if (anyAdded) {
- whereClause.append(" OR ");
+ // TODO find beter solution
+ private static boolean isFolderId(String str) {
+ if (str == null) {
+ return false;
+ }
+ int length = str.length();
+ if (length == 0) {
+ return false;
+ }
+ int i = 0;
+ if (str.charAt(0) == '-') {
+ return false;
+ }
+ for (; i < length; i++) {
+ char c = str.charAt(i);
+ if (c <= '/' || c >= ':') {
+ return false;
}
- whereClause.append(queryField).append(" LIKE ? ");
- args.add(likeString);
- anyAdded = true;
- }
-
-
- whereClause.append(" )");
}
- if (folders != null && !folders.isEmpty()) {
- whereClause.append(" AND folder_id in (");
- boolean anyAdded = false;
- for (LocalFolder folder : folders) {
- if (anyAdded) {
- whereClause.append(",");
- }
- anyAdded = true;
- whereClause.append("?");
- args.add(Long.toString(folder.getId()));
- }
- whereClause.append(" )");
- }
- if (messages != null && messages.length > 0) {
- whereClause.append(" AND ( ");
- boolean anyAdded = false;
- for (Message message : messages) {
- if (anyAdded) {
- whereClause.append(" OR ");
- }
- anyAdded = true;
- whereClause.append(" ( uid = ? AND folder_id = ? ) ");
- args.add(message.getUid());
- args.add(Long.toString(((LocalFolder)message.getFolder()).getId()));
- }
- whereClause.append(" )");
- }
- if (forbiddenFlags != null && forbiddenFlags.length > 0) {
- whereClause.append(" AND (");
- boolean anyAdded = false;
- for (Flag flag : forbiddenFlags) {
- if (anyAdded) {
- whereClause.append(" AND ");
- }
- anyAdded = true;
- whereClause.append(" flags NOT LIKE ?");
-
- args.add("%" + flag.toString() + "%");
- }
- whereClause.append(" )");
- }
- if (requiredFlags != null && requiredFlags.length > 0) {
- whereClause.append(" AND (");
- boolean anyAdded = false;
- for (Flag flag : requiredFlags) {
- if (anyAdded) {
- whereClause.append(" OR ");
- }
- anyAdded = true;
- whereClause.append(" flags LIKE ?");
-
- args.add("%" + flag.toString() + "%");
- }
- whereClause.append(" )");
- }
-
- if (K9.DEBUG) {
- Log.v(K9.LOG_TAG, "whereClause = " + whereClause.toString());
- Log.v(K9.LOG_TAG, "args = " + args);
- }
- return getMessages(
- listener,
- null,
- "SELECT "
- + GET_MESSAGES_COLS
- + "FROM messages WHERE (empty IS NULL OR empty != 1) AND deleted = 0 " + whereClause.toString() + " ORDER BY date DESC"
- , args.toArray(EMPTY_STRING_ARRAY)
- );
+ return true;
}
+
+ public Message[] searchForMessages(MessageRetrievalListener retrievalListener,
+ LocalSearch search) throws MessagingException {
+
+ // update some references in the search that have to be bound to this one store
+ for (ConditionsTreeNode node : search.getLeafSet()) {
+ if (node.mCondition.field == SEARCHFIELD.FOLDER) {
+ // TODO find better solution
+ if (isFolderId(node.mCondition.value)) {
+ continue;
+ }
+
+ if (node.mCondition.value.equals(LocalSearch.GENERIC_INBOX_NAME)) {
+ node.mCondition.value = mAccount.getInboxFolderName();
+ }
+ node.mCondition.value = String.valueOf(getFolderId(node.mCondition.value));
+ }
+ }
+
+ // build sql query
+ String sqlQuery = "SELECT " + GET_MESSAGES_COLS + "FROM messages WHERE deleted = 0 "
+ + (search.getConditions() != null ? "AND (" + search.getConditions() + ")" : "") + " ORDER BY date DESC";
+
+ if (K9.DEBUG) {
+ Log.d(K9.LOG_TAG, "Query = " + sqlQuery);
+ }
+
+ return getMessages(retrievalListener, null, sqlQuery, new String[] {});
+ }
+
/*
* Given a query string, actually do the query for the messages and
* call the MessageRetrievalListener for each one
From 442805fe6237d530c8fab9a4af563acee816b877 Mon Sep 17 00:00:00 2001
From: Sander Bogaert
Date: Sat, 13 Oct 2012 09:28:19 -0400
Subject: [PATCH 08/74] Made thread_root a searchable field of the message
table. This can be used to display threads.
---
src/com/fsck/k9/search/SearchSpecification.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/com/fsck/k9/search/SearchSpecification.java b/src/com/fsck/k9/search/SearchSpecification.java
index 3c422c64e..6f6a38fa5 100644
--- a/src/com/fsck/k9/search/SearchSpecification.java
+++ b/src/com/fsck/k9/search/SearchSpecification.java
@@ -90,7 +90,7 @@ public interface SearchSpecification extends Parcelable {
SUBJECT("subject"), DATE("date"), UID("uid"), FLAG("flags"),
SENDER("sender_list"), TO("to_list"), CC("cc_list"), FOLDER("folder_id"),
BCC("bcc_list"), REPLY_TO("reply_to_list"), MESSAGE("text_content"),
- ATTACHMENT_COUNT("attachment_count"), DELETED("deleted");
+ ATTACHMENT_COUNT("attachment_count"), DELETED("deleted"), THREAD_ROOT("thread_root");
private String dbName;
From 9883148b2f48f7854d2871ffc3d827f942f89f81 Mon Sep 17 00:00:00 2001
From: Sander Bogaert
Date: Sat, 13 Oct 2012 10:03:19 -0400
Subject: [PATCH 09/74] Fixed wrong construction of leaf sets and unrightfully
ignored exceptions.
---
src/com/fsck/k9/search/LocalSearch.java | 64 ++++++++++++-------------
1 file changed, 32 insertions(+), 32 deletions(-)
diff --git a/src/com/fsck/k9/search/LocalSearch.java b/src/com/fsck/k9/search/LocalSearch.java
index 48ca3ca7c..6b07459c4 100644
--- a/src/com/fsck/k9/search/LocalSearch.java
+++ b/src/com/fsck/k9/search/LocalSearch.java
@@ -153,8 +153,13 @@ public class LocalSearch implements SearchSpecification {
* @return New top AND node, new root.
*/
public ConditionsTreeNode and(SearchCondition condition) {
- ConditionsTreeNode tmp = new ConditionsTreeNode(condition);
- return and(tmp);
+ try {
+ ConditionsTreeNode tmp = new ConditionsTreeNode(condition);
+ return and(tmp);
+ } catch (Exception e) {
+ // impossible
+ return null;
+ }
}
/**
@@ -163,22 +168,17 @@ public class LocalSearch implements SearchSpecification {
*
* @param node Node to 'AND' with.
* @return New top AND node, new root.
+ * @throws Exception
*/
- public ConditionsTreeNode and(ConditionsTreeNode node) {
- try {
- mLeafSet.add(node);
-
- if (mConditions == null) {
- mConditions = node;
- return node;
- }
-
- mConditions = mConditions.and(node);
- return mConditions;
- } catch (Exception e) {
- // IMPOSSIBLE!
- return null;
+ public ConditionsTreeNode and(ConditionsTreeNode node) throws Exception {
+ mLeafSet.addAll(node.getLeafSet());
+
+ if (mConditions == null) {
+ mConditions = node;
+ return node;
}
+
+ return mConditions.and(node);
}
/**
@@ -189,8 +189,13 @@ public class LocalSearch implements SearchSpecification {
* @return New top OR node, new root.
*/
public ConditionsTreeNode or(SearchCondition condition) {
- ConditionsTreeNode tmp = new ConditionsTreeNode(condition);
- return or(tmp);
+ try {
+ ConditionsTreeNode tmp = new ConditionsTreeNode(condition);
+ return or(tmp);
+ } catch (Exception e) {
+ // impossible
+ return null;
+ }
}
/**
@@ -199,22 +204,17 @@ public class LocalSearch implements SearchSpecification {
*
* @param node Node to 'OR' with.
* @return New top OR node, new root.
+ * @throws Exception
*/
- public ConditionsTreeNode or(ConditionsTreeNode node) {
- try {
- mLeafSet.add(node);
-
- if (mConditions == null) {
- mConditions = node;
- return node;
- }
-
- mConditions = mConditions.or(node);
- return mConditions;
- } catch (Exception e) {
- // IMPOSSIBLE!
- return null;
+ public ConditionsTreeNode or(ConditionsTreeNode node) throws Exception {
+ mLeafSet.addAll(node.getLeafSet());
+
+ if (mConditions == null) {
+ mConditions = node;
+ return node;
}
+
+ return mConditions.or(node);
}
/**
From bdfc9d685283b99ecd7cc76502401d621c8a4865 Mon Sep 17 00:00:00 2001
From: Sander Bogaert
Date: Sat, 13 Oct 2012 10:08:39 -0400
Subject: [PATCH 10/74] Added two convenience methods to avoid dealing with
exceptions all the time.
---
.../fsck/k9/search/ConditionsTreeNode.java | 36 +++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/src/com/fsck/k9/search/ConditionsTreeNode.java b/src/com/fsck/k9/search/ConditionsTreeNode.java
index fbb31a7b9..650755a7d 100644
--- a/src/com/fsck/k9/search/ConditionsTreeNode.java
+++ b/src/com/fsck/k9/search/ConditionsTreeNode.java
@@ -141,6 +141,24 @@ public class ConditionsTreeNode implements Parcelable{
return add(expr, OPERATOR.AND);
}
+ /**
+ * Convenience method.
+ * Adds the provided condition as the second argument of an AND
+ * clause to this node.
+ *
+ * @param condition Condition to 'AND' with.
+ * @return New top AND node, new root.
+ */
+ public ConditionsTreeNode and(SearchCondition condition) {
+ try {
+ ConditionsTreeNode tmp = new ConditionsTreeNode(condition);
+ return and(tmp);
+ } catch (Exception e) {
+ // impossible
+ return null;
+ }
+ }
+
/**
* Adds the expression as the second argument of an OR
* clause to this node.
@@ -153,6 +171,24 @@ public class ConditionsTreeNode implements Parcelable{
return add(expr, OPERATOR.OR);
}
+ /**
+ * Convenience method.
+ * Adds the provided condition as the second argument of an OR
+ * clause to this node.
+ *
+ * @param condition Condition to 'OR' with.
+ * @return New top OR node, new root.
+ */
+ public ConditionsTreeNode or(SearchCondition condition) {
+ try {
+ ConditionsTreeNode tmp = new ConditionsTreeNode(condition);
+ return or(tmp);
+ } catch (Exception e) {
+ // impossible
+ return null;
+ }
+ }
+
/**
* This applies the MPTT labeling to the subtree of which this node
* is the root node.
From 235e1f913b8431a0d0efe080c29ebc0e1ca92456 Mon Sep 17 00:00:00 2001
From: Sander Bogaert
Date: Sat, 13 Oct 2012 10:40:13 -0400
Subject: [PATCH 11/74] Avoid needless nullpointers.
---
src/com/fsck/k9/search/LocalSearch.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/com/fsck/k9/search/LocalSearch.java b/src/com/fsck/k9/search/LocalSearch.java
index 6b07459c4..a9bff7348 100644
--- a/src/com/fsck/k9/search/LocalSearch.java
+++ b/src/com/fsck/k9/search/LocalSearch.java
@@ -298,7 +298,7 @@ public class LocalSearch implements SearchSpecification {
* @return Name of the search.
*/
public String getName() {
- return mName;
+ return (mName == null ? "" : mName);
}
/**
From f550aaefb58bb41ae4b5a358a63b07a7dccc273e Mon Sep 17 00:00:00 2001
From: Sander Bogaert
Date: Sat, 13 Oct 2012 14:03:40 -0400
Subject: [PATCH 12/74] Add new SearchAccount class and move it together with
SearchModifier to the search package ( refactor ).
---
src/com/fsck/k9/SearchAccount.java | 153 ------------------
src/com/fsck/k9/activity/AccountList.java | 2 +-
src/com/fsck/k9/activity/FolderList.java | 1 +
src/com/fsck/k9/provider/MessageProvider.java | 2 +-
src/com/fsck/k9/search/SearchAccount.java | 66 ++++++++
.../{activity => search}/SearchModifier.java | 4 +-
6 files changed, 71 insertions(+), 157 deletions(-)
delete mode 100644 src/com/fsck/k9/SearchAccount.java
create mode 100644 src/com/fsck/k9/search/SearchAccount.java
rename src/com/fsck/k9/{activity => search}/SearchModifier.java (90%)
diff --git a/src/com/fsck/k9/SearchAccount.java b/src/com/fsck/k9/SearchAccount.java
deleted file mode 100644
index 97e2092ee..000000000
--- a/src/com/fsck/k9/SearchAccount.java
+++ /dev/null
@@ -1,153 +0,0 @@
-package com.fsck.k9;
-
-import java.io.Serializable;
-import java.util.UUID;
-
-import android.content.Context;
-
-import com.fsck.k9.mail.Flag;
-import com.fsck.k9.search.SearchSpecification;
-
-/**
- * This is a meta-Account that represents one or more accounts with filters on them. The filter specification
- * is defined by {@link com.fsck.k9.activity.SearchModifier}.
- */
-public class SearchAccount implements BaseAccount, SearchSpecification, Serializable {
- /**
- * Create a {@code SearchAccount} instance for the Unified Inbox.
- *
- * @param context
- * A {@link Context} instance that will be used to get localized strings and will be
- * passed on to the {@code SearchAccount} instance.
- *
- * @return The {@link SearchAccount} instance for the Unified Inbox.
- */
- public static SearchAccount createUnifiedInboxAccount(Context context) {
- SearchAccount unifiedInbox = new SearchAccount(context, true, null, null);
- unifiedInbox.setDescription(context.getString(R.string.integrated_inbox_title));
- unifiedInbox.setEmail(context.getString(R.string.integrated_inbox_detail));
- return unifiedInbox;
- }
-
- /**
- * Create a {@code SearchAccount} instance for the special account "All messages".
- *
- * @param context
- * A {@link Context} instance that will be used to get localized strings and will be
- * passed on to the {@code SearchAccount} instance.
- *
- * @return The {@link SearchAccount} instance for the Unified Inbox.
- */
- public static SearchAccount createAllMessagesAccount(Context context) {
- SearchAccount allMessages = new SearchAccount(context, false, null, null);
- allMessages.setDescription(context.getString(R.string.search_all_messages_title));
- allMessages.setEmail(context.getString(R.string.search_all_messages_detail));
- return allMessages;
- }
-
-
- private static final long serialVersionUID = -4388420303235543976L;
- private Flag[] mRequiredFlags = null;
- private Flag[] mForbiddenFlags = null;
- private String email = null;
- private String description = null;
- private String query = "";
- private boolean integrate = false;
- private String mUuid = null;
- private boolean builtin = false;
- private String[] accountUuids = null;
- private String[] folderNames = null;
-
- public SearchAccount(Preferences preferences) {
- }
-
- protected synchronized void delete(Preferences preferences) {
- }
-
- public synchronized void save(Preferences preferences) {
- }
-
- public SearchAccount(Context context, boolean nintegrate, Flag[] requiredFlags, Flag[] forbiddenFlags) {
- mRequiredFlags = requiredFlags;
- mForbiddenFlags = forbiddenFlags;
- integrate = nintegrate;
- }
-
- @Override
- public synchronized String getEmail() {
- return email;
- }
-
- @Override
- public synchronized void setEmail(String email) {
- this.email = email;
- }
-
- public Flag[] getRequiredFlags() {
- return mRequiredFlags;
- }
-
- public Flag[] getForbiddenFlags() {
- return mForbiddenFlags;
- }
-
- public boolean isIntegrate() {
- return integrate;
- }
-
- public String getDescription() {
- return description;
- }
-
- public void setDescription(String description) {
- this.description = description;
- }
-
- public String getQuery() {
- return query;
- }
-
- public void setQuery(String query) {
- this.query = query;
- }
-
- public String getUuid() {
- if (mUuid == null) {
- setUuid(UUID.randomUUID().toString());
- }
- return mUuid;
- }
-
- public void setUuid(String nUuid) {
- mUuid = nUuid;
- }
-
- public void setIntegrate(boolean integrate) {
- this.integrate = integrate;
- }
-
- public boolean isBuiltin() {
- return builtin;
- }
-
- public void setBuiltin(boolean builtin) {
- this.builtin = builtin;
- }
-
- public String[] getAccountUuids() {
- return accountUuids;
- }
-
- public void setAccountUuids(String[] accountUuids) {
- this.accountUuids = accountUuids;
- }
-
- @Override
- public String[] getFolderNames() {
- return folderNames;
- }
-
- public void setFolderNames(String[] folderNames) {
- this.folderNames = folderNames;
- }
-}
diff --git a/src/com/fsck/k9/activity/AccountList.java b/src/com/fsck/k9/activity/AccountList.java
index 52c64dbe3..9c17ffd42 100644
--- a/src/com/fsck/k9/activity/AccountList.java
+++ b/src/com/fsck/k9/activity/AccountList.java
@@ -21,7 +21,7 @@ import com.fsck.k9.FontSizes;
import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
-import com.fsck.k9.SearchAccount;
+import com.fsck.k9.search.SearchAccount;
/**
diff --git a/src/com/fsck/k9/activity/FolderList.java b/src/com/fsck/k9/activity/FolderList.java
index dc034c4ba..e4c2a3e2a 100644
--- a/src/com/fsck/k9/activity/FolderList.java
+++ b/src/com/fsck/k9/activity/FolderList.java
@@ -67,6 +67,7 @@ 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.LocalFolder;
+import com.fsck.k9.search.SearchModifier;
import com.fsck.k9.search.SearchSpecification;
import com.fsck.k9.service.MailService;
diff --git a/src/com/fsck/k9/provider/MessageProvider.java b/src/com/fsck/k9/provider/MessageProvider.java
index 1e351d057..05428b6cb 100644
--- a/src/com/fsck/k9/provider/MessageProvider.java
+++ b/src/com/fsck/k9/provider/MessageProvider.java
@@ -21,7 +21,6 @@ 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;
@@ -34,6 +33,7 @@ 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 com.fsck.k9.search.SearchAccount;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
diff --git a/src/com/fsck/k9/search/SearchAccount.java b/src/com/fsck/k9/search/SearchAccount.java
new file mode 100644
index 000000000..d58c62de1
--- /dev/null
+++ b/src/com/fsck/k9/search/SearchAccount.java
@@ -0,0 +1,66 @@
+package com.fsck.k9.search;
+
+import java.util.UUID;
+
+import com.fsck.k9.BaseAccount;
+
+/**
+ * This class is basically a wrapper around a LocalSearch. It allows to expose it as
+ * an account. This is a meta-account containing all the e-mail that matches the search.
+ */
+public class SearchAccount implements BaseAccount {
+
+ private String mEmail = null;
+ private String mDescription = null;
+ private LocalSearch mSearch = null;
+ private String mFakeUuid = null;
+
+ public SearchAccount(LocalSearch search, String description, String email) throws IllegalArgumentException{
+ if (search == null) {
+ throw new IllegalArgumentException("Provided LocalSearch was null");
+ }
+
+ this.mSearch = search;
+ this.mDescription = description;
+ this.mEmail = email;
+ }
+
+ @Override
+ public synchronized String getEmail() {
+ return mEmail;
+ }
+
+ @Override
+ public synchronized void setEmail(String email) {
+ this.mEmail = email;
+ }
+
+ @Override
+ public String getDescription() {
+ return mDescription;
+ }
+
+ @Override
+ public void setDescription(String description) {
+ this.mDescription = description;
+ }
+
+ public LocalSearch getRelatedSearch() {
+ return mSearch;
+ }
+
+ @Override
+ /*
+ * This will only be used when accessed as an Account. If that
+ * is the case we don't want to return the uuid of a real account since
+ * this is posing as a fake meta-account. If this object is accesed as
+ * a Search then methods from LocalSearch will be called which do handle
+ * things nice.
+ */
+ public String getUuid() {
+ if (mFakeUuid == null){
+ mFakeUuid = UUID.randomUUID().toString();
+ }
+ return mFakeUuid;
+ }
+}
diff --git a/src/com/fsck/k9/activity/SearchModifier.java b/src/com/fsck/k9/search/SearchModifier.java
similarity index 90%
rename from src/com/fsck/k9/activity/SearchModifier.java
rename to src/com/fsck/k9/search/SearchModifier.java
index 8b610ff6e..536fee51a 100644
--- a/src/com/fsck/k9/activity/SearchModifier.java
+++ b/src/com/fsck/k9/search/SearchModifier.java
@@ -1,10 +1,10 @@
-package com.fsck.k9.activity;
+package com.fsck.k9.search;
import com.fsck.k9.R;
import com.fsck.k9.mail.Flag;
/**
- * This enum represents filtering parameters used by {@link com.fsck.k9.SearchAccount}.
+ * This enum represents filtering parameters used by {@link com.fsck.k9.search.SearchAccount}.
*/
enum SearchModifier {
FLAGGED(R.string.flagged_modifier, new Flag[]{Flag.FLAGGED}, null),
From a3d227649fc0ec8bb8f4290bbec8857cddafea80 Mon Sep 17 00:00:00 2001
From: Sander Bogaert
Date: Sat, 13 Oct 2012 14:12:52 -0400
Subject: [PATCH 13/74] Change visibility of SearchModifier class and members.
---
src/com/fsck/k9/search/SearchModifier.java | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/com/fsck/k9/search/SearchModifier.java b/src/com/fsck/k9/search/SearchModifier.java
index 536fee51a..f027ab062 100644
--- a/src/com/fsck/k9/search/SearchModifier.java
+++ b/src/com/fsck/k9/search/SearchModifier.java
@@ -6,13 +6,13 @@ import com.fsck.k9.mail.Flag;
/**
* This enum represents filtering parameters used by {@link com.fsck.k9.search.SearchAccount}.
*/
-enum SearchModifier {
+public enum SearchModifier {
FLAGGED(R.string.flagged_modifier, new Flag[]{Flag.FLAGGED}, null),
UNREAD(R.string.unread_modifier, null, new Flag[]{Flag.SEEN});
- final int resId;
- final Flag[] requiredFlags;
- final Flag[] forbiddenFlags;
+ public final int resId;
+ public final Flag[] requiredFlags;
+ public final Flag[] forbiddenFlags;
SearchModifier(int nResId, Flag[] nRequiredFlags, Flag[] nForbiddenFlags) {
resId = nResId;
From f01f2f15cde94825288db6cd6fe9f314b0c47ab1 Mon Sep 17 00:00:00 2001
From: Sander Bogaert
Date: Sat, 13 Oct 2012 14:19:50 -0400
Subject: [PATCH 14/74] Add static methods to create unifiedInbox and
allMessages accounts.
---
src/com/fsck/k9/search/SearchAccount.java | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/src/com/fsck/k9/search/SearchAccount.java b/src/com/fsck/k9/search/SearchAccount.java
index d58c62de1..d76a8a0dd 100644
--- a/src/com/fsck/k9/search/SearchAccount.java
+++ b/src/com/fsck/k9/search/SearchAccount.java
@@ -2,7 +2,10 @@ package com.fsck.k9.search;
import java.util.UUID;
+import android.content.Context;
+
import com.fsck.k9.BaseAccount;
+import com.fsck.k9.R;
/**
* This class is basically a wrapper around a LocalSearch. It allows to expose it as
@@ -10,6 +13,24 @@ import com.fsck.k9.BaseAccount;
*/
public class SearchAccount implements BaseAccount {
+ // create the all messages search ( all accounts is default when none specified )
+ public static SearchAccount createAllMessagesAccount(Context context) {
+ String name = context.getString(R.string.search_all_messages_title);
+ LocalSearch tmpSearch = new LocalSearch(name);
+ return new SearchAccount(tmpSearch, name,
+ context.getString(R.string.search_all_messages_detail));
+ }
+
+
+ // create the unified inbox meta account ( all accounts is default when none specified )
+ public static SearchAccount createUnifiedInboxAccount(Context context) {
+ String name = context.getString(R.string.integrated_inbox_title);
+ LocalSearch tmpSearch = new LocalSearch(name);
+ tmpSearch.addAllowedFolder(SearchSpecification.GENERIC_INBOX_NAME);
+ return new SearchAccount(tmpSearch, name,
+ context.getString(R.string.integrated_inbox_detail));
+ }
+
private String mEmail = null;
private String mDescription = null;
private LocalSearch mSearch = null;
From bf82d0af7cbb17ab1c2c2e74fffe3748c7a77e05 Mon Sep 17 00:00:00 2001
From: Sander Bogaert
Date: Sat, 13 Oct 2012 15:06:57 -0400
Subject: [PATCH 15/74] Changed all the calls to MessageList to work using the
new LocalSearch class. These are all tested and working changes.
---
src/com/fsck/k9/activity/Accounts.java | 80 ++++++--------
src/com/fsck/k9/activity/FolderList.java | 100 +++++-------------
.../fsck/k9/activity/LauncherShortcuts.java | 3 +-
.../k9/controller/MessagingController.java | 14 ++-
src/com/fsck/k9/provider/MessageProvider.java | 2 +-
.../k9/provider/UnreadWidgetProvider.java | 7 +-
6 files changed, 76 insertions(+), 130 deletions(-)
diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java
index cbaaaa8ef..596801554 100644
--- a/src/com/fsck/k9/activity/Accounts.java
+++ b/src/com/fsck/k9/activity/Accounts.java
@@ -62,7 +62,6 @@ import com.fsck.k9.FontSizes;
import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
-import com.fsck.k9.SearchAccount;
import com.fsck.k9.activity.misc.ExtendedAsyncTask;
import com.fsck.k9.activity.misc.NonConfigurationInstance;
import com.fsck.k9.activity.setup.AccountSettings;
@@ -79,6 +78,9 @@ import com.fsck.k9.mail.Transport;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.mail.store.WebDavStore;
+import com.fsck.k9.search.LocalSearch;
+import com.fsck.k9.search.SearchAccount;
+import com.fsck.k9.search.SearchModifier;
import com.fsck.k9.search.SearchSpecification;
import com.fsck.k9.view.ColorChip;
import com.fsck.k9.preferences.SettingsExporter;
@@ -424,8 +426,18 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
* Creates and initializes the special accounts ('Unified Inbox' and 'All Messages')
*/
private void createSpecialAccounts() {
- integratedInboxAccount = SearchAccount.createUnifiedInboxAccount(this);
- unreadAccount = SearchAccount.createAllMessagesAccount(this);
+ // create the unified inbox meta account ( all accounts is default when none specified )
+ String name = getString(R.string.integrated_inbox_title);
+ LocalSearch tmpSearch = new LocalSearch(name);
+ tmpSearch.addAllowedFolder(SearchSpecification.GENERIC_INBOX_NAME);
+ integratedInboxAccount = new SearchAccount(tmpSearch, name,
+ getString(R.string.integrated_inbox_detail));
+
+ // create the all messages search ( all accounts is default when none specified )
+ name = getString(R.string.search_all_messages_title);
+ tmpSearch = new LocalSearch(name);
+ unreadAccount = new SearchAccount(tmpSearch, name,
+ getString(R.string.search_all_messages_detail));
}
@SuppressWarnings("unchecked")
@@ -550,7 +562,8 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
pendingWork.put(account, "true");
final SearchAccount searchAccount = (SearchAccount)account;
- MessagingController.getInstance(getApplication()).searchLocalMessages(searchAccount, null, new MessagingListener() {
+ MessagingController.getInstance(getApplication())
+ .searchLocalMessages(searchAccount.getRelatedSearch(), new MessagingListener() {
@Override
public void searchStats(AccountStats stats) {
mListener.accountStatusChanged(searchAccount, stats);
@@ -607,7 +620,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
private boolean onOpenAccount(BaseAccount account) {
if (account instanceof SearchAccount) {
SearchAccount searchAccount = (SearchAccount)account;
- MessageList.actionHandle(this, searchAccount.getDescription(), searchAccount);
+ MessageList.actionDisplaySearch(this, searchAccount.getRelatedSearch(), false);
} else {
Account realAccount = (Account)account;
if (!realAccount.isEnabled()) {
@@ -624,8 +637,10 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
if (K9.FOLDER_NONE.equals(realAccount.getAutoExpandFolderName())) {
FolderList.actionHandleAccount(this, realAccount);
} else {
- MessageList.actionHandleFolder(this, realAccount, realAccount.getAutoExpandFolderName());
- }
+ LocalSearch search = new LocalSearch(realAccount.getAutoExpandFolderName());
+ search.addAllowedFolder(realAccount.getAutoExpandFolderName());
+ search.addAccountUuid(realAccount.getUuid());
+ MessageList.actionDisplaySearch(this, search, true);}
}
return true;
}
@@ -1769,49 +1784,20 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
}
@Override
public void onClick(View v) {
- String description = getString(R.string.search_title, account.getDescription(), getString(searchModifier.resId));
+ final String description = getString(R.string.search_title, account.getDescription(), getString(searchModifier.resId));
+ LocalSearch search = null;
+
if (account instanceof SearchAccount) {
- SearchAccount searchAccount = (SearchAccount)account;
-
- MessageList.actionHandle(Accounts.this,
- description, "", searchAccount.isIntegrate(),
- combine(searchAccount.getRequiredFlags(), searchModifier.requiredFlags),
- combine(searchAccount.getForbiddenFlags(), searchModifier.forbiddenFlags));
+ search = ((SearchAccount) account).getRelatedSearch();
+ search.setName(description);
} else {
- SearchSpecification searchSpec = new SearchSpecification() {
- @Override
- public String[] getAccountUuids() {
- return new String[] { account.getUuid() };
- }
-
- @Override
- public Flag[] getForbiddenFlags() {
- return searchModifier.forbiddenFlags;
- }
-
- @Override
- public String getQuery() {
- return "";
- }
-
- @Override
- public Flag[] getRequiredFlags() {
- return searchModifier.requiredFlags;
- }
-
- @Override
- public boolean isIntegrate() {
- return false;
- }
-
- @Override
- public String[] getFolderNames() {
- return null;
- }
-
- };
- MessageList.actionHandle(Accounts.this, description, searchSpec);
+ search = new LocalSearch(description);
+ search.addAccountUuid(account.getUuid());
}
+
+ search.allRequiredFlags(searchModifier.requiredFlags);
+ search.allForbiddenFlags(searchModifier.forbiddenFlags);
+ MessageList.actionDisplaySearch(Accounts.this, search, false);
}
}
diff --git a/src/com/fsck/k9/activity/FolderList.java b/src/com/fsck/k9/activity/FolderList.java
index e4c2a3e2a..6c8477833 100644
--- a/src/com/fsck/k9/activity/FolderList.java
+++ b/src/com/fsck/k9/activity/FolderList.java
@@ -67,6 +67,7 @@ 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.LocalFolder;
+import com.fsck.k9.search.LocalSearch;
import com.fsck.k9.search.SearchModifier;
import com.fsck.k9.search.SearchSpecification;
import com.fsck.k9.service.MailService;
@@ -621,7 +622,10 @@ public class FolderList extends K9ListActivity implements OnNavigationListener {
}
private void onOpenFolder(String folder) {
- MessageList.actionHandleFolder(this, mAccount, folder);
+ LocalSearch search = new LocalSearch(folder);
+ search.addAccountUuid(mAccount.getUuid());
+ search.addAllowedFolder(folder);
+ MessageList.actionDisplaySearch(this, search, false);
}
private void onCompact(Account account) {
@@ -1258,86 +1262,34 @@ public class FolderList extends K9ListActivity implements OnNavigationListener {
}
@Override
public void onClick(View v) {
- String description = getString(R.string.search_title,
+ final String description = getString(R.string.search_title,
getString(R.string.message_list_title, account.getDescription(), displayName),
getString(searchModifier.resId));
-
- SearchSpecification searchSpec = new SearchSpecification() {
- @Override
- public String[] getAccountUuids() {
- return new String[] { account.getUuid() };
- }
-
- @Override
- public Flag[] getForbiddenFlags() {
- return searchModifier.forbiddenFlags;
- }
-
- @Override
- public String getQuery() {
- return "";
- }
-
- @Override
- public Flag[] getRequiredFlags() {
- return searchModifier.requiredFlags;
- }
-
- @Override
- public boolean isIntegrate() {
- return false;
- }
-
- @Override
- public String[] getFolderNames() {
- return new String[] { folderName };
- }
-
- };
- MessageList.actionHandle(FolderList.this, description, searchSpec);
-
+
+ LocalSearch search = new LocalSearch(description);
+ try {
+ search.allRequiredFlags(searchModifier.requiredFlags);
+ search.allForbiddenFlags(searchModifier.forbiddenFlags);
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ search.addAllowedFolder(folderName);
+ search.addAccountUuid(account.getUuid());
+ MessageList.actionDisplaySearch(FolderList.this, search, false);
}
-
}
- private static Flag[] UNREAD_FLAG_ARRAY = { Flag.SEEN };
-
private void openUnreadSearch(Context context, final Account account) {
String description = getString(R.string.search_title, mAccount.getDescription(), getString(R.string.unread_modifier));
-
- SearchSpecification searchSpec = new SearchSpecification() {
- //interface has no override @Override
- public String[] getAccountUuids() {
- return new String[] { account.getUuid() };
- }
-
- //interface has no override @Override
- public Flag[] getForbiddenFlags() {
- return UNREAD_FLAG_ARRAY;
- }
-
- //interface has no override @Override
- public String getQuery() {
- return "";
- }
-
- @Override
- public Flag[] getRequiredFlags() {
- return null;
- }
-
- @Override
- public boolean isIntegrate() {
- return false;
- }
-
- @Override
- public String[] getFolderNames() {
- return null;
- }
-
- };
- MessageList.actionHandle(context, description, searchSpec);
+ LocalSearch search = new LocalSearch(description);
+ search.addAccountUuid(account.getUuid());
+ try {
+ search.allRequiredFlags(new Flag[]{Flag.SEEN});
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
}
}
diff --git a/src/com/fsck/k9/activity/LauncherShortcuts.java b/src/com/fsck/k9/activity/LauncherShortcuts.java
index add1d837a..f302051dc 100644
--- a/src/com/fsck/k9/activity/LauncherShortcuts.java
+++ b/src/com/fsck/k9/activity/LauncherShortcuts.java
@@ -31,8 +31,7 @@ public class LauncherShortcuts extends AccountList {
Intent shortcutIntent = null;
if (account instanceof SearchSpecification) {
- shortcutIntent = MessageList.actionHandleAccountIntent(this, account.getDescription(),
- (SearchSpecification) account);
+ shortcutIntent = MessageList.intentDisplaySearch(this, (SearchSpecification) account, true, true);
} else {
shortcutIntent = FolderList.actionHandleAccountIntent(this, (Account) account, null,
true);
diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java
index dfb638828..181cb0177 100644
--- a/src/com/fsck/k9/controller/MessagingController.java
+++ b/src/com/fsck/k9/controller/MessagingController.java
@@ -3160,8 +3160,11 @@ public class MessagingController implements Runnable {
builder.setContentTitle(mApplication.getString(R.string.notification_bg_send_title));
builder.setContentText(account.getDescription());
- Intent intent = MessageList.actionHandleFolderIntent(mApplication, account,
- account.getInboxFolderName());
+ LocalSearch search = new LocalSearch(account.getInboxFolderName());
+ search.addAllowedFolder(account.getInboxFolderName());
+ search.addAccountUuid(account.getUuid());
+ Intent intent = MessageList.intentDisplaySearch(mApplication, search, true, true);
+
PendingIntent pi = PendingIntent.getActivity(mApplication, 0, intent, 0);
builder.setContentIntent(pi);
@@ -3243,8 +3246,11 @@ public class MessagingController implements Runnable {
mApplication.getString(R.string.notification_bg_title_separator) +
folder.getName());
- Intent intent = MessageList.actionHandleFolderIntent(mApplication, account,
- account.getInboxFolderName());
+ LocalSearch search = new LocalSearch(account.getInboxFolderName());
+ search.addAllowedFolder(account.getInboxFolderName());
+ search.addAccountUuid(account.getUuid());
+ Intent intent = MessageList.intentDisplaySearch(mApplication, search, true, true);
+
PendingIntent pi = PendingIntent.getActivity(mApplication, 0, intent, 0);
builder.setContentIntent(pi);
diff --git a/src/com/fsck/k9/provider/MessageProvider.java b/src/com/fsck/k9/provider/MessageProvider.java
index 05428b6cb..d40ed23e0 100644
--- a/src/com/fsck/k9/provider/MessageProvider.java
+++ b/src/com/fsck/k9/provider/MessageProvider.java
@@ -302,7 +302,7 @@ public class MessageProvider extends ContentProvider {
final SearchAccount integratedInboxAccount = SearchAccount.createUnifiedInboxAccount(getContext());
final MessagingController msgController = MessagingController.getInstance(K9.app);
- msgController.searchLocalMessages(integratedInboxAccount, null,
+ msgController.searchLocalMessages(integratedInboxAccount.getRelatedSearch(),
new MesssageInfoHolderRetrieverListener(queue));
final List holders = queue.take();
diff --git a/src/com/fsck/k9/provider/UnreadWidgetProvider.java b/src/com/fsck/k9/provider/UnreadWidgetProvider.java
index 2af479d15..d8daeaa0b 100644
--- a/src/com/fsck/k9/provider/UnreadWidgetProvider.java
+++ b/src/com/fsck/k9/provider/UnreadWidgetProvider.java
@@ -8,6 +8,7 @@ import com.fsck.k9.R;
import com.fsck.k9.activity.UnreadWidgetConfiguration;
import com.fsck.k9.activity.FolderList;
import com.fsck.k9.activity.MessageList;
+import com.fsck.k9.search.LocalSearch;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
@@ -62,8 +63,10 @@ public class UnreadWidgetProvider extends AppWidgetProvider {
clickIntent = FolderList.actionHandleAccountIntent(context, account, null,
false);
} else {
- clickIntent = MessageList.actionHandleFolderIntent(context, account,
- account.getAutoExpandFolderName());
+ LocalSearch search = new LocalSearch(account.getAutoExpandFolderName());
+ search.addAllowedFolder(account.getAutoExpandFolderName());
+ search.addAccountUuid(account.getUuid());
+ clickIntent = MessageList.intentDisplaySearch(context, search, true, true);
}
clickIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
}
From 49197b4f1c3d599d0af773559e59fbc267d08286 Mon Sep 17 00:00:00 2001
From: Sander Bogaert
Date: Sat, 13 Oct 2012 15:07:28 -0400
Subject: [PATCH 16/74] First attempt at making the fragmented messagelist and
remote search play nice with the new search framework. Works partially.
---
src/com/fsck/k9/activity/MessageList.java | 249 ++++------
.../fsck/k9/fragment/MessageListFragment.java | 436 ++++++++----------
src/com/fsck/k9/search/LocalSearch.java | 14 +
3 files changed, 294 insertions(+), 405 deletions(-)
diff --git a/src/com/fsck/k9/activity/MessageList.java b/src/com/fsck/k9/activity/MessageList.java
index d984efdbe..aca636bbd 100644
--- a/src/com/fsck/k9/activity/MessageList.java
+++ b/src/com/fsck/k9/activity/MessageList.java
@@ -34,7 +34,11 @@ import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.store.StorageManager;
+import com.fsck.k9.search.LocalSearch;
import com.fsck.k9.search.SearchSpecification;
+import com.fsck.k9.search.SearchSpecification.ATTRIBUTE;
+import com.fsck.k9.search.SearchSpecification.SEARCHFIELD;
+import com.fsck.k9.search.SearchSpecification.SearchCondition;
/**
@@ -44,86 +48,36 @@ import com.fsck.k9.search.SearchSpecification;
*/
public class MessageList extends K9FragmentActivity implements MessageListFragmentListener,
OnBackStackChangedListener, OnSwipeGestureListener {
- private static final String EXTRA_ACCOUNT = "account";
- private static final String EXTRA_FOLDER = "folder";
+
+ // for this activity
+ private static final String EXTRA_SEARCH = "search";
+
+ // used for remote search
private static final String EXTRA_SEARCH_ACCOUNT = "com.fsck.k9.search_account";
private static final String EXTRA_SEARCH_FOLDER = "com.fsck.k9.search_folder";
- private static final String EXTRA_QUERY_FLAGS = "queryFlags";
- private static final String EXTRA_FORBIDDEN_FLAGS = "forbiddenFlags";
- private static final String EXTRA_INTEGRATE = "integrate";
- private static final String EXTRA_ACCOUNT_UUIDS = "accountUuids";
- private static final String EXTRA_FOLDER_NAMES = "folderNames";
- private static final String EXTRA_TITLE = "title";
-
- public static void actionHandleFolder(Context context, Account account, String folder) {
- Intent intent = new Intent(context, MessageList.class);
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
- intent.putExtra(EXTRA_ACCOUNT, account.getUuid());
-
- if (folder != null) {
- intent.putExtra(EXTRA_FOLDER, folder);
- }
- context.startActivity(intent);
+ public static void actionDisplaySearch(Context context, SearchSpecification search, boolean newTask) {
+ actionDisplaySearch(context, search, newTask, true);
}
-
- public static Intent actionHandleFolderIntent(Context context, Account account, String folder) {
+
+ public static void actionDisplaySearch(Context context, SearchSpecification search, boolean newTask, boolean clearTop) {
+ context.startActivity(intentDisplaySearch(context, search, newTask, clearTop));
+ }
+
+ public static Intent intentDisplaySearch(Context context, SearchSpecification search, boolean newTask, boolean clearTop) {
Intent intent = new Intent(context, MessageList.class);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP |
- Intent.FLAG_ACTIVITY_SINGLE_TOP);
- intent.putExtra(EXTRA_ACCOUNT, account.getUuid());
-
- if (folder != null) {
- intent.putExtra(EXTRA_FOLDER, folder);
+ intent.putExtra(EXTRA_SEARCH, search);
+
+ if (clearTop) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
}
+ if (newTask) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ }
+
return intent;
}
- public static void actionHandle(Context context, String title, String queryString, boolean integrate, Flag[] flags, Flag[] forbiddenFlags) {
- Intent intent = new Intent(context, MessageList.class);
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
- intent.putExtra(SearchManager.QUERY, queryString);
- if (flags != null) {
- intent.putExtra(EXTRA_QUERY_FLAGS, Utility.combine(flags, ','));
- }
- if (forbiddenFlags != null) {
- intent.putExtra(EXTRA_FORBIDDEN_FLAGS, Utility.combine(forbiddenFlags, ','));
- }
- intent.putExtra(EXTRA_INTEGRATE, integrate);
- intent.putExtra(EXTRA_TITLE, title);
- context.startActivity(intent);
- }
-
- /**
- * Creates and returns an intent that opens Unified Inbox or All Messages screen.
- */
- public static Intent actionHandleAccountIntent(Context context, String title,
- SearchSpecification searchSpecification) {
- Intent intent = new Intent(context, MessageList.class);
- intent.putExtra(SearchManager.QUERY, searchSpecification.getQuery());
- if (searchSpecification.getRequiredFlags() != null) {
- intent.putExtra(EXTRA_QUERY_FLAGS, Utility.combine(searchSpecification.getRequiredFlags(), ','));
- }
- if (searchSpecification.getForbiddenFlags() != null) {
- intent.putExtra(EXTRA_FORBIDDEN_FLAGS, Utility.combine(searchSpecification.getForbiddenFlags(), ','));
- }
- intent.putExtra(EXTRA_INTEGRATE, searchSpecification.isIntegrate());
- intent.putExtra(EXTRA_ACCOUNT_UUIDS, searchSpecification.getAccountUuids());
- intent.putExtra(EXTRA_FOLDER_NAMES, searchSpecification.getFolderNames());
- intent.putExtra(EXTRA_TITLE, title);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
-
- return intent;
- }
-
- public static void actionHandle(Context context, String title,
- SearchSpecification searchSpecification) {
- Intent intent = actionHandleAccountIntent(context, title, searchSpecification);
- context.startActivity(intent);
- }
-
private StorageManager.StorageListener mStorageListener = new StorageListenerImplementation();
@@ -131,31 +85,22 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
private TextView mActionBarTitle;
private TextView mActionBarSubTitle;
private TextView mActionBarUnread;
- private String mTitle;
private Menu mMenu;
private MessageListFragment mMessageListFragment;
private Account mAccount;
- private String mQueryString;
private String mFolderName;
- private Flag[] mQueryFlags;
- private Flag[] mForbiddenFlags;
- private String mSearchAccount = null;
- private String mSearchFolder = null;
- private boolean mIntegrate;
- private String[] mAccountUuids;
- private String[] mFolderNames;
-
-
+ private LocalSearch mSearch;
+ private boolean mSingleFolderMode;
+ private boolean mSingleAccountMode;
+ private boolean mIsRemote;
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.message_list);
- // need this for actionbar initialization
- mQueryString = getIntent().getStringExtra(SearchManager.QUERY);
-
mActionBar = getSupportActionBar();
initializeActionBar();
@@ -171,76 +116,56 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
if (mMessageListFragment == null) {
FragmentTransaction ft = fragmentManager.beginTransaction();
- if (mQueryString == null) {
- mMessageListFragment = MessageListFragment.newInstance(mAccount, mFolderName);
- } else if (mSearchAccount != null) {
- mMessageListFragment = MessageListFragment.newInstance(mSearchAccount,
- mSearchFolder, mQueryString, false);
- } else {
- mMessageListFragment = MessageListFragment.newInstance(mTitle, mAccountUuids,
- mFolderNames, mQueryString, mQueryFlags, mForbiddenFlags, mIntegrate);
- }
+ mMessageListFragment = MessageListFragment.newInstance(mSearch, mIsRemote);
ft.add(R.id.message_list_container, mMessageListFragment);
ft.commit();
}
}
private void decodeExtras(Intent intent) {
- mQueryString = intent.getStringExtra(SearchManager.QUERY);
- mFolderName = null;
- mSearchAccount = null;
- mSearchFolder = null;
- if (mQueryString != null) {
+ // check if this intent comes from the system search ( remote )
+ if (intent.getStringExtra(SearchManager.QUERY) != null) {
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
//Query was received from Search Dialog
Bundle appData = getIntent().getBundleExtra(SearchManager.APP_DATA);
if (appData != null) {
- mSearchAccount = appData.getString(EXTRA_SEARCH_ACCOUNT);
- mSearchFolder = appData.getString(EXTRA_SEARCH_FOLDER);
+ mSearch = new LocalSearch();
+ mSearch.addAccountUuid(appData.getString(EXTRA_SEARCH_ACCOUNT));
+ mSearch.addAllowedFolder(appData.getString(EXTRA_SEARCH_FOLDER));
+
+ String query = intent.getStringExtra(SearchManager.QUERY);
+ mSearch.or(new SearchCondition(SEARCHFIELD.SENDER, ATTRIBUTE.CONTAINS, query));
+ mSearch.or(new SearchCondition(SEARCHFIELD.SUBJECT, ATTRIBUTE.CONTAINS, query));
+
+ mIsRemote = true;
}
- } else {
- mSearchAccount = intent.getStringExtra(EXTRA_SEARCH_ACCOUNT);
- mSearchFolder = intent.getStringExtra(EXTRA_SEARCH_FOLDER);
}
+ } else {
+ // regular LocalSearch object was passed
+ mSearch = intent.getParcelableExtra(EXTRA_SEARCH);
}
- String accountUuid = intent.getStringExtra(EXTRA_ACCOUNT);
- mFolderName = intent.getStringExtra(EXTRA_FOLDER);
-
- mAccount = Preferences.getPreferences(this).getAccount(accountUuid);
-
- if (mAccount != null && !mAccount.isAvailable(this)) {
- Log.i(K9.LOG_TAG, "not opening MessageList of unavailable account");
- onAccountUnavailable();
- return;
- }
-
- String queryFlags = intent.getStringExtra(EXTRA_QUERY_FLAGS);
- if (queryFlags != null) {
- String[] flagStrings = queryFlags.split(",");
- mQueryFlags = new Flag[flagStrings.length];
- for (int i = 0; i < flagStrings.length; i++) {
- mQueryFlags[i] = Flag.valueOf(flagStrings[i]);
- }
- }
- String forbiddenFlags = intent.getStringExtra(EXTRA_FORBIDDEN_FLAGS);
- if (forbiddenFlags != null) {
- String[] flagStrings = forbiddenFlags.split(",");
- mForbiddenFlags = new Flag[flagStrings.length];
- for (int i = 0; i < flagStrings.length; i++) {
- mForbiddenFlags[i] = Flag.valueOf(flagStrings[i]);
- }
- }
- mIntegrate = intent.getBooleanExtra(EXTRA_INTEGRATE, false);
- mAccountUuids = intent.getStringArrayExtra(EXTRA_ACCOUNT_UUIDS);
- mFolderNames = intent.getStringArrayExtra(EXTRA_FOLDER_NAMES);
- mTitle = intent.getStringExtra(EXTRA_TITLE);
-
- // Take the initial folder into account only if we are *not* restoring
- // the activity already.
- if (mFolderName == null && mQueryString == null) {
- mFolderName = mAccount.getAutoExpandFolderName();
- }
+ String[] accounts = mSearch.getAccountUuids();
+ mSingleAccountMode = ( accounts != null && accounts.length == 1
+ && !accounts[0].equals(SearchSpecification.ALL_ACCOUNTS));
+ mSingleFolderMode = mSingleAccountMode && (mSearch.getFolderNames().size() == 1);
+
+ if (mSingleAccountMode) {
+ mAccount = Preferences.getPreferences(this).getAccount(accounts[0]);
+
+ if (mAccount != null && !mAccount.isAvailable(this)) {
+ Log.i(K9.LOG_TAG, "not opening MessageList of unavailable account");
+ onAccountUnavailable();
+ return;
+ }
+ }
+
+ if (mSingleFolderMode) {
+ mFolderName = mSearch.getFolderNames().get(0);
+ }
+
+ // now we know if we are in single account mode and need a subtitle
+ mActionBarSubTitle.setVisibility((!mSingleFolderMode) ? View.GONE : View.VISIBLE);
}
@Override
@@ -276,10 +201,6 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
mActionBarSubTitle = (TextView) customView.findViewById(R.id.actionbar_title_sub);
mActionBarUnread = (TextView) customView.findViewById(R.id.actionbar_unread_count);
- if (mQueryString != null) {
- mActionBarSubTitle.setVisibility(View.GONE);
- }
-
mActionBar.setDisplayHomeAsUpEnabled(true);
}
@@ -407,17 +328,11 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
FragmentManager fragmentManager = getSupportFragmentManager();
if (fragmentManager.getBackStackEntryCount() > 0) {
fragmentManager.popBackStack();
- } else if (mIntegrate) {
- // If we were in one of the integrated mailboxes (think All Mail or Integrated Inbox), then
- // go to accounts.
- onAccounts();
- } else if (mQueryString != null) {
- // We did a search of some sort. Go back to wherever the user searched from.
- onBackPressed();
- } else {
- // In a standard message list of a folder. Go to folder list.
- onShowFolderList();
- }
+ } else if (!mSingleFolderMode) {
+ onBackPressed();
+ } else {
+ onShowFolderList();
+ }
return true;
}
case R.id.compose: {
@@ -470,7 +385,7 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
}
}
- if (mQueryString != null) {
+ if (!mSingleFolderMode) {
// None of the options after this point are "safe" for search results
//TODO: This is not true for "unread" and "starred" searches in regular folders
return false;
@@ -534,7 +449,7 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
menu.findItem(R.id.select_all).setVisible(true);
menu.findItem(R.id.settings).setVisible(true);
- if (mMessageListFragment.isSearchQuery()) {
+ if (!mSingleAccountMode) {
menu.findItem(R.id.expunge).setVisible(false);
menu.findItem(R.id.check_mail).setVisible(false);
menu.findItem(R.id.send_messages).setVisible(false);
@@ -666,8 +581,11 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
@Override
public void showMoreFromSameSender(String senderAddress) {
- MessageListFragment fragment = MessageListFragment.newInstance("From " + senderAddress,
- null, null, senderAddress, null, null, false);
+ LocalSearch tmpSearch = new LocalSearch("From " + senderAddress);
+ tmpSearch.addAccountUuids(mSearch.getAccountUuids());
+ tmpSearch.and(SEARCHFIELD.SENDER, senderAddress, ATTRIBUTE.CONTAINS);
+
+ MessageListFragment fragment = MessageListFragment.newInstance(tmpSearch, false);
addMessageListFragment(fragment);
}
@@ -716,8 +634,7 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
@Override
public void remoteSearch(String searchAccount, String searchFolder, String queryString) {
- MessageListFragment fragment = MessageListFragment.newInstance(searchAccount, searchFolder,
- queryString, true);
+ MessageListFragment fragment = MessageListFragment.newInstance(mSearch, true);
addMessageListFragment(fragment);
}
@@ -751,9 +668,11 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
@Override
public void showThread(Account account, String folderName, long threadRootId) {
- MessageListFragment fragment = MessageListFragment.newInstance(account, folderName,
- threadRootId);
-
+ LocalSearch tmpSearch = new LocalSearch();
+ tmpSearch.addAccountUuids(mSearch.getAccountUuids());
+ tmpSearch.and(SEARCHFIELD.THREAD_ROOT, String.valueOf(threadRootId), ATTRIBUTE.EQUALS);
+
+ MessageListFragment fragment = MessageListFragment.newInstance(tmpSearch, false);
addMessageListFragment(fragment);
}
}
diff --git a/src/com/fsck/k9/fragment/MessageListFragment.java b/src/com/fsck/k9/fragment/MessageListFragment.java
index 1fab56b86..3932fed90 100644
--- a/src/com/fsck/k9/fragment/MessageListFragment.java
+++ b/src/com/fsck/k9/fragment/MessageListFragment.java
@@ -77,6 +77,12 @@ import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
import com.fsck.k9.mail.store.LocalStore.LocalMessage;
+import com.fsck.k9.search.ConditionsTreeNode;
+import com.fsck.k9.search.LocalSearch;
+import com.fsck.k9.search.SearchSpecification;
+import com.fsck.k9.search.SearchSpecification.ATTRIBUTE;
+import com.fsck.k9.search.SearchSpecification.SEARCHFIELD;
+import com.fsck.k9.search.SearchSpecification.SearchCondition;
import com.handmark.pulltorefresh.library.PullToRefreshBase;
import com.handmark.pulltorefresh.library.PullToRefreshListView;
@@ -84,67 +90,14 @@ import com.handmark.pulltorefresh.library.PullToRefreshListView;
public class MessageListFragment extends SherlockFragment implements OnItemClickListener,
ConfirmationDialogFragmentListener {
- public static MessageListFragment newInstance(Account account, String folderName) {
+ public static MessageListFragment newInstance(LocalSearch search, boolean remoteSearch) {
MessageListFragment fragment = new MessageListFragment();
-
Bundle args = new Bundle();
- args.putString(ARG_ACCOUNT, account.getUuid());
- args.putString(ARG_FOLDER, folderName);
- fragment.setArguments(args);
-
- return fragment;
- }
-
- public static MessageListFragment newInstance(Account account, String folderName,
- long threadRootId) {
- MessageListFragment fragment = new MessageListFragment();
-
- Bundle args = new Bundle();
- args.putString(ARG_ACCOUNT, account.getUuid());
- args.putString(ARG_FOLDER, folderName);
- args.putLong(ARG_THREAD_ID, threadRootId);
- fragment.setArguments(args);
-
- return fragment;
- }
-
- public static MessageListFragment newInstance(String title, String[] accountUuids,
- String[] folderNames, String queryString, Flag[] flags,
- Flag[] forbiddenFlags, boolean integrate) {
-
- MessageListFragment fragment = new MessageListFragment();
-
- Bundle args = new Bundle();
- args.putStringArray(ARG_ACCOUNT_UUIDS, accountUuids);
- args.putStringArray(ARG_FOLDER_NAMES, folderNames);
- args.putString(ARG_QUERY, queryString);
- if (flags != null) {
- args.putString(ARG_QUERY_FLAGS, Utility.combine(flags, ','));
- }
- if (forbiddenFlags != null) {
- args.putString(ARG_FORBIDDEN_FLAGS, Utility.combine(forbiddenFlags, ','));
- }
- args.putBoolean(ARG_INTEGRATE, integrate);
- args.putString(ARG_TITLE, title);
- fragment.setArguments(args);
-
- return fragment;
- }
-
- public static MessageListFragment newInstance(String searchAccount, String searchFolder,
- String queryString, boolean remoteSearch) {
- MessageListFragment fragment = new MessageListFragment();
-
- Bundle args = new Bundle();
- args.putString(ARG_SEARCH_ACCOUNT, searchAccount);
- args.putString(ARG_SEARCH_FOLDER, searchFolder);
- args.putString(ARG_QUERY, queryString);
+ args.putParcelable(ARG_SEARCH, search);
args.putBoolean(ARG_REMOTE_SEARCH, remoteSearch);
fragment.setArguments(args);
-
- return fragment;
- }
-
+ return fragment;
+ }
/**
* Reverses the result of a {@link Comparator}.
@@ -292,20 +245,8 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
private static final int ACTIVITY_CHOOSE_FOLDER_MOVE = 1;
private static final int ACTIVITY_CHOOSE_FOLDER_COPY = 2;
- private static final String ARG_ACCOUNT = "account";
- private static final String ARG_FOLDER = "folder";
- private static final String ARG_REMOTE_SEARCH = "remote_search";
- private static final String ARG_QUERY = "query";
- private static final String ARG_SEARCH_ACCOUNT = "search_account";
- private static final String ARG_SEARCH_FOLDER = "search_folder";
- private static final String ARG_QUERY_FLAGS = "queryFlags";
- private static final String ARG_FORBIDDEN_FLAGS = "forbiddenFlags";
- private static final String ARG_INTEGRATE = "integrate";
- private static final String ARG_ACCOUNT_UUIDS = "accountUuids";
- private static final String ARG_FOLDER_NAMES = "folderNames";
- private static final String ARG_TITLE = "title";
- private static final String ARG_THREAD_ID = "thread_id";
-
+ private static final String ARG_SEARCH = "searchObject";
+ private static final String ARG_REMOTE_SEARCH = "remoteSearch";
private static final String STATE_LIST_POSITION = "listPosition";
/**
@@ -356,18 +297,14 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
/**
* If we're doing a search, this contains the query string.
*/
- private String mQueryString;
- private Flag[] mQueryFlags = null;
- private Flag[] mForbiddenFlags = null;
private boolean mRemoteSearch = false;
- private String mSearchAccount = null;
- private String mSearchFolder = null;
private Future mRemoteSearchFuture = null;
- private boolean mIntegrate = false;
- private String[] mAccountUuids = null;
- private String[] mFolderNames = null;
+
private String mTitle;
-
+ private LocalSearch mSearch = null;
+ private boolean mSingleAccountMode;
+ private boolean mSingleFolderMode;
+
private MessageListHandler mHandler = new MessageListHandler();
private SortType mSortType = SortType.SORT_DATE;
@@ -596,7 +533,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
private void setWindowTitle() {
// regular folder content display
- if (mFolderName != null) {
+ if (mSingleFolderMode) {
Activity activity = getActivity();
String displayName = FolderInfoHolder.getDisplayName(activity, mAccount,
mFolderName);
@@ -609,7 +546,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
} else {
mFragmentListener.setMessageListSubTitle(operation);
}
- } else if (mQueryString != null) {
+ } else {
// query result display. This may be for a search folder as opposed to a user-initiated search.
if (mTitle != null) {
// This was a search folder; the search folder has overridden our title.
@@ -626,8 +563,8 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
if (mUnreadMessageCount == 0) {
mFragmentListener.setUnreadCount(0);
} else {
- if (mQueryString != null && mTitle == null) {
- // This is a search result. The unread message count is easily confused
+ if (!mSingleFolderMode && mTitle == null) {
+ // The unread message count is easily confused
// with total number of messages in the search result, so let's hide it.
mFragmentListener.setUnreadCount(0);
} else {
@@ -665,15 +602,14 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
if (view == mFooterView) {
if (mCurrentFolder != null && !mRemoteSearch) {
mController.loadMoreMessages(mAccount, mFolderName, mAdapter.mListener);
- } else if (mRemoteSearch && mAdapter.mExtraSearchResults != null && mAdapter.mExtraSearchResults.size() > 0 && mSearchAccount != null) {
+ } else if (mRemoteSearch && mAdapter.mExtraSearchResults != null && mAdapter.mExtraSearchResults.size() > 0 && mAccount != null) {
int numResults = mAdapter.mExtraSearchResults.size();
Context appContext = getActivity().getApplicationContext();
- Account account = Preferences.getPreferences(appContext).getAccount(mSearchAccount);
- if (account == null) {
+ if (mAccount == null) {
mHandler.updateFooter("", false);
return;
}
- int limit = account.getRemoteSearchNumResults();
+ int limit = mAccount.getRemoteSearchNumResults();
List toProcess = mAdapter.mExtraSearchResults;
if (limit > 0 && numResults > limit) {
toProcess = toProcess.subList(0, limit);
@@ -682,7 +618,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
mAdapter.mExtraSearchResults = null;
mHandler.updateFooter("", false);
}
- mController.loadSearchResults(account, mSearchFolder, toProcess, mAdapter.mListener);
+ mController.loadSearchResults(mAccount, mCurrentFolder.name, toProcess, mAdapter.mListener);
}
return;
}
@@ -754,56 +690,58 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
private void decodeArguments() {
Bundle args = getArguments();
- mQueryString = args.getString(SearchManager.QUERY);
- mFolderName = args.getString(ARG_FOLDER);
mRemoteSearch = args.getBoolean(ARG_REMOTE_SEARCH, false);
- mSearchAccount = args.getString(ARG_SEARCH_ACCOUNT);
- mSearchFolder = args.getString(ARG_SEARCH_FOLDER);
- mThreadId = args.getLong(ARG_THREAD_ID, -1);
-
- String accountUuid = args.getString(ARG_ACCOUNT);
-
- Context appContext = getActivity().getApplicationContext();
- mAccount = Preferences.getPreferences(appContext).getAccount(accountUuid);
-
- String queryFlags = args.getString(ARG_QUERY_FLAGS);
- if (queryFlags != null) {
- String[] flagStrings = queryFlags.split(",");
- mQueryFlags = new Flag[flagStrings.length];
- for (int i = 0; i < flagStrings.length; i++) {
- mQueryFlags[i] = Flag.valueOf(flagStrings[i]);
- }
- }
-
- String forbiddenFlags = args.getString(ARG_FORBIDDEN_FLAGS);
- if (forbiddenFlags != null) {
- String[] flagStrings = forbiddenFlags.split(",");
- mForbiddenFlags = new Flag[flagStrings.length];
- for (int i = 0; i < flagStrings.length; i++) {
- mForbiddenFlags[i] = Flag.valueOf(flagStrings[i]);
- }
- }
-
- mIntegrate = args.getBoolean(ARG_INTEGRATE, false);
- mAccountUuids = args.getStringArray(ARG_ACCOUNT_UUIDS);
- mFolderNames = args.getStringArray(ARG_FOLDER_NAMES);
- mTitle = args.getString(ARG_TITLE);
+ mSearch = args.getParcelable(ARG_SEARCH);
+ mTitle = args.getString(mSearch.getName());
+
+ Context appContext = getActivity().getApplicationContext();
+ String[] accounts = mSearch.getAccountUuids();
+
+ mSingleAccountMode = false;
+ if (accounts != null && accounts.length == 1
+ && !accounts[0].equals(SearchSpecification.ALL_ACCOUNTS)) {
+ mSingleAccountMode = true;
+ mAccount = Preferences.getPreferences(appContext).getAccount(accounts[0]);
+ }
+
+ mSingleFolderMode = false;
+ if (mSingleAccountMode && (mSearch.getFolderNames().size() == 1)) {
+ mSingleFolderMode = true;
+ mFolderName = mSearch.getFolderNames().get(0);
+ mCurrentFolder = getFolder(mFolderName, mAccount);
+ }
}
private void initializeMessageList() {
mAdapter = new MessageListAdapter();
if (mFolderName != null) {
- mCurrentFolder = mAdapter.getFolder(mFolderName, mAccount);
+ mCurrentFolder = getFolder(mFolderName, mAccount);
}
-
- // Hide "Load up to x more" footer for search views
- mFooterView.setVisibility((mQueryString != null) ? View.GONE : View.VISIBLE);
+
+ // Hide "Load up to x more" footer for search views
+ mFooterView.setVisibility((!mSingleFolderMode) ? View.GONE : View.VISIBLE);
mController = MessagingController.getInstance(getActivity().getApplication());
mListView.setAdapter(mAdapter);
}
+ private FolderInfoHolder getFolder(String folder, Account account) {
+ LocalFolder local_folder = null;
+ try {
+ LocalStore localStore = account.getLocalStore();
+ local_folder = localStore.getFolder(folder);
+ return new FolderInfoHolder(mContext, local_folder, account);
+ } catch (Exception e) {
+ Log.e(K9.LOG_TAG, "getFolder(" + folder + ") goes boom: ", e);
+ return null;
+ } finally {
+ if (local_folder != null) {
+ local_folder.close();
+ }
+ }
+ }
+
@Override
public void onPause() {
super.onPause();
@@ -851,14 +789,6 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
final Preferences prefs = Preferences.getPreferences(appContext);
- boolean allowRemoteSearch = false;
- if (mSearchAccount != null) {
- final Account searchAccount = prefs.getAccount(mSearchAccount);
- if (searchAccount != null) {
- allowRemoteSearch = searchAccount.allowRemoteSearch();
- }
- }
-
// Check if we have connectivity. Cache the value.
if (mHasConnectivity == null) {
final ConnectivityManager connectivityManager =
@@ -872,24 +802,26 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
}
}
- if (mQueryString == null) {
- mPullToRefreshView.setOnRefreshListener(new PullToRefreshBase.OnRefreshListener() {
- @Override
- public void onRefresh(PullToRefreshBase refreshView) {
- checkMail();
- }
- });
- } else if (allowRemoteSearch && !mRemoteSearch && !mIntegrate && mHasConnectivity) {
- // mQueryString != null is implied if we get this far.
- mPullToRefreshView.setOnRefreshListener(new PullToRefreshBase.OnRefreshListener() {
- @Override
- public void onRefresh(PullToRefreshBase refreshView) {
- mPullToRefreshView.onRefreshComplete();
- onRemoteSearchRequested(true);
- }
- });
- mPullToRefreshView.setPullLabel(getString(R.string.pull_to_refresh_remote_search_from_local_search_pull));
- mPullToRefreshView.setReleaseLabel(getString(R.string.pull_to_refresh_remote_search_from_local_search_release));
+ if (mSingleFolderMode) {
+ if (!mAccount.allowRemoteSearch()) {
+ mPullToRefreshView.setOnRefreshListener(new PullToRefreshBase.OnRefreshListener() {
+ @Override
+ public void onRefresh(PullToRefreshBase refreshView) {
+ checkMail();
+ }
+ });
+ // TODO this has to go! find better remote search integration
+ } else {
+ mPullToRefreshView.setOnRefreshListener(new PullToRefreshBase.OnRefreshListener() {
+ @Override
+ public void onRefresh(PullToRefreshBase refreshView) {
+ mPullToRefreshView.onRefreshComplete();
+ onRemoteSearchRequested();
+ }
+ });
+ mPullToRefreshView.setPullLabel(getString(R.string.pull_to_refresh_remote_search_from_local_search_pull));
+ mPullToRefreshView.setReleaseLabel(getString(R.string.pull_to_refresh_remote_search_from_local_search_release));
+ }
} else {
mPullToRefreshView.setMode(PullToRefreshBase.Mode.DISABLED);
}
@@ -899,7 +831,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
//Cancel pending new mail notifications when we open an account
Account[] accountsWithNotification;
- Account account = getCurrentAccount(prefs);
+ Account account = mAccount;
if (account != null) {
accountsWithNotification = new Account[] { account };
@@ -918,9 +850,40 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
}
if (mAdapter.isEmpty()) {
+ mController.searchLocalMessages(mSearch, mAdapter.mListener);
if (mRemoteSearch) {
//TODO: Support flag based search
- mRemoteSearchFuture = mController.searchRemoteMessages(mSearchAccount, mSearchFolder, mQueryString, null, null, mAdapter.mListener);
+ /* mRemoteSearchFuture = mController.searchRemoteMessages(mAccount.getUuid(), mCurrentFolder.name, mSearch.getRemoteSearchArguments(),
+ null, null, mAdapter.mListener);*/
+ }
+ } else {
+ // reread the selected date format preference in case it has changed
+ mMessageHelper.refresh();
+ mAdapter.markAllMessagesAsDirty();
+
+ new Thread() {
+ @Override
+ public void run() {
+ mController.searchLocalMessages(mSearch, mAdapter.mListener);
+
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mAdapter.pruneDirtyMessages();
+ mAdapter.notifyDataSetChanged();
+ restoreListState();
+ }
+ });
+ }
+
+ }.start();
+ }
+
+ /*if (mAdapter.isEmpty()) {
+ if (mRemoteSearch) {
+ //TODO: Support flag based search
+ mRemoteSearchFuture = mController.searchRemoteMessages(mAccount.getUuid(), mCurrentFolder.name, mSearch.getRemoteSearchArguments(),
+ null, null, mAdapter.mListener);
} else if (mFolderName != null) {
mController.listLocalMessages(mAccount, mFolderName, mAdapter.mListener, mThreadViewEnabled, mThreadId);
@@ -928,8 +891,8 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
if (!mAccount.hasArchiveFolder()) {
// mBatchArchiveButton.setVisibility(View.GONE);
}
- } else if (mQueryString != null) {
- mController.searchLocalMessages(mAccountUuids, mFolderNames, null, mQueryString, mIntegrate, mQueryFlags, mForbiddenFlags, mAdapter.mListener);
+ } else {
+ mController.searchLocalMessages(mSearch, mAdapter.mListener);
// Don't show the archive button if this is a search.
// mBatchArchiveButton.setVisibility(View.GONE);
}
@@ -944,11 +907,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
new Thread() {
@Override
public void run() {
- if (mFolderName != null) {
- mController.listLocalMessagesSynchronous(mAccount, mFolderName, mAdapter.mListener, mThreadViewEnabled, mThreadId);
- } else if (mQueryString != null) {
- mController.searchLocalMessagesSynchronous(mAccountUuids, mFolderNames, null, mQueryString, mIntegrate, mQueryFlags, mForbiddenFlags, mAdapter.mListener);
- }
+ mController.searchLocalMessages(mSearch, mAdapter.mListener);
mHandler.post(new Runnable() {
@Override
@@ -963,7 +922,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
}
.start();
}
- }
+ }*/
if (mAccount != null && mFolderName != null && !mRemoteSearch) {
mController.getFolderUnreadMessageCount(mAccount, mFolderName, mAdapter.mListener);
@@ -989,7 +948,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
}
public void onCompose() {
- if (mQueryString != null) {
+ if (!mSingleAccountMode) {
/*
* If we have a query string, we don't have an account to let
* compose start the default action.
@@ -1023,22 +982,15 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
/**
* User has requested a remote search. Setup the bundle and start the intent.
- * @param fromLocalSearch true if this is being called from a local search result screen. This affects
- * where we pull the account and folder info used for the next search.
*/
- public void onRemoteSearchRequested(final boolean fromLocalSearch) {
+ public void onRemoteSearchRequested() {
String searchAccount;
String searchFolder;
- if (fromLocalSearch) {
- searchAccount = mSearchAccount;
- searchFolder = mSearchFolder;
- } else {
- searchAccount = mAccount.getUuid();
- searchFolder = mCurrentFolder.name;
- }
+ searchAccount = mAccount.getUuid();
+ searchFolder = mCurrentFolder.name;
- mFragmentListener.remoteSearch(searchAccount, searchFolder, mQueryString);
+ mFragmentListener.remoteSearch(searchAccount, searchFolder, mSearch.getRemoteSearchArguments());
}
/**
@@ -1055,7 +1007,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
mSortType = sortType;
Preferences prefs = Preferences.getPreferences(getActivity().getApplicationContext());
- Account account = getCurrentAccount(prefs);
+ Account account = mAccount;
if (account != null) {
account.setSortType(mSortType);
@@ -1247,7 +1199,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
}
}
- if (mQueryString != null) {
+ if (!mSingleAccountMode) {
// None of the options after this point are "safe" for search results
//TODO: This is not true for "unread" and "starred" searches in regular folders
return false;
@@ -1531,7 +1483,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
@Override
public void listLocalMessagesStarted(Account account, String folder) {
- if ((mQueryString != null && folder == null) || (account != null && account.equals(mAccount))) {
+ if ((!mSingleAccountMode && folder == null) || (account != null && account.equals(mAccount))) {
mHandler.progress(true);
if (folder != null) {
mHandler.folderLoading(folder, true);
@@ -1541,7 +1493,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
@Override
public void listLocalMessagesFailed(Account account, String folder, String message) {
- if ((mQueryString != null && folder == null) || (account != null && account.equals(mAccount))) {
+ if ((!mSingleAccountMode && folder == null) || (account != null && account.equals(mAccount))) {
mHandler.sortMessages();
mHandler.progress(false);
if (folder != null) {
@@ -1552,7 +1504,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
@Override
public void listLocalMessagesFinished(Account account, String folder) {
- if ((mQueryString != null && folder == null) || (account != null && account.equals(mAccount))) {
+ if ((!mSingleAccountMode && folder == null) || (account != null && account.equals(mAccount))) {
mHandler.sortMessages();
mHandler.progress(false);
if (folder != null) {
@@ -1601,12 +1553,17 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
}
};
- private boolean updateForMe(Account account, String folder) {
+ private boolean updateForMe(Account account, String folder) {
+ // TODO get a contentprovider :D
+ return true;
+
+ /*
if ((account.equals(mAccount) && mFolderName != null && folder.equals(mFolderName))) {
return true;
} else {
return false;
}
+ */
}
public List getMessages() {
@@ -1726,7 +1683,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
}
public void resetUnreadCount() {
- if (mQueryString != null) {
+ if (!mSingleFolderMode) {
int unreadCount = 0;
for (MessageInfoHolder holder : mMessages) {
@@ -1802,7 +1759,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
} else {
messagesToAdd.add(m);
}
- } else {
+ } /*else {
if (mQueryString != null) {
if (verifyAgainstSearch) {
messagesToSearch.add(message);
@@ -1815,7 +1772,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
messagesToAdd.add(m);
}
}
- }
+ }*/
} else {
m.dirty = false; // as we reload the message, unset its dirty flag
FolderInfoHolder folderInfoHolder = new FolderInfoHolder(mContext,
@@ -1827,9 +1784,29 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
}
if (!messagesToSearch.isEmpty()) {
- mController.searchLocalMessages(mAccountUuids, mFolderNames,
- messagesToSearch.toArray(EMPTY_MESSAGE_ARRAY), mQueryString, mIntegrate,
- mQueryFlags, mForbiddenFlags,
+ // building a tree with all possible message id's we want to search in
+ ConditionsTreeNode msgIdTree = new ConditionsTreeNode(
+ new SearchCondition(SEARCHFIELD.UID, ATTRIBUTE.EQUALS,
+ String.valueOf(messagesToSearch.get(0).getUid())));
+
+ if (messagesToSearch.size() > 1) {
+ for(int i=1; i
Date: Tue, 16 Oct 2012 15:46:40 +0200
Subject: [PATCH 17/74] Switched to ContentProvider and CursorLoader to display
the message list
---
AndroidManifest.xml | 5 +
.../fsck/k9/fragment/MessageListFragment.java | 1872 +++++++----------
src/com/fsck/k9/helper/MessageHelper.java | 24 +
src/com/fsck/k9/mail/store/LocalStore.java | 3 +
.../fsck/k9/mail/store/LockableDatabase.java | 2 +-
src/com/fsck/k9/provider/EmailProvider.java | 246 +++
6 files changed, 1053 insertions(+), 1099 deletions(-)
create mode 100644 src/com/fsck/k9/provider/EmailProvider.java
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index d6f8aa48a..2a951fb0e 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -402,6 +402,11 @@ otherwise it would make K-9 start at the wrong time
android:readPermission="com.fsck.k9.permission.READ_MESSAGES"
android:writePermission="com.fsck.k9.permission.DELETE_MESSAGES"
/>
+
{
+
+ private static final String[] PROJECTION = {
+ MessageColumns.ID,
+ MessageColumns.UID,
+ MessageColumns.INTERNAL_DATE,
+ MessageColumns.SUBJECT,
+ MessageColumns.DATE,
+ MessageColumns.SENDER_LIST,
+ MessageColumns.TO_LIST,
+ MessageColumns.CC_LIST,
+ MessageColumns.FLAGS,
+ MessageColumns.ATTACHMENT_COUNT,
+ MessageColumns.FOLDER_ID,
+ MessageColumns.PREVIEW,
+ MessageColumns.THREAD_ROOT,
+ MessageColumns.THREAD_PARENT
+ };
+
+ private static final int ID_COLUMN = 0;
+ private static final int UID_COLUMN = 1;
+ private static final int INTERNAL_DATE_COLUMN = 2;
+ private static final int SUBJECT_COLUMN = 3;
+ private static final int DATE_COLUMN = 4;
+ private static final int SENDER_LIST_COLUMN = 5;
+ private static final int TO_LIST_COLUMN = 6;
+ private static final int CC_LIST_COLUMN = 7;
+ private static final int FLAGS_COLUMN = 8;
+ private static final int ATTACHMENT_COUNT_COLUMN = 9;
+ private static final int FOLDER_ID_COLUMN = 10;
+ private static final int PREVIEW_COLUMN = 11;
+ private static final int THREAD_ROOT_COLUMN = 12;
+ private static final int THREAD_PARENT_COLUMN = 13;
+
public static MessageListFragment newInstance(Account account, String folderName) {
MessageListFragment fragment = new MessageListFragment();
@@ -283,11 +323,6 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
}
- /**
- * Immutable empty {@link Message} array
- */
- private static final Message[] EMPTY_MESSAGE_ARRAY = new Message[0];
-
private static final int ACTIVITY_CHOOSE_FOLDER_MOVE = 1;
private static final int ACTIVITY_CHOOSE_FOLDER_COPY = 2;
@@ -376,6 +411,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
private boolean mSenderAboveSubject = false;
private int mSelectedCount = 0;
+ private SparseBooleanArray mSelected;
private FontSizes mFontSizes = K9.getFontSizes();
@@ -391,7 +427,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
* chosen messages between user interactions (eg. Selecting a folder for
* move operation)
*/
- private List mActiveMessages;
+ private Message[] mActiveMessages;
/* package visibility for faster inner class access */
MessageHelper mMessageHelper;
@@ -412,6 +448,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
private Context mContext;
+ private final ActivityListener mListener = new MessageListActivityListener();
/**
* This class is used to run operations that modify UI elements in the UI thread.
@@ -423,20 +460,12 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
* perform the operation in the calling thread.
*/
class MessageListHandler extends Handler {
- private static final int ACTION_REMOVE_MESSAGE = 1;
- private static final int ACTION_RESET_UNREAD_COUNT = 2;
- private static final int ACTION_SORT_MESSAGES = 3;
- private static final int ACTION_FOLDER_LOADING = 4;
- private static final int ACTION_REFRESH_TITLE = 5;
- private static final int ACTION_PROGRESS = 6;
+ private static final int ACTION_SORT_MESSAGES = 1;
+ private static final int ACTION_FOLDER_LOADING = 2;
+ private static final int ACTION_REFRESH_TITLE = 3;
+ private static final int ACTION_PROGRESS = 4;
- public void removeMessage(MessageReference messageReference) {
- android.os.Message msg = android.os.Message.obtain(this, ACTION_REMOVE_MESSAGE,
- messageReference);
- sendMessage(msg);
- }
-
public void sortMessages() {
android.os.Message msg = android.os.Message.obtain(this, ACTION_SORT_MESSAGES);
sendMessage(msg);
@@ -469,43 +498,9 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
});
}
- public void changeMessageUid(final MessageReference ref, final String newUid) {
- // Instead of explicitly creating a container to be able to pass both arguments in a
- // Message we post a Runnable to the message queue.
- post(new Runnable() {
- @Override
- public void run() {
- mAdapter.changeMessageUid(ref, newUid);
- }
- });
- }
-
- public void addOrUpdateMessages(final Account account, final String folderName,
- final List providedMessages, final boolean verifyAgainstSearch) {
- // We copy the message list because it's later modified by MessagingController
- final List messages = new ArrayList(providedMessages);
-
- post(new Runnable() {
- @Override
- public void run() {
- mAdapter.addOrUpdateMessages(account, folderName, messages,
- verifyAgainstSearch);
- }
- });
- }
-
@Override
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
- case ACTION_REMOVE_MESSAGE: {
- MessageReference messageReference = (MessageReference) msg.obj;
- mAdapter.removeMessage(messageReference);
- break;
- }
- case ACTION_RESET_UNREAD_COUNT: {
- mAdapter.resetUnreadCount();
- break;
- }
case ACTION_SORT_MESSAGES: {
mAdapter.sortMessages();
break;
@@ -581,10 +576,10 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
private void setWindowProgress() {
int level = Window.PROGRESS_END;
- if (mCurrentFolder != null && mCurrentFolder.loading && mAdapter.mListener.getFolderTotal() > 0) {
- int divisor = mAdapter.mListener.getFolderTotal();
+ if (mCurrentFolder != null && mCurrentFolder.loading && mListener.getFolderTotal() > 0) {
+ int divisor = mListener.getFolderTotal();
if (divisor != 0) {
- level = (Window.PROGRESS_END / divisor) * (mAdapter.mListener.getFolderCompleted()) ;
+ level = (Window.PROGRESS_END / divisor) * (mListener.getFolderCompleted()) ;
if (level > Window.PROGRESS_END) {
level = Window.PROGRESS_END;
}
@@ -603,7 +598,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
mFragmentListener.setMessageListTitle(displayName);
- String operation = mAdapter.mListener.getOperation(activity, getTimeFormat()).trim();
+ String operation = mListener.getOperation(activity, getTimeFormat()).trim();
if (operation.length() < 1) {
mFragmentListener.setMessageListSubTitle(mAccount.getEmail());
} else {
@@ -664,13 +659,13 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
public void onItemClick(AdapterView> parent, View view, int position, long id) {
if (view == mFooterView) {
if (mCurrentFolder != null && !mRemoteSearch) {
- mController.loadMoreMessages(mAccount, mFolderName, mAdapter.mListener);
- } else if (mRemoteSearch && mAdapter.mExtraSearchResults != null && mAdapter.mExtraSearchResults.size() > 0 && mSearchAccount != null) {
+ mController.loadMoreMessages(mAccount, mFolderName, null);
+ } /*else if (mRemoteSearch && mAdapter.mExtraSearchResults != null && mAdapter.mExtraSearchResults.size() > 0 && mSearchAccount != null) {
int numResults = mAdapter.mExtraSearchResults.size();
Context appContext = getActivity().getApplicationContext();
Account account = Preferences.getPreferences(appContext).getAccount(mSearchAccount);
if (account == null) {
- mHandler.updateFooter("", false);
+ updateFooter("", false);
return;
}
int limit = account.getRemoteSearchNumResults();
@@ -680,22 +675,26 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
mAdapter.mExtraSearchResults = mAdapter.mExtraSearchResults.subList(limit, mAdapter.mExtraSearchResults.size());
} else {
mAdapter.mExtraSearchResults = null;
- mHandler.updateFooter("", false);
+ updateFooter("", false);
}
mController.loadSearchResults(account, mSearchFolder, toProcess, mAdapter.mListener);
- }
+ }*/
return;
}
- final MessageInfoHolder message = (MessageInfoHolder) parent.getItemAtPosition(position);
+ Cursor cursor = (Cursor) parent.getItemAtPosition(position);
if (mSelectedCount > 0) {
- toggleMessageSelect(message);
- } else if (message.threadCount > 1) {
- Folder folder = message.message.getFolder();
- long rootId = ((LocalMessage) message.message).getRootId();
- mFragmentListener.showThread(folder.getAccount(), folder.getName(), rootId);
+ toggleMessageSelect(position);
+// } else if (message.threadCount > 1) {
+// Folder folder = message.message.getFolder();
+// long rootId = ((LocalMessage) message.message).getRootId();
+// mFragmentListener.showThread(folder.getAccount(), folder.getName(), rootId);
} else {
- onOpenMessage(message);
+ MessageReference ref = new MessageReference();
+ ref.accountUuid = mAccount.getUuid();
+ ref.folderName = mCurrentFolder.name;
+ ref.uid = cursor.getString(UID_COLUMN);
+ onOpenMessage(ref);
}
}
@@ -749,6 +748,8 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
mMessageHelper = MessageHelper.getInstance(getActivity());
initializeMessageList();
+
+ getLoaderManager().initLoader(0, null, this);
}
private void decodeArguments() {
@@ -794,7 +795,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
mAdapter = new MessageListAdapter();
if (mFolderName != null) {
- mCurrentFolder = mAdapter.getFolder(mFolderName, mAccount);
+ mCurrentFolder = getFolder(mFolderName, mAccount);
}
// Hide "Load up to x more" footer for search views
@@ -804,10 +805,26 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
mListView.setAdapter(mAdapter);
}
+ private FolderInfoHolder getFolder(String folder, Account account) {
+ LocalFolder local_folder = null;
+ try {
+ LocalStore localStore = account.getLocalStore();
+ local_folder = localStore.getFolder(folder);
+ return new FolderInfoHolder(mContext, local_folder, account);
+ } catch (Exception e) {
+ Log.e(K9.LOG_TAG, "getFolder(" + folder + ") goes boom: ", e);
+ return null;
+ } finally {
+ if (local_folder != null) {
+ local_folder.close();
+ }
+ }
+ }
+
@Override
public void onPause() {
super.onPause();
- mController.removeListener(mAdapter.mListener);
+ mController.removeListener(mListener);
saveListState();
}
@@ -894,7 +911,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
mPullToRefreshView.setMode(PullToRefreshBase.Mode.DISABLED);
}
- mController.addListener(mAdapter.mListener);
+ mController.addListener(mListener);
//Cancel pending new mail notifications when we open an account
Account[] accountsWithNotification;
@@ -917,6 +934,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
mController.notifyAccountCancel(appContext, accountWithNotification);
}
+ /*
if (mAdapter.isEmpty()) {
if (mRemoteSearch) {
//TODO: Support flag based search
@@ -964,9 +982,10 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
.start();
}
}
+ */
if (mAccount != null && mFolderName != null && !mRemoteSearch) {
- mController.getFolderUnreadMessageCount(mAccount, mFolderName, mAdapter.mListener);
+ mController.getFolderUnreadMessageCount(mAccount, mFolderName, mListener);
}
refreshTitle();
@@ -980,12 +999,13 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
mListView.setScrollingCacheEnabled(false);
mListView.setOnItemClickListener(this);
mListView.addFooterView(getFooterView(mListView));
+ //mListView.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE);
registerForContextMenu(mListView);
}
- private void onOpenMessage(MessageInfoHolder message) {
- mFragmentListener.openMessage(message.message.makeMessageReference());
+ private void onOpenMessage(MessageReference reference) {
+ mFragmentListener.openMessage(reference);
}
public void onCompose() {
@@ -1000,20 +1020,20 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
}
}
- public void onReply(MessageInfoHolder holder) {
- mFragmentListener.onReply(holder.message);
+ public void onReply(Message message) {
+ mFragmentListener.onReply(message);
}
- public void onReplyAll(MessageInfoHolder holder) {
- mFragmentListener.onReplyAll(holder.message);
+ public void onReplyAll(Message message) {
+ mFragmentListener.onReplyAll(message);
}
- public void onForward(MessageInfoHolder holder) {
- mFragmentListener.onForward(holder.message);
+ public void onForward(Message message) {
+ mFragmentListener.onForward(message);
}
- public void onResendMessage(MessageInfoHolder holder) {
- mFragmentListener.onResendMessage(holder.message);
+ public void onResendMessage(Message message) {
+ mFragmentListener.onResendMessage(message);
}
public void changeSort(SortType sortType) {
@@ -1117,17 +1137,12 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
changeSort(sorts[curIndex]);
}
- /**
- * @param holders
- * Never {@code null}.
- */
- private void onDelete(final List holders) {
- final List messagesToRemove = new ArrayList();
- for (MessageInfoHolder holder : holders) {
- messagesToRemove.add(holder.message);
- }
- mAdapter.removeMessages(holders);
- mController.deleteMessages(messagesToRemove.toArray(EMPTY_MESSAGE_ARRAY), null);
+ private void onDelete(Message message) {
+ onDelete(new Message[] { message });
+ }
+
+ private void onDelete(Message[] messages) {
+ mController.deleteMessages(messages, null);
}
@Override
@@ -1144,22 +1159,22 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
}
final String destFolderName = data.getStringExtra(ChooseFolder.EXTRA_NEW_FOLDER);
- final List holders = mActiveMessages;
+ final Message[] messages = mActiveMessages;
if (destFolderName != null) {
mActiveMessages = null; // don't need it any more
- final Account account = holders.get(0).message.getFolder().getAccount();
+ final Account account = messages[0].getFolder().getAccount();
account.setLastSelectedFolderName(destFolderName);
switch (requestCode) {
case ACTIVITY_CHOOSE_FOLDER_MOVE:
- move(holders, destFolderName);
+ move(messages, destFolderName);
break;
case ACTIVITY_CHOOSE_FOLDER_COPY:
- copy(holders, destFolderName);
+ copy(messages, destFolderName);
break;
}
}
@@ -1184,7 +1199,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
case R.id.dialog_confirm_spam: {
String title = getString(R.string.dialog_confirm_spam_title);
- int selectionSize = mActiveMessages.size();
+ int selectionSize = mActiveMessages.length;
String message = getResources().getQuantityString(
R.plurals.dialog_confirm_spam_message, selectionSize,
Integer.valueOf(selectionSize));
@@ -1242,7 +1257,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
return true;
}
case R.id.select_all: {
- setSelectionState(true);
+ selectAll();
return true;
}
}
@@ -1271,110 +1286,144 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
}
public void onSendPendingMessages() {
- mController.sendPendingMessages(mAccount, mAdapter.mListener);
+ mController.sendPendingMessages(mAccount, null);
}
@Override
public boolean onContextItemSelected(android.view.MenuItem item) {
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
- final MessageInfoHolder message = (MessageInfoHolder) mListView.getItemAtPosition(info.position);
+ int adapterPosition = listViewToAdapterPosition(info.position);
+ Message message = getMessageAtPosition(adapterPosition);
-
- final List selection = getSelectionFromMessage(message);
- switch (item.getItemId()) {
- case R.id.reply: {
- onReply(message);
- break;
- }
- case R.id.reply_all: {
- onReplyAll(message);
- break;
- }
- case R.id.forward: {
- onForward(message);
- break;
- }
- case R.id.send_again: {
- onResendMessage(message);
- mSelectedCount = 0;
- break;
- }
- case R.id.same_sender: {
- mFragmentListener.showMoreFromSameSender(message.senderAddress);
- break;
- }
- case R.id.delete: {
- onDelete(selection);
- break;
- }
- case R.id.mark_as_read: {
- setFlag(selection, Flag.SEEN, true);
- break;
- }
- case R.id.mark_as_unread: {
- setFlag(selection, Flag.SEEN, false);
- break;
- }
- case R.id.flag: {
- setFlag(selection, Flag.FLAGGED, true);
- break;
- }
- case R.id.unflag: {
- setFlag(selection, Flag.FLAGGED, false);
- break;
- }
-
- // only if the account supports this
- case R.id.archive: {
- onArchive(selection);
- break;
- }
- case R.id.spam: {
- onSpam(selection);
- break;
- }
- case R.id.move: {
- onMove(selection);
- break;
- }
- case R.id.copy: {
- onCopy(selection);
- break;
+ switch (item.getItemId()) {
+ case R.id.reply: {
+ onReply(message);
+ break;
+ }
+ case R.id.reply_all: {
+ onReplyAll(message);
+ break;
+ }
+ case R.id.forward: {
+ onForward(message);
+ break;
+ }
+ case R.id.send_again: {
+ onResendMessage(message);
+ mSelectedCount = 0;
+ break;
+ }
+ case R.id.same_sender: {
+ Cursor cursor = (Cursor) mAdapter.getItem(adapterPosition);
+ String senderAddress = getSenderAddressFromCursor(cursor);
+ if (senderAddress != null) {
+ mFragmentListener.showMoreFromSameSender(senderAddress);
}
+ break;
+ }
+ case R.id.delete: {
+ onDelete(message);
+ break;
+ }
+ case R.id.mark_as_read: {
+ setFlag(message, Flag.SEEN, true);
+ break;
+ }
+ case R.id.mark_as_unread: {
+ setFlag(message, Flag.SEEN, false);
+ break;
+ }
+ case R.id.flag: {
+ setFlag(message, Flag.FLAGGED, true);
+ break;
+ }
+ case R.id.unflag: {
+ setFlag(message, Flag.FLAGGED, false);
+ break;
}
- return true;
+ // only if the account supports this
+ case R.id.archive: {
+ onArchive(message);
+ break;
+ }
+ case R.id.spam: {
+ onSpam(message);
+ break;
+ }
+ case R.id.move: {
+ onMove(message);
+ break;
+ }
+ case R.id.copy: {
+ onCopy(message);
+ break;
+ }
}
+ return true;
+ }
+
+
+ private String getSenderAddressFromCursor(Cursor cursor) {
+ String fromList = cursor.getString(SENDER_LIST_COLUMN);
+ Address[] fromAddrs = Address.unpack(fromList);
+ return (fromAddrs.length > 0) ? fromAddrs[0].getAddress() : null;
+ }
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
- MessageInfoHolder message = (MessageInfoHolder) mListView.getItemAtPosition(info.position);
+ Cursor cursor = (Cursor) mListView.getItemAtPosition(info.position);
- if (message == null) {
+ if (cursor == null) {
return;
}
getActivity().getMenuInflater().inflate(R.menu.message_list_item_context, menu);
- menu.setHeaderTitle(message.message.getSubject());
+ //TODO: get account from cursor
+ Account account = mAccount;
- if (message.read) {
+ String subject = cursor.getString(SUBJECT_COLUMN);
+ String flagList = cursor.getString(FLAGS_COLUMN);
+ String[] flags = flagList.split(",");
+ boolean read = false;
+ boolean flagged = false;
+ for (int i = 0, len = flags.length; i < len; i++) {
+ try {
+ switch (Flag.valueOf(flags[i])) {
+ case SEEN: {
+ read = true;
+ break;
+ }
+ case FLAGGED: {
+ flagged = true;
+ break;
+ }
+ default: {
+ // We don't care about the other flags
+ }
+ }
+ } catch (Exception e) { /* ignore */ }
+ }
+
+ menu.setHeaderTitle(subject);
+
+ if (read) {
menu.findItem(R.id.mark_as_read).setVisible(false);
} else {
menu.findItem(R.id.mark_as_unread).setVisible(false);
}
- if (message.flagged) {
+ if (flagged) {
menu.findItem(R.id.flag).setVisible(false);
} else {
menu.findItem(R.id.unflag).setVisible(false);
}
- Account account = message.message.getFolder().getAccount();
if (!mController.isCopyCapable(account)) {
menu.findItem(R.id.copy).setVisible(false);
}
@@ -1413,209 +1462,155 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
private void handleSwipe(final MotionEvent downMotion, final boolean selected) {
int[] listPosition = new int[2];
mListView.getLocationOnScreen(listPosition);
- int position = mListView.pointToPosition((int) downMotion.getRawX() - listPosition[0], (int) downMotion.getRawY() - listPosition[1]);
- if (position != AdapterView.INVALID_POSITION) {
- final MessageInfoHolder message = (MessageInfoHolder) mListView.getItemAtPosition(position);
- toggleMessageSelect(message);
+
+ int listX = (int) downMotion.getRawX() - listPosition[0];
+ int listY = (int) downMotion.getRawY() - listPosition[1];
+
+ int listViewPosition = mListView.pointToPosition(listX, listY);
+
+ toggleMessageSelect(listViewPosition);
+ }
+
+ private int listViewToAdapterPosition(int position) {
+ if (position > 0 && position <= mAdapter.getCount()) {
+ return position - 1;
+ }
+
+ return AdapterView.INVALID_POSITION;
+ }
+
+ class MessageListActivityListener extends ActivityListener {
+ @Override
+ public void remoteSearchFailed(Account acct, String folder, final String err) {
+ //TODO: Better error handling
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(getActivity(), err, Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+
+ @Override
+ public void remoteSearchStarted(Account acct, String folder) {
+ mHandler.progress(true);
+ mHandler.updateFooter(mContext.getString(R.string.remote_search_sending_query), true);
+ }
+
+
+ @Override
+ public void remoteSearchFinished(Account acct, String folder, int numResults, List extraResults) {
+ mHandler.progress(false);
+ if (extraResults != null && extraResults.size() > 0) {
+ mHandler.updateFooter(String.format(mContext.getString(R.string.load_more_messages_fmt), acct.getRemoteSearchNumResults()), false);
+ } else {
+ mHandler.updateFooter("", false);
+ }
+ mFragmentListener.setMessageListProgress(Window.PROGRESS_END);
+
+ }
+
+ @Override
+ public void remoteSearchServerQueryComplete(Account account, String folderName, int numResults) {
+ mHandler.progress(true);
+ if (account != null && account.getRemoteSearchNumResults() != 0 && numResults > account.getRemoteSearchNumResults()) {
+ mHandler.updateFooter(mContext.getString(R.string.remote_search_downloading_limited, account.getRemoteSearchNumResults(), numResults), true);
+ } else {
+ mHandler.updateFooter(mContext.getString(R.string.remote_search_downloading, numResults), true);
+ }
+ mFragmentListener.setMessageListProgress(Window.PROGRESS_START);
+ }
+
+ @Override
+ public void informUserOfStatus() {
+ mHandler.refreshTitle();
+ }
+
+ @Override
+ public void synchronizeMailboxStarted(Account account, String folder) {
+ if (updateForMe(account, folder)) {
+ mHandler.progress(true);
+ mHandler.folderLoading(folder, true);
+ }
+ super.synchronizeMailboxStarted(account, folder);
+ }
+
+ @Override
+ public void synchronizeMailboxFinished(Account account, String folder,
+ int totalMessagesInMailbox, int numNewMessages) {
+
+ if (updateForMe(account, folder)) {
+ mHandler.progress(false);
+ mHandler.folderLoading(folder, false);
+ mHandler.sortMessages();
+ }
+ super.synchronizeMailboxFinished(account, folder, totalMessagesInMailbox, numNewMessages);
+ }
+
+ @Override
+ public void synchronizeMailboxFailed(Account account, String folder, String message) {
+
+ if (updateForMe(account, folder)) {
+ mHandler.progress(false);
+ mHandler.folderLoading(folder, false);
+ mHandler.sortMessages();
+ }
+ super.synchronizeMailboxFailed(account, folder, message);
+ }
+
+ @Override
+ public void listLocalMessagesStarted(Account account, String folder) {
+ if ((mQueryString != null && folder == null) || (account != null && account.equals(mAccount))) {
+ mHandler.progress(true);
+ if (folder != null) {
+ mHandler.folderLoading(folder, true);
+ }
+ }
+ }
+
+ @Override
+ public void listLocalMessagesFailed(Account account, String folder, String message) {
+ if ((mQueryString != null && folder == null) || (account != null && account.equals(mAccount))) {
+ mHandler.sortMessages();
+ mHandler.progress(false);
+ if (folder != null) {
+ mHandler.folderLoading(folder, false);
+ }
+ }
+ }
+
+ @Override
+ public void listLocalMessagesFinished(Account account, String folder) {
+ if ((mQueryString != null && folder == null) || (account != null && account.equals(mAccount))) {
+ mHandler.sortMessages();
+ mHandler.progress(false);
+ if (folder != null) {
+ mHandler.folderLoading(folder, false);
+ }
+ }
+ }
+
+ @Override
+ public void searchStats(AccountStats stats) {
+ mUnreadMessageCount = stats.unreadMessageCount;
+ super.searchStats(stats);
+ }
+
+ @Override
+ public void folderStatusChanged(Account account, String folder, int unreadMessageCount) {
+ if (updateForMe(account, folder)) {
+ mUnreadMessageCount = unreadMessageCount;
+ }
+ super.folderStatusChanged(account, folder, unreadMessageCount);
+ }
+
+ private boolean updateForMe(Account account, String folder) {
+ return ((account.equals(mAccount) && folder.equals(mFolderName)));
}
}
- class MessageListAdapter extends BaseAdapter {
- private final List mMessages =
- Collections.synchronizedList(new ArrayList());
- public List mExtraSearchResults;
-
- private final ActivityListener mListener = new ActivityListener() {
-
- @Override
- public void remoteSearchAddMessage(Account account, String folderName, Message message, final int numDone, final int numTotal) {
-
- if (numTotal > 0 && numDone < numTotal) {
- mFragmentListener.setMessageListProgress(Window.PROGRESS_END / numTotal * numDone);
- } else {
- mFragmentListener.setMessageListProgress(Window.PROGRESS_END);
- }
-
- mHandler.addOrUpdateMessages(account, folderName, Collections.singletonList(message), false);
- }
-
- @Override
- public void remoteSearchFailed(Account acct, String folder, final String err) {
- //TODO: Better error handling
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- Toast.makeText(getActivity(), err, Toast.LENGTH_LONG).show();
- }
- });
- }
-
- @Override
- public void remoteSearchStarted(Account acct, String folder) {
- mHandler.progress(true);
- mHandler.updateFooter(mContext.getString(R.string.remote_search_sending_query), true);
- }
-
-
- @Override
- public void remoteSearchFinished(Account acct, String folder, int numResults, List extraResults) {
- mHandler.progress(false);
- if (extraResults != null && extraResults.size() > 0) {
- mExtraSearchResults = extraResults;
- mHandler.updateFooter(String.format(mContext.getString(R.string.load_more_messages_fmt), acct.getRemoteSearchNumResults()), false);
- } else {
- mHandler.updateFooter("", false);
- }
- mFragmentListener.setMessageListProgress(Window.PROGRESS_END);
-
- }
-
- @Override
- public void remoteSearchServerQueryComplete(Account account, String folderName, int numResults) {
- mHandler.progress(true);
- if (account != null && account.getRemoteSearchNumResults() != 0 && numResults > account.getRemoteSearchNumResults()) {
- mHandler.updateFooter(mContext.getString(R.string.remote_search_downloading_limited, account.getRemoteSearchNumResults(), numResults), true);
- } else {
- mHandler.updateFooter(mContext.getString(R.string.remote_search_downloading, numResults), true);
- }
- mFragmentListener.setMessageListProgress(Window.PROGRESS_START);
- }
-
- @Override
- public void informUserOfStatus() {
- mHandler.refreshTitle();
- }
-
- @Override
- public void synchronizeMailboxStarted(Account account, String folder) {
- if (updateForMe(account, folder)) {
- mHandler.progress(true);
- mHandler.folderLoading(folder, true);
- }
- super.synchronizeMailboxStarted(account, folder);
- }
-
- @Override
- public void synchronizeMailboxFinished(Account account, String folder,
- int totalMessagesInMailbox, int numNewMessages) {
-
- if (updateForMe(account, folder)) {
- mHandler.progress(false);
- mHandler.folderLoading(folder, false);
- mHandler.sortMessages();
- }
- super.synchronizeMailboxFinished(account, folder, totalMessagesInMailbox, numNewMessages);
- }
-
- @Override
- public void synchronizeMailboxFailed(Account account, String folder, String message) {
-
- if (updateForMe(account, folder)) {
- mHandler.progress(false);
- mHandler.folderLoading(folder, false);
- mHandler.sortMessages();
- }
- super.synchronizeMailboxFailed(account, folder, message);
- }
-
- @Override
- public void synchronizeMailboxAddOrUpdateMessage(Account account, String folder, Message message) {
- mHandler.addOrUpdateMessages(account, folder, Collections.singletonList(message), true);
- }
-
- @Override
- public void synchronizeMailboxRemovedMessage(Account account, String folder, Message message) {
- mHandler.removeMessage(message.makeMessageReference());
- }
-
- @Override
- public void listLocalMessagesStarted(Account account, String folder) {
- if ((mQueryString != null && folder == null) || (account != null && account.equals(mAccount))) {
- mHandler.progress(true);
- if (folder != null) {
- mHandler.folderLoading(folder, true);
- }
- }
- }
-
- @Override
- public void listLocalMessagesFailed(Account account, String folder, String message) {
- if ((mQueryString != null && folder == null) || (account != null && account.equals(mAccount))) {
- mHandler.sortMessages();
- mHandler.progress(false);
- if (folder != null) {
- mHandler.folderLoading(folder, false);
- }
- }
- }
-
- @Override
- public void listLocalMessagesFinished(Account account, String folder) {
- if ((mQueryString != null && folder == null) || (account != null && account.equals(mAccount))) {
- mHandler.sortMessages();
- mHandler.progress(false);
- if (folder != null) {
- mHandler.folderLoading(folder, false);
- }
- }
- }
-
- @Override
- public void listLocalMessagesRemoveMessage(Account account, String folder, Message message) {
- mHandler.removeMessage(message.makeMessageReference());
- }
-
- @Override
- public void listLocalMessagesAddMessages(Account account, String folder, List messages) {
- mHandler.addOrUpdateMessages(account, folder, messages, false);
- }
-
- @Override
- public void listLocalMessagesUpdateMessage(Account account, String folder, Message message) {
- mHandler.addOrUpdateMessages(account, folder, Collections.singletonList(message), false);
- }
-
- @Override
- public void searchStats(AccountStats stats) {
- mUnreadMessageCount = stats.unreadMessageCount;
- super.searchStats(stats);
- }
-
- @Override
- public void folderStatusChanged(Account account, String folder, int unreadMessageCount) {
- if (updateForMe(account, folder)) {
- mUnreadMessageCount = unreadMessageCount;
- }
- super.folderStatusChanged(account, folder, unreadMessageCount);
- }
-
- @Override
- public void messageUidChanged(Account account, String folder, String oldUid, String newUid) {
- MessageReference ref = new MessageReference();
- ref.accountUuid = account.getUuid();
- ref.folderName = folder;
- ref.uid = oldUid;
-
- mHandler.changeMessageUid(ref, newUid);
- }
- };
-
- private boolean updateForMe(Account account, String folder) {
- if ((account.equals(mAccount) && mFolderName != null && folder.equals(mFolderName))) {
- return true;
- } else {
- return false;
- }
- }
-
- public List getMessages() {
- return mMessages;
- }
-
- public void restoreMessages(List messages) {
- mMessages.addAll(messages);
- }
+ class MessageListAdapter extends CursorAdapter {
private Drawable mAttachmentIcon;
private Drawable mForwardedIcon;
@@ -1623,576 +1618,33 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
private Drawable mForwardedAnsweredIcon;
MessageListAdapter() {
+ super(getActivity(), null, 0);
mAttachmentIcon = getResources().getDrawable(R.drawable.ic_email_attachment_small);
mAnsweredIcon = getResources().getDrawable(R.drawable.ic_email_answered_small);
mForwardedIcon = getResources().getDrawable(R.drawable.ic_email_forwarded_small);
mForwardedAnsweredIcon = getResources().getDrawable(R.drawable.ic_email_forwarded_answered_small);
}
- public void markAllMessagesAsDirty() {
- for (MessageInfoHolder holder : mMessages) {
- holder.dirty = true;
- }
- }
-
- public void pruneDirtyMessages() {
- List messagesToRemove = new ArrayList();
-
- for (MessageInfoHolder holder : mMessages) {
- if (holder.dirty) {
- messagesToRemove.add(holder);
- }
- }
- removeMessages(messagesToRemove);
- }
-
- public void removeMessage(MessageReference messageReference) {
- MessageInfoHolder holder = getMessage(messageReference);
- if (holder == null) {
- Log.w(K9.LOG_TAG, "Got callback to remove non-existent message with UID " +
- messageReference.uid);
- } else {
- removeMessages(Collections.singletonList(holder));
- }
- }
-
- public void removeMessages(final List messages) {
- if (messages.isEmpty()) {
- return;
- }
-
- for (MessageInfoHolder message : messages) {
- if (message != null && (mFolderName == null || (
- message.folder != null &&
- message.folder.name.equals(mFolderName)))) {
- if (message.selected && mSelectedCount > 0) {
- mSelectedCount--;
- }
- mMessages.remove(message);
- }
- }
- resetUnreadCount();
-
- notifyDataSetChanged();
- computeSelectAllVisibility();
- }
-
/**
* Set the selection state for all messages at once.
* @param selected Selection state to set.
*/
public void setSelectionForAllMesages(final boolean selected) {
- for (MessageInfoHolder message : mMessages) {
- message.selected = selected;
- }
+ //TODO: implement
- notifyDataSetChanged();
- }
-
- public void addMessages(final List messages) {
- if (messages.isEmpty()) {
- return;
- }
-
- final boolean wasEmpty = mMessages.isEmpty();
-
- for (final MessageInfoHolder message : messages) {
- if (mFolderName == null || (message.folder != null && message.folder.name.equals(mFolderName))) {
- int index = Collections.binarySearch(mMessages, message, getComparator());
-
- if (index < 0) {
- index = (index * -1) - 1;
- }
-
- mMessages.add(index, message);
- }
- }
-
- if (wasEmpty) {
- mListView.setSelection(0);
- }
- resetUnreadCount();
-
- notifyDataSetChanged();
- computeSelectAllVisibility();
- }
-
- public void changeMessageUid(MessageReference ref, String newUid) {
- MessageInfoHolder holder = getMessage(ref);
- if (holder != null) {
- holder.uid = newUid;
- holder.message.setUid(newUid);
- }
- }
-
- public void resetUnreadCount() {
- if (mQueryString != null) {
- int unreadCount = 0;
-
- for (MessageInfoHolder holder : mMessages) {
- unreadCount += holder.read ? 0 : 1;
- }
-
- mUnreadMessageCount = unreadCount;
- refreshTitle();
- }
+ //notifyDataSetChanged();
}
public void sortMessages() {
- final Comparator chainComparator = getComparator();
+ //TODO: implement
- Collections.sort(mMessages, chainComparator);
-
- notifyDataSetChanged();
+ //notifyDataSetChanged();
}
- public void addOrUpdateMessages(final Account account, final String folderName,
- final List messages, final boolean verifyAgainstSearch) {
-
- boolean needsSort = false;
- final List messagesToAdd = new ArrayList();
- List messagesToRemove = new ArrayList();
- List messagesToSearch = new ArrayList();
-
- // cache field into local variable for faster access for JVM without JIT
- final MessageHelper messageHelper = mMessageHelper;
-
- for (Message message : messages) {
- MessageInfoHolder m = getMessage(message);
- if (message.isSet(Flag.DELETED)) {
- if (m != null) {
- messagesToRemove.add(m);
- }
- } else {
- final Folder messageFolder = message.getFolder();
- final Account messageAccount = messageFolder.getAccount();
- if (m == null) {
- if (updateForMe(account, folderName)) {
- m = new MessageInfoHolder();
- FolderInfoHolder folderInfoHolder = new FolderInfoHolder(
- mContext, messageFolder, messageAccount);
- messageHelper.populate(m, message, folderInfoHolder, messageAccount);
-
- if (verifyAgainstSearch) {
- LocalMessage localMessage = (LocalMessage) message;
-
- if (mThreadId != -1) {
- if (localMessage.getRootId() == mThreadId ||
- localMessage.getId() == mThreadId) {
- messagesToAdd.add(m);
- }
- } else if (mThreadViewEnabled) {
- long threadId = localMessage.getRootId();
- if (threadId == -1) {
- threadId = localMessage.getId();
- }
-
- MessageInfoHolder threadPlaceHolder = getThread(threadId);
- if (threadPlaceHolder == null) {
- messagesToAdd.add(m);
- } else if (m.compareDate.after(threadPlaceHolder.compareDate)) {
- messagesToRemove.add(threadPlaceHolder);
- messagesToAdd.add(m);
- } else {
- threadPlaceHolder.threadCount = m.threadCount;
- }
- } else {
- messagesToAdd.add(m);
- }
- } else {
- messagesToAdd.add(m);
- }
- } else {
- if (mQueryString != null) {
- if (verifyAgainstSearch) {
- messagesToSearch.add(message);
- } else {
- m = new MessageInfoHolder();
- FolderInfoHolder folderInfoHolder = new FolderInfoHolder(
- mContext, messageFolder, messageAccount);
- messageHelper.populate(m, message, folderInfoHolder,
- messageAccount);
- messagesToAdd.add(m);
- }
- }
- }
- } else {
- m.dirty = false; // as we reload the message, unset its dirty flag
- FolderInfoHolder folderInfoHolder = new FolderInfoHolder(mContext,
- messageFolder, account);
- messageHelper.populate(m, message, folderInfoHolder, account);
- needsSort = true;
- }
- }
- }
-
- if (!messagesToSearch.isEmpty()) {
- mController.searchLocalMessages(mAccountUuids, mFolderNames,
- messagesToSearch.toArray(EMPTY_MESSAGE_ARRAY), mQueryString, mIntegrate,
- mQueryFlags, mForbiddenFlags,
- new MessagingListener() {
- @Override
- public void listLocalMessagesAddMessages(Account account, String folder,
- List messages) {
- mHandler.addOrUpdateMessages(account, folder, messages, false);
- }
- });
- }
-
- if (!messagesToRemove.isEmpty()) {
- removeMessages(messagesToRemove);
- }
-
- if (!messagesToAdd.isEmpty()) {
- addMessages(messagesToAdd);
- }
-
- if (needsSort) {
- sortMessages();
- resetUnreadCount();
- }
- }
-
- /**
- * Find a specific message in the message list.
- *
- * Note:
- * This method was optimized because it is called a lot. Don't change it unless you know
- * what you are doing.
- *
- * @param message
- * A {@link Message} instance describing the message to look for.
- *
- * @return The corresponding {@link MessageInfoHolder} instance if the message was found in
- * the message list. {@code null} otherwise.
- */
- private MessageInfoHolder getMessage(Message message) {
- String uid;
- Folder folder;
- for (MessageInfoHolder holder : mMessages) {
- uid = message.getUid();
- if (uid != null && (holder.uid == uid || uid.equals(holder.uid))) {
- folder = message.getFolder();
- if (holder.folder.name.equals(folder.getName()) &&
- holder.account.equals(folder.getAccount().getUuid())) {
- return holder;
- }
- }
- }
-
- return null;
- }
-
- /**
- * Find a specific message in the message list.
- *
- * Note:
- * This method was optimized because it is called a lot. Don't change it unless you know
- * what you are doing.
- *
- * @param messageReference
- * A {@link MessageReference} instance describing the message to look for.
- *
- * @return The corresponding {@link MessageInfoHolder} instance if the message was found in
- * the message list. {@code null} otherwise.
- */
- private MessageInfoHolder getMessage(MessageReference messageReference) {
- String uid;
- for (MessageInfoHolder holder : mMessages) {
- uid = messageReference.uid;
- if ((holder.uid == uid || uid.equals(holder.uid)) &&
- holder.folder.name.equals(messageReference.folderName) &&
- holder.account.equals(messageReference.accountUuid)) {
- return holder;
- }
- }
-
- return null;
- }
-
- private MessageInfoHolder getThread(long threadId) {
- for (MessageInfoHolder holder : mMessages) {
- LocalMessage localMessage = (LocalMessage) holder.message;
- if (localMessage.getId() == threadId || localMessage.getRootId() == threadId) {
- return holder;
- }
- }
-
- return null;
- }
-
- public FolderInfoHolder getFolder(String folder, Account account) {
- LocalFolder local_folder = null;
- try {
- LocalStore localStore = account.getLocalStore();
- local_folder = localStore.getFolder(folder);
- return new FolderInfoHolder(mContext, local_folder, account);
- } catch (Exception e) {
- Log.e(K9.LOG_TAG, "getFolder(" + folder + ") goes boom: ", e);
- return null;
- } finally {
- if (local_folder != null) {
- local_folder.close();
- }
- }
- }
-
-
- @Override
- public int getCount() {
- return mMessages.size();
- }
-
- @Override
- public long getItemId(int position) {
- try {
- MessageInfoHolder messageHolder = (MessageInfoHolder) getItem(position);
- if (messageHolder != null) {
- return messageHolder.message.getId();
- }
- } catch (Exception e) {
- Log.i(K9.LOG_TAG, "getItemId(" + position + ") ", e);
- }
- return -1;
- }
-
- @Override
- public Object getItem(int position) {
- try {
- if (position < mMessages.size()) {
- return mMessages.get(position);
- }
- } catch (Exception e) {
- Log.e(K9.LOG_TAG, "getItem(" + position + "), but folder.messages.size() = " + mMessages.size(), e);
- }
- return null;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- MessageInfoHolder message = (MessageInfoHolder) getItem(position);
- View view;
-
- if ((convertView != null) && (convertView.getId() == R.layout.message_list_item)) {
- view = convertView;
- } else {
- view = mInflater.inflate(R.layout.message_list_item, parent, false);
- view.setId(R.layout.message_list_item);
- }
-
- MessageViewHolder holder = (MessageViewHolder) view.getTag();
-
- if (holder == null) {
- holder = new MessageViewHolder();
- holder.date = (TextView) view.findViewById(R.id.date);
- holder.chip = view.findViewById(R.id.chip);
- holder.preview = (TextView) view.findViewById(R.id.preview);
-
- if (mSenderAboveSubject) {
- holder.from = (TextView) view.findViewById(R.id.subject);
- holder.from.setTextSize(TypedValue.COMPLEX_UNIT_SP, mFontSizes.getMessageListSender());
- } else {
- holder.subject = (TextView) view.findViewById(R.id.subject);
- holder.subject.setTextSize(TypedValue.COMPLEX_UNIT_SP, mFontSizes.getMessageListSubject());
- }
-
- holder.date.setTextSize(TypedValue.COMPLEX_UNIT_SP, mFontSizes.getMessageListDate());
-
- holder.preview.setLines(mPreviewLines);
- holder.preview.setTextSize(TypedValue.COMPLEX_UNIT_SP, mFontSizes.getMessageListPreview());
- holder.threadCount = (TextView) view.findViewById(R.id.thread_count);
-
- view.setTag(holder);
- }
-
- if (message != null) {
- bindView(position, view, holder, message);
- } else {
- // This branch code is triggered when the local store
- // hands us an invalid message
-
- holder.chip.getBackground().setAlpha(0);
- if (holder.subject != null) {
- holder.subject.setText(getString(R.string.general_no_subject));
- holder.subject.setTypeface(null, Typeface.NORMAL);
- }
-
- String noSender = getString(R.string.general_no_sender);
-
- if (holder.preview != null) {
- holder.preview.setText(noSender, TextView.BufferType.SPANNABLE);
- Spannable str = (Spannable) holder.preview.getText();
-
- str.setSpan(new StyleSpan(Typeface.NORMAL),
- 0,
- noSender.length(),
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- str.setSpan(new AbsoluteSizeSpan(mFontSizes.getMessageListSender(), true),
- 0,
- noSender.length(),
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- } else {
- holder.from.setText(noSender);
- holder.from.setTypeface(null, Typeface.NORMAL);
- holder.from.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
- }
-
- holder.date.setText(getString(R.string.general_no_date));
-
- //WARNING: Order of the next 2 lines matter
- holder.position = -1;
- }
-
-
- return view;
- }
-
- /**
- * Associate model data to view object.
- *
- * @param position
- * The position of the item within the adapter's data set of
- * the item whose view we want.
- * @param view
- * Main view component to alter. Never null
.
- * @param holder
- * Convenience view holder - eases access to view
- * child views. Never null
.
- * @param message
- * Never null
.
- */
-
-
-
- private void bindView(final int position, final View view, final MessageViewHolder holder,
- final MessageInfoHolder message) {
-
- int maybeBoldTypeface = message.read ? Typeface.NORMAL : Typeface.BOLD;
-
- // So that the mSelectedCount is only incremented/decremented
- // when a user checks the checkbox (vs code)
- holder.position = -1;
-
- if (message.selected) {
-
- holder.chip.setBackgroundDrawable(message.message.getFolder().getAccount().getCheckmarkChip().drawable());
- }
-
- else {
- holder.chip.setBackgroundDrawable(message.message.getFolder().getAccount().generateColorChip(message.read,message.message.toMe(), message.message.ccMe(), message.message.fromMe(), message.flagged).drawable());
-
- }
-
- if (K9.useBackgroundAsUnreadIndicator()) {
- int res = (message.read) ? R.attr.messageListReadItemBackgroundColor :
- R.attr.messageListUnreadItemBackgroundColor;
-
- TypedValue outValue = new TypedValue();
- getActivity().getTheme().resolveAttribute(res, outValue, true);
- view.setBackgroundColor(outValue.data);
- }
-
- String subject = null;
-
- if ((message.message.getSubject() == null) || message.message.getSubject().equals("")) {
- subject = (String) getText(R.string.general_no_subject);
-
- } else {
- subject = message.message.getSubject();
- }
-
- int threadCount = message.threadCount;
- if (mThreadId == -1 && threadCount > 1) {
- holder.threadCount.setText(Integer.toString(threadCount));
- holder.threadCount.setVisibility(View.VISIBLE);
- } else {
- holder.threadCount.setVisibility(View.GONE);
- }
-
-
- // We'll get badge support soon --jrv
-// if (holder.badge != null) {
-// String email = message.counterpartyAddress;
-// holder.badge.assignContactFromEmail(email, true);
-// if (email != null) {
-// mContactsPictureLoader.loadContactPicture(email, holder.badge);
-// }
-// }
-
- if (holder.preview != null) {
- /*
- * In the touchable UI, we have previews. Otherwise, we
- * have just a "from" line.
- * Because text views can't wrap around each other(?) we
- * compose a custom view containing the preview and the
- * from.
- */
-
- CharSequence beforePreviewText = null;
- if (mSenderAboveSubject) {
- beforePreviewText = subject;
- } else {
- beforePreviewText = message.sender;
- }
-
- holder.preview.setText(new SpannableStringBuilder(recipientSigil(message))
- .append(beforePreviewText).append(" ").append(message.message.getPreview()),
- TextView.BufferType.SPANNABLE);
- Spannable str = (Spannable)holder.preview.getText();
-
- // Create a span section for the sender, and assign the correct font size and weight.
- str.setSpan(new AbsoluteSizeSpan((mSenderAboveSubject ? mFontSizes.getMessageListSubject(): mFontSizes.getMessageListSender()), true),
- 0, beforePreviewText.length() + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-
- int color = (K9.getK9Theme() == K9.THEME_LIGHT) ?
- Color.rgb(105, 105, 105) :
- Color.rgb(160, 160, 160);
-
- // set span for preview message.
- str.setSpan(new ForegroundColorSpan(color), // How do I can specify the android.R.attr.textColorTertiary
- beforePreviewText.length() + 1,
- str.length(),
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
-
-
- if (holder.from != null ) {
- holder.from.setTypeface(null, maybeBoldTypeface);
- if (mSenderAboveSubject) {
- holder.from.setCompoundDrawablesWithIntrinsicBounds(
- message.answered ? mAnsweredIcon : null, // left
- null, // top
- message.message.hasAttachments() ? mAttachmentIcon : null, // right
- null); // bottom
-
- holder.from.setText(message.sender);
- } else {
- holder.from.setText(new SpannableStringBuilder(recipientSigil(message)).append(message.sender));
- }
- }
-
- if (holder.subject != null ) {
- if (!mSenderAboveSubject) {
- holder.subject.setCompoundDrawablesWithIntrinsicBounds(
- message.answered ? mAnsweredIcon : null, // left
- null, // top
- message.message.hasAttachments() ? mAttachmentIcon : null, // right
- null); // bottom
- }
-
- holder.subject.setTypeface(null, maybeBoldTypeface);
- holder.subject.setText(subject);
- }
-
- holder.date.setText(message.getDate(mMessageHelper));
- holder.position = position;
- }
-
-
- private String recipientSigil(MessageInfoHolder message) {
- if (message.message.toMe()) {
+ private String recipientSigil(boolean toMe, boolean ccMe) {
+ if (toMe) {
return getString(R.string.messagelist_sent_to_me_sigil);
- } else if (message.message.ccMe()) {
+ } else if (ccMe) {
return getString(R.string.messagelist_sent_cc_me_sigil);
} else {
return "";
@@ -2200,31 +1652,212 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
}
@Override
- public boolean hasStableIds() {
- return true;
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ View view = mInflater.inflate(R.layout.message_list_item, parent, false);
+ view.setId(R.layout.message_list_item);
+
+ MessageViewHolder holder = new MessageViewHolder();
+ holder.date = (TextView) view.findViewById(R.id.date);
+ holder.chip = view.findViewById(R.id.chip);
+ holder.preview = (TextView) view.findViewById(R.id.preview);
+
+ if (mSenderAboveSubject) {
+ holder.from = (TextView) view.findViewById(R.id.subject);
+ holder.from.setTextSize(TypedValue.COMPLEX_UNIT_SP, mFontSizes.getMessageListSender());
+ } else {
+ holder.subject = (TextView) view.findViewById(R.id.subject);
+ holder.subject.setTextSize(TypedValue.COMPLEX_UNIT_SP, mFontSizes.getMessageListSubject());
+ }
+
+ holder.date.setTextSize(TypedValue.COMPLEX_UNIT_SP, mFontSizes.getMessageListDate());
+
+ holder.preview.setLines(mPreviewLines);
+ holder.preview.setTextSize(TypedValue.COMPLEX_UNIT_SP, mFontSizes.getMessageListPreview());
+ holder.threadCount = (TextView) view.findViewById(R.id.thread_count);
+
+ view.setTag(holder);
+
+ return view;
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ //TODO: make this work for search results
+ Account account = mAccount;
+
+ String fromList = cursor.getString(SENDER_LIST_COLUMN);
+ String toList = cursor.getString(TO_LIST_COLUMN);
+ String ccList = cursor.getString(CC_LIST_COLUMN);
+ Address[] fromAddrs = Address.unpack(fromList);
+ Address[] toAddrs = Address.unpack(toList);
+ Address[] ccAddrs = Address.unpack(ccList);
+
+ boolean fromMe = mMessageHelper.toMe(account, fromAddrs);
+ boolean toMe = mMessageHelper.toMe(account, toAddrs);
+ boolean ccMe = mMessageHelper.toMe(account, ccAddrs);
+
+ CharSequence displayName = mMessageHelper.getDisplayName(account, fromAddrs, toAddrs);
+
+ Date sentDate = new Date(cursor.getLong(DATE_COLUMN));
+ String displayDate = mMessageHelper.formatDate(sentDate);
+
+ String preview = cursor.getString(PREVIEW_COLUMN);
+ if (preview == null) {
+ preview = "";
+ }
+
+ String subject = cursor.getString(SUBJECT_COLUMN);
+ if (StringUtils.isNullOrEmpty(subject)) {
+ subject = getString(R.string.general_no_subject);
+ }
+
+ int threadCount = 0; //TODO: get thread count from cursor
+
+ String flagList = cursor.getString(FLAGS_COLUMN);
+ String[] flags = flagList.split(",");
+ boolean read = false;
+ boolean flagged = false;
+ boolean answered = false;
+ boolean forwarded = false;
+ for (int i = 0, len = flags.length; i < len; i++) {
+ try {
+ switch (Flag.valueOf(flags[i])) {
+ case SEEN: {
+ read = true;
+ break;
+ }
+ case FLAGGED: {
+ flagged = true;
+ break;
+ }
+ case ANSWERED: {
+ answered = true;
+ break;
+ }
+ case FORWARDED: {
+ forwarded = true;
+ break;
+ }
+ default: {
+ // We don't care about the other flags
+ }
+ }
+ } catch (Exception e) { /* ignore */ }
+ }
+
+ boolean hasAttachments = (cursor.getInt(ATTACHMENT_COUNT_COLUMN) > 0);
+
+ MessageViewHolder holder = (MessageViewHolder) view.getTag();
+
+ int maybeBoldTypeface = (read) ? Typeface.NORMAL : Typeface.BOLD;
+
+ int adapterPosition = cursor.getPosition();
+ boolean selected = mSelected.get(adapterPosition, false);
+
+ if (selected) {
+ holder.chip.setBackgroundDrawable(account.getCheckmarkChip().drawable());
+ } else {
+ holder.chip.setBackgroundDrawable(account.generateColorChip(read, toMe, ccMe,
+ fromMe, flagged).drawable());
+ }
+
+ // Background indicator
+ if (K9.useBackgroundAsUnreadIndicator()) {
+ int res = (read) ? R.attr.messageListReadItemBackgroundColor :
+ R.attr.messageListUnreadItemBackgroundColor;
+
+ TypedValue outValue = new TypedValue();
+ getActivity().getTheme().resolveAttribute(res, outValue, true);
+ view.setBackgroundColor(outValue.data);
+ }
+
+ // Thread count
+ if (mThreadId == -1 && threadCount > 1) {
+ holder.threadCount.setText(Integer.toString(threadCount));
+ holder.threadCount.setVisibility(View.VISIBLE);
+ } else {
+ holder.threadCount.setVisibility(View.GONE);
+ }
+
+ CharSequence beforePreviewText = (mSenderAboveSubject) ? subject : displayName;
+
+ String sigil = recipientSigil(toMe, ccMe);
+
+ holder.preview.setText(
+ new SpannableStringBuilder(sigil)
+ .append(beforePreviewText)
+ .append(" ")
+ .append(preview), TextView.BufferType.SPANNABLE);
+
+ Spannable str = (Spannable)holder.preview.getText();
+
+ // Create a span section for the sender, and assign the correct font size and weight
+ int fontSize = (mSenderAboveSubject) ?
+ mFontSizes.getMessageListSubject():
+ mFontSizes.getMessageListSender();
+
+ AbsoluteSizeSpan span = new AbsoluteSizeSpan(fontSize, true);
+ str.setSpan(span, 0, beforePreviewText.length() + 1,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ //TODO: make this part of the theme
+ int color = (K9.getK9Theme() == K9.THEME_LIGHT) ?
+ Color.rgb(105, 105, 105) :
+ Color.rgb(160, 160, 160);
+
+ // Set span (color) for preview message
+ str.setSpan(new ForegroundColorSpan(color), beforePreviewText.length() + 1,
+ str.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ Drawable statusHolder = null;
+ if (forwarded && answered) {
+ statusHolder = mForwardedAnsweredIcon;
+ } else if (answered) {
+ statusHolder = mAnsweredIcon;
+ } else if (forwarded) {
+ statusHolder = mForwardedIcon;
+ }
+
+ if (holder.from != null ) {
+ holder.from.setTypeface(null, maybeBoldTypeface);
+ if (mSenderAboveSubject) {
+ holder.from.setCompoundDrawablesWithIntrinsicBounds(
+ statusHolder, // left
+ null, // top
+ hasAttachments ? mAttachmentIcon : null, // right
+ null); // bottom
+
+ holder.from.setText(displayName);
+ } else {
+ holder.from.setText(new SpannableStringBuilder(sigil).append(displayName));
+ }
+ }
+
+ if (holder.subject != null ) {
+ if (!mSenderAboveSubject) {
+ holder.subject.setCompoundDrawablesWithIntrinsicBounds(
+ statusHolder, // left
+ null, // top
+ hasAttachments ? mAttachmentIcon : null, // right
+ null); // bottom
+ }
+
+ holder.subject.setTypeface(null, maybeBoldTypeface);
+ holder.subject.setText(subject);
+ }
+
+ holder.date.setText(displayDate);
}
}
- class MessageViewHolder
- implements OnCheckedChangeListener {
+ class MessageViewHolder {
public TextView subject;
public TextView preview;
public TextView from;
public TextView time;
public TextView date;
public View chip;
- public int position = -1;
public TextView threadCount;
-
- @Override
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- if (position != -1) {
- MessageInfoHolder message = (MessageInfoHolder) mAdapter.getItem(position);
- toggleMessageSelect(message);
-
-
- }
- }
}
@@ -2286,23 +1919,6 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
public TextView main;
}
- private void setAllSelected(boolean isSelected) {
- mSelectedCount = 0;
-
- for (MessageInfoHolder holder : mAdapter.getMessages()) {
- holder.selected = isSelected;
- mSelectedCount += (isSelected ? 1 : 0);
- }
-
- computeBatchDirection();
- mAdapter.notifyDataSetChanged();
-
- if (isSelected) {
- updateActionModeTitle();
- computeSelectAllVisibility();
- }
- }
-
/**
* Set selection state for all messages.
*
@@ -2311,40 +1927,54 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
* action mode is finished.
*/
private void setSelectionState(boolean selected) {
- mAdapter.setSelectionForAllMesages(selected);
-
if (selected) {
mSelectedCount = mAdapter.getCount();
- mActionMode = getSherlockActivity().startActionMode(mActionModeCallback);
+ for (int i = 0, end = mSelectedCount; i < end; i++) {
+ mSelected.put(i, true);
+ }
+
+ if (mActionMode == null) {
+ mActionMode = getSherlockActivity().startActionMode(mActionModeCallback);
+ }
+ computeBatchDirection();
updateActionModeTitle();
computeSelectAllVisibility();
- computeBatchDirection();
} else {
+ mSelected.clear();
mSelectedCount = 0;
if (mActionMode != null) {
mActionMode.finish();
+ mActionMode = null;
}
}
+
+ mAdapter.notifyDataSetChanged();
}
- private void toggleMessageSelect(final MessageInfoHolder holder){
+ private void toggleMessageSelect(int listViewPosition) {
+ int adapterPosition = listViewToAdapterPosition(listViewPosition);
+ if (adapterPosition == AdapterView.INVALID_POSITION) {
+ return;
+ }
+
+ boolean selected = mSelected.get(adapterPosition, false);
+ mSelected.put(adapterPosition, !selected);
+
if (mActionMode != null) {
- if (mSelectedCount == 1 && holder.selected) {
+ if (mSelectedCount == 1 && selected) {
mActionMode.finish();
+ mActionMode = null;
return;
}
} else {
mActionMode = getSherlockActivity().startActionMode(mActionModeCallback);
}
- if (holder.selected) {
- holder.selected = false;
+ if (selected) {
mSelectedCount -= 1;
} else {
- holder.selected = true;
mSelectedCount += 1;
}
- mAdapter.notifyDataSetChanged();
computeBatchDirection();
updateActionModeTitle();
@@ -2353,6 +1983,8 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
mActionMode.invalidate();
computeSelectAllVisibility();
+
+ mAdapter.notifyDataSetChanged();
}
private void updateActionModeTitle() {
@@ -2367,6 +1999,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
boolean isBatchFlag = false;
boolean isBatchRead = false;
+ /*
for (MessageInfoHolder holder : mAdapter.getMessages()) {
if (holder.selected) {
if (!holder.flagged) {
@@ -2381,54 +2014,48 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
}
}
}
+ */
+ //TODO: implement
mActionModeCallback.showMarkAsRead(isBatchRead);
mActionModeCallback.showFlag(isBatchFlag);
}
- /**
- * @param holders
- * Messages to update. Never {@code null}.
- * @param flag
- * Flag to be updated on the specified messages. Never
- * {@code null}.
- * @param newState
- * State to set for the given flag.
- */
- private void setFlag(final List holders, final Flag flag, final boolean newState) {
- if (holders.isEmpty()) {
+ private void setFlag(Message message, final Flag flag, final boolean newState) {
+ setFlag(new Message[] { message }, flag, newState);
+ }
+
+ private void setFlag(Message[] messages, final Flag flag, final boolean newState) {
+ if (messages.length == 0) {
return;
}
- final Message[] messageList = new Message[holders.size()];
- int i = 0;
- for (final Iterator iterator = holders.iterator(); iterator.hasNext(); i++) {
- final MessageInfoHolder holder = iterator.next();
- messageList[i] = holder.message;
- if (flag == Flag.SEEN) {
- holder.read = newState;
- } else if (flag == Flag.FLAGGED) {
- holder.flagged = newState;
- }
- }
- mController.setFlag(messageList, flag, newState);
- mAdapter.sortMessages();
+
+ mController.setFlag(messages, flag, newState);
computeBatchDirection();
}
+ private void onMove(Message message) {
+ onMove(new Message[] { message });
+ }
+
/**
* Display the message move activity.
*
* @param holders
* Never {@code null}.
*/
- private void onMove(final List holders) {
- if (!checkCopyOrMovePossible(holders, FolderOperation.MOVE)) {
+ private void onMove(Message[] messages) {
+ if (!checkCopyOrMovePossible(messages, FolderOperation.MOVE)) {
return;
}
- final Folder folder = holders.size() == 1 ? holders.get(0).message.getFolder() : mCurrentFolder.folder;
- displayFolderChoice(ACTIVITY_CHOOSE_FOLDER_MOVE, folder, holders);
+ final Folder folder = messages.length == 1 ? messages[0].getFolder() : mCurrentFolder.folder;
+ displayFolderChoice(ACTIVITY_CHOOSE_FOLDER_MOVE, folder, messages);
+ }
+
+ private void onCopy(Message message) {
+ onCopy(new Message[] { message });
}
/**
@@ -2437,13 +2064,13 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
* @param holders
* Never {@code null}.
*/
- private void onCopy(final List holders) {
- if (!checkCopyOrMovePossible(holders, FolderOperation.COPY)) {
+ private void onCopy(Message[] messages) {
+ if (!checkCopyOrMovePossible(messages, FolderOperation.COPY)) {
return;
}
- final Folder folder = holders.size() == 1 ? holders.get(0).message.getFolder() : mCurrentFolder.folder;
- displayFolderChoice(ACTIVITY_CHOOSE_FOLDER_COPY, folder, holders);
+ final Folder folder = messages.length == 1 ? messages[0].getFolder() : mCurrentFolder.folder;
+ displayFolderChoice(ACTIVITY_CHOOSE_FOLDER_COPY, folder, messages);
}
/**
@@ -2462,41 +2089,45 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
* {@code null}.
* @see #startActivityForResult(Intent, int)
*/
- private void displayFolderChoice(final int requestCode, final Folder folder, final List holders) {
+ private void displayFolderChoice(final int requestCode, final Folder folder, final Message[] messages) {
final Intent intent = new Intent(getActivity(), ChooseFolder.class);
intent.putExtra(ChooseFolder.EXTRA_ACCOUNT, folder.getAccount().getUuid());
intent.putExtra(ChooseFolder.EXTRA_CUR_FOLDER, folder.getName());
intent.putExtra(ChooseFolder.EXTRA_SEL_FOLDER, folder.getAccount().getLastSelectedFolderName());
// remember the selected messages for #onActivityResult
- mActiveMessages = holders;
+ mActiveMessages = messages;
startActivityForResult(intent, requestCode);
}
- /**
- * @param holders
- * Never {@code null}.
- */
- private void onArchive(final List holders) {
- final String folderName = holders.get(0).message.getFolder().getAccount().getArchiveFolderName();
+ private void onArchive(final Message message) {
+ onArchive(new Message[] { message });
+ }
+
+ private void onArchive(final Message[] messages) {
+ final String folderName = messages[0].getFolder().getAccount().getArchiveFolderName();
if (K9.FOLDER_NONE.equalsIgnoreCase(folderName)) {
return;
}
// TODO one should separate messages by account and call move afterwards
// (because each account might have a specific Archive folder name)
- move(holders, folderName);
+ move(messages, folderName);
+ }
+
+ private void onSpam(Message message) {
+ onSpam(new Message[] { message });
}
/**
* @param holders
* Never {@code null}.
*/
- private void onSpam(final List holders) {
+ private void onSpam(Message[] messages) {
if (K9.confirmSpam()) {
// remember the message selection for #onCreateDialog(int)
- mActiveMessages = holders;
+ mActiveMessages = messages;
showDialog(R.id.dialog_confirm_spam);
} else {
- onSpamConfirmed(holders);
+ onSpamConfirmed(messages);
}
}
@@ -2504,14 +2135,14 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
* @param holders
* Never {@code null}.
*/
- private void onSpamConfirmed(final List holders) {
- final String folderName = holders.get(0).message.getFolder().getAccount().getSpamFolderName();
+ private void onSpamConfirmed(Message[] messages) {
+ final String folderName = messages[0].getFolder().getAccount().getSpamFolderName();
if (K9.FOLDER_NONE.equalsIgnoreCase(folderName)) {
return;
}
// TODO one should separate messages by account and call move afterwards
// (because each account might have a specific Spam folder name)
- move(holders, folderName);
+ move(messages, folderName);
}
private static enum FolderOperation {
@@ -2528,23 +2159,25 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
*
* @return true
if operation is possible
*/
- private boolean checkCopyOrMovePossible(final List holders, final FolderOperation operation) {
- if (holders.isEmpty()) {
+ private boolean checkCopyOrMovePossible(final Message[] messages, final FolderOperation operation) {
+ if (messages.length == 0) {
return false;
}
+
boolean first = true;
- for (final MessageInfoHolder holder : holders) {
- final Message message = holder.message;
+ for (final Message message : messages) {
if (first) {
first = false;
// account check
final Account account = message.getFolder().getAccount();
- if ((operation == FolderOperation.MOVE && !mController.isMoveCapable(account)) || (operation == FolderOperation.COPY && !mController.isCopyCapable(account))) {
+ if ((operation == FolderOperation.MOVE && !mController.isMoveCapable(account)) ||
+ (operation == FolderOperation.COPY && !mController.isCopyCapable(account))) {
return false;
}
}
// message check
- if ((operation == FolderOperation.MOVE && !mController.isMoveCapable(message)) || (operation == FolderOperation.COPY && !mController.isCopyCapable(message))) {
+ if ((operation == FolderOperation.MOVE && !mController.isMoveCapable(message)) ||
+ (operation == FolderOperation.COPY && !mController.isCopyCapable(message))) {
final Toast toast = Toast.makeText(getActivity(), R.string.move_copy_cannot_copy_unsynced_message,
Toast.LENGTH_LONG);
toast.show();
@@ -2554,42 +2187,14 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
return true;
}
- /**
- * Helper method to get a List of message ready to be processed. This implementation will return a list containing the sole argument.
- *
- * @param holder Never {@code null}.
- * @return Never {@code null}.
- */
- private List getSelectionFromMessage(final MessageInfoHolder holder) {
- final List selection = Collections.singletonList(holder);
- return selection;
- }
-
- /**
- * Helper method to get a List of message ready to be processed. This implementation will iterate over messages and choose the checked ones.
- *
- * @return Never {@code null}.
- */
- private List getSelectionFromCheckboxes() {
- final List selection = new ArrayList();
-
- for (final MessageInfoHolder holder : mAdapter.getMessages()) {
- if (holder.selected) {
- selection.add(holder);
- }
- }
-
- return selection;
- }
-
/**
* Copy the specified messages to the specified folder.
*
* @param holders Never {@code null}.
* @param destination Never {@code null}.
*/
- private void copy(final List holders, final String destination) {
- copyOrMove(holders, destination, FolderOperation.COPY);
+ private void copy(Message[] messages, final String destination) {
+ copyOrMove(messages, destination, FolderOperation.COPY);
}
/**
@@ -2598,8 +2203,8 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
* @param holders Never {@code null}.
* @param destination Never {@code null}.
*/
- private void move(final List holders, final String destination) {
- copyOrMove(holders, destination, FolderOperation.MOVE);
+ private void move(Message[] messages, final String destination) {
+ copyOrMove(messages, destination, FolderOperation.MOVE);
}
/**
@@ -2616,7 +2221,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
* @param operation
* Never {@code null}.
*/
- private void copyOrMove(final List holders, final String destination, final FolderOperation operation) {
+ private void copyOrMove(Message[] messages, final String destination, final FolderOperation operation) {
if (K9.FOLDER_NONE.equalsIgnoreCase(destination)) {
return;
}
@@ -2625,10 +2230,9 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
Account account = null;
String folderName = null;
- final List messages = new ArrayList(holders.size());
+ List outMessages = new ArrayList();
- for (final MessageInfoHolder holder : holders) {
- final Message message = holder.message;
+ for (Message message : messages) {
if (first) {
first = false;
folderName = message.getFolder().getName();
@@ -2652,15 +2256,14 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
// message isn't synchronized
return;
}
- messages.add(message);
+ outMessages.add(message);
}
if (operation == FolderOperation.MOVE) {
- mController.moveMessages(account, folderName, messages.toArray(new Message[messages.size()]), destination,
+ mController.moveMessages(account, folderName, outMessages.toArray(new Message[outMessages.size()]), destination,
null);
- mAdapter.removeMessages(holders);
} else {
- mController.copyMessages(account, folderName, messages.toArray(new Message[messages.size()]), destination,
+ mController.copyMessages(account, folderName, outMessages.toArray(new Message[outMessages.size()]), destination,
null);
}
}
@@ -2715,11 +2318,11 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
* TODO think of a better way then looping over all
* messages.
*/
- final List selection = getSelectionFromCheckboxes();
+ Message[] messages = getCheckedMessages();
Account account;
- for (MessageInfoHolder holder : selection) {
- account = holder.message.getFolder().getAccount();
+ for (Message message : messages) {
+ account = message.getFolder().getAccount();
setContextCapabilities(account, menu);
}
@@ -2735,7 +2338,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
mMarkAsUnread = null;
mFlag = null;
mUnflag = null;
- setAllSelected(false);
+ setSelectionState(false);
}
@Override
@@ -2816,7 +2419,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
- final List selection = getSelectionFromCheckboxes();
+ Message[] messages = getCheckedMessages();
/*
* In the following we assume that we can't move or copy
@@ -2827,49 +2430,51 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
*/
switch (item.getItemId()) {
case R.id.delete: {
- onDelete(selection);
+ onDelete(messages);
+
+ //FIXME
mSelectedCount = 0;
break;
}
case R.id.mark_as_read: {
- setFlag(selection, Flag.SEEN, true);
+ setFlag(messages, Flag.SEEN, true);
break;
}
case R.id.mark_as_unread: {
- setFlag(selection, Flag.SEEN, false);
+ setFlag(messages, Flag.SEEN, false);
break;
}
case R.id.flag: {
- setFlag(selection, Flag.FLAGGED, true);
+ setFlag(messages, Flag.FLAGGED, true);
break;
}
case R.id.unflag: {
- setFlag(selection, Flag.FLAGGED, false);
+ setFlag(messages, Flag.FLAGGED, false);
break;
}
case R.id.select_all: {
- setAllSelected(true);
+ selectAll();
break;
}
// only if the account supports this
case R.id.archive: {
- onArchive(selection);
+ onArchive(messages);
mSelectedCount = 0;
break;
}
case R.id.spam: {
- onSpam(selection);
+ onSpam(messages);
mSelectedCount = 0;
break;
}
case R.id.move: {
- onMove(selection);
+ onMove(messages);
mSelectedCount = 0;
break;
}
case R.id.copy: {
- onCopy(selection);
+ onCopy(messages);
mSelectedCount = 0;
break;
}
@@ -2911,8 +2516,8 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
}
public void checkMail() {
- mController.synchronizeMailbox(mAccount, mFolderName, mAdapter.mListener, null);
- mController.sendPendingMessages(mAccount, mAdapter.mListener);
+ mController.synchronizeMailbox(mAccount, mFolderName, mListener, null);
+ mController.sendPendingMessages(mAccount, mListener);
}
/**
@@ -2937,7 +2542,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
final Folder remoteFolder = remoteStore.getFolder(mSearchFolder);
remoteFolder.close();
// Send a remoteSearchFinished() message for good measure.
- mAdapter.mListener.remoteSearchFinished(searchAccount, mSearchFolder, 0, null);
+ //mAdapter.mListener.remoteSearchFinished(searchAccount, mSearchFolder, 0, null);
} catch (Exception e) {
// Since the user is going back, log and squash any exceptions.
Log.e(K9.LOG_TAG, "Could not abort remote search before going back", e);
@@ -2949,10 +2554,13 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
public ArrayList getMessageReferences() {
ArrayList messageRefs = new ArrayList();
+ /*
for (MessageInfoHolder holder : mAdapter.getMessages()) {
MessageReference ref = holder.message.makeMessageReference();
messageRefs.add(ref);
}
+ */
+ //TODO: implement
return messageRefs;
}
@@ -3003,56 +2611,89 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
changeSort(mSortType);
}
- private MessageInfoHolder getSelection() {
- return (MessageInfoHolder) mListView.getSelectedItem();
+ private Message getSelectedMessage() {
+ int listViewPosition = mListView.getSelectedItemPosition();
+ int adapterPosition = listViewToAdapterPosition(listViewPosition);
+
+ return getMessageAtPosition(adapterPosition);
+ }
+
+ private Message getMessageAtPosition(int adapterPosition) {
+ if (adapterPosition == AdapterView.INVALID_POSITION) {
+ return null;
+ }
+
+ Cursor cursor = (Cursor) mAdapter.getItem(adapterPosition);
+ String uid = cursor.getString(UID_COLUMN);
+
+ //TODO: get account and folder from cursor
+ Folder folder = mCurrentFolder.folder;
+
+ try {
+ return folder.getMessage(uid);
+ } catch (MessagingException e) {
+ Log.e(K9.LOG_TAG, "Something went wrong while fetching a message", e);
+ }
+
+ return null;
+ }
+
+ private Message[] getCheckedMessages() {
+ Message[] messages = new Message[mSelectedCount];
+ int out = 0;
+ for (int position = 0, end = mAdapter.getCount(); position < end; position++) {
+ if (mSelected.get(position, false)) {
+ messages[out++] = getMessageAtPosition(position);
+ }
+ }
+
+ return messages;
}
public void onDelete() {
- MessageInfoHolder message = getSelection();
+ Message message = getSelectedMessage();
if (message != null) {
- onDelete(Collections.singletonList(message));
+ onDelete(new Message[] { message });
}
}
public void toggleMessageSelect() {
- MessageInfoHolder message = getSelection();
- if (message != null) {
- toggleMessageSelect(message);
- }
+ toggleMessageSelect(mListView.getSelectedItemPosition());
}
public void onToggleFlag() {
- MessageInfoHolder message = getSelection();
+ Message message = getSelectedMessage();
if (message != null) {
- setFlag(Collections.singletonList(message), Flag.FLAGGED, !message.flagged);
+ System.out.println("FLAGGED: " + message.isSet(Flag.FLAGGED));
+ setFlag(message, Flag.FLAGGED, !message.isSet(Flag.FLAGGED));
}
}
public void onMove() {
- MessageInfoHolder message = getSelection();
+ Message message = getSelectedMessage();
if (message != null) {
- onMove(Collections.singletonList(message));
+ onMove(message);
}
}
public void onArchive() {
- MessageInfoHolder message = getSelection();
+ Message message = getSelectedMessage();
if (message != null) {
- onArchive(Collections.singletonList(message));
+ onArchive(message);
}
}
public void onCopy() {
- MessageInfoHolder message = getSelection();
+ Message message = getSelectedMessage();
if (message != null) {
- onCopy(Collections.singletonList(message));
+ onCopy(message);
}
}
public void onToggleRead() {
- MessageInfoHolder message = getSelection();
+ Message message = getSelectedMessage();
if (message != null) {
- setFlag(Collections.singletonList(message), Flag.SEEN, !message.read);
+ setFlag(message, Flag.SEEN, !message.isSet(Flag.SEEN));
}
}
@@ -3124,4 +2765,39 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
String folderName = (mCurrentFolder != null) ? mCurrentFolder.name : null;
return mFragmentListener.startSearch(mAccount, folderName);
}
+
+ @Override
+ public Loader onCreateLoader(int id, Bundle args) {
+ String accountUuid = mAccount.getUuid();
+
+ Uri uri = Uri.withAppendedPath(EmailProvider.CONTENT_URI, "account/" + accountUuid + "/messages");
+
+ long folderId = 0;
+ try {
+ LocalFolder folder = (LocalFolder) mCurrentFolder.folder;
+ folder.open(OpenMode.READ_ONLY);
+ folderId = folder.getId();
+ } catch (MessagingException e) {
+ //FIXME
+ e.printStackTrace();
+ }
+
+ String selection = MessageColumns.FOLDER_ID + "=?";
+ String[] selectionArgs = { Long.toString(folderId) };
+
+ return new CursorLoader(getActivity(), uri, PROJECTION, selection, selectionArgs,
+ MessageColumns.DATE + " DESC");
+ }
+
+ @Override
+ public void onLoadFinished(Loader loader, Cursor data) {
+ mSelected = new SparseBooleanArray(data.getCount());
+ mAdapter.swapCursor(data);
+ }
+
+ @Override
+ public void onLoaderReset(Loader loader) {
+ mSelected = null;
+ mAdapter.swapCursor(null);
+ }
}
diff --git a/src/com/fsck/k9/helper/MessageHelper.java b/src/com/fsck/k9/helper/MessageHelper.java
index ae708f224..ce2e9d2ed 100644
--- a/src/com/fsck/k9/helper/MessageHelper.java
+++ b/src/com/fsck/k9/helper/MessageHelper.java
@@ -108,4 +108,28 @@ public class MessageHelper {
mDateFormat = DateFormatter.getDateFormat(mContext);
mTodayDateFormat = android.text.format.DateFormat.getTimeFormat(mContext);
}
+
+ public CharSequence getDisplayName(Account account, Address[] fromAddrs, Address[] toAddrs) {
+ final Contacts contactHelper = K9.showContactName() ? Contacts.getInstance(mContext) : null;
+
+ CharSequence displayName;
+ if (fromAddrs.length > 0 && account.isAnIdentity(fromAddrs[0])) {
+ CharSequence to = Address.toFriendly(toAddrs, contactHelper);
+ displayName = new SpannableStringBuilder(
+ mContext.getString(R.string.message_to_label)).append(to);
+ } else {
+ displayName = Address.toFriendly(fromAddrs, contactHelper);
+ }
+
+ return displayName;
+ }
+
+ public boolean toMe(Account account, Address[] toAddrs) {
+ for (Address address : toAddrs) {
+ if (account.isAnIdentity(address)) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java
index 8c42b6f48..641466b3c 100644
--- a/src/com/fsck/k9/mail/store/LocalStore.java
+++ b/src/com/fsck/k9/mail/store/LocalStore.java
@@ -3976,4 +3976,7 @@ public class LocalStore extends Store implements Serializable {
}
}
+ public LockableDatabase getDatabase() {
+ return database;
+ }
}
diff --git a/src/com/fsck/k9/mail/store/LockableDatabase.java b/src/com/fsck/k9/mail/store/LockableDatabase.java
index b303c2aef..67090aace 100644
--- a/src/com/fsck/k9/mail/store/LockableDatabase.java
+++ b/src/com/fsck/k9/mail/store/LockableDatabase.java
@@ -49,7 +49,7 @@ public class LockableDatabase {
* Workaround exception wrapper used to keep the inner exception generated
* in a {@link DbCallback}.
*/
- protected static class WrappedException extends RuntimeException {
+ public static class WrappedException extends RuntimeException {
/**
*
*/
diff --git a/src/com/fsck/k9/provider/EmailProvider.java b/src/com/fsck/k9/provider/EmailProvider.java
new file mode 100644
index 000000000..1cf799d75
--- /dev/null
+++ b/src/com/fsck/k9/provider/EmailProvider.java
@@ -0,0 +1,246 @@
+package com.fsck.k9.provider;
+
+import java.util.List;
+
+import com.fsck.k9.Account;
+import com.fsck.k9.Preferences;
+import com.fsck.k9.helper.StringUtils;
+import com.fsck.k9.mail.MessagingException;
+import com.fsck.k9.mail.store.LocalStore;
+import com.fsck.k9.mail.store.LockableDatabase;
+import com.fsck.k9.mail.store.LockableDatabase.DbCallback;
+import com.fsck.k9.mail.store.LockableDatabase.WrappedException;
+import com.fsck.k9.mail.store.UnavailableStorageException;
+
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.CursorWrapper;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+
+/**
+ * Content Provider used to display the message list etc.
+ *
+ *
+ * For now this content provider is for internal use only. In the future we may allow third-party
+ * apps to access K-9 Mail content using this content provider.
+ *
+ */
+/*
+ * TODO:
+ * - modify MessagingController (or LocalStore?) to call ContentResolver.notifyChange() to trigger
+ * notifications when the underlying data changes.
+ * - add support for message threading
+ * - add support for search views
+ * - add support for querying multiple accounts (e.g. "Unified Inbox")
+ * - add support for account list and folder list
+ */
+public class EmailProvider extends ContentProvider {
+ private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+
+ public static final String AUTHORITY = "org.k9mail.provider.email";
+
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);
+
+
+ /*
+ * Constants that are used for the URI matching.
+ */
+ private static final int MESSAGE_BASE = 0;
+ private static final int MESSAGES = MESSAGE_BASE;
+ //private static final int MESSAGES_THREADED = MESSAGE_BASE + 1;
+ //private static final int MESSAGES_THREAD = MESSAGE_BASE + 2;
+
+
+ private static final String MESSAGES_TABLE = "messages";
+
+
+ static {
+ UriMatcher matcher = sUriMatcher;
+
+ matcher.addURI(AUTHORITY, "account/*/messages", MESSAGES);
+ //matcher.addURI(AUTHORITY, "account/*/messages/threaded", MESSAGES_THREADED);
+ //matcher.addURI(AUTHORITY, "account/*/thread/#", MESSAGES_THREAD);
+ }
+
+
+ public interface MessageColumns {
+ public static final String ID = "id";
+ public static final String UID = "uid";
+ public static final String INTERNAL_DATE = "internal_date";
+ public static final String SUBJECT = "subject";
+ public static final String DATE = "date";
+ public static final String MESSAGE_ID = "message_id";
+ public static final String SENDER_LIST = "sender_list";
+ public static final String TO_LIST = "to_list";
+ public static final String CC_LIST = "cc_list";
+ public static final String BCC_LIST = "bcc_list";
+ public static final String REPLY_TO_LIST = "reply_to_list";
+ public static final String FLAGS = "flags";
+ public static final String ATTACHMENT_COUNT = "attachment_count";
+ public static final String FOLDER_ID = "folder_id";
+ public static final String PREVIEW = "preview";
+ public static final String THREAD_ROOT = "thread_root";
+ public static final String THREAD_PARENT = "thread_parent";
+ }
+
+ private interface InternalMessageColumns extends MessageColumns {
+ public static final String DELETED = "deleted";
+ public static final String EMPTY = "empty";
+ public static final String TEXT_CONTENT = "text_content";
+ public static final String HTML_CONTENT = "html_content";
+ public static final String MIME_TYPE = "mime_type";
+ }
+
+
+ private Preferences mPreferences;
+
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ throw new RuntimeException("not implemented yet");
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+
+ int match = sUriMatcher.match(uri);
+ if (match < 0) {
+ throw new IllegalArgumentException("Unknown URI: " + uri);
+ }
+
+ ContentResolver contentResolver = getContext().getContentResolver();
+ Cursor cursor = null;
+ switch (match) {
+ case MESSAGES: {
+ List segments = uri.getPathSegments();
+ String accountUuid = segments.get(1);
+
+ cursor = getMessages(accountUuid, projection, selection, selectionArgs, sortOrder);
+
+ cursor.setNotificationUri(contentResolver, uri);
+ break;
+ }
+ }
+
+ return new IdTrickeryCursor(cursor);
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ throw new RuntimeException("not implemented yet");
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ throw new RuntimeException("not implemented yet");
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ throw new RuntimeException("not implemented yet");
+ }
+
+ protected Cursor getMessages(String accountUuid, final String[] projection,
+ final String selection, final String[] selectionArgs, final String sortOrder) {
+
+ Account account = getAccount(accountUuid);
+ LockableDatabase database = getDatabase(account);
+
+ try {
+ return database.execute(false, new DbCallback() {
+ @Override
+ public Cursor doDbWork(SQLiteDatabase db) throws WrappedException,
+ UnavailableStorageException {
+
+ String where;
+ if (StringUtils.isNullOrEmpty(selection)) {
+ where = InternalMessageColumns.DELETED + "=0 AND " +
+ InternalMessageColumns.EMPTY + "!=1";
+ } else {
+ where = "(" + selection + ") AND " +
+ InternalMessageColumns.DELETED + "=0 AND " +
+ InternalMessageColumns.EMPTY + "!=1";
+ }
+
+ return db.query(MESSAGES_TABLE, projection, where, selectionArgs, null, null,
+ sortOrder);
+ }
+ });
+ } catch (UnavailableStorageException e) {
+ throw new RuntimeException("Storage not available", e);
+ }
+ }
+
+ private Account getAccount(String accountUuid) {
+ if (mPreferences == null) {
+ Context appContext = getContext().getApplicationContext();
+ mPreferences = Preferences.getPreferences(appContext);
+ }
+
+ Account account = mPreferences.getAccount(accountUuid);
+
+ if (account == null) {
+ throw new IllegalArgumentException("Unknown account: " + accountUuid);
+ }
+
+ return account;
+ }
+
+ private LockableDatabase getDatabase(Account account) {
+ LocalStore localStore;
+ try {
+ localStore = account.getLocalStore();
+ } catch (MessagingException e) {
+ throw new RuntimeException("Couldn't get LocalStore", e);
+ }
+
+ return localStore.getDatabase();
+ }
+
+ /**
+ * This class is needed to make {@link CursorAdapter} work with our database schema.
+ *
+ *
+ * {@code CursorAdapter} requires a column named {@code "_id"} containing a stable id. We use
+ * the column name {@code "id"} as primary key in all our tables. So this {@link CursorWrapper}
+ * maps all queries for {@code "_id"} to {@code "id"}.
+ *
+ * Please note that this only works for the returned {@code Cursor}. When querying the content
+ * provider you still need to use {@link MessageColumns#ID}.
+ *
+ */
+ static class IdTrickeryCursor extends CursorWrapper {
+ public IdTrickeryCursor(Cursor cursor) {
+ super(cursor);
+ }
+
+ @Override
+ public int getColumnIndex(String columnName) {
+ if ("_id".equals(columnName)) {
+ return super.getColumnIndex("id");
+ }
+
+ return super.getColumnIndex(columnName);
+ }
+
+ @Override
+ public int getColumnIndexOrThrow(String columnName) {
+ if ("_id".equals(columnName)) {
+ return super.getColumnIndexOrThrow("id");
+ }
+
+ return super.getColumnIndexOrThrow(columnName);
+ }
+ }
+}
From f1e433e6df85d3978062092d7dfa4b618de909df Mon Sep 17 00:00:00 2001
From: cketti
Date: Tue, 16 Oct 2012 22:42:51 +0200
Subject: [PATCH 18/74] Whitespace cleanup
---
src/com/fsck/k9/activity/Accounts.java | 36 +-
src/com/fsck/k9/activity/FolderList.java | 40 +-
src/com/fsck/k9/activity/MessageList.java | 98 +--
.../k9/controller/MessagingController.java | 36 +-
src/com/fsck/k9/mail/store/LocalStore.java | 60 +-
.../k9/provider/UnreadWidgetProvider.java | 6 +-
.../fsck/k9/search/ConditionsTreeNode.java | 742 +++++++++---------
src/com/fsck/k9/search/LocalSearch.java | 568 +++++++-------
src/com/fsck/k9/search/SearchAccount.java | 58 +-
.../fsck/k9/search/SearchSpecification.java | 216 ++---
10 files changed, 930 insertions(+), 930 deletions(-)
diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java
index 596801554..9d6482a64 100644
--- a/src/com/fsck/k9/activity/Accounts.java
+++ b/src/com/fsck/k9/activity/Accounts.java
@@ -426,18 +426,18 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
* Creates and initializes the special accounts ('Unified Inbox' and 'All Messages')
*/
private void createSpecialAccounts() {
- // create the unified inbox meta account ( all accounts is default when none specified )
+ // create the unified inbox meta account ( all accounts is default when none specified )
String name = getString(R.string.integrated_inbox_title);
LocalSearch tmpSearch = new LocalSearch(name);
tmpSearch.addAllowedFolder(SearchSpecification.GENERIC_INBOX_NAME);
integratedInboxAccount = new SearchAccount(tmpSearch, name,
- getString(R.string.integrated_inbox_detail));
-
+ getString(R.string.integrated_inbox_detail));
+
// create the all messages search ( all accounts is default when none specified )
name = getString(R.string.search_all_messages_title);
tmpSearch = new LocalSearch(name);
- unreadAccount = new SearchAccount(tmpSearch, name,
- getString(R.string.search_all_messages_detail));
+ unreadAccount = new SearchAccount(tmpSearch, name,
+ getString(R.string.search_all_messages_detail));
}
@SuppressWarnings("unchecked")
@@ -563,7 +563,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
final SearchAccount searchAccount = (SearchAccount)account;
MessagingController.getInstance(getApplication())
- .searchLocalMessages(searchAccount.getRelatedSearch(), new MessagingListener() {
+ .searchLocalMessages(searchAccount.getRelatedSearch(), new MessagingListener() {
@Override
public void searchStats(AccountStats stats) {
mListener.accountStatusChanged(searchAccount, stats);
@@ -637,9 +637,9 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
if (K9.FOLDER_NONE.equals(realAccount.getAutoExpandFolderName())) {
FolderList.actionHandleAccount(this, realAccount);
} else {
- LocalSearch search = new LocalSearch(realAccount.getAutoExpandFolderName());
- search.addAllowedFolder(realAccount.getAutoExpandFolderName());
- search.addAccountUuid(realAccount.getUuid());
+ LocalSearch search = new LocalSearch(realAccount.getAutoExpandFolderName());
+ search.addAllowedFolder(realAccount.getAutoExpandFolderName());
+ search.addAccountUuid(realAccount.getUuid());
MessageList.actionDisplaySearch(this, search, true);}
}
return true;
@@ -1786,18 +1786,18 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
public void onClick(View v) {
final String description = getString(R.string.search_title, account.getDescription(), getString(searchModifier.resId));
LocalSearch search = null;
-
+
if (account instanceof SearchAccount) {
- search = ((SearchAccount) account).getRelatedSearch();
- search.setName(description);
+ search = ((SearchAccount) account).getRelatedSearch();
+ search.setName(description);
} else {
- search = new LocalSearch(description);
- search.addAccountUuid(account.getUuid());
+ search = new LocalSearch(description);
+ search.addAccountUuid(account.getUuid());
}
-
- search.allRequiredFlags(searchModifier.requiredFlags);
- search.allForbiddenFlags(searchModifier.forbiddenFlags);
- MessageList.actionDisplaySearch(Accounts.this, search, false);
+
+ search.allRequiredFlags(searchModifier.requiredFlags);
+ search.allForbiddenFlags(searchModifier.forbiddenFlags);
+ MessageList.actionDisplaySearch(Accounts.this, search, false);
}
}
diff --git a/src/com/fsck/k9/activity/FolderList.java b/src/com/fsck/k9/activity/FolderList.java
index 6c8477833..487e8f44e 100644
--- a/src/com/fsck/k9/activity/FolderList.java
+++ b/src/com/fsck/k9/activity/FolderList.java
@@ -622,9 +622,9 @@ public class FolderList extends K9ListActivity implements OnNavigationListener {
}
private void onOpenFolder(String folder) {
- LocalSearch search = new LocalSearch(folder);
- search.addAccountUuid(mAccount.getUuid());
- search.addAllowedFolder(folder);
+ LocalSearch search = new LocalSearch(folder);
+ search.addAccountUuid(mAccount.getUuid());
+ search.addAllowedFolder(folder);
MessageList.actionDisplaySearch(this, search, false);
}
@@ -1265,18 +1265,18 @@ public class FolderList extends K9ListActivity implements OnNavigationListener {
final String description = getString(R.string.search_title,
getString(R.string.message_list_title, account.getDescription(), displayName),
getString(searchModifier.resId));
-
- LocalSearch search = new LocalSearch(description);
- try {
- search.allRequiredFlags(searchModifier.requiredFlags);
- search.allForbiddenFlags(searchModifier.forbiddenFlags);
- } catch (Exception e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- search.addAllowedFolder(folderName);
- search.addAccountUuid(account.getUuid());
- MessageList.actionDisplaySearch(FolderList.this, search, false);
+
+ LocalSearch search = new LocalSearch(description);
+ try {
+ search.allRequiredFlags(searchModifier.requiredFlags);
+ search.allForbiddenFlags(searchModifier.forbiddenFlags);
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ search.addAllowedFolder(folderName);
+ search.addAccountUuid(account.getUuid());
+ MessageList.actionDisplaySearch(FolderList.this, search, false);
}
}
@@ -1285,11 +1285,11 @@ public class FolderList extends K9ListActivity implements OnNavigationListener {
LocalSearch search = new LocalSearch(description);
search.addAccountUuid(account.getUuid());
try {
- search.allRequiredFlags(new Flag[]{Flag.SEEN});
- } catch (Exception e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
+ search.allRequiredFlags(new Flag[]{Flag.SEEN});
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
}
}
diff --git a/src/com/fsck/k9/activity/MessageList.java b/src/com/fsck/k9/activity/MessageList.java
index aca636bbd..9eae7ffbe 100644
--- a/src/com/fsck/k9/activity/MessageList.java
+++ b/src/com/fsck/k9/activity/MessageList.java
@@ -48,8 +48,8 @@ import com.fsck.k9.search.SearchSpecification.SearchCondition;
*/
public class MessageList extends K9FragmentActivity implements MessageListFragmentListener,
OnBackStackChangedListener, OnSwipeGestureListener {
-
- // for this activity
+
+ // for this activity
private static final String EXTRA_SEARCH = "search";
// used for remote search
@@ -59,22 +59,22 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
public static void actionDisplaySearch(Context context, SearchSpecification search, boolean newTask) {
actionDisplaySearch(context, search, newTask, true);
}
-
+
public static void actionDisplaySearch(Context context, SearchSpecification search, boolean newTask, boolean clearTop) {
context.startActivity(intentDisplaySearch(context, search, newTask, clearTop));
}
-
+
public static Intent intentDisplaySearch(Context context, SearchSpecification search, boolean newTask, boolean clearTop) {
Intent intent = new Intent(context, MessageList.class);
intent.putExtra(EXTRA_SEARCH, search);
-
+
if (clearTop) {
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
}
if (newTask) {
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
-
+
return intent;
}
@@ -91,11 +91,11 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
private Account mAccount;
private String mFolderName;
- private LocalSearch mSearch;
+ private LocalSearch mSearch;
private boolean mSingleFolderMode;
private boolean mSingleAccountMode;
private boolean mIsRemote;
-
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -123,8 +123,8 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
}
private void decodeExtras(Intent intent) {
- // check if this intent comes from the system search ( remote )
- if (intent.getStringExtra(SearchManager.QUERY) != null) {
+ // check if this intent comes from the system search ( remote )
+ if (intent.getStringExtra(SearchManager.QUERY) != null) {
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
//Query was received from Search Dialog
Bundle appData = getIntent().getBundleExtra(SearchManager.APP_DATA);
@@ -132,40 +132,40 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
mSearch = new LocalSearch();
mSearch.addAccountUuid(appData.getString(EXTRA_SEARCH_ACCOUNT));
mSearch.addAllowedFolder(appData.getString(EXTRA_SEARCH_FOLDER));
-
+
String query = intent.getStringExtra(SearchManager.QUERY);
mSearch.or(new SearchCondition(SEARCHFIELD.SENDER, ATTRIBUTE.CONTAINS, query));
mSearch.or(new SearchCondition(SEARCHFIELD.SUBJECT, ATTRIBUTE.CONTAINS, query));
-
+
mIsRemote = true;
}
}
} else {
- // regular LocalSearch object was passed
- mSearch = intent.getParcelableExtra(EXTRA_SEARCH);
+ // regular LocalSearch object was passed
+ mSearch = intent.getParcelableExtra(EXTRA_SEARCH);
}
- String[] accounts = mSearch.getAccountUuids();
- mSingleAccountMode = ( accounts != null && accounts.length == 1
- && !accounts[0].equals(SearchSpecification.ALL_ACCOUNTS));
- mSingleFolderMode = mSingleAccountMode && (mSearch.getFolderNames().size() == 1);
-
- if (mSingleAccountMode) {
- mAccount = Preferences.getPreferences(this).getAccount(accounts[0]);
-
- if (mAccount != null && !mAccount.isAvailable(this)) {
- Log.i(K9.LOG_TAG, "not opening MessageList of unavailable account");
- onAccountUnavailable();
- return;
- }
- }
-
- if (mSingleFolderMode) {
- mFolderName = mSearch.getFolderNames().get(0);
- }
-
- // now we know if we are in single account mode and need a subtitle
- mActionBarSubTitle.setVisibility((!mSingleFolderMode) ? View.GONE : View.VISIBLE);
+ String[] accounts = mSearch.getAccountUuids();
+ mSingleAccountMode = ( accounts != null && accounts.length == 1
+ && !accounts[0].equals(SearchSpecification.ALL_ACCOUNTS));
+ mSingleFolderMode = mSingleAccountMode && (mSearch.getFolderNames().size() == 1);
+
+ if (mSingleAccountMode) {
+ mAccount = Preferences.getPreferences(this).getAccount(accounts[0]);
+
+ if (mAccount != null && !mAccount.isAvailable(this)) {
+ Log.i(K9.LOG_TAG, "not opening MessageList of unavailable account");
+ onAccountUnavailable();
+ return;
+ }
+ }
+
+ if (mSingleFolderMode) {
+ mFolderName = mSearch.getFolderNames().get(0);
+ }
+
+ // now we know if we are in single account mode and need a subtitle
+ mActionBarSubTitle.setVisibility((!mSingleFolderMode) ? View.GONE : View.VISIBLE);
}
@Override
@@ -329,10 +329,10 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
if (fragmentManager.getBackStackEntryCount() > 0) {
fragmentManager.popBackStack();
} else if (!mSingleFolderMode) {
- onBackPressed();
- } else {
- onShowFolderList();
- }
+ onBackPressed();
+ } else {
+ onShowFolderList();
+ }
return true;
}
case R.id.compose: {
@@ -581,10 +581,10 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
@Override
public void showMoreFromSameSender(String senderAddress) {
- LocalSearch tmpSearch = new LocalSearch("From " + senderAddress);
- tmpSearch.addAccountUuids(mSearch.getAccountUuids());
- tmpSearch.and(SEARCHFIELD.SENDER, senderAddress, ATTRIBUTE.CONTAINS);
-
+ LocalSearch tmpSearch = new LocalSearch("From " + senderAddress);
+ tmpSearch.addAccountUuids(mSearch.getAccountUuids());
+ tmpSearch.and(SEARCHFIELD.SENDER, senderAddress, ATTRIBUTE.CONTAINS);
+
MessageListFragment fragment = MessageListFragment.newInstance(tmpSearch, false);
addMessageListFragment(fragment);
@@ -668,10 +668,10 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
@Override
public void showThread(Account account, String folderName, long threadRootId) {
- LocalSearch tmpSearch = new LocalSearch();
- tmpSearch.addAccountUuids(mSearch.getAccountUuids());
- tmpSearch.and(SEARCHFIELD.THREAD_ROOT, String.valueOf(threadRootId), ATTRIBUTE.EQUALS);
-
+ LocalSearch tmpSearch = new LocalSearch();
+ tmpSearch.addAccountUuids(mSearch.getAccountUuids());
+ tmpSearch.and(SEARCHFIELD.THREAD_ROOT, String.valueOf(threadRootId), ATTRIBUTE.EQUALS);
+
MessageListFragment fragment = MessageListFragment.newInstance(tmpSearch, false);
addMessageListFragment(fragment);
}
diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java
index 181cb0177..8314866f3 100644
--- a/src/com/fsck/k9/controller/MessagingController.java
+++ b/src/com/fsck/k9/controller/MessagingController.java
@@ -632,20 +632,20 @@ public class MessagingController implements Runnable {
}
});
}
-
+
public void searchLocalMessagesSynchronous(final LocalSearch search, final MessagingListener listener) {
final AccountStats stats = new AccountStats();
final HashSet uuidSet = new HashSet(Arrays.asList(search.getAccountUuids()));
Account[] accounts = Preferences.getPreferences(mApplication.getApplicationContext()).getAccounts();
boolean allAccounts = uuidSet.contains(SearchSpecification.ALL_ACCOUNTS);
-
- // for every account we want to search do the query in the localstore
- for (final Account account : accounts) {
-
- if (!allAccounts && !uuidSet.contains(account.getUuid())) {
- continue;
- }
-
+
+ // for every account we want to search do the query in the localstore
+ for (final Account account : accounts) {
+
+ if (!allAccounts && !uuidSet.contains(account.getUuid())) {
+ continue;
+ }
+
// Collecting statistics of the search result
MessageRetrievalListener retrievalListener = new MessageRetrievalListener() {
@Override
@@ -666,15 +666,15 @@ public class MessagingController implements Runnable {
}
}
};
-
- // alert everyone the search has started
+
+ // alert everyone the search has started
if (listener != null) {
listener.listLocalMessagesStarted(account, null);
}
-
+
// build and do the query in the localstore
try {
- LocalStore localStore = account.getLocalStore();
+ LocalStore localStore = account.getLocalStore();
localStore.searchForMessages(retrievalListener, search);
} catch (Exception e) {
if (listener != null) {
@@ -686,9 +686,9 @@ public class MessagingController implements Runnable {
listener.listLocalMessagesFinished(account, null);
}
}
- }
-
- // publish the total search statistics
+ }
+
+ // publish the total search statistics
if (listener != null) {
listener.searchStats(stats);
}
@@ -3164,7 +3164,7 @@ public class MessagingController implements Runnable {
search.addAllowedFolder(account.getInboxFolderName());
search.addAccountUuid(account.getUuid());
Intent intent = MessageList.intentDisplaySearch(mApplication, search, true, true);
-
+
PendingIntent pi = PendingIntent.getActivity(mApplication, 0, intent, 0);
builder.setContentIntent(pi);
@@ -3250,7 +3250,7 @@ public class MessagingController implements Runnable {
search.addAllowedFolder(account.getInboxFolderName());
search.addAccountUuid(account.getUuid());
Intent intent = MessageList.intentDisplaySearch(mApplication, search, true, true);
-
+
PendingIntent pi = PendingIntent.getActivity(mApplication, 0, intent, 0);
builder.setContentIntent(pi);
diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java
index 13302b896..d4778bc3b 100644
--- a/src/com/fsck/k9/mail/store/LocalStore.java
+++ b/src/com/fsck/k9/mail/store/LocalStore.java
@@ -667,16 +667,16 @@ public class LocalStore extends Store implements Serializable {
public Long doDbWork(final SQLiteDatabase db) {
Cursor cursor = null;
try {
- cursor = db.rawQuery("SELECT id FROM folders WHERE name = '" + name + "'", null);
+ cursor = db.rawQuery("SELECT id FROM folders WHERE name = '" + name + "'", null);
cursor.moveToFirst();
- return cursor.getLong(0);
+ return cursor.getLong(0);
} finally {
Utility.closeQuietly(cursor);
}
}
});
}
-
+
// TODO this takes about 260-300ms, seems slow.
@Override
public List extends Folder > getPersonalNamespaces(boolean forceListAll) throws MessagingException {
@@ -920,7 +920,7 @@ public class LocalStore extends Store implements Serializable {
}
int i = 0;
if (str.charAt(0) == '-') {
- return false;
+ return false;
}
for (; i < length; i++) {
char c = str.charAt(i);
@@ -930,36 +930,36 @@ public class LocalStore extends Store implements Serializable {
}
return true;
}
-
- public Message[] searchForMessages(MessageRetrievalListener retrievalListener,
- LocalSearch search) throws MessagingException {
-
- // update some references in the search that have to be bound to this one store
- for (ConditionsTreeNode node : search.getLeafSet()) {
- if (node.mCondition.field == SEARCHFIELD.FOLDER) {
- // TODO find better solution
- if (isFolderId(node.mCondition.value)) {
- continue;
- }
-
- if (node.mCondition.value.equals(LocalSearch.GENERIC_INBOX_NAME)) {
- node.mCondition.value = mAccount.getInboxFolderName();
- }
- node.mCondition.value = String.valueOf(getFolderId(node.mCondition.value));
- }
- }
- // build sql query
- String sqlQuery = "SELECT " + GET_MESSAGES_COLS + "FROM messages WHERE deleted = 0 "
- + (search.getConditions() != null ? "AND (" + search.getConditions() + ")" : "") + " ORDER BY date DESC";
-
+ public Message[] searchForMessages(MessageRetrievalListener retrievalListener,
+ LocalSearch search) throws MessagingException {
+
+ // update some references in the search that have to be bound to this one store
+ for (ConditionsTreeNode node : search.getLeafSet()) {
+ if (node.mCondition.field == SEARCHFIELD.FOLDER) {
+ // TODO find better solution
+ if (isFolderId(node.mCondition.value)) {
+ continue;
+ }
+
+ if (node.mCondition.value.equals(LocalSearch.GENERIC_INBOX_NAME)) {
+ node.mCondition.value = mAccount.getInboxFolderName();
+ }
+ node.mCondition.value = String.valueOf(getFolderId(node.mCondition.value));
+ }
+ }
+
+ // build sql query
+ String sqlQuery = "SELECT " + GET_MESSAGES_COLS + "FROM messages WHERE deleted = 0 "
+ + (search.getConditions() != null ? "AND (" + search.getConditions() + ")" : "") + " ORDER BY date DESC";
+
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Query = " + sqlQuery);
}
-
- return getMessages(retrievalListener, null, sqlQuery, new String[] {});
- }
-
+
+ return getMessages(retrievalListener, null, sqlQuery, new String[] {});
+ }
+
/*
* Given a query string, actually do the query for the messages and
* call the MessageRetrievalListener for each one
diff --git a/src/com/fsck/k9/provider/UnreadWidgetProvider.java b/src/com/fsck/k9/provider/UnreadWidgetProvider.java
index d8daeaa0b..180b8c22a 100644
--- a/src/com/fsck/k9/provider/UnreadWidgetProvider.java
+++ b/src/com/fsck/k9/provider/UnreadWidgetProvider.java
@@ -63,9 +63,9 @@ public class UnreadWidgetProvider extends AppWidgetProvider {
clickIntent = FolderList.actionHandleAccountIntent(context, account, null,
false);
} else {
- LocalSearch search = new LocalSearch(account.getAutoExpandFolderName());
- search.addAllowedFolder(account.getAutoExpandFolderName());
- search.addAccountUuid(account.getUuid());
+ LocalSearch search = new LocalSearch(account.getAutoExpandFolderName());
+ search.addAllowedFolder(account.getAutoExpandFolderName());
+ search.addAccountUuid(account.getUuid());
clickIntent = MessageList.intentDisplaySearch(context, search, true, true);
}
clickIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
diff --git a/src/com/fsck/k9/search/ConditionsTreeNode.java b/src/com/fsck/k9/search/ConditionsTreeNode.java
index 650755a7d..03ba2039d 100644
--- a/src/com/fsck/k9/search/ConditionsTreeNode.java
+++ b/src/com/fsck/k9/search/ConditionsTreeNode.java
@@ -14,388 +14,388 @@ import com.fsck.k9.search.SearchSpecification.SEARCHFIELD;
import com.fsck.k9.search.SearchSpecification.SearchCondition;
/**
- * This class stores search conditions. It's basically a boolean expression binary tree.
+ * This class stores search conditions. It's basically a boolean expression binary tree.
* The output will be SQL queries ( obtained by traversing inorder ).
- *
+ *
* TODO removing conditions from the tree
* TODO implement NOT as a node again
- *
+ *
* @author dzan
*/
public class ConditionsTreeNode implements Parcelable{
-
- public enum OPERATOR {
- AND, OR, CONDITION;
- }
-
- public ConditionsTreeNode mLeft;
- public ConditionsTreeNode mRight;
- public ConditionsTreeNode mParent;
-
- /*
- * If mValue isn't CONDITION then mCondition contains a real
- * condition, otherwise it's null.
- */
- public OPERATOR mValue;
- public SearchCondition mCondition;
-
- /*
- * Used for storing and retrieving the tree to/from the database.
- * The algorithm is called "modified preorder tree traversal".
- */
- public int mLeftMPTTMarker;
- public int mRightMPTTMarker;
-
-
- ///////////////////////////////////////////////////////////////
- // Static Helpers to restore a tree from a database cursor
- ///////////////////////////////////////////////////////////////
- /**
- * Builds a condition tree starting from a database cursor. The cursor
- * should point to rows representing the nodes of the tree.
- *
- * @param cursor Cursor pointing to the first of a bunch or rows. Each rows
- * should contains 1 tree node.
- * @return A condition tree.
- */
- public static ConditionsTreeNode buildTreeFromDB(Cursor cursor) {
- Stack stack = new Stack();
- ConditionsTreeNode tmp = null;
-
- // root node
- if (cursor.moveToFirst()) {
- tmp = buildNodeFromRow(cursor);
- stack.push(tmp);
- }
-
- // other nodes
+
+ public enum OPERATOR {
+ AND, OR, CONDITION;
+ }
+
+ public ConditionsTreeNode mLeft;
+ public ConditionsTreeNode mRight;
+ public ConditionsTreeNode mParent;
+
+ /*
+ * If mValue isn't CONDITION then mCondition contains a real
+ * condition, otherwise it's null.
+ */
+ public OPERATOR mValue;
+ public SearchCondition mCondition;
+
+ /*
+ * Used for storing and retrieving the tree to/from the database.
+ * The algorithm is called "modified preorder tree traversal".
+ */
+ public int mLeftMPTTMarker;
+ public int mRightMPTTMarker;
+
+
+ ///////////////////////////////////////////////////////////////
+ // Static Helpers to restore a tree from a database cursor
+ ///////////////////////////////////////////////////////////////
+ /**
+ * Builds a condition tree starting from a database cursor. The cursor
+ * should point to rows representing the nodes of the tree.
+ *
+ * @param cursor Cursor pointing to the first of a bunch or rows. Each rows
+ * should contains 1 tree node.
+ * @return A condition tree.
+ */
+ public static ConditionsTreeNode buildTreeFromDB(Cursor cursor) {
+ Stack stack = new Stack();
+ ConditionsTreeNode tmp = null;
+
+ // root node
+ if (cursor.moveToFirst()) {
+ tmp = buildNodeFromRow(cursor);
+ stack.push(tmp);
+ }
+
+ // other nodes
while (cursor.moveToNext()) {
- tmp = buildNodeFromRow(cursor);
- if (tmp.mRightMPTTMarker < stack.peek().mRightMPTTMarker ){
- stack.peek().mLeft = tmp;
- stack.push(tmp);
- } else {
- while (stack.peek().mRightMPTTMarker < tmp.mRightMPTTMarker) {
- stack.pop();
- }
- stack.peek().mRight = tmp;
- }
+ tmp = buildNodeFromRow(cursor);
+ if (tmp.mRightMPTTMarker < stack.peek().mRightMPTTMarker ){
+ stack.peek().mLeft = tmp;
+ stack.push(tmp);
+ } else {
+ while (stack.peek().mRightMPTTMarker < tmp.mRightMPTTMarker) {
+ stack.pop();
+ }
+ stack.peek().mRight = tmp;
+ }
}
return tmp;
- }
-
- /**
- * Converts a single database row to a single condition node.
- *
- * @param cursor Cursor pointing to the row we want to convert.
- * @return A single ConditionsTreeNode
- */
- private static ConditionsTreeNode buildNodeFromRow(Cursor cursor) {
- ConditionsTreeNode result = null;
- SearchCondition condition = null;
-
- OPERATOR tmpValue = ConditionsTreeNode.OPERATOR.valueOf(cursor.getString(5));
-
- if (tmpValue == OPERATOR.CONDITION) {
- condition = new SearchCondition(SEARCHFIELD.valueOf(cursor.getString(0)),
- ATTRIBUTE.valueOf(cursor.getString(2)), cursor.getString(1));
- }
-
- result = new ConditionsTreeNode(condition);
- result.mValue = tmpValue;
- result.mLeftMPTTMarker = cursor.getInt(3);
- result.mRightMPTTMarker = cursor.getInt(4);
-
- return result;
}
-
-
- ///////////////////////////////////////////////////////////////
- // Constructors
- ///////////////////////////////////////////////////////////////
- public ConditionsTreeNode(SearchCondition condition) {
- mParent = null;
- mCondition = condition;
- mValue = OPERATOR.CONDITION;
- }
- public ConditionsTreeNode(ConditionsTreeNode parent, OPERATOR op) {
- mParent = parent;
- mValue = op;
- mCondition = null;
- }
-
-
- ///////////////////////////////////////////////////////////////
- // Public modifiers
- ///////////////////////////////////////////////////////////////
- /**
- * Adds the expression as the second argument of an AND
- * clause to this node.
- *
- * @param expr Expression to 'AND' with.
- * @return New top AND node.
- * @throws Exception
- */
- public ConditionsTreeNode and(ConditionsTreeNode expr) throws Exception {
- return add(expr, OPERATOR.AND);
- }
-
- /**
- * Convenience method.
- * Adds the provided condition as the second argument of an AND
- * clause to this node.
- *
- * @param condition Condition to 'AND' with.
- * @return New top AND node, new root.
- */
- public ConditionsTreeNode and(SearchCondition condition) {
- try {
- ConditionsTreeNode tmp = new ConditionsTreeNode(condition);
- return and(tmp);
- } catch (Exception e) {
- // impossible
- return null;
- }
- }
-
- /**
- * Adds the expression as the second argument of an OR
- * clause to this node.
- *
- * @param expr Expression to 'OR' with.
- * @return New top OR node.
- * @throws Exception
- */
- public ConditionsTreeNode or(ConditionsTreeNode expr) throws Exception {
- return add(expr, OPERATOR.OR);
- }
-
- /**
- * Convenience method.
- * Adds the provided condition as the second argument of an OR
- * clause to this node.
- *
- * @param condition Condition to 'OR' with.
- * @return New top OR node, new root.
- */
- public ConditionsTreeNode or(SearchCondition condition) {
- try {
- ConditionsTreeNode tmp = new ConditionsTreeNode(condition);
- return or(tmp);
- } catch (Exception e) {
- // impossible
- return null;
- }
- }
-
- /**
- * This applies the MPTT labeling to the subtree of which this node
- * is the root node.
- *
- * For a description on MPTT see:
- * http://www.sitepoint.com/hierarchical-data-database-2/
- */
- public void applyMPTTLabel() {
- applyMPTTLabel(1);
- }
-
-
- ///////////////////////////////////////////////////////////////
- // Public accessors
- ///////////////////////////////////////////////////////////////
- /**
- * Returns the condition stored in this node.
- * @return Condition stored in the node.
- */
- public SearchCondition getCondition() {
- return mCondition;
- }
-
-
- /**
- * This will traverse the tree inorder and call toString recursively resulting
- * in a valid SQL where clause.
- */
- @Override
- public String toString() {
- return (mLeft == null ? "" : "(" + mLeft + ")")
- + " " + ( mCondition == null ? mValue.name() : mCondition ) + " "
- + (mRight == null ? "" : "(" + mRight + ")") ;
- }
-
- /**
- * Get a set of all the leaves in the tree.
- * @return Set of all the leaves.
- */
- public HashSet getLeafSet() {
- HashSet leafSet = new HashSet();
- return getLeafSet(leafSet);
- }
-
- /**
- * Returns a list of all the nodes in the subtree of which this node
- * is the root. The list contains the nodes in a pre traversal order.
- *
- * @return List of all nodes in subtree in preorder.
- */
- public List preorder() {
- ArrayList result = new ArrayList();
- Stack stack = new Stack();
- stack.push(this);
-
- while(!stack.isEmpty()) {
- ConditionsTreeNode current = stack.pop( );
- if( current.mLeft != null ) stack.push( current.mLeft );
- if( current.mRight != null ) stack.push( current.mRight );
- result.add(current);
- }
-
- return result;
- }
-
-
- ///////////////////////////////////////////////////////////////
- // Private class logic
- ///////////////////////////////////////////////////////////////
- /**
- * Adds two new ConditionTreeNodes, one for the operator and one for the
- * new condition. The current node will end up on the same level as the
- * one provided in the arguments, they will be siblings. Their common
- * parent node will be one containing the operator provided in the arguments.
- * The method will update all the required references so the tree ends up in
- * a valid state.
- *
- * This method only supports node arguments with a null parent node.
- *
- * @param Node to add.
- * @param Operator that will connect the new node with this one.
- * @return New parent node, containing the operator.
- * @throws Exception Throws when the provided new node does not have a null parent.
- */
- private ConditionsTreeNode add(ConditionsTreeNode node, OPERATOR op) throws Exception{
- if (node.mParent != null) {
- throw new Exception("Can only add new expressions from root node down.");
- }
-
- ConditionsTreeNode tmpNode = new ConditionsTreeNode(mParent, op);
- tmpNode.mLeft = this;
- tmpNode.mRight = node;
-
- if (mParent != null) {
- mParent.updateChild(this, tmpNode);
- }
- this.mParent = tmpNode;
-
- node.mParent = tmpNode;
-
- return tmpNode;
- }
+ /**
+ * Converts a single database row to a single condition node.
+ *
+ * @param cursor Cursor pointing to the row we want to convert.
+ * @return A single ConditionsTreeNode
+ */
+ private static ConditionsTreeNode buildNodeFromRow(Cursor cursor) {
+ ConditionsTreeNode result = null;
+ SearchCondition condition = null;
- /**
- * Helper method that replaces a child of the current node with a new node.
- * If the provided old child node was the left one, left will be replaced with
- * the new one. Same goes for the right one.
- *
- * @param oldChild Old child node to be replaced.
- * @param newChild New child node.
- */
- private void updateChild(ConditionsTreeNode oldChild, ConditionsTreeNode newChild) {
- // we can compare objects id's because this is the desired behaviour in this case
- if (mLeft == oldChild) {
- mLeft = newChild;
- } else if (mRight == oldChild) {
- mRight = newChild;
- }
- }
-
- /**
- * Recursive function to gather all the leaves in the subtree of which
- * this node is the root.
- *
- * @param leafSet Leafset that's being built.
- * @return Set of leaves being completed.
- */
- private HashSet getLeafSet(HashSet leafSet) {
- // if we ended up in a leaf, add ourself and return
- if (mLeft == null && mRight == null) {
- leafSet.add(this);
- return leafSet;
- // we didn't end up in a leaf
- } else {
- if (mLeft != null) {
- mLeft.getLeafSet(leafSet);
- }
-
- if (mRight != null) {
- mRight.getLeafSet(leafSet);
- }
- return leafSet;
- }
- }
-
- /**
- * This applies the MPTT labeling to the subtree of which this node
- * is the root node.
- *
- * For a description on MPTT see:
- * http://www.sitepoint.com/hierarchical-data-database-2/
- */
- private int applyMPTTLabel(int label) {
- mLeftMPTTMarker = label;
- if (mLeft != null){
- label = mLeft.applyMPTTLabel(label += 1);
- }
- if (mRight != null){
- label = mRight.applyMPTTLabel(label += 1);
- }
- ++label;
- mRightMPTTMarker = label;
- return label;
- }
-
-
- ///////////////////////////////////////////////////////////////
- // Parcelable
- //
- // This whole class has to be parcelable because it's passed
- // on through intents.
- ///////////////////////////////////////////////////////////////
- @Override
- public int describeContents() {
- return 0;
- }
+ OPERATOR tmpValue = ConditionsTreeNode.OPERATOR.valueOf(cursor.getString(5));
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mValue.ordinal());
- dest.writeParcelable(mCondition, flags);
- dest.writeParcelable(mLeft, flags);
- dest.writeParcelable(mRight, flags);
- }
-
- public static final Parcelable.Creator CREATOR
- = new Parcelable.Creator() {
- public ConditionsTreeNode createFromParcel(Parcel in) {
- return new ConditionsTreeNode(in);
- }
-
- public ConditionsTreeNode[] newArray(int size) {
- return new ConditionsTreeNode[size];
- }
- };
-
- private ConditionsTreeNode(Parcel in) {
- mValue = OPERATOR.values()[in.readInt()];
- mCondition = in.readParcelable(ConditionsTreeNode.class.getClassLoader());
- mLeft = in.readParcelable(ConditionsTreeNode.class.getClassLoader());
- mRight = in.readParcelable(ConditionsTreeNode.class.getClassLoader());
- mParent = null;
- if (mLeft != null) {
- mLeft.mParent = this;
- }
- if (mRight != null) {
- mRight.mParent = this;
- }
- }
+ if (tmpValue == OPERATOR.CONDITION) {
+ condition = new SearchCondition(SEARCHFIELD.valueOf(cursor.getString(0)),
+ ATTRIBUTE.valueOf(cursor.getString(2)), cursor.getString(1));
+ }
+
+ result = new ConditionsTreeNode(condition);
+ result.mValue = tmpValue;
+ result.mLeftMPTTMarker = cursor.getInt(3);
+ result.mRightMPTTMarker = cursor.getInt(4);
+
+ return result;
+ }
+
+
+ ///////////////////////////////////////////////////////////////
+ // Constructors
+ ///////////////////////////////////////////////////////////////
+ public ConditionsTreeNode(SearchCondition condition) {
+ mParent = null;
+ mCondition = condition;
+ mValue = OPERATOR.CONDITION;
+ }
+
+ public ConditionsTreeNode(ConditionsTreeNode parent, OPERATOR op) {
+ mParent = parent;
+ mValue = op;
+ mCondition = null;
+ }
+
+
+ ///////////////////////////////////////////////////////////////
+ // Public modifiers
+ ///////////////////////////////////////////////////////////////
+ /**
+ * Adds the expression as the second argument of an AND
+ * clause to this node.
+ *
+ * @param expr Expression to 'AND' with.
+ * @return New top AND node.
+ * @throws Exception
+ */
+ public ConditionsTreeNode and(ConditionsTreeNode expr) throws Exception {
+ return add(expr, OPERATOR.AND);
+ }
+
+ /**
+ * Convenience method.
+ * Adds the provided condition as the second argument of an AND
+ * clause to this node.
+ *
+ * @param condition Condition to 'AND' with.
+ * @return New top AND node, new root.
+ */
+ public ConditionsTreeNode and(SearchCondition condition) {
+ try {
+ ConditionsTreeNode tmp = new ConditionsTreeNode(condition);
+ return and(tmp);
+ } catch (Exception e) {
+ // impossible
+ return null;
+ }
+ }
+
+ /**
+ * Adds the expression as the second argument of an OR
+ * clause to this node.
+ *
+ * @param expr Expression to 'OR' with.
+ * @return New top OR node.
+ * @throws Exception
+ */
+ public ConditionsTreeNode or(ConditionsTreeNode expr) throws Exception {
+ return add(expr, OPERATOR.OR);
+ }
+
+ /**
+ * Convenience method.
+ * Adds the provided condition as the second argument of an OR
+ * clause to this node.
+ *
+ * @param condition Condition to 'OR' with.
+ * @return New top OR node, new root.
+ */
+ public ConditionsTreeNode or(SearchCondition condition) {
+ try {
+ ConditionsTreeNode tmp = new ConditionsTreeNode(condition);
+ return or(tmp);
+ } catch (Exception e) {
+ // impossible
+ return null;
+ }
+ }
+
+ /**
+ * This applies the MPTT labeling to the subtree of which this node
+ * is the root node.
+ *
+ * For a description on MPTT see:
+ * http://www.sitepoint.com/hierarchical-data-database-2/
+ */
+ public void applyMPTTLabel() {
+ applyMPTTLabel(1);
+ }
+
+
+ ///////////////////////////////////////////////////////////////
+ // Public accessors
+ ///////////////////////////////////////////////////////////////
+ /**
+ * Returns the condition stored in this node.
+ * @return Condition stored in the node.
+ */
+ public SearchCondition getCondition() {
+ return mCondition;
+ }
+
+
+ /**
+ * This will traverse the tree inorder and call toString recursively resulting
+ * in a valid SQL where clause.
+ */
+ @Override
+ public String toString() {
+ return (mLeft == null ? "" : "(" + mLeft + ")")
+ + " " + ( mCondition == null ? mValue.name() : mCondition ) + " "
+ + (mRight == null ? "" : "(" + mRight + ")") ;
+ }
+
+ /**
+ * Get a set of all the leaves in the tree.
+ * @return Set of all the leaves.
+ */
+ public HashSet getLeafSet() {
+ HashSet leafSet = new HashSet();
+ return getLeafSet(leafSet);
+ }
+
+ /**
+ * Returns a list of all the nodes in the subtree of which this node
+ * is the root. The list contains the nodes in a pre traversal order.
+ *
+ * @return List of all nodes in subtree in preorder.
+ */
+ public List preorder() {
+ ArrayList result = new ArrayList();
+ Stack stack = new Stack();
+ stack.push(this);
+
+ while(!stack.isEmpty()) {
+ ConditionsTreeNode current = stack.pop( );
+ if( current.mLeft != null ) stack.push( current.mLeft );
+ if( current.mRight != null ) stack.push( current.mRight );
+ result.add(current);
+ }
+
+ return result;
+ }
+
+
+ ///////////////////////////////////////////////////////////////
+ // Private class logic
+ ///////////////////////////////////////////////////////////////
+ /**
+ * Adds two new ConditionTreeNodes, one for the operator and one for the
+ * new condition. The current node will end up on the same level as the
+ * one provided in the arguments, they will be siblings. Their common
+ * parent node will be one containing the operator provided in the arguments.
+ * The method will update all the required references so the tree ends up in
+ * a valid state.
+ *
+ * This method only supports node arguments with a null parent node.
+ *
+ * @param Node to add.
+ * @param Operator that will connect the new node with this one.
+ * @return New parent node, containing the operator.
+ * @throws Exception Throws when the provided new node does not have a null parent.
+ */
+ private ConditionsTreeNode add(ConditionsTreeNode node, OPERATOR op) throws Exception{
+ if (node.mParent != null) {
+ throw new Exception("Can only add new expressions from root node down.");
+ }
+
+ ConditionsTreeNode tmpNode = new ConditionsTreeNode(mParent, op);
+ tmpNode.mLeft = this;
+ tmpNode.mRight = node;
+
+ if (mParent != null) {
+ mParent.updateChild(this, tmpNode);
+ }
+ this.mParent = tmpNode;
+
+ node.mParent = tmpNode;
+
+ return tmpNode;
+ }
+
+ /**
+ * Helper method that replaces a child of the current node with a new node.
+ * If the provided old child node was the left one, left will be replaced with
+ * the new one. Same goes for the right one.
+ *
+ * @param oldChild Old child node to be replaced.
+ * @param newChild New child node.
+ */
+ private void updateChild(ConditionsTreeNode oldChild, ConditionsTreeNode newChild) {
+ // we can compare objects id's because this is the desired behaviour in this case
+ if (mLeft == oldChild) {
+ mLeft = newChild;
+ } else if (mRight == oldChild) {
+ mRight = newChild;
+ }
+ }
+
+ /**
+ * Recursive function to gather all the leaves in the subtree of which
+ * this node is the root.
+ *
+ * @param leafSet Leafset that's being built.
+ * @return Set of leaves being completed.
+ */
+ private HashSet getLeafSet(HashSet leafSet) {
+ // if we ended up in a leaf, add ourself and return
+ if (mLeft == null && mRight == null) {
+ leafSet.add(this);
+ return leafSet;
+ // we didn't end up in a leaf
+ } else {
+ if (mLeft != null) {
+ mLeft.getLeafSet(leafSet);
+ }
+
+ if (mRight != null) {
+ mRight.getLeafSet(leafSet);
+ }
+ return leafSet;
+ }
+ }
+
+ /**
+ * This applies the MPTT labeling to the subtree of which this node
+ * is the root node.
+ *
+ * For a description on MPTT see:
+ * http://www.sitepoint.com/hierarchical-data-database-2/
+ */
+ private int applyMPTTLabel(int label) {
+ mLeftMPTTMarker = label;
+ if (mLeft != null){
+ label = mLeft.applyMPTTLabel(label += 1);
+ }
+ if (mRight != null){
+ label = mRight.applyMPTTLabel(label += 1);
+ }
+ ++label;
+ mRightMPTTMarker = label;
+ return label;
+ }
+
+
+ ///////////////////////////////////////////////////////////////
+ // Parcelable
+ //
+ // This whole class has to be parcelable because it's passed
+ // on through intents.
+ ///////////////////////////////////////////////////////////////
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mValue.ordinal());
+ dest.writeParcelable(mCondition, flags);
+ dest.writeParcelable(mLeft, flags);
+ dest.writeParcelable(mRight, flags);
+ }
+
+ public static final Parcelable.Creator CREATOR
+ = new Parcelable.Creator() {
+ public ConditionsTreeNode createFromParcel(Parcel in) {
+ return new ConditionsTreeNode(in);
+ }
+
+ public ConditionsTreeNode[] newArray(int size) {
+ return new ConditionsTreeNode[size];
+ }
+ };
+
+ private ConditionsTreeNode(Parcel in) {
+ mValue = OPERATOR.values()[in.readInt()];
+ mCondition = in.readParcelable(ConditionsTreeNode.class.getClassLoader());
+ mLeft = in.readParcelable(ConditionsTreeNode.class.getClassLoader());
+ mRight = in.readParcelable(ConditionsTreeNode.class.getClassLoader());
+ mParent = null;
+ if (mLeft != null) {
+ mLeft.mParent = this;
+ }
+ if (mRight != null) {
+ mRight.mParent = this;
+ }
+ }
}
diff --git a/src/com/fsck/k9/search/LocalSearch.java b/src/com/fsck/k9/search/LocalSearch.java
index c801a5308..28973482d 100644
--- a/src/com/fsck/k9/search/LocalSearch.java
+++ b/src/com/fsck/k9/search/LocalSearch.java
@@ -11,81 +11,81 @@ import android.os.Parcelable;
import com.fsck.k9.mail.Flag;
/**
- * This class represents a local search.
+ * This class represents a local search.
* Removing conditions could be done through matching there unique id in the leafset and then
* removing them from the tree.
- *
+ *
* @author dzan
- *
+ *
* TODO implement a complete addAllowedFolder method
* TODO conflicting conditions check on add
* TODO duplicate condition checking?
* TODO assign each node a unique id that's used to retrieve it from the leaveset and remove.
- *
+ *
*/
public class LocalSearch implements SearchSpecification {
private String mName;
- private boolean mPredefined;
-
- // since the uuid isn't in the message table it's not in the tree neither
- private HashSet mAccountUuids = new HashSet();
- private ConditionsTreeNode mConditions = null;
+ private boolean mPredefined;
+
+ // since the uuid isn't in the message table it's not in the tree neither
+ private HashSet mAccountUuids = new HashSet();
+ private ConditionsTreeNode mConditions = null;
private HashSet mLeafSet = new HashSet();
-
-
- ///////////////////////////////////////////////////////////////
- // Constructors
- ///////////////////////////////////////////////////////////////
+
+
+ ///////////////////////////////////////////////////////////////
+ // Constructors
+ ///////////////////////////////////////////////////////////////
/**
- * Use this only if the search won't be saved. Saved searches need
+ * Use this only if the search won't be saved. Saved searches need
* a name!
*/
public LocalSearch(){}
-
- /**
- *
+
+ /**
+ *
* @param name
*/
public LocalSearch(String name) {
this.mName = name;
}
-
+
/**
- * Use this constructor when you know what you'r doing. Normally it's only used
+ * Use this constructor when you know what you'r doing. Normally it's only used
* when restoring these search objects from the database.
- *
+ *
* @param name Name of the search
* @param searchConditions SearchConditions, may contains flags and folders
* @param accounts Relative Account's uuid's
* @param predefined Is this a predefined search or a user created one?
*/
- protected LocalSearch(String name, ConditionsTreeNode searchConditions,
- String accounts, boolean predefined) {
- this(name);
- mConditions = searchConditions;
- mPredefined = predefined;
- mLeafSet = new HashSet();
- if (mConditions != null) {
- mLeafSet.addAll(mConditions.getLeafSet());
- }
-
- // initialize accounts
- if (accounts != null) {
- for (String account : accounts.split(",")) {
- mAccountUuids.add(account);
- }
- } else {
- // impossible but still not unrecoverable
- }
+ protected LocalSearch(String name, ConditionsTreeNode searchConditions,
+ String accounts, boolean predefined) {
+ this(name);
+ mConditions = searchConditions;
+ mPredefined = predefined;
+ mLeafSet = new HashSet();
+ if (mConditions != null) {
+ mLeafSet.addAll(mConditions.getLeafSet());
+ }
+
+ // initialize accounts
+ if (accounts != null) {
+ for (String account : accounts.split(",")) {
+ mAccountUuids.add(account);
+ }
+ } else {
+ // impossible but still not unrecoverable
+ }
}
-
-
- ///////////////////////////////////////////////////////////////
- // Public manipulation methods
- ///////////////////////////////////////////////////////////////
+
+
+ ///////////////////////////////////////////////////////////////
+ // Public manipulation methods
+ ///////////////////////////////////////////////////////////////
/**
* Sets the name of the saved search. If one existed it will
* be overwritten.
@@ -97,215 +97,215 @@ public class LocalSearch implements SearchSpecification {
}
/**
- * Add a new account to the search. When no accounts are
+ * Add a new account to the search. When no accounts are
* added manually we search all accounts on the device.
- *
+ *
* @param uuid Uuid of the account to be added.
*/
public void addAccountUuid(String uuid) {
- if (uuid.equals(ALL_ACCOUNTS)) {
- mAccountUuids.clear();
- }
- mAccountUuids.add(uuid);
+ if (uuid.equals(ALL_ACCOUNTS)) {
+ mAccountUuids.clear();
+ }
+ mAccountUuids.add(uuid);
}
/**
* Adds all the account uuids in the provided array to
* be matched by the seach.
- *
+ *
* @param accountUuids
*/
- public void addAccountUuids(String[] accountUuids) {
- for (String acc : accountUuids) {
- addAccountUuid(acc);
- }
- }
-
+ public void addAccountUuids(String[] accountUuids) {
+ for (String acc : accountUuids) {
+ addAccountUuid(acc);
+ }
+ }
+
/**
* Removes an account UUID from the current search.
- *
+ *
* @param uuid Account UUID to remove.
* @return True if removed, false otherwise.
*/
public boolean removeAccountUuid(String uuid) {
- return mAccountUuids.remove(uuid);
+ return mAccountUuids.remove(uuid);
}
-
- /**
- * Adds the provided node as the second argument of an AND
- * clause to this node.
- *
- * @param field Message table field to match against.
- * @param string Value to look for.
- * @param contains Attribute to use when matching.
- *
- * @throws IllegalConditionException
- */
- public void and(SEARCHFIELD field, String value, ATTRIBUTE attribute) {
- and(new SearchCondition(field, attribute, value));
- }
-
- /**
- * Adds the provided condition as the second argument of an AND
- * clause to this node.
- *
- * @param condition Condition to 'AND' with.
- * @return New top AND node, new root.
- */
- public ConditionsTreeNode and(SearchCondition condition) {
- try {
- ConditionsTreeNode tmp = new ConditionsTreeNode(condition);
- return and(tmp);
- } catch (Exception e) {
- // impossible
- return null;
- }
- }
-
- /**
- * Adds the provided node as the second argument of an AND
- * clause to this node.
- *
- * @param node Node to 'AND' with.
- * @return New top AND node, new root.
- * @throws Exception
- */
- public ConditionsTreeNode and(ConditionsTreeNode node) throws Exception {
- mLeafSet.addAll(node.getLeafSet());
-
- if (mConditions == null) {
- mConditions = node;
- return node;
- }
-
- return mConditions.and(node);
- }
-
- /**
- * Adds the provided condition as the second argument of an OR
- * clause to this node.
- *
- * @param condition Condition to 'OR' with.
- * @return New top OR node, new root.
- */
- public ConditionsTreeNode or(SearchCondition condition) {
- try {
- ConditionsTreeNode tmp = new ConditionsTreeNode(condition);
- return or(tmp);
- } catch (Exception e) {
- // impossible
- return null;
- }
- }
-
- /**
- * Adds the provided node as the second argument of an OR
- * clause to this node.
- *
- * @param node Node to 'OR' with.
- * @return New top OR node, new root.
- * @throws Exception
- */
- public ConditionsTreeNode or(ConditionsTreeNode node) throws Exception {
- mLeafSet.addAll(node.getLeafSet());
-
- if (mConditions == null) {
- mConditions = node;
- return node;
- }
-
- return mConditions.or(node);
- }
-
- /**
- * Add all the flags to this node as required flags. The
- * provided flags will be combined using AND with the root.
- *
- * @param requiredFlags Array of required flags.
- */
- public void allRequiredFlags(Flag[] requiredFlags) {
- if (requiredFlags != null) {
- for (Flag f : requiredFlags) {
- and(new SearchCondition(SEARCHFIELD.FLAG, ATTRIBUTE.CONTAINS, f.name()));
- }
- }
- }
-
- /**
- * Add all the flags to this node as forbidden flags. The
- * provided flags will be combined using AND with the root.
- *
- * @param forbiddenFlags Array of forbidden flags.
- */
- public void allForbiddenFlags(Flag[] forbiddenFlags) {
- if (forbiddenFlags != null) {
- for (Flag f : forbiddenFlags) {
- and(new SearchCondition(SEARCHFIELD.FLAG, ATTRIBUTE.NOT_CONTAINS, f.name()));
- }
- }
- }
-
- /**
- * TODO
- * FOR NOW: And the folder with the root.
- *
- * Add the folder as another folder to search in. The folder
- * will be added AND to the root if no 'folder subtree' was found.
- * Otherwise the folder will be added OR to that tree.
- *
- * @param name Name of the folder to add.
- */
- public void addAllowedFolder(String name) {
- /*
- * TODO find folder sub-tree
- * - do and on root of it & rest of search
- * - do or between folder nodes
- */
- and(new SearchCondition(SEARCHFIELD.FOLDER, ATTRIBUTE.EQUALS, name));
- }
-
- /*
- * TODO make this more advanced!
- * This is a temporarely solution that does NOT WORK for
- * real searches because of possible extra conditions to a folder requirement.
- */
- public List getFolderNames() {
- ArrayList results = new ArrayList();
- for (ConditionsTreeNode node : mLeafSet) {
- if (node.mCondition.field == SEARCHFIELD.FOLDER
- && node.mCondition.attribute == ATTRIBUTE.EQUALS) {
- results.add(node.mCondition.value);
- }
- }
- return results;
- }
-
- /**
- * Gets the leafset of the related condition tree.
- *
- * @return All the leaf conditions as a set.
- */
- public Set getLeafSet() {
- return mLeafSet;
- }
-
- ///////////////////////////////////////////////////////////////
- // Public accesor methods
- ///////////////////////////////////////////////////////////////
- /**
- * TODO THIS HAS TO GO!!!!
- * very dirty fix for remotesearch support atm
- */
- public String getRemoteSearchArguments() {
- for (ConditionsTreeNode node : getLeafSet()) {
- if (node.getCondition().field == SEARCHFIELD.SUBJECT
- || node.getCondition().field == SEARCHFIELD.SENDER ) {
- return node.getCondition().value;
- }
- }
- return null;
- }
-
+
+ /**
+ * Adds the provided node as the second argument of an AND
+ * clause to this node.
+ *
+ * @param field Message table field to match against.
+ * @param string Value to look for.
+ * @param contains Attribute to use when matching.
+ *
+ * @throws IllegalConditionException
+ */
+ public void and(SEARCHFIELD field, String value, ATTRIBUTE attribute) {
+ and(new SearchCondition(field, attribute, value));
+ }
+
+ /**
+ * Adds the provided condition as the second argument of an AND
+ * clause to this node.
+ *
+ * @param condition Condition to 'AND' with.
+ * @return New top AND node, new root.
+ */
+ public ConditionsTreeNode and(SearchCondition condition) {
+ try {
+ ConditionsTreeNode tmp = new ConditionsTreeNode(condition);
+ return and(tmp);
+ } catch (Exception e) {
+ // impossible
+ return null;
+ }
+ }
+
+ /**
+ * Adds the provided node as the second argument of an AND
+ * clause to this node.
+ *
+ * @param node Node to 'AND' with.
+ * @return New top AND node, new root.
+ * @throws Exception
+ */
+ public ConditionsTreeNode and(ConditionsTreeNode node) throws Exception {
+ mLeafSet.addAll(node.getLeafSet());
+
+ if (mConditions == null) {
+ mConditions = node;
+ return node;
+ }
+
+ return mConditions.and(node);
+ }
+
+ /**
+ * Adds the provided condition as the second argument of an OR
+ * clause to this node.
+ *
+ * @param condition Condition to 'OR' with.
+ * @return New top OR node, new root.
+ */
+ public ConditionsTreeNode or(SearchCondition condition) {
+ try {
+ ConditionsTreeNode tmp = new ConditionsTreeNode(condition);
+ return or(tmp);
+ } catch (Exception e) {
+ // impossible
+ return null;
+ }
+ }
+
+ /**
+ * Adds the provided node as the second argument of an OR
+ * clause to this node.
+ *
+ * @param node Node to 'OR' with.
+ * @return New top OR node, new root.
+ * @throws Exception
+ */
+ public ConditionsTreeNode or(ConditionsTreeNode node) throws Exception {
+ mLeafSet.addAll(node.getLeafSet());
+
+ if (mConditions == null) {
+ mConditions = node;
+ return node;
+ }
+
+ return mConditions.or(node);
+ }
+
+ /**
+ * Add all the flags to this node as required flags. The
+ * provided flags will be combined using AND with the root.
+ *
+ * @param requiredFlags Array of required flags.
+ */
+ public void allRequiredFlags(Flag[] requiredFlags) {
+ if (requiredFlags != null) {
+ for (Flag f : requiredFlags) {
+ and(new SearchCondition(SEARCHFIELD.FLAG, ATTRIBUTE.CONTAINS, f.name()));
+ }
+ }
+ }
+
+ /**
+ * Add all the flags to this node as forbidden flags. The
+ * provided flags will be combined using AND with the root.
+ *
+ * @param forbiddenFlags Array of forbidden flags.
+ */
+ public void allForbiddenFlags(Flag[] forbiddenFlags) {
+ if (forbiddenFlags != null) {
+ for (Flag f : forbiddenFlags) {
+ and(new SearchCondition(SEARCHFIELD.FLAG, ATTRIBUTE.NOT_CONTAINS, f.name()));
+ }
+ }
+ }
+
+ /**
+ * TODO
+ * FOR NOW: And the folder with the root.
+ *
+ * Add the folder as another folder to search in. The folder
+ * will be added AND to the root if no 'folder subtree' was found.
+ * Otherwise the folder will be added OR to that tree.
+ *
+ * @param name Name of the folder to add.
+ */
+ public void addAllowedFolder(String name) {
+ /*
+ * TODO find folder sub-tree
+ * - do and on root of it & rest of search
+ * - do or between folder nodes
+ */
+ and(new SearchCondition(SEARCHFIELD.FOLDER, ATTRIBUTE.EQUALS, name));
+ }
+
+ /*
+ * TODO make this more advanced!
+ * This is a temporarely solution that does NOT WORK for
+ * real searches because of possible extra conditions to a folder requirement.
+ */
+ public List getFolderNames() {
+ ArrayList results = new ArrayList();
+ for (ConditionsTreeNode node : mLeafSet) {
+ if (node.mCondition.field == SEARCHFIELD.FOLDER
+ && node.mCondition.attribute == ATTRIBUTE.EQUALS) {
+ results.add(node.mCondition.value);
+ }
+ }
+ return results;
+ }
+
+ /**
+ * Gets the leafset of the related condition tree.
+ *
+ * @return All the leaf conditions as a set.
+ */
+ public Set getLeafSet() {
+ return mLeafSet;
+ }
+
+ ///////////////////////////////////////////////////////////////
+ // Public accesor methods
+ ///////////////////////////////////////////////////////////////
+ /**
+ * TODO THIS HAS TO GO!!!!
+ * very dirty fix for remotesearch support atm
+ */
+ public String getRemoteSearchArguments() {
+ for (ConditionsTreeNode node : getLeafSet()) {
+ if (node.getCondition().field == SEARCHFIELD.SUBJECT
+ || node.getCondition().field == SEARCHFIELD.SENDER ) {
+ return node.getCondition().value;
+ }
+ }
+ return null;
+ }
+
/**
* Returns the name of the saved search.
*
@@ -314,22 +314,22 @@ public class LocalSearch implements SearchSpecification {
public String getName() {
return (mName == null ? "" : mName);
}
-
+
/**
* Checks if this search was hard coded and shipped with K-9
- *
+ *
* @return True is search was shipped with K-9
*/
- public boolean isPredefined() {
- return mPredefined;
- }
+ public boolean isPredefined() {
+ return mPredefined;
+ }
- /**
- * Returns all the account uuids that this search will try to
- * match against.
- *
- * @return Array of account uuids.
- */
+ /**
+ * Returns all the account uuids that this search will try to
+ * match against.
+ *
+ * @return Array of account uuids.
+ */
@Override
public String[] getAccountUuids() {
if (mAccountUuids.size() == 0) {
@@ -346,43 +346,43 @@ public class LocalSearch implements SearchSpecification {
*
* @return The root node of the related conditions tree.
*/
- @Override
- public ConditionsTreeNode getConditions() {
- return mConditions;
- }
-
- ///////////////////////////////////////////////////////////////
- // Parcelable
- ///////////////////////////////////////////////////////////////
- @Override
- public int describeContents() {
- return 0;
- }
+ @Override
+ public ConditionsTreeNode getConditions() {
+ return mConditions;
+ }
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(mName);
- dest.writeByte((byte) (mPredefined ? 1 : 0));
- dest.writeStringList(new ArrayList(mAccountUuids));
- dest.writeParcelable(mConditions, flags);
- }
-
- public static final Parcelable.Creator CREATOR
- = new Parcelable.Creator() {
- public LocalSearch createFromParcel(Parcel in) {
- return new LocalSearch(in);
- }
-
- public LocalSearch[] newArray(int size) {
- return new LocalSearch[size];
- }
- };
-
- public LocalSearch(Parcel in) {
- mName = in.readString();
- mPredefined = in.readByte() == 1;
- mAccountUuids.addAll(in.createStringArrayList());
- mConditions = in.readParcelable(LocalSearch.class.getClassLoader());
- mLeafSet = mConditions.getLeafSet();
- }
+ ///////////////////////////////////////////////////////////////
+ // Parcelable
+ ///////////////////////////////////////////////////////////////
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mName);
+ dest.writeByte((byte) (mPredefined ? 1 : 0));
+ dest.writeStringList(new ArrayList(mAccountUuids));
+ dest.writeParcelable(mConditions, flags);
+ }
+
+ public static final Parcelable.Creator CREATOR
+ = new Parcelable.Creator() {
+ public LocalSearch createFromParcel(Parcel in) {
+ return new LocalSearch(in);
+ }
+
+ public LocalSearch[] newArray(int size) {
+ return new LocalSearch[size];
+ }
+ };
+
+ public LocalSearch(Parcel in) {
+ mName = in.readString();
+ mPredefined = in.readByte() == 1;
+ mAccountUuids.addAll(in.createStringArrayList());
+ mConditions = in.readParcelable(LocalSearch.class.getClassLoader());
+ mLeafSet = mConditions.getLeafSet();
+ }
}
\ No newline at end of file
diff --git a/src/com/fsck/k9/search/SearchAccount.java b/src/com/fsck/k9/search/SearchAccount.java
index d76a8a0dd..00df5c741 100644
--- a/src/com/fsck/k9/search/SearchAccount.java
+++ b/src/com/fsck/k9/search/SearchAccount.java
@@ -14,36 +14,36 @@ import com.fsck.k9.R;
public class SearchAccount implements BaseAccount {
// create the all messages search ( all accounts is default when none specified )
- public static SearchAccount createAllMessagesAccount(Context context) {
+ public static SearchAccount createAllMessagesAccount(Context context) {
String name = context.getString(R.string.search_all_messages_title);
LocalSearch tmpSearch = new LocalSearch(name);
- return new SearchAccount(tmpSearch, name,
- context.getString(R.string.search_all_messages_detail));
- }
-
+ return new SearchAccount(tmpSearch, name,
+ context.getString(R.string.search_all_messages_detail));
+ }
- // create the unified inbox meta account ( all accounts is default when none specified )
- public static SearchAccount createUnifiedInboxAccount(Context context) {
+
+ // create the unified inbox meta account ( all accounts is default when none specified )
+ public static SearchAccount createUnifiedInboxAccount(Context context) {
String name = context.getString(R.string.integrated_inbox_title);
LocalSearch tmpSearch = new LocalSearch(name);
tmpSearch.addAllowedFolder(SearchSpecification.GENERIC_INBOX_NAME);
return new SearchAccount(tmpSearch, name,
- context.getString(R.string.integrated_inbox_detail));
- }
-
+ context.getString(R.string.integrated_inbox_detail));
+ }
+
private String mEmail = null;
private String mDescription = null;
private LocalSearch mSearch = null;
private String mFakeUuid = null;
-
+
public SearchAccount(LocalSearch search, String description, String email) throws IllegalArgumentException{
- if (search == null) {
- throw new IllegalArgumentException("Provided LocalSearch was null");
- }
-
- this.mSearch = search;
- this.mDescription = description;
- this.mEmail = email;
+ if (search == null) {
+ throw new IllegalArgumentException("Provided LocalSearch was null");
+ }
+
+ this.mSearch = search;
+ this.mDescription = description;
+ this.mEmail = email;
}
@Override
@@ -55,33 +55,33 @@ public class SearchAccount implements BaseAccount {
public synchronized void setEmail(String email) {
this.mEmail = email;
}
-
+
@Override
public String getDescription() {
return mDescription;
}
-
+
@Override
public void setDescription(String description) {
this.mDescription = description;
- }
+ }
public LocalSearch getRelatedSearch() {
- return mSearch;
+ return mSearch;
}
-
+
@Override
/*
* This will only be used when accessed as an Account. If that
- * is the case we don't want to return the uuid of a real account since
+ * is the case we don't want to return the uuid of a real account since
* this is posing as a fake meta-account. If this object is accesed as
- * a Search then methods from LocalSearch will be called which do handle
+ * a Search then methods from LocalSearch will be called which do handle
* things nice.
*/
public String getUuid() {
- if (mFakeUuid == null){
- mFakeUuid = UUID.randomUUID().toString();
- }
- return mFakeUuid;
+ if (mFakeUuid == null){
+ mFakeUuid = UUID.randomUUID().toString();
+ }
+ return mFakeUuid;
}
}
diff --git a/src/com/fsck/k9/search/SearchSpecification.java b/src/com/fsck/k9/search/SearchSpecification.java
index 6f6a38fa5..4244b4b4c 100644
--- a/src/com/fsck/k9/search/SearchSpecification.java
+++ b/src/com/fsck/k9/search/SearchSpecification.java
@@ -4,79 +4,79 @@ import android.os.Parcel;
import android.os.Parcelable;
public interface SearchSpecification extends Parcelable {
-
- /**
- * Get all the uuids of accounts this search acts on.
- * @return Array of uuids.
- */
+
+ /**
+ * Get all the uuids of accounts this search acts on.
+ * @return Array of uuids.
+ */
public String[] getAccountUuids();
-
+
/**
* Returns the search's name if it was named.
* @return Name of the search.
*/
- public String getName();
-
- /**
- * Returns the root node of the condition tree accompanying
- * the search.
- *
- * @return Root node of conditions tree.
- */
- public ConditionsTreeNode getConditions();
-
- /*
- * Some meta names for certain conditions.
- */
+ public String getName();
+
+ /**
+ * Returns the root node of the condition tree accompanying
+ * the search.
+ *
+ * @return Root node of conditions tree.
+ */
+ public ConditionsTreeNode getConditions();
+
+ /*
+ * Some meta names for certain conditions.
+ */
public static final String ALL_ACCOUNTS = "allAccounts";
- public static final String GENERIC_INBOX_NAME = "genericInboxName";
-
- ///////////////////////////////////////////////////////////////
- // ATTRIBUTE enum
- ///////////////////////////////////////////////////////////////
+ public static final String GENERIC_INBOX_NAME = "genericInboxName";
+
+ ///////////////////////////////////////////////////////////////
+ // ATTRIBUTE enum
+ ///////////////////////////////////////////////////////////////
public enum ATTRIBUTE {
- CONTAINS(false), EQUALS(false), STARTSWITH(false), ENDSWITH(false),
- NOT_CONTAINS(true), NOT_EQUALS(true), NOT_STARTSWITH(true), NOT_ENDSWITH(true);
-
- private boolean mNegation;
-
- private ATTRIBUTE(boolean negation) {
- this.mNegation = negation;
- }
-
+ CONTAINS(false), EQUALS(false), STARTSWITH(false), ENDSWITH(false),
+ NOT_CONTAINS(true), NOT_EQUALS(true), NOT_STARTSWITH(true), NOT_ENDSWITH(true);
+
+ private boolean mNegation;
+
+ private ATTRIBUTE(boolean negation) {
+ this.mNegation = negation;
+ }
+
public String formQuery(String value) {
- String queryPart = "";
-
- switch (this) {
- case NOT_CONTAINS:
- case CONTAINS:
- queryPart = "'%"+value+"%'";
- break;
- case NOT_EQUALS:
- case EQUALS:
- queryPart = "'"+value+"'";
- break;
- case NOT_STARTSWITH:
- case STARTSWITH:
- queryPart = "'%"+value+"'";
- break;
- case NOT_ENDSWITH:
- case ENDSWITH:
- queryPart = "'"+value+"%'";
- break;
- default: queryPart = "'"+value+"'";
- }
-
- return (mNegation ? " NOT LIKE " : " LIKE ") + queryPart;
+ String queryPart = "";
+
+ switch (this) {
+ case NOT_CONTAINS:
+ case CONTAINS:
+ queryPart = "'%"+value+"%'";
+ break;
+ case NOT_EQUALS:
+ case EQUALS:
+ queryPart = "'"+value+"'";
+ break;
+ case NOT_STARTSWITH:
+ case STARTSWITH:
+ queryPart = "'%"+value+"'";
+ break;
+ case NOT_ENDSWITH:
+ case ENDSWITH:
+ queryPart = "'"+value+"%'";
+ break;
+ default: queryPart = "'"+value+"'";
+ }
+
+ return (mNegation ? " NOT LIKE " : " LIKE ") + queryPart;
}
};
-
- ///////////////////////////////////////////////////////////////
- // SEARCHFIELD enum
- ///////////////////////////////////////////////////////////////
- /*
- * Using an enum in order to have more robust code. Users ( & coders )
- * are prevented from passing illegal fields. No database overhead
+
+ ///////////////////////////////////////////////////////////////
+ // SEARCHFIELD enum
+ ///////////////////////////////////////////////////////////////
+ /*
+ * Using an enum in order to have more robust code. Users ( & coders )
+ * are prevented from passing illegal fields. No database overhead
* when invalid fields passed.
*
* By result, only the fields in here are searchable.
@@ -84,7 +84,7 @@ public interface SearchSpecification extends Parcelable {
* Fields not in here at this moment ( and by effect not searchable ):
* id, html_content, internal_date, message_id,
* preview, mime_type
- *
+ *
*/
public enum SEARCHFIELD {
SUBJECT("subject"), DATE("date"), UID("uid"), FLAG("flags"),
@@ -93,7 +93,7 @@ public interface SearchSpecification extends Parcelable {
ATTACHMENT_COUNT("attachment_count"), DELETED("deleted"), THREAD_ROOT("thread_root");
private String dbName;
-
+
private SEARCHFIELD(String dbName) {
this.dbName = dbName;
}
@@ -102,25 +102,25 @@ public interface SearchSpecification extends Parcelable {
return dbName;
}
}
-
-
- ///////////////////////////////////////////////////////////////
- // SearchCondition class
- ///////////////////////////////////////////////////////////////
+
+
+ ///////////////////////////////////////////////////////////////
+ // SearchCondition class
+ ///////////////////////////////////////////////////////////////
/**
* This class represents 1 value for a certain search field. One
- * value consists of three things:
+ * value consists of three things:
* an attribute: equals, starts with, contains,...
* a searchfield: date, flags, sender, subject,...
* a value: "apple", "jesse",..
- *
+ *
* @author dzan
*/
public class SearchCondition implements Parcelable{
public String value;
public ATTRIBUTE attribute;
public SEARCHFIELD field;
-
+
public SearchCondition(SEARCHFIELD field, ATTRIBUTE attribute, String value) {
this.value = value;
this.attribute = attribute;
@@ -128,26 +128,26 @@ public interface SearchSpecification extends Parcelable {
}
private SearchCondition(Parcel in) {
- this.value = in.readString();
- this.attribute = ATTRIBUTE.values()[in.readInt()];
- this.field = SEARCHFIELD.values()[in.readInt()];
+ this.value = in.readString();
+ this.attribute = ATTRIBUTE.values()[in.readInt()];
+ this.field = SEARCHFIELD.values()[in.readInt()];
}
-
- public String toHumanString() {
- return field.toString() + attribute.toString();
- }
-
- @Override
- public String toString() {
- return field.getDatabaseName() + attribute.formQuery(value);
- }
-
+
+ public String toHumanString() {
+ return field.toString() + attribute.toString();
+ }
+
+ @Override
+ public String toString() {
+ return field.getDatabaseName() + attribute.formQuery(value);
+ }
+
@Override
public boolean equals(Object o) {
if (o instanceof SearchCondition) {
SearchCondition tmp = (SearchCondition) o;
if (tmp.attribute == attribute
- && tmp.value.equals(value)
+ && tmp.value.equals(value)
&& tmp.field == field) {
return true;
} else {
@@ -158,27 +158,27 @@ public interface SearchSpecification extends Parcelable {
}
}
- @Override
- public int describeContents() {
- return 0;
- }
+ @Override
+ public int describeContents() {
+ return 0;
+ }
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(value);
- dest.writeInt(attribute.ordinal());
- dest.writeInt(field.ordinal());
- }
-
- public static final Parcelable.Creator CREATOR
- = new Parcelable.Creator() {
- public SearchCondition createFromParcel(Parcel in) {
- return new SearchCondition(in);
- }
-
- public SearchCondition[] newArray(int size) {
- return new SearchCondition[size];
- }
- };
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(value);
+ dest.writeInt(attribute.ordinal());
+ dest.writeInt(field.ordinal());
+ }
+
+ public static final Parcelable.Creator CREATOR
+ = new Parcelable.Creator() {
+ public SearchCondition createFromParcel(Parcel in) {
+ return new SearchCondition(in);
+ }
+
+ public SearchCondition[] newArray(int size) {
+ return new SearchCondition[size];
+ }
+ };
}
}
\ No newline at end of file
From 502771dd0e0b8d382adc568b682394216bba9ea2 Mon Sep 17 00:00:00 2001
From: cketti
Date: Tue, 16 Oct 2012 22:48:31 +0200
Subject: [PATCH 19/74] Revert changing the default value for debug logging
---
src/com/fsck/k9/K9.java | 4 ++--
src/com/fsck/k9/preferences/GlobalSettings.java | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/com/fsck/k9/K9.java b/src/com/fsck/k9/K9.java
index ef0f21fb0..e6caa157e 100644
--- a/src/com/fsck/k9/K9.java
+++ b/src/com/fsck/k9/K9.java
@@ -579,8 +579,8 @@ public class K9 extends Application {
public static void loadPrefs(Preferences prefs) {
SharedPreferences sprefs = prefs.getPreferences();
- DEBUG = sprefs.getBoolean("enableDebugLogging", true);
- DEBUG_SENSITIVE = sprefs.getBoolean("enableSensitiveLogging", true);
+ DEBUG = sprefs.getBoolean("enableDebugLogging", false);
+ DEBUG_SENSITIVE = sprefs.getBoolean("enableSensitiveLogging", false);
mAnimations = sprefs.getBoolean("animations", true);
mGesturesEnabled = sprefs.getBoolean("gesturesEnabled", false);
mUseVolumeKeysForNavigation = sprefs.getBoolean("useVolumeKeysForNavigation", false);
diff --git a/src/com/fsck/k9/preferences/GlobalSettings.java b/src/com/fsck/k9/preferences/GlobalSettings.java
index 019391003..e49face93 100644
--- a/src/com/fsck/k9/preferences/GlobalSettings.java
+++ b/src/com/fsck/k9/preferences/GlobalSettings.java
@@ -64,10 +64,10 @@ public class GlobalSettings {
new V(1, new DateFormatSetting(DateFormatter.DEFAULT_FORMAT))
));
s.put("enableDebugLogging", Settings.versions(
- new V(1, new BooleanSetting(true))
+ new V(1, new BooleanSetting(false))
));
s.put("enableSensitiveLogging", Settings.versions(
- new V(1, new BooleanSetting(true))
+ new V(1, new BooleanSetting(false))
));
s.put("fontSizeAccountDescription", Settings.versions(
new V(1, new FontSizeSetting(FontSizes.SMALL))
From fff94956f4fda33468b65ad535da202e387ae545 Mon Sep 17 00:00:00 2001
From: cketti
Date: Tue, 16 Oct 2012 22:51:01 +0200
Subject: [PATCH 20/74] Fixed LocalSearch.addAllowedFolder()
We need to use the node returned by add() as new root node.
---
src/com/fsck/k9/search/LocalSearch.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/com/fsck/k9/search/LocalSearch.java b/src/com/fsck/k9/search/LocalSearch.java
index 28973482d..72ec45578 100644
--- a/src/com/fsck/k9/search/LocalSearch.java
+++ b/src/com/fsck/k9/search/LocalSearch.java
@@ -261,7 +261,7 @@ public class LocalSearch implements SearchSpecification {
* - do and on root of it & rest of search
* - do or between folder nodes
*/
- and(new SearchCondition(SEARCHFIELD.FOLDER, ATTRIBUTE.EQUALS, name));
+ mConditions = and(new SearchCondition(SEARCHFIELD.FOLDER, ATTRIBUTE.EQUALS, name));
}
/*
From 20ed1ebe61e4bc933a0481b8c957728d3475ca44 Mon Sep 17 00:00:00 2001
From: cketti
Date: Wed, 17 Oct 2012 20:52:03 +0200
Subject: [PATCH 21/74] Code style cleanup
---
src/com/fsck/k9/activity/MessageList.java | 12 +--
src/com/fsck/k9/mail/store/LocalStore.java | 4 +-
.../fsck/k9/search/ConditionsTreeNode.java | 89 +++++++++++--------
src/com/fsck/k9/search/LocalSearch.java | 36 ++++----
src/com/fsck/k9/search/SearchAccount.java | 22 ++---
src/com/fsck/k9/search/SearchModifier.java | 4 +-
.../fsck/k9/search/SearchSpecification.java | 41 ++++-----
7 files changed, 113 insertions(+), 95 deletions(-)
diff --git a/src/com/fsck/k9/activity/MessageList.java b/src/com/fsck/k9/activity/MessageList.java
index 9eae7ffbe..c5f148b8e 100644
--- a/src/com/fsck/k9/activity/MessageList.java
+++ b/src/com/fsck/k9/activity/MessageList.java
@@ -36,8 +36,8 @@ import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.search.LocalSearch;
import com.fsck.k9.search.SearchSpecification;
-import com.fsck.k9.search.SearchSpecification.ATTRIBUTE;
-import com.fsck.k9.search.SearchSpecification.SEARCHFIELD;
+import com.fsck.k9.search.SearchSpecification.Attribute;
+import com.fsck.k9.search.SearchSpecification.Searchfield;
import com.fsck.k9.search.SearchSpecification.SearchCondition;
@@ -134,8 +134,8 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
mSearch.addAllowedFolder(appData.getString(EXTRA_SEARCH_FOLDER));
String query = intent.getStringExtra(SearchManager.QUERY);
- mSearch.or(new SearchCondition(SEARCHFIELD.SENDER, ATTRIBUTE.CONTAINS, query));
- mSearch.or(new SearchCondition(SEARCHFIELD.SUBJECT, ATTRIBUTE.CONTAINS, query));
+ mSearch.or(new SearchCondition(Searchfield.SENDER, Attribute.CONTAINS, query));
+ mSearch.or(new SearchCondition(Searchfield.SUBJECT, Attribute.CONTAINS, query));
mIsRemote = true;
}
@@ -583,7 +583,7 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
public void showMoreFromSameSender(String senderAddress) {
LocalSearch tmpSearch = new LocalSearch("From " + senderAddress);
tmpSearch.addAccountUuids(mSearch.getAccountUuids());
- tmpSearch.and(SEARCHFIELD.SENDER, senderAddress, ATTRIBUTE.CONTAINS);
+ tmpSearch.and(Searchfield.SENDER, senderAddress, Attribute.CONTAINS);
MessageListFragment fragment = MessageListFragment.newInstance(tmpSearch, false);
@@ -670,7 +670,7 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
public void showThread(Account account, String folderName, long threadRootId) {
LocalSearch tmpSearch = new LocalSearch();
tmpSearch.addAccountUuids(mSearch.getAccountUuids());
- tmpSearch.and(SEARCHFIELD.THREAD_ROOT, String.valueOf(threadRootId), ATTRIBUTE.EQUALS);
+ tmpSearch.and(Searchfield.THREAD_ROOT, String.valueOf(threadRootId), Attribute.EQUALS);
MessageListFragment fragment = MessageListFragment.newInstance(tmpSearch, false);
addMessageListFragment(fragment);
diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java
index d4778bc3b..86b8b2a09 100644
--- a/src/com/fsck/k9/mail/store/LocalStore.java
+++ b/src/com/fsck/k9/mail/store/LocalStore.java
@@ -71,7 +71,7 @@ import com.fsck.k9.mail.store.StorageManager.StorageProvider;
import com.fsck.k9.provider.AttachmentProvider;
import com.fsck.k9.search.ConditionsTreeNode;
import com.fsck.k9.search.LocalSearch;
-import com.fsck.k9.search.SearchSpecification.SEARCHFIELD;
+import com.fsck.k9.search.SearchSpecification.Searchfield;
/**
*
@@ -936,7 +936,7 @@ public class LocalStore extends Store implements Serializable {
// update some references in the search that have to be bound to this one store
for (ConditionsTreeNode node : search.getLeafSet()) {
- if (node.mCondition.field == SEARCHFIELD.FOLDER) {
+ if (node.mCondition.field == Searchfield.FOLDER) {
// TODO find better solution
if (isFolderId(node.mCondition.value)) {
continue;
diff --git a/src/com/fsck/k9/search/ConditionsTreeNode.java b/src/com/fsck/k9/search/ConditionsTreeNode.java
index 03ba2039d..430f31255 100644
--- a/src/com/fsck/k9/search/ConditionsTreeNode.java
+++ b/src/com/fsck/k9/search/ConditionsTreeNode.java
@@ -9,8 +9,8 @@ import android.database.Cursor;
import android.os.Parcel;
import android.os.Parcelable;
-import com.fsck.k9.search.SearchSpecification.ATTRIBUTE;
-import com.fsck.k9.search.SearchSpecification.SEARCHFIELD;
+import com.fsck.k9.search.SearchSpecification.Attribute;
+import com.fsck.k9.search.SearchSpecification.Searchfield;
import com.fsck.k9.search.SearchSpecification.SearchCondition;
/**
@@ -19,12 +19,10 @@ import com.fsck.k9.search.SearchSpecification.SearchCondition;
*
* TODO removing conditions from the tree
* TODO implement NOT as a node again
- *
- * @author dzan
*/
-public class ConditionsTreeNode implements Parcelable{
+public class ConditionsTreeNode implements Parcelable {
- public enum OPERATOR {
+ public enum Operator {
AND, OR, CONDITION;
}
@@ -36,7 +34,7 @@ public class ConditionsTreeNode implements Parcelable{
* If mValue isn't CONDITION then mCondition contains a real
* condition, otherwise it's null.
*/
- public OPERATOR mValue;
+ public Operator mValue;
public SearchCondition mCondition;
/*
@@ -71,7 +69,7 @@ public class ConditionsTreeNode implements Parcelable{
// other nodes
while (cursor.moveToNext()) {
tmp = buildNodeFromRow(cursor);
- if (tmp.mRightMPTTMarker < stack.peek().mRightMPTTMarker ){
+ if (tmp.mRightMPTTMarker < stack.peek().mRightMPTTMarker) {
stack.peek().mLeft = tmp;
stack.push(tmp);
} else {
@@ -94,11 +92,11 @@ public class ConditionsTreeNode implements Parcelable{
ConditionsTreeNode result = null;
SearchCondition condition = null;
- OPERATOR tmpValue = ConditionsTreeNode.OPERATOR.valueOf(cursor.getString(5));
+ Operator tmpValue = ConditionsTreeNode.Operator.valueOf(cursor.getString(5));
- if (tmpValue == OPERATOR.CONDITION) {
- condition = new SearchCondition(SEARCHFIELD.valueOf(cursor.getString(0)),
- ATTRIBUTE.valueOf(cursor.getString(2)), cursor.getString(1));
+ if (tmpValue == Operator.CONDITION) {
+ condition = new SearchCondition(Searchfield.valueOf(cursor.getString(0)),
+ Attribute.valueOf(cursor.getString(2)), cursor.getString(1));
}
result = new ConditionsTreeNode(condition);
@@ -116,10 +114,10 @@ public class ConditionsTreeNode implements Parcelable{
public ConditionsTreeNode(SearchCondition condition) {
mParent = null;
mCondition = condition;
- mValue = OPERATOR.CONDITION;
+ mValue = Operator.CONDITION;
}
- public ConditionsTreeNode(ConditionsTreeNode parent, OPERATOR op) {
+ public ConditionsTreeNode(ConditionsTreeNode parent, Operator op) {
mParent = parent;
mValue = op;
mCondition = null;
@@ -138,7 +136,7 @@ public class ConditionsTreeNode implements Parcelable{
* @throws Exception
*/
public ConditionsTreeNode and(ConditionsTreeNode expr) throws Exception {
- return add(expr, OPERATOR.AND);
+ return add(expr, Operator.AND);
}
/**
@@ -168,7 +166,7 @@ public class ConditionsTreeNode implements Parcelable{
* @throws Exception
*/
public ConditionsTreeNode or(ConditionsTreeNode expr) throws Exception {
- return add(expr, OPERATOR.OR);
+ return add(expr, Operator.OR);
}
/**
@@ -244,10 +242,17 @@ public class ConditionsTreeNode implements Parcelable{
Stack stack = new Stack();
stack.push(this);
- while(!stack.isEmpty()) {
- ConditionsTreeNode current = stack.pop( );
- if( current.mLeft != null ) stack.push( current.mLeft );
- if( current.mRight != null ) stack.push( current.mRight );
+ while (!stack.isEmpty()) {
+ ConditionsTreeNode current = stack.pop();
+
+ if (current.mLeft != null) {
+ stack.push(current.mLeft);
+ }
+
+ if (current.mRight != null) {
+ stack.push(current.mRight);
+ }
+
result.add(current);
}
@@ -273,7 +278,7 @@ public class ConditionsTreeNode implements Parcelable{
* @return New parent node, containing the operator.
* @throws Exception Throws when the provided new node does not have a null parent.
*/
- private ConditionsTreeNode add(ConditionsTreeNode node, OPERATOR op) throws Exception{
+ private ConditionsTreeNode add(ConditionsTreeNode node, Operator op) throws Exception {
if (node.mParent != null) {
throw new Exception("Can only add new expressions from root node down.");
}
@@ -285,8 +290,8 @@ public class ConditionsTreeNode implements Parcelable{
if (mParent != null) {
mParent.updateChild(this, tmpNode);
}
- this.mParent = tmpNode;
+ this.mParent = tmpNode;
node.mParent = tmpNode;
return tmpNode;
@@ -317,21 +322,21 @@ public class ConditionsTreeNode implements Parcelable{
* @return Set of leaves being completed.
*/
private HashSet getLeafSet(HashSet leafSet) {
- // if we ended up in a leaf, add ourself and return
if (mLeft == null && mRight == null) {
+ // if we ended up in a leaf, add ourself and return
leafSet.add(this);
return leafSet;
- // we didn't end up in a leaf
- } else {
- if (mLeft != null) {
- mLeft.getLeafSet(leafSet);
- }
-
- if (mRight != null) {
- mRight.getLeafSet(leafSet);
- }
- return leafSet;
}
+
+ // we didn't end up in a leaf
+ if (mLeft != null) {
+ mLeft.getLeafSet(leafSet);
+ }
+
+ if (mRight != null) {
+ mRight.getLeafSet(leafSet);
+ }
+ return leafSet;
}
/**
@@ -343,12 +348,15 @@ public class ConditionsTreeNode implements Parcelable{
*/
private int applyMPTTLabel(int label) {
mLeftMPTTMarker = label;
- if (mLeft != null){
+
+ if (mLeft != null) {
label = mLeft.applyMPTTLabel(label += 1);
}
- if (mRight != null){
+
+ if (mRight != null) {
label = mRight.applyMPTTLabel(label += 1);
}
+
++label;
mRightMPTTMarker = label;
return label;
@@ -374,26 +382,31 @@ public class ConditionsTreeNode implements Parcelable{
dest.writeParcelable(mRight, flags);
}
- public static final Parcelable.Creator CREATOR
- = new Parcelable.Creator() {
+ public static final Parcelable.Creator CREATOR =
+ new Parcelable.Creator() {
+
+ @Override
public ConditionsTreeNode createFromParcel(Parcel in) {
return new ConditionsTreeNode(in);
}
+ @Override
public ConditionsTreeNode[] newArray(int size) {
return new ConditionsTreeNode[size];
}
};
private ConditionsTreeNode(Parcel in) {
- mValue = OPERATOR.values()[in.readInt()];
+ mValue = Operator.values()[in.readInt()];
mCondition = in.readParcelable(ConditionsTreeNode.class.getClassLoader());
mLeft = in.readParcelable(ConditionsTreeNode.class.getClassLoader());
mRight = in.readParcelable(ConditionsTreeNode.class.getClassLoader());
mParent = null;
+
if (mLeft != null) {
mLeft.mParent = this;
}
+
if (mRight != null) {
mRight.mParent = this;
}
diff --git a/src/com/fsck/k9/search/LocalSearch.java b/src/com/fsck/k9/search/LocalSearch.java
index 72ec45578..83c5fb522 100644
--- a/src/com/fsck/k9/search/LocalSearch.java
+++ b/src/com/fsck/k9/search/LocalSearch.java
@@ -12,12 +12,10 @@ import com.fsck.k9.mail.Flag;
/**
* This class represents a local search.
-
+ *
* Removing conditions could be done through matching there unique id in the leafset and then
* removing them from the tree.
*
- * @author dzan
- *
* TODO implement a complete addAllowedFolder method
* TODO conflicting conditions check on add
* TODO duplicate condition checking?
@@ -43,7 +41,7 @@ public class LocalSearch implements SearchSpecification {
* Use this only if the search won't be saved. Saved searches need
* a name!
*/
- public LocalSearch(){}
+ public LocalSearch() {}
/**
*
@@ -141,7 +139,7 @@ public class LocalSearch implements SearchSpecification {
*
* @throws IllegalConditionException
*/
- public void and(SEARCHFIELD field, String value, ATTRIBUTE attribute) {
+ public void and(Searchfield field, String value, Attribute attribute) {
and(new SearchCondition(field, attribute, value));
}
@@ -226,7 +224,7 @@ public class LocalSearch implements SearchSpecification {
public void allRequiredFlags(Flag[] requiredFlags) {
if (requiredFlags != null) {
for (Flag f : requiredFlags) {
- and(new SearchCondition(SEARCHFIELD.FLAG, ATTRIBUTE.CONTAINS, f.name()));
+ and(new SearchCondition(Searchfield.FLAG, Attribute.CONTAINS, f.name()));
}
}
}
@@ -240,7 +238,7 @@ public class LocalSearch implements SearchSpecification {
public void allForbiddenFlags(Flag[] forbiddenFlags) {
if (forbiddenFlags != null) {
for (Flag f : forbiddenFlags) {
- and(new SearchCondition(SEARCHFIELD.FLAG, ATTRIBUTE.NOT_CONTAINS, f.name()));
+ and(new SearchCondition(Searchfield.FLAG, Attribute.NOT_CONTAINS, f.name()));
}
}
}
@@ -261,7 +259,7 @@ public class LocalSearch implements SearchSpecification {
* - do and on root of it & rest of search
* - do or between folder nodes
*/
- mConditions = and(new SearchCondition(SEARCHFIELD.FOLDER, ATTRIBUTE.EQUALS, name));
+ mConditions = and(new SearchCondition(Searchfield.FOLDER, Attribute.EQUALS, name));
}
/*
@@ -272,8 +270,8 @@ public class LocalSearch implements SearchSpecification {
public List getFolderNames() {
ArrayList results = new ArrayList();
for (ConditionsTreeNode node : mLeafSet) {
- if (node.mCondition.field == SEARCHFIELD.FOLDER
- && node.mCondition.attribute == ATTRIBUTE.EQUALS) {
+ if (node.mCondition.field == Searchfield.FOLDER &&
+ node.mCondition.attribute == Attribute.EQUALS) {
results.add(node.mCondition.value);
}
}
@@ -298,8 +296,8 @@ public class LocalSearch implements SearchSpecification {
*/
public String getRemoteSearchArguments() {
for (ConditionsTreeNode node : getLeafSet()) {
- if (node.getCondition().field == SEARCHFIELD.SUBJECT
- || node.getCondition().field == SEARCHFIELD.SENDER ) {
+ if (node.getCondition().field == Searchfield.SUBJECT ||
+ node.getCondition().field == Searchfield.SENDER ) {
return node.getCondition().value;
}
}
@@ -311,8 +309,9 @@ public class LocalSearch implements SearchSpecification {
*
* @return Name of the search.
*/
+ @Override
public String getName() {
- return (mName == null ? "" : mName);
+ return (mName == null) ? "" : mName;
}
/**
@@ -333,7 +332,7 @@ public class LocalSearch implements SearchSpecification {
@Override
public String[] getAccountUuids() {
if (mAccountUuids.size() == 0) {
- return new String[] {SearchSpecification.ALL_ACCOUNTS};
+ return new String[] { SearchSpecification.ALL_ACCOUNTS };
}
String[] tmp = new String[mAccountUuids.size()];
@@ -367,12 +366,15 @@ public class LocalSearch implements SearchSpecification {
dest.writeParcelable(mConditions, flags);
}
- public static final Parcelable.Creator CREATOR
- = new Parcelable.Creator() {
+ public static final Parcelable.Creator CREATOR =
+ new Parcelable.Creator() {
+
+ @Override
public LocalSearch createFromParcel(Parcel in) {
return new LocalSearch(in);
}
+ @Override
public LocalSearch[] newArray(int size) {
return new LocalSearch[size];
}
@@ -380,7 +382,7 @@ public class LocalSearch implements SearchSpecification {
public LocalSearch(Parcel in) {
mName = in.readString();
- mPredefined = in.readByte() == 1;
+ mPredefined = (in.readByte() == 1);
mAccountUuids.addAll(in.createStringArrayList());
mConditions = in.readParcelable(LocalSearch.class.getClassLoader());
mLeafSet = mConditions.getLeafSet();
diff --git a/src/com/fsck/k9/search/SearchAccount.java b/src/com/fsck/k9/search/SearchAccount.java
index 00df5c741..201b7eb0a 100644
--- a/src/com/fsck/k9/search/SearchAccount.java
+++ b/src/com/fsck/k9/search/SearchAccount.java
@@ -31,19 +31,21 @@ public class SearchAccount implements BaseAccount {
context.getString(R.string.integrated_inbox_detail));
}
- private String mEmail = null;
- private String mDescription = null;
- private LocalSearch mSearch = null;
- private String mFakeUuid = null;
+ private String mEmail;
+ private String mDescription;
+ private LocalSearch mSearch;
+ private String mFakeUuid;
+
+ public SearchAccount(LocalSearch search, String description, String email)
+ throws IllegalArgumentException {
- public SearchAccount(LocalSearch search, String description, String email) throws IllegalArgumentException{
if (search == null) {
throw new IllegalArgumentException("Provided LocalSearch was null");
}
- this.mSearch = search;
- this.mDescription = description;
- this.mEmail = email;
+ mSearch = search;
+ mDescription = description;
+ mEmail = email;
}
@Override
@@ -70,7 +72,6 @@ public class SearchAccount implements BaseAccount {
return mSearch;
}
- @Override
/*
* This will only be used when accessed as an Account. If that
* is the case we don't want to return the uuid of a real account since
@@ -78,8 +79,9 @@ public class SearchAccount implements BaseAccount {
* a Search then methods from LocalSearch will be called which do handle
* things nice.
*/
+ @Override
public String getUuid() {
- if (mFakeUuid == null){
+ if (mFakeUuid == null) {
mFakeUuid = UUID.randomUUID().toString();
}
return mFakeUuid;
diff --git a/src/com/fsck/k9/search/SearchModifier.java b/src/com/fsck/k9/search/SearchModifier.java
index f027ab062..22ec2740b 100644
--- a/src/com/fsck/k9/search/SearchModifier.java
+++ b/src/com/fsck/k9/search/SearchModifier.java
@@ -7,8 +7,8 @@ import com.fsck.k9.mail.Flag;
* This enum represents filtering parameters used by {@link com.fsck.k9.search.SearchAccount}.
*/
public enum SearchModifier {
- FLAGGED(R.string.flagged_modifier, new Flag[]{Flag.FLAGGED}, null),
- UNREAD(R.string.unread_modifier, null, new Flag[]{Flag.SEEN});
+ FLAGGED(R.string.flagged_modifier, new Flag[] { Flag.FLAGGED }, null),
+ UNREAD(R.string.unread_modifier, null, new Flag[] { Flag.SEEN });
public final int resId;
public final Flag[] requiredFlags;
diff --git a/src/com/fsck/k9/search/SearchSpecification.java b/src/com/fsck/k9/search/SearchSpecification.java
index 4244b4b4c..31bf9b387 100644
--- a/src/com/fsck/k9/search/SearchSpecification.java
+++ b/src/com/fsck/k9/search/SearchSpecification.java
@@ -34,13 +34,13 @@ public interface SearchSpecification extends Parcelable {
///////////////////////////////////////////////////////////////
// ATTRIBUTE enum
///////////////////////////////////////////////////////////////
- public enum ATTRIBUTE {
+ public enum Attribute {
CONTAINS(false), EQUALS(false), STARTSWITH(false), ENDSWITH(false),
NOT_CONTAINS(true), NOT_EQUALS(true), NOT_STARTSWITH(true), NOT_ENDSWITH(true);
private boolean mNegation;
- private ATTRIBUTE(boolean negation) {
+ private Attribute(boolean negation) {
this.mNegation = negation;
}
@@ -69,7 +69,7 @@ public interface SearchSpecification extends Parcelable {
return (mNegation ? " NOT LIKE " : " LIKE ") + queryPart;
}
- };
+ }
///////////////////////////////////////////////////////////////
// SEARCHFIELD enum
@@ -86,7 +86,7 @@ public interface SearchSpecification extends Parcelable {
* preview, mime_type
*
*/
- public enum SEARCHFIELD {
+ public enum Searchfield {
SUBJECT("subject"), DATE("date"), UID("uid"), FLAG("flags"),
SENDER("sender_list"), TO("to_list"), CC("cc_list"), FOLDER("folder_id"),
BCC("bcc_list"), REPLY_TO("reply_to_list"), MESSAGE("text_content"),
@@ -94,7 +94,7 @@ public interface SearchSpecification extends Parcelable {
private String dbName;
- private SEARCHFIELD(String dbName) {
+ private Searchfield(String dbName) {
this.dbName = dbName;
}
@@ -116,12 +116,12 @@ public interface SearchSpecification extends Parcelable {
*
* @author dzan
*/
- public class SearchCondition implements Parcelable{
+ public class SearchCondition implements Parcelable {
public String value;
- public ATTRIBUTE attribute;
- public SEARCHFIELD field;
+ public Attribute attribute;
+ public Searchfield field;
- public SearchCondition(SEARCHFIELD field, ATTRIBUTE attribute, String value) {
+ public SearchCondition(Searchfield field, Attribute attribute, String value) {
this.value = value;
this.attribute = attribute;
this.field = field;
@@ -129,8 +129,8 @@ public interface SearchSpecification extends Parcelable {
private SearchCondition(Parcel in) {
this.value = in.readString();
- this.attribute = ATTRIBUTE.values()[in.readInt()];
- this.field = SEARCHFIELD.values()[in.readInt()];
+ this.attribute = Attribute.values()[in.readInt()];
+ this.field = Searchfield.values()[in.readInt()];
}
public String toHumanString() {
@@ -146,16 +146,14 @@ public interface SearchSpecification extends Parcelable {
public boolean equals(Object o) {
if (o instanceof SearchCondition) {
SearchCondition tmp = (SearchCondition) o;
- if (tmp.attribute == attribute
- && tmp.value.equals(value)
- && tmp.field == field) {
+ if (tmp.attribute == attribute &&
+ tmp.field == field &&
+ tmp.value.equals(value)) {
return true;
- } else {
- return false;
}
- } else {
- return false;
}
+
+ return false;
}
@Override
@@ -170,12 +168,15 @@ public interface SearchSpecification extends Parcelable {
dest.writeInt(field.ordinal());
}
- public static final Parcelable.Creator CREATOR
- = new Parcelable.Creator() {
+ public static final Parcelable.Creator CREATOR =
+ new Parcelable.Creator() {
+
+ @Override
public SearchCondition createFromParcel(Parcel in) {
return new SearchCondition(in);
}
+ @Override
public SearchCondition[] newArray(int size) {
return new SearchCondition[size];
}
From 1d655f5bc2ad129ec2ec36459da941969ff77acb Mon Sep 17 00:00:00 2001
From: cketti
Date: Thu, 18 Oct 2012 05:15:40 +0200
Subject: [PATCH 22/74] Added ability to display special folders combining
multiple accounts
---
.../fsck/k9/fragment/MessageListFragment.java | 168 +++++++--
src/com/fsck/k9/helper/MergeCursor.java | 347 ++++++++++++++++++
.../k9/helper/MergeCursorWithUniqueId.java | 83 +++++
src/com/fsck/k9/mail/store/LocalStore.java | 4 +
src/com/fsck/k9/provider/EmailProvider.java | 187 +++++++++-
5 files changed, 752 insertions(+), 37 deletions(-)
create mode 100644 src/com/fsck/k9/helper/MergeCursor.java
create mode 100644 src/com/fsck/k9/helper/MergeCursorWithUniqueId.java
diff --git a/src/com/fsck/k9/fragment/MessageListFragment.java b/src/com/fsck/k9/fragment/MessageListFragment.java
index 31538910d..e3ba609a3 100644
--- a/src/com/fsck/k9/fragment/MessageListFragment.java
+++ b/src/com/fsck/k9/fragment/MessageListFragment.java
@@ -6,6 +6,8 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
@@ -24,6 +26,7 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.DialogFragment;
+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;
@@ -68,8 +71,10 @@ import com.fsck.k9.activity.FolderInfoHolder;
import com.fsck.k9.activity.MessageInfoHolder;
import com.fsck.k9.activity.MessageReference;
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.StringUtils;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Address;
@@ -77,18 +82,15 @@ import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
-import com.fsck.k9.mail.Store;
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.mail.store.LocalStore.LocalMessage;
import com.fsck.k9.provider.EmailProvider;
import com.fsck.k9.provider.EmailProvider.MessageColumns;
+import com.fsck.k9.provider.EmailProvider.SpecialColumns;
import com.fsck.k9.search.ConditionsTreeNode;
import com.fsck.k9.search.LocalSearch;
import com.fsck.k9.search.SearchSpecification;
-import com.fsck.k9.search.SearchSpecification.ATTRIBUTE;
-import com.fsck.k9.search.SearchSpecification.SEARCHFIELD;
import com.fsck.k9.search.SearchSpecification.SearchCondition;
import com.handmark.pulltorefresh.library.PullToRefreshBase;
import com.handmark.pulltorefresh.library.PullToRefreshListView;
@@ -111,7 +113,8 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
MessageColumns.FOLDER_ID,
MessageColumns.PREVIEW,
MessageColumns.THREAD_ROOT,
- MessageColumns.THREAD_PARENT
+ MessageColumns.THREAD_PARENT,
+ SpecialColumns.ACCOUNT_UUID
};
private static final int ID_COLUMN = 0;
@@ -128,6 +131,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
private static final int PREVIEW_COLUMN = 11;
private static final int THREAD_ROOT_COLUMN = 12;
private static final int THREAD_PARENT_COLUMN = 13;
+ private static final int ACCOUNT_UUID_COLUMN = 14;
public static MessageListFragment newInstance(LocalSearch search, boolean remoteSearch) {
@@ -321,8 +325,11 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
private MessagingController mController;
private Account mAccount;
+ private String[] mAccountUuids;
private int mUnreadMessageCount = 0;
+ private Map mCursors = new HashMap();
+
/**
* Stores the name of the folder that we want to open as soon as possible
* after load.
@@ -387,6 +394,8 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
private final ActivityListener mListener = new MessageListActivityListener();
+ private Preferences mPreferences;
+
/**
* This class is used to run operations that modify UI elements in the UI thread.
*
@@ -627,9 +636,14 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
// long rootId = ((LocalMessage) message.message).getRootId();
// mFragmentListener.showThread(folder.getAccount(), folder.getName(), rootId);
} else {
+ Account account = getAccountFromCursor(cursor);
+
+ long folderId = cursor.getLong(FOLDER_ID_COLUMN);
+ String folderName = getFolderNameById(account, folderId);
+
MessageReference ref = new MessageReference();
- ref.accountUuid = mAccount.getUuid();
- ref.folderName = mCurrentFolder.name;
+ ref.accountUuid = account.getUuid();
+ ref.folderName = folderName;
ref.uid = cursor.getString(UID_COLUMN);
onOpenMessage(ref);
}
@@ -653,6 +667,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ mPreferences = Preferences.getPreferences(getActivity().getApplicationContext());
mController = MessagingController.getInstance(getActivity().getApplication());
mPreviewLines = K9.messageListPreviewLines();
@@ -686,7 +701,10 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
initializeMessageList();
- getLoaderManager().initLoader(0, null, this);
+ LoaderManager loaderManager = getLoaderManager();
+ for (int i = 0, len = mAccountUuids.length; i < len; i++) {
+ loaderManager.initLoader(i, null, this);
+ }
}
private void decodeArguments() {
@@ -696,14 +714,12 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
mSearch = args.getParcelable(ARG_SEARCH);
mTitle = args.getString(mSearch.getName());
- Context appContext = getActivity().getApplicationContext();
- String[] accounts = mSearch.getAccountUuids();
+ String[] accountUuids = mSearch.getAccountUuids();
mSingleAccountMode = false;
- if (accounts != null && accounts.length == 1
- && !accounts[0].equals(SearchSpecification.ALL_ACCOUNTS)) {
+ if (accountUuids.length == 1 && !accountUuids[0].equals(SearchSpecification.ALL_ACCOUNTS)) {
mSingleAccountMode = true;
- mAccount = Preferences.getPreferences(appContext).getAccount(accounts[0]);
+ mAccount = mPreferences.getAccount(accountUuids[0]);
}
mSingleFolderMode = false;
@@ -712,6 +728,23 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
mFolderName = mSearch.getFolderNames().get(0);
mCurrentFolder = getFolder(mFolderName, mAccount);
}
+
+ if (mSingleAccountMode) {
+ mAccountUuids = new String[] { mAccount.getUuid() };
+ } else {
+ if (accountUuids.length == 1 &&
+ accountUuids[0].equals(SearchSpecification.ALL_ACCOUNTS)) {
+
+ Account[] accounts = mPreferences.getAccounts();
+
+ mAccountUuids = new String[accounts.length];
+ for (int i = 0, len = accounts.length; i < len; i++) {
+ mAccountUuids[i] = accounts[i].getUuid();
+ }
+ } else {
+ mAccountUuids = accountUuids;
+ }
+ }
}
private void initializeMessageList() {
@@ -744,6 +777,18 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
}
}
+ private String getFolderNameById(Account account, long folderId) {
+ try {
+ LocalStore localStore = account.getLocalStore();
+ LocalFolder localFolder = localStore.getFolderById(folderId);
+ localFolder.open(OpenMode.READ_ONLY);
+ return localFolder.getName();
+ } catch (Exception e) {
+ Log.e(K9.LOG_TAG, "getFolderNameById() failed.", e);
+ return null;
+ }
+ }
+
@Override
public void onPause() {
super.onPause();
@@ -789,8 +834,6 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
mSenderAboveSubject = K9.messageListSenderAboveSubject();
- final Preferences prefs = Preferences.getPreferences(appContext);
-
// Check if we have connectivity. Cache the value.
if (mHasConnectivity == null) {
final ConnectivityManager connectivityManager =
@@ -841,7 +884,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
mSortAscending = account.isSortAscending(mSortType);
mSortDateAscending = account.isSortAscending(SortType.SORT_DATE);
} else {
- accountsWithNotification = prefs.getAccounts();
+ accountsWithNotification = mPreferences.getAccounts();
mSortType = K9.getSortType();
mSortAscending = K9.isSortAscending(mSortType);
mSortDateAscending = K9.isSortAscending(SortType.SORT_DATE);
@@ -934,7 +977,6 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
private void changeSort(SortType sortType, Boolean sortAscending) {
mSortType = sortType;
- Preferences prefs = Preferences.getPreferences(getActivity().getApplicationContext());
Account account = mAccount;
if (account != null) {
@@ -948,7 +990,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
account.setSortAscending(mSortType, mSortAscending);
mSortDateAscending = account.isSortAscending(SortType.SORT_DATE);
- account.save(prefs);
+ account.save(mPreferences);
} else {
K9.setSortType(mSortType);
@@ -960,7 +1002,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
K9.setSortAscending(mSortType, mSortAscending);
mSortDateAscending = K9.isSortAscending(SortType.SORT_DATE);
- Editor editor = prefs.getPreferences().edit();
+ Editor editor = mPreferences.getPreferences().edit();
K9.save(editor);
editor.commit();
}
@@ -1244,8 +1286,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
getActivity().getMenuInflater().inflate(R.menu.message_list_item_context, menu);
- //TODO: get account from cursor
- Account account = mAccount;
+ Account account = getAccountFromCursor(cursor);
String subject = cursor.getString(SUBJECT_COLUMN);
String flagList = cursor.getString(FLAGS_COLUMN);
@@ -1511,8 +1552,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
@Override
public void bindView(View view, Context context, Cursor cursor) {
- //TODO: make this work for search results
- Account account = mAccount;
+ Account account = getAccountFromCursor(cursor);
String fromList = cursor.getString(SENDER_LIST_COLUMN);
String toList = cursor.getString(TO_LIST_COLUMN);
@@ -2574,9 +2614,6 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
return false;
}
- Context appContext = getActivity().getApplicationContext();
- final Preferences prefs = Preferences.getPreferences(appContext);
-
boolean allowRemoteSearch = false;
final Account searchAccount = mAccount;
if (searchAccount != null) {
@@ -2593,13 +2630,61 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
@Override
public Loader onCreateLoader(int id, Bundle args) {
- String accountUuid = mAccount.getUuid();
+ String accountUuid = mAccountUuids[id];
+ Account account = mPreferences.getAccount(accountUuid);
Uri uri = Uri.withAppendedPath(EmailProvider.CONTENT_URI, "account/" + accountUuid + "/messages");
+ StringBuilder query = new StringBuilder();
+ List queryArgs = new ArrayList();
+ buildQuery(account, mSearch.getConditions(), query, queryArgs);
+
+ String selection = query.toString();
+ String[] selectionArgs = queryArgs.toArray(new String[0]);
+
+ return new CursorLoader(getActivity(), uri, PROJECTION, selection, selectionArgs,
+ MessageColumns.DATE + " DESC");
+ }
+
+ private void buildQuery(Account account, ConditionsTreeNode node, StringBuilder query,
+ List selectionArgs) {
+
+ if (node.mLeft == null && node.mRight == null) {
+ SearchCondition condition = node.mCondition;
+ switch (condition.field) {
+ case FOLDER: {
+ String folderName;
+ //TODO: Fix the search condition used by the Unified Inbox
+ if (LocalSearch.GENERIC_INBOX_NAME.equals(condition.value) ||
+ "1".equals(condition.value)) {
+ folderName = account.getInboxFolderName();
+ } else {
+ folderName = condition.value;
+ }
+ long folderId = getFolderId(account, folderName);
+ query.append("folder_id = ?");
+ selectionArgs.add(Long.toString(folderId));
+ break;
+ }
+ default: {
+ query.append(condition.toString());
+ }
+ }
+ } else {
+ query.append("(");
+ buildQuery(account, node.mLeft, query, selectionArgs);
+ query.append(") ");
+ query.append(node.mValue.name());
+ query.append(" (");
+ buildQuery(account, node.mRight, query, selectionArgs);
+ query.append(")");
+ }
+ }
+
+ private long getFolderId(Account account, String folderName) {
long folderId = 0;
try {
- LocalFolder folder = (LocalFolder) mCurrentFolder.folder;
+ LocalFolder folder = (LocalFolder) getFolder(folderName, account).folder;
folder.open(OpenMode.READ_ONLY);
folderId = folder.getId();
} catch (MessagingException e) {
@@ -2607,17 +2692,25 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
e.printStackTrace();
}
- String selection = MessageColumns.FOLDER_ID + "=?";
- String[] selectionArgs = { Long.toString(folderId) };
-
- return new CursorLoader(getActivity(), uri, PROJECTION, selection, selectionArgs,
- MessageColumns.DATE + " DESC");
+ return folderId;
}
@Override
public void onLoadFinished(Loader loader, Cursor data) {
- mSelected = new SparseBooleanArray(data.getCount());
- mAdapter.swapCursor(data);
+ mCursors.put(loader.getId(), data);
+
+ List list = new LinkedList(mCursors.keySet());
+ Collections.sort(list);
+ List cursors = new ArrayList(list.size());
+ for (Integer id : list) {
+ cursors.add(mCursors.get(id));
+ }
+
+ MergeCursorWithUniqueId cursor = new MergeCursorWithUniqueId(cursors);
+
+ mSelected = new SparseBooleanArray(cursor.getCount());
+ //TODO: use the (stable) IDs as index and reuse the old mSelected
+ mAdapter.swapCursor(cursor);
}
@Override
@@ -2625,4 +2718,9 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
mSelected = null;
mAdapter.swapCursor(null);
}
+
+ private Account getAccountFromCursor(Cursor cursor) {
+ String accountUuid = cursor.getString(ACCOUNT_UUID_COLUMN);
+ return mPreferences.getAccount(accountUuid);
+ }
}
diff --git a/src/com/fsck/k9/helper/MergeCursor.java b/src/com/fsck/k9/helper/MergeCursor.java
new file mode 100644
index 000000000..efba70d23
--- /dev/null
+++ b/src/com/fsck/k9/helper/MergeCursor.java
@@ -0,0 +1,347 @@
+package com.fsck.k9.helper;
+
+import java.util.List;
+
+import android.content.ContentResolver;
+import android.database.CharArrayBuffer;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.database.DataSetObserver;
+import android.net.Uri;
+import android.os.Bundle;
+
+
+/**
+ * This class can be used to combine multiple {@link Cursor}s into one.
+ */
+public class MergeCursor implements Cursor {
+ /**
+ * List of the cursors combined in this object.
+ */
+ protected final List mCursors;
+
+ /**
+ * The currently active cursor.
+ */
+ protected Cursor mActiveCursor;
+
+ /**
+ * The index of the currently active cursor in {@link #mCursors}.
+ *
+ * @see #mActiveCursor
+ */
+ protected int mActiveCursorIndex;
+
+ /**
+ * Used to cache the value of {@link #getCount()}
+ */
+ private int mCount = -1;
+
+
+ /**
+ * Constructor
+ *
+ * @param cursors
+ * The list of cursors this {@code MultiCursor} should combine.
+ */
+ public MergeCursor(List cursors) {
+ mCursors = cursors;
+ mActiveCursorIndex = 0;
+ mActiveCursor = cursors.get(0);
+ }
+
+ @Override
+ public void close() {
+ for (Cursor cursor : mCursors) {
+ cursor.close();
+ }
+ }
+
+ @Override
+ public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
+ mActiveCursor.copyStringToBuffer(columnIndex, buffer);
+ }
+
+ @Override
+ public void deactivate() {
+ for (Cursor cursor : mCursors) {
+ cursor.deactivate();
+ }
+ }
+
+ @Override
+ public byte[] getBlob(int columnIndex) {
+ return mActiveCursor.getBlob(columnIndex);
+ }
+
+ @Override
+ public int getColumnCount() {
+ return mActiveCursor.getColumnCount();
+ }
+
+ @Override
+ public int getColumnIndex(String columnName) {
+ return mActiveCursor.getColumnIndex(columnName);
+ }
+
+ @Override
+ public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException {
+ return mActiveCursor.getColumnIndexOrThrow(columnName);
+ }
+
+ @Override
+ public String getColumnName(int columnIndex) {
+ return mActiveCursor.getColumnName(columnIndex);
+ }
+
+ @Override
+ public String[] getColumnNames() {
+ return mActiveCursor.getColumnNames();
+ }
+
+ @Override
+ public int getCount() {
+ // CursorLoaders seem to call getCount() a lot. So we're caching the aggregated count.
+ if (mCount == -1) {
+ int count = 0;
+ for (Cursor cursor : mCursors) {
+ count += cursor.getCount();
+ }
+
+ mCount = count;
+ }
+
+ return mCount;
+ }
+
+ @Override
+ public double getDouble(int columnIndex) {
+ return mActiveCursor.getDouble(columnIndex);
+ }
+
+ @Override
+ public float getFloat(int columnIndex) {
+ return mActiveCursor.getFloat(columnIndex);
+ }
+
+ @Override
+ public int getInt(int columnIndex) {
+ return mActiveCursor.getInt(columnIndex);
+ }
+
+ @Override
+ public long getLong(int columnIndex) {
+ return mActiveCursor.getLong(columnIndex);
+ }
+
+ @Override
+ public int getPosition() {
+ int pos = 0;
+ for (int i = 0; i < mActiveCursorIndex; i++) {
+ pos += mCursors.get(i).getCount();
+ }
+
+ return pos + mActiveCursor.getPosition();
+ }
+
+ @Override
+ public short getShort(int columnIndex) {
+ return mActiveCursor.getShort(columnIndex);
+ }
+
+ @Override
+ public String getString(int columnIndex) {
+ return mActiveCursor.getString(columnIndex);
+ }
+
+ @Override
+ public int getType(int columnIndex) {
+ return mActiveCursor.getType(columnIndex);
+ }
+
+ @Override
+ public boolean getWantsAllOnMoveCalls() {
+ return mActiveCursor.getWantsAllOnMoveCalls();
+ }
+
+ @Override
+ public boolean isAfterLast() {
+ if (mActiveCursorIndex == mCursors.size() - 1) {
+ return mActiveCursor.isAfterLast();
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean isBeforeFirst() {
+ if (mActiveCursorIndex == 0) {
+ return mActiveCursor.isBeforeFirst();
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean isClosed() {
+ return mActiveCursor.isClosed();
+ }
+
+ @Override
+ public boolean isFirst() {
+ if (mActiveCursorIndex == 0) {
+ return mActiveCursor.isFirst();
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean isLast() {
+ if (mActiveCursorIndex == mCursors.size() - 1) {
+ return mActiveCursor.isLast();
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean isNull(int columnIndex) {
+ return mActiveCursor.isNull(columnIndex);
+ }
+
+ @Override
+ public boolean move(int offset) {
+ int ofs = offset;
+ int pos = mActiveCursor.getPosition();
+ if (offset >= 0) {
+ while (pos + ofs > mActiveCursor.getCount() &
+ mActiveCursorIndex < mCursors.size() - 1) {
+
+ // Adjust the "move offset"
+ ofs -= mActiveCursor.getCount() - pos;
+
+ // Move to the next cursor
+ mActiveCursor = mCursors.get(++mActiveCursorIndex);
+
+ // Move the new cursor to the first position
+ mActiveCursor.moveToFirst();
+ pos = 0;
+ }
+ } else {
+ while (pos + ofs < 0 && mActiveCursorIndex > 0) {
+ // Adjust the "move offset"
+ ofs += pos;
+
+ // Move to the next cursor
+ mActiveCursor = mCursors.get(--mActiveCursorIndex);
+
+ // Move the new cursor to the first position
+ mActiveCursor.moveToLast();
+ pos = mActiveCursor.getPosition();
+ }
+ }
+
+ return mActiveCursor.move(ofs);
+ }
+
+ @Override
+ public boolean moveToFirst() {
+ mActiveCursorIndex = 0;
+ mActiveCursor = mCursors.get(mActiveCursorIndex);
+ return mActiveCursor.moveToFirst();
+ }
+
+ @Override
+ public boolean moveToLast() {
+ mActiveCursorIndex = mCursors.size() - 1;
+ mActiveCursor = mCursors.get(mActiveCursorIndex);
+ return mActiveCursor.moveToLast();
+ }
+
+ @Override
+ public boolean moveToNext() {
+ return move(1);
+ }
+
+ @Override
+ public boolean moveToPosition(int position) {
+ // Start at the beginning
+ mActiveCursorIndex = 0;
+ mActiveCursor = mCursors.get(mActiveCursorIndex);
+
+ int pos = position;
+ while (pos > mActiveCursor.getCount() - 1 &&
+ mActiveCursorIndex < mCursors.size() - 1) {
+
+ // Adjust the position
+ pos -= mActiveCursor.getCount();
+
+ // Move to the next cursor
+ mActiveCursor = mCursors.get(++mActiveCursorIndex);
+ }
+
+ return mActiveCursor.moveToPosition(pos);
+ }
+
+ @Override
+ public boolean moveToPrevious() {
+ return move(-1);
+ }
+
+ @Override
+ public void registerContentObserver(ContentObserver observer) {
+ for (Cursor cursor : mCursors) {
+ cursor.registerContentObserver(observer);
+ }
+ }
+
+ @Override
+ public void registerDataSetObserver(DataSetObserver observer) {
+ for (Cursor cursor : mCursors) {
+ cursor.registerDataSetObserver(observer);
+ }
+ }
+
+ @Deprecated
+ @Override
+ public boolean requery() {
+ boolean success = true;
+ for (Cursor cursor : mCursors) {
+ success &= cursor.requery();
+ }
+
+ return success;
+ }
+
+ @Override
+ public void setNotificationUri(ContentResolver cr, Uri uri) {
+ for (Cursor cursor : mCursors) {
+ cursor.setNotificationUri(cr, uri);
+ }
+ }
+
+ @Override
+ public void unregisterContentObserver(ContentObserver observer) {
+ for (Cursor cursor : mCursors) {
+ cursor.unregisterContentObserver(observer);
+ }
+ }
+
+ @Override
+ public void unregisterDataSetObserver(DataSetObserver observer) {
+ for (Cursor cursor : mCursors) {
+ cursor.unregisterDataSetObserver(observer);
+ }
+ }
+
+ @Override
+ public Bundle getExtras() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public Bundle respond(Bundle extras) {
+ throw new RuntimeException("Not implemented");
+ }
+}
diff --git a/src/com/fsck/k9/helper/MergeCursorWithUniqueId.java b/src/com/fsck/k9/helper/MergeCursorWithUniqueId.java
new file mode 100644
index 000000000..284fcf9f4
--- /dev/null
+++ b/src/com/fsck/k9/helper/MergeCursorWithUniqueId.java
@@ -0,0 +1,83 @@
+package com.fsck.k9.helper;
+
+import java.util.List;
+
+import android.database.Cursor;
+
+
+public class MergeCursorWithUniqueId extends MergeCursor {
+ private static final int SHIFT = 48;
+ private static final long MAX_ID = (1L << SHIFT) - 1;
+ private static final long MAX_CURSORS = 1L << (63 - SHIFT);
+
+ private int mColumnCount = -1;
+ private int mIdColumnIndex = -1;
+
+
+ public MergeCursorWithUniqueId(List cursors) {
+ super(cursors);
+
+ if (cursors.size() > MAX_CURSORS) {
+ throw new IllegalArgumentException("This class only supports up to " +
+ MAX_CURSORS + " cursors");
+ }
+ }
+
+ @Override
+ public int getColumnCount() {
+ if (mColumnCount == -1) {
+ mColumnCount = super.getColumnCount();
+ }
+
+ return mColumnCount + 1;
+ }
+
+ @Override
+ public int getColumnIndex(String columnName) {
+ if ("_id".equals(columnName)) {
+ return getUniqueIdColumnIndex();
+ }
+
+ return super.getColumnIndexOrThrow(columnName);
+ }
+
+ @Override
+ public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException {
+ if ("_id".equals(columnName)) {
+ return getUniqueIdColumnIndex();
+ }
+
+ return super.getColumnIndexOrThrow(columnName);
+ }
+
+ @Override
+ public long getLong(int columnIndex) {
+ if (columnIndex == getUniqueIdColumnIndex()) {
+ long id = getPerCursorId();
+ if (id > MAX_ID) {
+ throw new RuntimeException("Sorry, " + this.getClass().getName() +
+ " can only handle '_id' values up to " + SHIFT + " bits.");
+ }
+
+ return (((long) mActiveCursorIndex) << SHIFT) + id;
+ }
+
+ return super.getLong(columnIndex);
+ }
+
+ protected int getUniqueIdColumnIndex() {
+ if (mColumnCount == -1) {
+ mColumnCount = super.getColumnCount();
+ }
+
+ return mColumnCount;
+ }
+
+ protected long getPerCursorId() {
+ if (mIdColumnIndex == -1) {
+ mIdColumnIndex = super.getColumnIndexOrThrow("_id");
+ }
+
+ return super.getLong(mIdColumnIndex);
+ }
+}
diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java
index 86b8b2a09..8fe2577e1 100644
--- a/src/com/fsck/k9/mail/store/LocalStore.java
+++ b/src/com/fsck/k9/mail/store/LocalStore.java
@@ -661,6 +661,10 @@ public class LocalStore extends Store implements Serializable {
return new LocalFolder(name);
}
+ public LocalFolder getFolderById(long folderId) {
+ return new LocalFolder(folderId);
+ }
+
private long getFolderId(final String name) throws MessagingException {
return database.execute(false, new DbCallback() {
@Override
diff --git a/src/com/fsck/k9/provider/EmailProvider.java b/src/com/fsck/k9/provider/EmailProvider.java
index 1cf799d75..b1b774bfa 100644
--- a/src/com/fsck/k9/provider/EmailProvider.java
+++ b/src/com/fsck/k9/provider/EmailProvider.java
@@ -1,6 +1,9 @@
package com.fsck.k9.provider;
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import com.fsck.k9.Account;
import com.fsck.k9.Preferences;
@@ -12,6 +15,7 @@ import com.fsck.k9.mail.store.LockableDatabase.DbCallback;
import com.fsck.k9.mail.store.LockableDatabase.WrappedException;
import com.fsck.k9.mail.store.UnavailableStorageException;
+import android.annotation.TargetApi;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
@@ -67,6 +71,9 @@ public class EmailProvider extends ContentProvider {
//matcher.addURI(AUTHORITY, "account/*/thread/#", MESSAGES_THREAD);
}
+ public interface SpecialColumns {
+ public static final String ACCOUNT_UUID = "account_uuid";
+ }
public interface MessageColumns {
public static final String ID = "id";
@@ -126,14 +133,31 @@ public class EmailProvider extends ContentProvider {
List segments = uri.getPathSegments();
String accountUuid = segments.get(1);
- cursor = getMessages(accountUuid, projection, selection, selectionArgs, sortOrder);
+ List dbColumnNames = new ArrayList(projection.length);
+ Map specialColumns = new HashMap();
+ for (String columnName : projection) {
+ if (SpecialColumns.ACCOUNT_UUID.equals(columnName)) {
+ specialColumns.put(SpecialColumns.ACCOUNT_UUID, accountUuid);
+ } else {
+ dbColumnNames.add(columnName);
+ }
+ }
+
+ String[] dbProjection = dbColumnNames.toArray(new String[0]);
+
+ cursor = getMessages(accountUuid, dbProjection, selection, selectionArgs,
+ sortOrder);
cursor.setNotificationUri(contentResolver, uri);
+
+ cursor = new SpecialColumnsCursor(new IdTrickeryCursor(cursor), projection,
+ specialColumns);
+
break;
}
}
- return new IdTrickeryCursor(cursor);
+ return cursor;
}
@Override
@@ -243,4 +267,163 @@ public class EmailProvider extends ContentProvider {
return super.getColumnIndexOrThrow(columnName);
}
}
+
+ static class SpecialColumnsCursor extends CursorWrapper {
+ private int[] mColumnMapping;
+ private String[] mSpecialColumnValues;
+ private String[] mColumnNames;
+
+ public SpecialColumnsCursor(Cursor cursor, String[] allColumnNames,
+ Map specialColumns) {
+ super(cursor);
+
+ mColumnNames = allColumnNames;
+ mColumnMapping = new int[allColumnNames.length];
+ mSpecialColumnValues = new String[specialColumns.size()];
+ for (int i = 0, columnIndex = 0, specialColumnCount = 0, len = allColumnNames.length;
+ i < len; i++) {
+
+ String columnName = allColumnNames[i];
+
+ if (specialColumns.containsKey(columnName)) {
+ // This is a special column name, so save the value in mSpecialColumnValues
+ mSpecialColumnValues[specialColumnCount] = specialColumns.get(columnName);
+
+ // Write the index into mSpecialColumnValues negated into mColumnMapping
+ mColumnMapping[i] = -(specialColumnCount + 1);
+ specialColumnCount++;
+ } else {
+ mColumnMapping[i] = columnIndex++;
+ }
+ }
+ }
+
+ @Override
+ public byte[] getBlob(int columnIndex) {
+ int realColumnIndex = mColumnMapping[columnIndex];
+ if (realColumnIndex < 0) {
+ throw new RuntimeException("Special column can only be retrieved as string.");
+ }
+
+ return super.getBlob(realColumnIndex);
+ }
+
+ @Override
+ public int getColumnCount() {
+ return mColumnMapping.length;
+ }
+
+ @Override
+ public int getColumnIndex(String columnName) {
+ for (int i = 0, len = mColumnNames.length; i < len; i++) {
+ if (mColumnNames[i].equals(columnName)) {
+ return i;
+ }
+ }
+
+ return super.getColumnIndex(columnName);
+ }
+
+ @Override
+ public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException {
+ int index = getColumnIndex(columnName);
+
+ if (index == -1) {
+ throw new IllegalArgumentException("Unknown column name");
+ }
+
+ return index;
+ }
+
+ @Override
+ public String getColumnName(int columnIndex) {
+ return mColumnNames[columnIndex];
+ }
+
+ @Override
+ public String[] getColumnNames() {
+ return mColumnNames.clone();
+ }
+
+ @Override
+ public double getDouble(int columnIndex) {
+ int realColumnIndex = mColumnMapping[columnIndex];
+ if (realColumnIndex < 0) {
+ throw new RuntimeException("Special column can only be retrieved as string.");
+ }
+
+ return super.getDouble(realColumnIndex);
+ }
+
+ @Override
+ public float getFloat(int columnIndex) {
+ int realColumnIndex = mColumnMapping[columnIndex];
+ if (realColumnIndex < 0) {
+ throw new RuntimeException("Special column can only be retrieved as string.");
+ }
+
+ return super.getFloat(realColumnIndex);
+ }
+
+ @Override
+ public int getInt(int columnIndex) {
+ int realColumnIndex = mColumnMapping[columnIndex];
+ if (realColumnIndex < 0) {
+ throw new RuntimeException("Special column can only be retrieved as string.");
+ }
+
+ return super.getInt(realColumnIndex);
+ }
+
+ @Override
+ public long getLong(int columnIndex) {
+ int realColumnIndex = mColumnMapping[columnIndex];
+ if (realColumnIndex < 0) {
+ throw new RuntimeException("Special column can only be retrieved as string.");
+ }
+
+ return super.getLong(realColumnIndex);
+ }
+
+ @Override
+ public short getShort(int columnIndex) {
+ int realColumnIndex = mColumnMapping[columnIndex];
+ if (realColumnIndex < 0) {
+ throw new RuntimeException("Special column can only be retrieved as string.");
+ }
+
+ return super.getShort(realColumnIndex);
+ }
+
+ @Override
+ public String getString(int columnIndex) {
+ int realColumnIndex = mColumnMapping[columnIndex];
+ if (realColumnIndex < 0) {
+ return mSpecialColumnValues[-realColumnIndex - 1];
+ }
+
+ return super.getString(realColumnIndex);
+ }
+
+ @TargetApi(11)
+ @Override
+ public int getType(int columnIndex) {
+ int realColumnIndex = mColumnMapping[columnIndex];
+ if (realColumnIndex < 0) {
+ return FIELD_TYPE_STRING;
+ }
+
+ return super.getType(realColumnIndex);
+ }
+
+ @Override
+ public boolean isNull(int columnIndex) {
+ int realColumnIndex = mColumnMapping[columnIndex];
+ if (realColumnIndex < 0) {
+ return (mSpecialColumnValues[-realColumnIndex - 1] == null);
+ }
+
+ return super.isNull(realColumnIndex);
+ }
+ }
}
From f093b84142dc3df06d352d8fc91701937522f44b Mon Sep 17 00:00:00 2001
From: cketti
Date: Sun, 21 Oct 2012 19:17:01 +0200
Subject: [PATCH 23/74] Make (de)serialization work when LocalSearch has no
search conditions
---
src/com/fsck/k9/search/LocalSearch.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/com/fsck/k9/search/LocalSearch.java b/src/com/fsck/k9/search/LocalSearch.java
index 83c5fb522..0ffd3b488 100644
--- a/src/com/fsck/k9/search/LocalSearch.java
+++ b/src/com/fsck/k9/search/LocalSearch.java
@@ -385,6 +385,6 @@ public class LocalSearch implements SearchSpecification {
mPredefined = (in.readByte() == 1);
mAccountUuids.addAll(in.createStringArrayList());
mConditions = in.readParcelable(LocalSearch.class.getClassLoader());
- mLeafSet = mConditions.getLeafSet();
+ mLeafSet = (mConditions == null) ? null : mConditions.getLeafSet();
}
}
\ No newline at end of file
From 93ef3a7b0fba7f1f2fc569c5c11f40777f262553 Mon Sep 17 00:00:00 2001
From: cketti
Date: Sun, 21 Oct 2012 19:18:57 +0200
Subject: [PATCH 24/74] Fix message list when no search condition is given (All
messages)
---
src/com/fsck/k9/fragment/MessageListFragment.java | 4 ++++
src/com/fsck/k9/search/LocalSearch.java | 7 ++++++-
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/src/com/fsck/k9/fragment/MessageListFragment.java b/src/com/fsck/k9/fragment/MessageListFragment.java
index e3ba609a3..44b10de72 100644
--- a/src/com/fsck/k9/fragment/MessageListFragment.java
+++ b/src/com/fsck/k9/fragment/MessageListFragment.java
@@ -2649,6 +2649,10 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
private void buildQuery(Account account, ConditionsTreeNode node, StringBuilder query,
List selectionArgs) {
+ if (node == null) {
+ return;
+ }
+
if (node.mLeft == null && node.mRight == null) {
SearchCondition condition = node.mCondition;
switch (condition.field) {
diff --git a/src/com/fsck/k9/search/LocalSearch.java b/src/com/fsck/k9/search/LocalSearch.java
index 0ffd3b488..1cca98b81 100644
--- a/src/com/fsck/k9/search/LocalSearch.java
+++ b/src/com/fsck/k9/search/LocalSearch.java
@@ -295,7 +295,12 @@ public class LocalSearch implements SearchSpecification {
* very dirty fix for remotesearch support atm
*/
public String getRemoteSearchArguments() {
- for (ConditionsTreeNode node : getLeafSet()) {
+ Set leafSet = getLeafSet();
+ if (leafSet == null) {
+ return null;
+ }
+
+ for (ConditionsTreeNode node : leafSet) {
if (node.getCondition().field == Searchfield.SUBJECT ||
node.getCondition().field == Searchfield.SENDER ) {
return node.getCondition().value;
From 05a2571570a8ac639e361475049bc4a484f4bf79 Mon Sep 17 00:00:00 2001
From: cketti
Date: Mon, 22 Oct 2012 18:10:47 +0200
Subject: [PATCH 25/74] Fix display of named searches (e.g. "Unified Inbox")
---
src/com/fsck/k9/fragment/MessageListFragment.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/com/fsck/k9/fragment/MessageListFragment.java b/src/com/fsck/k9/fragment/MessageListFragment.java
index 44b10de72..ab58743bd 100644
--- a/src/com/fsck/k9/fragment/MessageListFragment.java
+++ b/src/com/fsck/k9/fragment/MessageListFragment.java
@@ -712,7 +712,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
mRemoteSearch = args.getBoolean(ARG_REMOTE_SEARCH, false);
mSearch = args.getParcelable(ARG_SEARCH);
- mTitle = args.getString(mSearch.getName());
+ mTitle = mSearch.getName();
String[] accountUuids = mSearch.getAccountUuids();
From 95b39c71d2539061ef2c93669a228e1b70f8a1a7 Mon Sep 17 00:00:00 2001
From: cketti
Date: Tue, 23 Oct 2012 03:01:50 +0200
Subject: [PATCH 26/74] Add threading support to content provider
---
src/com/fsck/k9/activity/MessageList.java | 15 +--
.../fsck/k9/fragment/MessageListFragment.java | 57 ++++++---
src/com/fsck/k9/provider/EmailProvider.java | 118 ++++++++++++++++--
src/com/fsck/k9/search/LocalSearch.java | 6 +-
.../fsck/k9/search/SearchSpecification.java | 3 +-
5 files changed, 161 insertions(+), 38 deletions(-)
diff --git a/src/com/fsck/k9/activity/MessageList.java b/src/com/fsck/k9/activity/MessageList.java
index c5f148b8e..020e07110 100644
--- a/src/com/fsck/k9/activity/MessageList.java
+++ b/src/com/fsck/k9/activity/MessageList.java
@@ -30,8 +30,6 @@ import com.fsck.k9.activity.setup.FolderSettings;
import com.fsck.k9.activity.setup.Prefs;
import com.fsck.k9.fragment.MessageListFragment;
import com.fsck.k9.fragment.MessageListFragment.MessageListFragmentListener;
-import com.fsck.k9.helper.Utility;
-import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.search.LocalSearch;
@@ -95,6 +93,7 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
private boolean mSingleFolderMode;
private boolean mSingleAccountMode;
private boolean mIsRemote;
+ private boolean mThreadViewEnabled = true; //TODO: this should be a setting
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -116,7 +115,8 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
if (mMessageListFragment == null) {
FragmentTransaction ft = fragmentManager.beginTransaction();
- mMessageListFragment = MessageListFragment.newInstance(mSearch, mIsRemote);
+ mMessageListFragment = MessageListFragment.newInstance(mSearch, mThreadViewEnabled,
+ mIsRemote);
ft.add(R.id.message_list_container, mMessageListFragment);
ft.commit();
}
@@ -585,7 +585,7 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
tmpSearch.addAccountUuids(mSearch.getAccountUuids());
tmpSearch.and(Searchfield.SENDER, senderAddress, Attribute.CONTAINS);
- MessageListFragment fragment = MessageListFragment.newInstance(tmpSearch, false);
+ MessageListFragment fragment = MessageListFragment.newInstance(tmpSearch, false, false);
addMessageListFragment(fragment);
}
@@ -634,7 +634,7 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
@Override
public void remoteSearch(String searchAccount, String searchFolder, String queryString) {
- MessageListFragment fragment = MessageListFragment.newInstance(mSearch, true);
+ MessageListFragment fragment = MessageListFragment.newInstance(mSearch, false, true);
addMessageListFragment(fragment);
}
@@ -669,10 +669,11 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
@Override
public void showThread(Account account, String folderName, long threadRootId) {
LocalSearch tmpSearch = new LocalSearch();
- tmpSearch.addAccountUuids(mSearch.getAccountUuids());
+ tmpSearch.addAccountUuid(account.getUuid());
tmpSearch.and(Searchfield.THREAD_ROOT, String.valueOf(threadRootId), Attribute.EQUALS);
+ tmpSearch.or(new SearchCondition(Searchfield.ID, Attribute.EQUALS, String.valueOf(threadRootId)));
- MessageListFragment fragment = MessageListFragment.newInstance(tmpSearch, false);
+ MessageListFragment fragment = MessageListFragment.newInstance(tmpSearch, false, false);
addMessageListFragment(fragment);
}
}
diff --git a/src/com/fsck/k9/fragment/MessageListFragment.java b/src/com/fsck/k9/fragment/MessageListFragment.java
index ab58743bd..21b9dbba8 100644
--- a/src/com/fsck/k9/fragment/MessageListFragment.java
+++ b/src/com/fsck/k9/fragment/MessageListFragment.java
@@ -2,6 +2,7 @@ package com.fsck.k9.fragment;
import java.text.DateFormat;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
@@ -99,7 +100,7 @@ import com.handmark.pulltorefresh.library.PullToRefreshListView;
public class MessageListFragment extends SherlockFragment implements OnItemClickListener,
ConfirmationDialogFragmentListener, LoaderCallbacks {
- private static final String[] PROJECTION = {
+ private static final String[] THREADED_PROJECTION = {
MessageColumns.ID,
MessageColumns.UID,
MessageColumns.INTERNAL_DATE,
@@ -114,7 +115,9 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
MessageColumns.PREVIEW,
MessageColumns.THREAD_ROOT,
MessageColumns.THREAD_PARENT,
- SpecialColumns.ACCOUNT_UUID
+ SpecialColumns.ACCOUNT_UUID,
+
+ MessageColumns.THREAD_COUNT,
};
private static final int ID_COLUMN = 0;
@@ -132,12 +135,18 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
private static final int THREAD_ROOT_COLUMN = 12;
private static final int THREAD_PARENT_COLUMN = 13;
private static final int ACCOUNT_UUID_COLUMN = 14;
+ private static final int THREAD_COUNT_COLUMN = 15;
+
+ private static final String[] PROJECTION = Arrays.copyOf(THREADED_PROJECTION,
+ THREAD_COUNT_COLUMN);
- public static MessageListFragment newInstance(LocalSearch search, boolean remoteSearch) {
+ public static MessageListFragment newInstance(LocalSearch search, boolean threadedList,
+ boolean remoteSearch) {
MessageListFragment fragment = new MessageListFragment();
Bundle args = new Bundle();
args.putParcelable(ARG_SEARCH, search);
+ args.putBoolean(ARG_THREADED_LIST, threadedList);
args.putBoolean(ARG_REMOTE_SEARCH, remoteSearch);
fragment.setArguments(args);
return fragment;
@@ -285,6 +294,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
private static final int ACTIVITY_CHOOSE_FOLDER_COPY = 2;
private static final String ARG_SEARCH = "searchObject";
+ private static final String ARG_THREADED_LIST = "threadedList";
private static final String ARG_REMOTE_SEARCH = "remoteSearch";
private static final String STATE_LIST_POSITION = "listPosition";
@@ -384,10 +394,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
private DateFormat mTimeFormat;
- //TODO: make this a setting
- private boolean mThreadViewEnabled = true;
-
- private long mThreadId;
+ private boolean mThreadedList;
private Context mContext;
@@ -631,21 +638,22 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
Cursor cursor = (Cursor) parent.getItemAtPosition(position);
if (mSelectedCount > 0) {
toggleMessageSelect(position);
-// } else if (message.threadCount > 1) {
-// Folder folder = message.message.getFolder();
-// long rootId = ((LocalMessage) message.message).getRootId();
-// mFragmentListener.showThread(folder.getAccount(), folder.getName(), rootId);
} else {
Account account = getAccountFromCursor(cursor);
long folderId = cursor.getLong(FOLDER_ID_COLUMN);
String folderName = getFolderNameById(account, folderId);
- MessageReference ref = new MessageReference();
- ref.accountUuid = account.getUuid();
- ref.folderName = folderName;
- ref.uid = cursor.getString(UID_COLUMN);
- onOpenMessage(ref);
+ if (mThreadedList && cursor.getInt(THREAD_COUNT_COLUMN) > 1) {
+ long rootId = cursor.getLong(THREAD_ROOT_COLUMN);
+ mFragmentListener.showThread(account, folderName, rootId);
+ } else {
+ MessageReference ref = new MessageReference();
+ ref.accountUuid = account.getUuid();
+ ref.folderName = folderName;
+ ref.uid = cursor.getString(UID_COLUMN);
+ onOpenMessage(ref);
+ }
}
}
@@ -710,6 +718,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
private void decodeArguments() {
Bundle args = getArguments();
+ mThreadedList = args.getBoolean(ARG_THREADED_LIST, false);
mRemoteSearch = args.getBoolean(ARG_REMOTE_SEARCH, false);
mSearch = args.getParcelable(ARG_SEARCH);
mTitle = mSearch.getName();
@@ -1580,7 +1589,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
subject = getString(R.string.general_no_subject);
}
- int threadCount = 0; //TODO: get thread count from cursor
+ int threadCount = (mThreadedList) ? cursor.getInt(THREAD_COUNT_COLUMN) : 0;
String flagList = cursor.getString(FLAGS_COLUMN);
String[] flags = flagList.split(",");
@@ -1641,7 +1650,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
}
// Thread count
- if (mThreadId == -1 && threadCount > 1) {
+ if (threadCount > 1) {
holder.threadCount.setText(Integer.toString(threadCount));
holder.threadCount.setVisibility(View.VISIBLE);
} else {
@@ -2633,7 +2642,15 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
String accountUuid = mAccountUuids[id];
Account account = mPreferences.getAccount(accountUuid);
- Uri uri = Uri.withAppendedPath(EmailProvider.CONTENT_URI, "account/" + accountUuid + "/messages");
+ Uri uri;
+ String[] projection;
+ if (mThreadedList) {
+ uri = Uri.withAppendedPath(EmailProvider.CONTENT_URI, "account/" + accountUuid + "/messages/threaded");
+ projection = THREADED_PROJECTION;
+ } else {
+ uri = Uri.withAppendedPath(EmailProvider.CONTENT_URI, "account/" + accountUuid + "/messages");
+ projection = PROJECTION;
+ }
StringBuilder query = new StringBuilder();
List queryArgs = new ArrayList();
@@ -2642,7 +2659,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
String selection = query.toString();
String[] selectionArgs = queryArgs.toArray(new String[0]);
- return new CursorLoader(getActivity(), uri, PROJECTION, selection, selectionArgs,
+ return new CursorLoader(getActivity(), uri, projection, selection, selectionArgs,
MessageColumns.DATE + " DESC");
}
diff --git a/src/com/fsck/k9/provider/EmailProvider.java b/src/com/fsck/k9/provider/EmailProvider.java
index b1b774bfa..55fff0e96 100644
--- a/src/com/fsck/k9/provider/EmailProvider.java
+++ b/src/com/fsck/k9/provider/EmailProvider.java
@@ -38,9 +38,6 @@ import android.net.Uri;
* TODO:
* - modify MessagingController (or LocalStore?) to call ContentResolver.notifyChange() to trigger
* notifications when the underlying data changes.
- * - add support for message threading
- * - add support for search views
- * - add support for querying multiple accounts (e.g. "Unified Inbox")
* - add support for account list and folder list
*/
public class EmailProvider extends ContentProvider {
@@ -56,18 +53,42 @@ public class EmailProvider extends ContentProvider {
*/
private static final int MESSAGE_BASE = 0;
private static final int MESSAGES = MESSAGE_BASE;
- //private static final int MESSAGES_THREADED = MESSAGE_BASE + 1;
+ private static final int MESSAGES_THREADED = MESSAGE_BASE + 1;
//private static final int MESSAGES_THREAD = MESSAGE_BASE + 2;
private static final String MESSAGES_TABLE = "messages";
+ private static final String[] MESSAGES_COLUMNS = {
+ MessageColumns.ID,
+ MessageColumns.UID,
+ MessageColumns.INTERNAL_DATE,
+ MessageColumns.SUBJECT,
+ MessageColumns.DATE,
+ MessageColumns.MESSAGE_ID,
+ MessageColumns.SENDER_LIST,
+ MessageColumns.TO_LIST,
+ MessageColumns.CC_LIST,
+ MessageColumns.BCC_LIST,
+ MessageColumns.REPLY_TO_LIST,
+ MessageColumns.FLAGS,
+ MessageColumns.ATTACHMENT_COUNT,
+ MessageColumns.FOLDER_ID,
+ MessageColumns.PREVIEW,
+ MessageColumns.THREAD_ROOT,
+ MessageColumns.THREAD_PARENT,
+ InternalMessageColumns.DELETED,
+ InternalMessageColumns.EMPTY,
+ InternalMessageColumns.TEXT_CONTENT,
+ InternalMessageColumns.HTML_CONTENT,
+ InternalMessageColumns.MIME_TYPE
+ };
static {
UriMatcher matcher = sUriMatcher;
matcher.addURI(AUTHORITY, "account/*/messages", MESSAGES);
- //matcher.addURI(AUTHORITY, "account/*/messages/threaded", MESSAGES_THREADED);
+ matcher.addURI(AUTHORITY, "account/*/messages/threaded", MESSAGES_THREADED);
//matcher.addURI(AUTHORITY, "account/*/thread/#", MESSAGES_THREAD);
}
@@ -93,6 +114,7 @@ public class EmailProvider extends ContentProvider {
public static final String PREVIEW = "preview";
public static final String THREAD_ROOT = "thread_root";
public static final String THREAD_PARENT = "thread_parent";
+ public static final String THREAD_COUNT = "thread_count";
}
private interface InternalMessageColumns extends MessageColumns {
@@ -129,7 +151,8 @@ public class EmailProvider extends ContentProvider {
ContentResolver contentResolver = getContext().getContentResolver();
Cursor cursor = null;
switch (match) {
- case MESSAGES: {
+ case MESSAGES:
+ case MESSAGES_THREADED: {
List segments = uri.getPathSegments();
String accountUuid = segments.get(1);
@@ -145,8 +168,15 @@ public class EmailProvider extends ContentProvider {
String[] dbProjection = dbColumnNames.toArray(new String[0]);
- cursor = getMessages(accountUuid, dbProjection, selection, selectionArgs,
- sortOrder);
+ if (match == MESSAGES) {
+ cursor = getMessages(accountUuid, dbProjection, selection, selectionArgs,
+ sortOrder);
+ } else if (match == MESSAGES_THREADED) {
+ cursor = getThreadedMessages(accountUuid, dbProjection, selection,
+ selectionArgs, sortOrder);
+ } else {
+ throw new RuntimeException("Not implemented");
+ }
cursor.setNotificationUri(contentResolver, uri);
@@ -206,6 +236,78 @@ public class EmailProvider extends ContentProvider {
}
}
+ protected Cursor getThreadedMessages(String accountUuid, final String[] projection,
+ final String selection, final String[] selectionArgs, final String sortOrder) {
+
+ Account account = getAccount(accountUuid);
+ LockableDatabase database = getDatabase(account);
+
+ try {
+ return database.execute(false, new DbCallback() {
+ @Override
+ public Cursor doDbWork(SQLiteDatabase db) throws WrappedException,
+ UnavailableStorageException {
+
+ StringBuilder query = new StringBuilder();
+ query.append("SELECT ");
+ boolean first = true;
+ for (String columnName : projection) {
+ if (!first) {
+ query.append(",");
+ } else {
+ first = false;
+ }
+
+ if (MessageColumns.DATE.equals(columnName)) {
+ query.append("MAX(m.date) AS " + MessageColumns.DATE);
+ } else if (MessageColumns.THREAD_COUNT.equals(columnName)) {
+ query.append("COUNT(h.id) AS " + MessageColumns.THREAD_COUNT);
+ } else {
+ query.append("m.");
+ query.append(columnName);
+ query.append(" AS ");
+ query.append(columnName);
+ }
+ }
+
+ query.append(
+ " FROM messages h JOIN messages m " +
+ "ON (h.id = m.thread_root OR h.id = m.id) " +
+ "WHERE " +
+ "(h.deleted = 0 AND m.deleted = 0 AND " +
+ "(m.empty IS NULL OR m.empty != 1) AND " +
+ "h.thread_root IS NULL) ");
+
+ if (!StringUtils.isNullOrEmpty(selection)) {
+ query.append("AND (");
+ query.append(addPrefixToSelection(MESSAGES_COLUMNS, "h.", selection));
+ query.append(") ");
+ }
+
+ query.append("GROUP BY h.id");
+
+ if (!StringUtils.isNullOrEmpty(sortOrder)) {
+ query.append(" ORDER BY ");
+ query.append(sortOrder);
+ }
+
+ return db.rawQuery(query.toString(), selectionArgs);
+ }
+ });
+ } catch (UnavailableStorageException e) {
+ throw new RuntimeException("Storage not available", e);
+ }
+ }
+
+ private String addPrefixToSelection(String[] columnNames, String prefix, String selection) {
+ String result = selection;
+ for (String columnName : columnNames) {
+ result = result.replaceAll("\\b" + columnName + "\\b", prefix + columnName);
+ }
+
+ return result;
+ }
+
private Account getAccount(String accountUuid) {
if (mPreferences == null) {
Context appContext = getContext().getApplicationContext();
diff --git a/src/com/fsck/k9/search/LocalSearch.java b/src/com/fsck/k9/search/LocalSearch.java
index 1cca98b81..c52dec469 100644
--- a/src/com/fsck/k9/search/LocalSearch.java
+++ b/src/com/fsck/k9/search/LocalSearch.java
@@ -176,7 +176,8 @@ public class LocalSearch implements SearchSpecification {
return node;
}
- return mConditions.and(node);
+ mConditions = mConditions.and(node);
+ return mConditions;
}
/**
@@ -212,7 +213,8 @@ public class LocalSearch implements SearchSpecification {
return node;
}
- return mConditions.or(node);
+ mConditions = mConditions.or(node);
+ return mConditions;
}
/**
diff --git a/src/com/fsck/k9/search/SearchSpecification.java b/src/com/fsck/k9/search/SearchSpecification.java
index 31bf9b387..8f22622c6 100644
--- a/src/com/fsck/k9/search/SearchSpecification.java
+++ b/src/com/fsck/k9/search/SearchSpecification.java
@@ -90,7 +90,8 @@ public interface SearchSpecification extends Parcelable {
SUBJECT("subject"), DATE("date"), UID("uid"), FLAG("flags"),
SENDER("sender_list"), TO("to_list"), CC("cc_list"), FOLDER("folder_id"),
BCC("bcc_list"), REPLY_TO("reply_to_list"), MESSAGE("text_content"),
- ATTACHMENT_COUNT("attachment_count"), DELETED("deleted"), THREAD_ROOT("thread_root");
+ ATTACHMENT_COUNT("attachment_count"), DELETED("deleted"), THREAD_ROOT("thread_root"),
+ ID("id");
private String dbName;
From 1b98ce57e14bc67ffa1bdade1e7954a5576b2bc5 Mon Sep 17 00:00:00 2001
From: cketti
Date: Wed, 24 Oct 2012 00:08:44 +0200
Subject: [PATCH 27/74] Notify observers (e.g. CursorAdapter) when the messages
table changed
---
src/com/fsck/k9/mail/store/LocalStore.java | 29 +++++++++++++++++++++-
1 file changed, 28 insertions(+), 1 deletion(-)
diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java
index 8fe2577e1..c65e57ce4 100644
--- a/src/com/fsck/k9/mail/store/LocalStore.java
+++ b/src/com/fsck/k9/mail/store/LocalStore.java
@@ -26,6 +26,7 @@ import java.util.regex.Pattern;
import org.apache.commons.io.IOUtils;
import android.app.Application;
+import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
@@ -69,6 +70,7 @@ import com.fsck.k9.mail.store.LockableDatabase.DbCallback;
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.search.ConditionsTreeNode;
import com.fsck.k9.search.LocalSearch;
import com.fsck.k9.search.SearchSpecification.Searchfield;
@@ -112,6 +114,8 @@ public class LocalStore extends Store implements Serializable {
private LockableDatabase database;
+ private ContentResolver mContentResolver;
+
/**
* local://localhost/path/to/database/uuid.db
* This constructor is only used by {@link Store#getLocalInstance(Account, Application)}
@@ -124,6 +128,7 @@ public class LocalStore extends Store implements Serializable {
database = new LockableDatabase(application, account.getUuid(), new StoreSchemaDefinition());
mApplication = application;
+ mContentResolver = application.getContentResolver();
database.setStorageProviderId(account.getLocalStorageProviderId());
uUid = account.getUuid();
@@ -2191,6 +2196,9 @@ public class LocalStore extends Store implements Serializable {
return null;
}
});
+
+ notifyChange();
+
return uidMap;
} catch (WrappedException e) {
throw(MessagingException) e.getCause();
@@ -2492,6 +2500,9 @@ public class LocalStore extends Store implements Serializable {
return null;
}
});
+
+ notifyChange();
+
return uidMap;
} catch (WrappedException e) {
throw(MessagingException) e.getCause();
@@ -2572,6 +2583,8 @@ public class LocalStore extends Store implements Serializable {
} catch (WrappedException e) {
throw(MessagingException) e.getCause();
}
+
+ notifyChange();
}
/**
@@ -2821,6 +2834,9 @@ public class LocalStore extends Store implements Serializable {
return null;
}
});
+
+ //TODO: remove this once the UI code exclusively uses the database id
+ notifyChange();
}
@Override
@@ -2865,6 +2881,8 @@ public class LocalStore extends Store implements Serializable {
}
});
resetUnreadAndFlaggedCounts();
+
+ notifyChange();
}
public void clearMessagesOlderThan(long cutoff) throws MessagingException {
@@ -3592,7 +3610,7 @@ public class LocalStore extends Store implements Serializable {
throw(MessagingException) e.getCause();
}
-
+ notifyChange();
}
/*
@@ -3648,6 +3666,8 @@ public class LocalStore extends Store implements Serializable {
throw(MessagingException) e.getCause();
}
((LocalFolder)mFolder).deleteHeaders(mId);
+
+ notifyChange();
}
/*
@@ -3768,6 +3788,8 @@ public class LocalStore extends Store implements Serializable {
} catch (WrappedException e) {
throw(MessagingException) e.getCause();
}
+
+ notifyChange();
}
private void updateFolderCountsOnFlag(Flag flag, boolean set) {
@@ -3962,4 +3984,9 @@ public class LocalStore extends Store implements Serializable {
public LockableDatabase getDatabase() {
return database;
}
+
+ private void notifyChange() {
+ Uri uri = Uri.withAppendedPath(EmailProvider.CONTENT_URI, "account/" + uUid + "/messages");
+ mContentResolver.notifyChange(uri, null);
+ }
}
From cba9d8fc88acdc71b1a367edcc04e7b371911c18 Mon Sep 17 00:00:00 2001
From: cketti
Date: Wed, 24 Oct 2012 00:09:33 +0200
Subject: [PATCH 28/74] Fix pull to refresh for normal list vs. "remote
search"-able
---
.../fsck/k9/fragment/MessageListFragment.java | 17 ++++++++---------
1 file changed, 8 insertions(+), 9 deletions(-)
diff --git a/src/com/fsck/k9/fragment/MessageListFragment.java b/src/com/fsck/k9/fragment/MessageListFragment.java
index 21b9dbba8..9657b9802 100644
--- a/src/com/fsck/k9/fragment/MessageListFragment.java
+++ b/src/com/fsck/k9/fragment/MessageListFragment.java
@@ -857,15 +857,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
}
if (mSingleFolderMode) {
- if (!mAccount.allowRemoteSearch()) {
- mPullToRefreshView.setOnRefreshListener(new PullToRefreshBase.OnRefreshListener() {
- @Override
- public void onRefresh(PullToRefreshBase refreshView) {
- checkMail();
- }
- });
- // TODO this has to go! find better remote search integration
- } else {
+ if (mRemoteSearch && mAccount.allowRemoteSearch()) {
mPullToRefreshView.setOnRefreshListener(new PullToRefreshBase.OnRefreshListener() {
@Override
public void onRefresh(PullToRefreshBase refreshView) {
@@ -875,6 +867,13 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
});
mPullToRefreshView.setPullLabel(getString(R.string.pull_to_refresh_remote_search_from_local_search_pull));
mPullToRefreshView.setReleaseLabel(getString(R.string.pull_to_refresh_remote_search_from_local_search_release));
+ } else {
+ mPullToRefreshView.setOnRefreshListener(new PullToRefreshBase.OnRefreshListener() {
+ @Override
+ public void onRefresh(PullToRefreshBase refreshView) {
+ checkMail();
+ }
+ });
}
} else {
mPullToRefreshView.setMode(PullToRefreshBase.Mode.DISABLED);
From 3bef05b55d670fcafdaaaa859ec7bd35feb0ef5e Mon Sep 17 00:00:00 2001
From: cketti
Date: Wed, 24 Oct 2012 00:32:29 +0200
Subject: [PATCH 29/74] Removed LocalStore hack introduced in 16d2326
---
src/com/fsck/k9/mail/store/LocalStore.java | 22 ++++------------------
1 file changed, 4 insertions(+), 18 deletions(-)
diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java
index c65e57ce4..fdb1c2af3 100644
--- a/src/com/fsck/k9/mail/store/LocalStore.java
+++ b/src/com/fsck/k9/mail/store/LocalStore.java
@@ -98,10 +98,6 @@ public class LocalStore extends Store implements Serializable {
"subject, sender_list, date, uid, flags, id, to_list, cc_list, "
+ "bcc_list, reply_to_list, attachment_count, internal_date, message_id, folder_id, preview, thread_root, thread_parent, empty ";
- static private String GET_MESSAGES_COLS_PREFIX =
- "m.subject, m.sender_list, m.date, m.uid, m.flags, m.id, m.to_list, m.cc_list, " +
- "m.bcc_list, m.reply_to_list, m.attachment_count, m.internal_date, m.message_id, " +
- "m.folder_id, m.preview, m.thread_root, m.thread_parent, m.empty ";
static private String GET_FOLDER_COLS = "id, name, unread_count, visible_limit, last_updated, status, push_state, last_pushed, flagged_count, integrate, top_group, poll_class, push_class, display_class";
@@ -1883,23 +1879,13 @@ public class LocalStore extends Store implements Serializable {
try {
cursor = db.rawQuery(
- "SELECT " +
- GET_MESSAGES_COLS_PREFIX + ", COUNT(c.id) " +
- "FROM messages m LEFT JOIN messages c " +
- "ON (" +
- "(" +
- "m.thread_root IN (c.thread_root, c.id)" +
- " OR " +
- "(m.thread_root IS NULL AND m.id IN (c.thread_root, c.id))" +
- ") AND c.empty != 1) " +
- "WHERE m.uid = ? AND m.folder_id = ?",
+ "SELECT "
+ + GET_MESSAGES_COLS
+ + "FROM messages WHERE uid = ? AND folder_id = ?",
new String[] {
message.getUid(), Long.toString(mFolderId)
});
-
- // Note: Because of the COUNT(c.id) we will always get one result
- // row even if nothing was found. So we check if 'id' is NULL
- if (!cursor.moveToNext() || cursor.isNull(0)) {
+ if (!cursor.moveToNext()) {
return null;
}
message.populateFromGetMessageCursor(cursor);
From 1fcce6fb8a22f2d538ea05d5b4e48d1c998e03c1 Mon Sep 17 00:00:00 2001
From: cketti
Date: Wed, 24 Oct 2012 01:03:59 +0200
Subject: [PATCH 30/74] Add database indices for thread columns in 'messages'
table
---
src/com/fsck/k9/mail/store/LocalStore.java | 27 ++++++++++++++++++++--
1 file changed, 25 insertions(+), 2 deletions(-)
diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java
index fdb1c2af3..4407415b9 100644
--- a/src/com/fsck/k9/mail/store/LocalStore.java
+++ b/src/com/fsck/k9/mail/store/LocalStore.java
@@ -102,7 +102,7 @@ public class LocalStore extends Store implements Serializable {
static private String GET_FOLDER_COLS = "id, name, unread_count, visible_limit, last_updated, status, push_state, last_pushed, flagged_count, integrate, top_group, poll_class, push_class, display_class";
- protected static final int DB_VERSION = 44;
+ protected static final int DB_VERSION = 45;
protected String uUid = null;
@@ -204,7 +204,14 @@ public class LocalStore extends Store implements Serializable {
db.execSQL("DROP INDEX IF EXISTS msg_folder_id_date");
db.execSQL("CREATE INDEX IF NOT EXISTS msg_folder_id_deleted_date ON messages (folder_id,deleted,internal_date)");
- //TODO: add indices for the thread columns
+ db.execSQL("DROP INDEX IF EXISTS msg_empty");
+ db.execSQL("CREATE INDEX IF NOT EXISTS msg_empty ON messages (empty)");
+
+ db.execSQL("DROP INDEX IF EXISTS msg_thread_root");
+ db.execSQL("CREATE INDEX IF NOT EXISTS msg_thread_root ON messages (thread_root)");
+
+ db.execSQL("DROP INDEX IF EXISTS msg_thread_parent");
+ db.execSQL("CREATE INDEX IF NOT EXISTS msg_thread_parent ON messages (thread_parent)");
db.execSQL("DROP TABLE IF EXISTS attachments");
db.execSQL("CREATE TABLE attachments (id INTEGER PRIMARY KEY, message_id INTEGER,"
@@ -403,6 +410,22 @@ public class LocalStore extends Store implements Serializable {
}
}
}
+ if (db.getVersion() < 45) {
+ try {
+ db.execSQL("DROP INDEX IF EXISTS msg_empty");
+ db.execSQL("CREATE INDEX IF NOT EXISTS msg_empty ON messages (empty)");
+
+ db.execSQL("DROP INDEX IF EXISTS msg_thread_root");
+ db.execSQL("CREATE INDEX IF NOT EXISTS msg_thread_root ON messages (thread_root)");
+
+ db.execSQL("DROP INDEX IF EXISTS msg_thread_parent");
+ db.execSQL("CREATE INDEX IF NOT EXISTS msg_thread_parent ON messages (thread_parent)");
+ } catch (SQLiteException e) {
+ if (! e.getMessage().startsWith("duplicate column name:")) {
+ throw e;
+ }
+ }
+ }
}
} catch (SQLiteException e) {
Log.e(K9.LOG_TAG, "Exception while upgrading database. Resetting the DB to v0");
From ec76dca57fdc862f171040b273f4afb19ae66ded Mon Sep 17 00:00:00 2001
From: cketti
Date: Wed, 24 Oct 2012 01:37:33 +0200
Subject: [PATCH 31/74] Remove unused stuff
---
.../fsck/k9/activity/MessageInfoHolder.java | 1 -
.../k9/controller/MessagingController.java | 110 ------------
src/com/fsck/k9/helper/MessageHelper.java | 3 -
src/com/fsck/k9/mail/Message.java | 11 --
.../fsck/k9/mail/internet/MimeMessage.java | 23 ---
src/com/fsck/k9/mail/store/LocalStore.java | 166 +-----------------
6 files changed, 3 insertions(+), 311 deletions(-)
diff --git a/src/com/fsck/k9/activity/MessageInfoHolder.java b/src/com/fsck/k9/activity/MessageInfoHolder.java
index 56db97ced..4669daec6 100644
--- a/src/com/fsck/k9/activity/MessageInfoHolder.java
+++ b/src/com/fsck/k9/activity/MessageInfoHolder.java
@@ -24,7 +24,6 @@ public class MessageInfoHolder {
public boolean selected;
public String account;
public String uri;
- public int threadCount;
// Empty constructor for comparison
public MessageInfoHolder() {
diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java
index 8314866f3..2c3ee24ea 100644
--- a/src/com/fsck/k9/controller/MessagingController.java
+++ b/src/com/fsck/k9/controller/MessagingController.java
@@ -510,116 +510,6 @@ public class MessagingController implements Runnable {
});
}
-
-
- /**
- * List the messages in the local message store for the given folder asynchronously.
- *
- * @param account
- * @param folder
- * @param listener
- * @param threaded
- * @param threadId
- * @throws MessagingException
- */
- public void listLocalMessages(final Account account, final String folder,
- final MessagingListener listener, final boolean threaded, final long threadId) {
- threadPool.execute(new Runnable() {
- @Override
- public void run() {
- listLocalMessagesSynchronous(account, folder, listener, threaded, threadId);
- }
- });
- }
-
-
- /**
- * List the messages in the local message store for the given folder synchronously.
- *
- * @param account
- * @param folder
- * @param listener
- * @param threaded
- * @param threadId
- * @throws MessagingException
- */
- public void listLocalMessagesSynchronous(final Account account, final String folder,
- final MessagingListener listener, boolean threaded, long threadId) {
-
- for (MessagingListener l : getListeners(listener)) {
- l.listLocalMessagesStarted(account, folder);
- }
-
- LocalFolder localFolder = null;
- MessageRetrievalListener retrievalListener =
- new MessageRetrievalListener() {
- List pendingMessages = new ArrayList();
-
-
- @Override
- public void messageStarted(String message, int number, int ofTotal) {}
- @Override
- public void messageFinished(Message message, int number, int ofTotal) {
-
- if (!isMessageSuppressed(account, folder, message)) {
- pendingMessages.add(message);
- if (pendingMessages.size() > 10) {
- addPendingMessages();
- }
-
- } else {
- for (MessagingListener l : getListeners(listener)) {
- l.listLocalMessagesRemoveMessage(account, folder, message);
- }
- }
- }
- @Override
- public void messagesFinished(int number) {
- addPendingMessages();
- }
- private void addPendingMessages() {
- for (MessagingListener l : getListeners(listener)) {
- l.listLocalMessagesAddMessages(account, folder, pendingMessages);
- }
- pendingMessages.clear();
- }
- };
-
-
-
- try {
- LocalStore localStore = account.getLocalStore();
- localFolder = localStore.getFolder(folder);
- localFolder.open(OpenMode.READ_WRITE);
-
- //Purging followed by getting requires 2 DB queries.
- //TODO: Fix getMessages to allow auto-pruning at visible limit?
- localFolder.purgeToVisibleLimit(null);
-
- if (threadId != -1) {
- localFolder.getMessagesInThread(threadId, retrievalListener);
- } else if (threaded) {
- localFolder.getThreadedMessages(retrievalListener);
- } else {
- localFolder.getMessages(retrievalListener, false);
- }
-
- if (K9.DEBUG)
- Log.v(K9.LOG_TAG, "Got ack that callbackRunner finished");
-
- for (MessagingListener l : getListeners(listener)) {
- l.listLocalMessagesFinished(account, folder);
- }
- } catch (Exception e) {
- for (MessagingListener l : getListeners(listener)) {
- l.listLocalMessagesFailed(account, folder, e.getMessage());
- }
- addErrorMessage(account, null, e);
- } finally {
- closeFolder(localFolder);
- }
- }
-
/**
* Find all messages in any local account which match the query 'query'
* @throws MessagingException
diff --git a/src/com/fsck/k9/helper/MessageHelper.java b/src/com/fsck/k9/helper/MessageHelper.java
index ce2e9d2ed..51ab08705 100644
--- a/src/com/fsck/k9/helper/MessageHelper.java
+++ b/src/com/fsck/k9/helper/MessageHelper.java
@@ -17,7 +17,6 @@ import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Message.RecipientType;
-import com.fsck.k9.mail.store.LocalStore.LocalMessage;
import com.fsck.k9.helper.DateFormatter;
public class MessageHelper {
@@ -87,8 +86,6 @@ public class MessageHelper {
target.account = account.getUuid();
target.uri = "email://messages/" + account.getAccountNumber() + "/" + message.getFolder().getName() + "/" + message.getUid();
- target.threadCount = ((LocalMessage) message).getThreadCount();
-
} catch (MessagingException me) {
Log.w(K9.LOG_TAG, "Unable to load message info", me);
}
diff --git a/src/com/fsck/k9/mail/Message.java b/src/com/fsck/k9/mail/Message.java
index 48e97d585..3d31f0839 100644
--- a/src/com/fsck/k9/mail/Message.java
+++ b/src/com/fsck/k9/mail/Message.java
@@ -143,10 +143,6 @@ public abstract class Message implements Part, Body {
return getContentType().startsWith(mimeType);
}
- public abstract boolean toMe();
- public abstract boolean ccMe();
- public abstract boolean bccMe();
- public abstract boolean fromMe();
public abstract long getId();
public abstract String getPreview();
@@ -197,8 +193,6 @@ public abstract class Message implements Part, Body {
public void destroy() throws MessagingException {}
- public abstract void saveChanges() throws MessagingException;
-
public abstract void setEncoding(String encoding) throws UnavailableStorageException;
public abstract void setCharset(String charset) throws MessagingException;
@@ -213,11 +207,6 @@ public abstract class Message implements Part, Body {
return mReference;
}
- public boolean equalsReference(MessageReference ref) {
- MessageReference tmpReference = makeMessageReference();
- return tmpReference.equals(ref);
- }
-
public long calculateSize() {
try {
diff --git a/src/com/fsck/k9/mail/internet/MimeMessage.java b/src/com/fsck/k9/mail/internet/MimeMessage.java
index 93c80ba1c..2777420de 100644
--- a/src/com/fsck/k9/mail/internet/MimeMessage.java
+++ b/src/com/fsck/k9/mail/internet/MimeMessage.java
@@ -15,7 +15,6 @@ import java.util.UUID;
import org.apache.james.mime4j.MimeException;
import org.apache.james.mime4j.dom.field.DateTimeField;
-import org.apache.james.mime4j.dom.field.ParsedField;
import org.apache.james.mime4j.field.DefaultFieldParser;
import org.apache.james.mime4j.io.EOLConvertingInputStream;
import org.apache.james.mime4j.parser.ContentHandler;
@@ -23,7 +22,6 @@ import org.apache.james.mime4j.parser.MimeStreamParser;
import org.apache.james.mime4j.stream.BodyDescriptor;
import org.apache.james.mime4j.stream.Field;
import org.apache.james.mime4j.stream.MimeConfig;
-import org.apache.james.mime4j.stream.RawField;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Body;
@@ -345,11 +343,6 @@ public class MimeMessage extends Message {
setHeader("References", references);
}
- @Override
- public void saveChanges() throws MessagingException {
- throw new MessagingException("saveChanges not yet implemented");
- }
-
@Override
public Body getBody() {
return mBody;
@@ -593,22 +586,6 @@ public class MimeMessage extends Message {
return message;
}
- public boolean toMe() {
- return false;
- }
-
- public boolean ccMe() {
- return false;
- }
-
- public boolean bccMe() {
- return false;
- }
-
- public boolean fromMe() {
- return false;
- }
-
public long getId() {
return Long.parseLong(mUid); //or maybe .mMessageId?
}
diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java
index 4407415b9..459015064 100644
--- a/src/com/fsck/k9/mail/store/LocalStore.java
+++ b/src/com/fsck/k9/mail/store/LocalStore.java
@@ -88,15 +88,13 @@ public class LocalStore extends Store implements Serializable {
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private static final Flag[] EMPTY_FLAG_ARRAY = new Flag[0];
- private static final Flag[] PERMANENT_FLAGS = { Flag.DELETED, Flag.X_DESTROYED, Flag.SEEN, Flag.FLAGGED };
-
/*
* a String containing the columns getMessages expects to work with
* in the correct order.
*/
static private String GET_MESSAGES_COLS =
"subject, sender_list, date, uid, flags, id, to_list, cc_list, "
- + "bcc_list, reply_to_list, attachment_count, internal_date, message_id, folder_id, preview, thread_root, thread_parent, empty ";
+ + "bcc_list, reply_to_list, attachment_count, internal_date, message_id, folder_id, preview, thread_root, thread_parent ";
static private String GET_FOLDER_COLS = "id, name, unread_count, visible_limit, last_updated, status, push_state, last_pushed, flagged_count, integrate, top_group, poll_class, push_class, display_class";
@@ -1960,76 +1958,6 @@ public class LocalStore extends Store implements Serializable {
}
}
- public Message[] getThreadedMessages(final MessageRetrievalListener listener)
- throws MessagingException {
- try {
- return database.execute(false, new DbCallback() {
- @Override
- public Message[] doDbWork(final SQLiteDatabase db) throws WrappedException,
- UnavailableStorageException {
- try {
- open(OpenMode.READ_WRITE);
-
- return LocalStore.this.getMessages(
- listener,
- LocalFolder.this,
- "SELECT " +
- "m.subject, m.sender_list, MAX(m.date), m.uid, m.flags, " +
- "m.id, m.to_list, m.cc_list, m.bcc_list, m.reply_to_list, " +
- "m.attachment_count, m.internal_date, m.message_id, " +
- "m.folder_id, m.preview, m.thread_root, m.thread_parent, " +
- "m.empty, COUNT(h.id) " +
- "FROM messages h JOIN messages m " +
- "ON (h.id = m.thread_root OR h.id = m.id) " +
- "WHERE h.deleted = 0 AND m.deleted = 0 AND " +
- "(m.empty IS NULL OR m.empty != 1) AND " +
- "h.thread_root IS NULL AND h.folder_id = ? " +
- "GROUP BY h.id",
- new String[] { Long.toString(mFolderId) });
- } catch (MessagingException e) {
- throw new WrappedException(e);
- }
- }
- });
- } catch (WrappedException e) {
- throw(MessagingException) e.getCause();
- }
- }
-
- public Message[] getMessagesInThread(final long threadId,
- final MessageRetrievalListener listener) throws MessagingException {
- try {
- return database.execute(false, new DbCallback() {
- @Override
- public Message[] doDbWork(final SQLiteDatabase db) throws WrappedException,
- UnavailableStorageException {
- try {
- open(OpenMode.READ_WRITE);
-
- String threadIdString = Long.toString(threadId);
-
- return LocalStore.this.getMessages(
- listener,
- LocalFolder.this,
- "SELECT " + GET_MESSAGES_COLS + " " +
- "FROM messages " +
- "WHERE deleted = 0 AND (empty IS NULL OR empty != 1) AND " +
- "(thread_root = ? OR id = ?) AND folder_id = ?",
- new String[] {
- threadIdString,
- threadIdString,
- Long.toString(mFolderId)
- });
- } catch (MessagingException e) {
- throw new WrappedException(e);
- }
- }
- });
- } catch (WrappedException e) {
- throw(MessagingException) e.getCause();
- }
- }
-
@Override
public Message[] getMessages(String[] uids, MessageRetrievalListener listener)
throws MessagingException {
@@ -2125,11 +2053,11 @@ public class LocalStore extends Store implements Serializable {
cv.clear();
cv.put("thread_root", id);
- int x = db.update("messages", cv, "thread_root = ?", oldIdArg);
+ db.update("messages", cv, "thread_root = ?", oldIdArg);
cv.clear();
cv.put("thread_parent", id);
- x = db.update("messages", cv, "thread_parent = ?", oldIdArg);
+ db.update("messages", cv, "thread_parent = ?", oldIdArg);
}
/*
@@ -3303,23 +3231,11 @@ public class LocalStore extends Store implements Serializable {
private String mPreview = "";
- private boolean mToMeCalculated = false;
- private boolean mCcMeCalculated = false;
- private boolean mFromMeCalculated = false;
- private boolean mToMe = false;
- private boolean mCcMe = false;
- private boolean mFromMe = false;
-
-
-
-
private boolean mHeadersLoaded = false;
private boolean mMessageDirty = false;
private long mRootId;
private long mParentId;
- private boolean mEmpty;
- private int mThreadCount = 0;
public LocalMessage() {
}
@@ -3377,12 +3293,6 @@ public class LocalStore extends Store implements Serializable {
mRootId = (cursor.isNull(15)) ? -1 : cursor.getLong(15);
mParentId = (cursor.isNull(16)) ? -1 : cursor.getLong(16);
-
- mEmpty = (cursor.isNull(17)) ? false : (cursor.getInt(17) == 1);
-
- if (cursor.getColumnCount() > 18) {
- mThreadCount = cursor.getInt(18);
- }
}
/**
@@ -3522,64 +3432,6 @@ public class LocalStore extends Store implements Serializable {
mMessageDirty = true;
}
-
- public boolean fromMe() {
- if (!mFromMeCalculated) {
- if (mAccount.isAnIdentity(getFrom())) {
- mFromMe = true;
- mFromMeCalculated = true;
- }
- }
- return mFromMe;
- }
-
-
- public boolean toMe() {
- try {
- if (!mToMeCalculated) {
- for (Address address : getRecipients(RecipientType.TO)) {
- if (mAccount.isAnIdentity(address)) {
- mToMe = true;
- mToMeCalculated = true;
- }
- }
- }
- } catch (MessagingException e) {
- // do something better than ignore this
- // getRecipients can throw a messagingexception
- }
- return mToMe;
- }
-
-
-
-
-
- public boolean ccMe() {
- try {
-
- if (!mCcMeCalculated) {
- for (Address address : getRecipients(RecipientType.CC)) {
- if (mAccount.isAnIdentity(address)) {
- mCcMe = true;
- mCcMeCalculated = true;
- }
- }
-
- }
- } catch (MessagingException e) {
- // do something better than ignore this
- // getRecipients can throw a messagingexception
- }
-
- return mCcMe;
- }
-
-
-
-
-
-
public void setFlagInternal(Flag flag, boolean set) throws MessagingException {
super.setFlag(flag, set);
}
@@ -3886,10 +3738,6 @@ public class LocalStore extends Store implements Serializable {
message.mAttachmentCount = mAttachmentCount;
message.mSubject = mSubject;
message.mPreview = mPreview;
- message.mToMeCalculated = mToMeCalculated;
- message.mCcMeCalculated = mCcMeCalculated;
- message.mToMe = mToMe;
- message.mCcMe = mCcMe;
message.mHeadersLoaded = mHeadersLoaded;
message.mMessageDirty = mMessageDirty;
@@ -3903,14 +3751,6 @@ public class LocalStore extends Store implements Serializable {
public long getParentId() {
return mParentId;
}
-
- public boolean isEmpty() {
- return mEmpty;
- }
-
- public int getThreadCount() {
- return mThreadCount;
- }
}
public static class LocalAttachmentBodyPart extends MimeBodyPart {
From c7a2080b34abcfc3f4f25c3d31552a2bccbeb511 Mon Sep 17 00:00:00 2001
From: cketti
Date: Wed, 24 Oct 2012 04:34:41 +0200
Subject: [PATCH 32/74] Only show message list footer when displaying a single
folder
---
src/com/fsck/k9/fragment/MessageListFragment.java | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/src/com/fsck/k9/fragment/MessageListFragment.java b/src/com/fsck/k9/fragment/MessageListFragment.java
index 9657b9802..a87c08ed8 100644
--- a/src/com/fsck/k9/fragment/MessageListFragment.java
+++ b/src/com/fsck/k9/fragment/MessageListFragment.java
@@ -763,8 +763,10 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
mCurrentFolder = getFolder(mFolderName, mAccount);
}
- // Hide "Load up to x more" footer for search views
- mFooterView.setVisibility((!mSingleFolderMode) ? View.GONE : View.VISIBLE);
+ if (mSingleFolderMode) {
+ mListView.addFooterView(getFooterView(mListView));
+ updateFooterView();
+ }
mController = MessagingController.getInstance(getActivity().getApplication());
mListView.setAdapter(mAdapter);
@@ -916,8 +918,6 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
mListView.setFastScrollEnabled(true);
mListView.setScrollingCacheEnabled(false);
mListView.setOnItemClickListener(this);
- mListView.addFooterView(getFooterView(mListView));
- //mListView.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE);
registerForContextMenu(mListView);
}
@@ -1778,6 +1778,10 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
}
public void updateFooter(final String text, final boolean progressVisible) {
+ if (mFooterView == null) {
+ return;
+ }
+
FooterViewHolder holder = (FooterViewHolder) mFooterView.getTag();
holder.progress.setVisibility(progressVisible ? ProgressBar.VISIBLE : ProgressBar.INVISIBLE);
From 508e9e8aa669075852a2e5906d3613177fcd0730 Mon Sep 17 00:00:00 2001
From: cketti
Date: Wed, 24 Oct 2012 05:28:38 +0200
Subject: [PATCH 33/74] Don't display threaded message list for filtered views
---
src/com/fsck/k9/activity/Accounts.java | 6 ++--
src/com/fsck/k9/activity/FolderList.java | 4 +--
.../fsck/k9/activity/LauncherShortcuts.java | 3 +-
src/com/fsck/k9/activity/MessageList.java | 28 ++++++++++++++-----
.../k9/controller/MessagingController.java | 4 +--
.../k9/provider/UnreadWidgetProvider.java | 3 +-
6 files changed, 32 insertions(+), 16 deletions(-)
diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java
index 9d6482a64..f45f908e3 100644
--- a/src/com/fsck/k9/activity/Accounts.java
+++ b/src/com/fsck/k9/activity/Accounts.java
@@ -620,7 +620,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
private boolean onOpenAccount(BaseAccount account) {
if (account instanceof SearchAccount) {
SearchAccount searchAccount = (SearchAccount)account;
- MessageList.actionDisplaySearch(this, searchAccount.getRelatedSearch(), false);
+ MessageList.actionDisplaySearch(this, searchAccount.getRelatedSearch(), false, false);
} else {
Account realAccount = (Account)account;
if (!realAccount.isEnabled()) {
@@ -640,7 +640,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
LocalSearch search = new LocalSearch(realAccount.getAutoExpandFolderName());
search.addAllowedFolder(realAccount.getAutoExpandFolderName());
search.addAccountUuid(realAccount.getUuid());
- MessageList.actionDisplaySearch(this, search, true);}
+ MessageList.actionDisplaySearch(this, search, false, true);}
}
return true;
}
@@ -1797,7 +1797,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
search.allRequiredFlags(searchModifier.requiredFlags);
search.allForbiddenFlags(searchModifier.forbiddenFlags);
- MessageList.actionDisplaySearch(Accounts.this, search, false);
+ MessageList.actionDisplaySearch(Accounts.this, search, true, false);
}
}
diff --git a/src/com/fsck/k9/activity/FolderList.java b/src/com/fsck/k9/activity/FolderList.java
index 487e8f44e..28eab3ec7 100644
--- a/src/com/fsck/k9/activity/FolderList.java
+++ b/src/com/fsck/k9/activity/FolderList.java
@@ -625,7 +625,7 @@ public class FolderList extends K9ListActivity implements OnNavigationListener {
LocalSearch search = new LocalSearch(folder);
search.addAccountUuid(mAccount.getUuid());
search.addAllowedFolder(folder);
- MessageList.actionDisplaySearch(this, search, false);
+ MessageList.actionDisplaySearch(this, search, false, false);
}
private void onCompact(Account account) {
@@ -1276,7 +1276,7 @@ public class FolderList extends K9ListActivity implements OnNavigationListener {
}
search.addAllowedFolder(folderName);
search.addAccountUuid(account.getUuid());
- MessageList.actionDisplaySearch(FolderList.this, search, false);
+ MessageList.actionDisplaySearch(FolderList.this, search, true, false);
}
}
diff --git a/src/com/fsck/k9/activity/LauncherShortcuts.java b/src/com/fsck/k9/activity/LauncherShortcuts.java
index f302051dc..94e6a4ff8 100644
--- a/src/com/fsck/k9/activity/LauncherShortcuts.java
+++ b/src/com/fsck/k9/activity/LauncherShortcuts.java
@@ -31,7 +31,8 @@ public class LauncherShortcuts extends AccountList {
Intent shortcutIntent = null;
if (account instanceof SearchSpecification) {
- shortcutIntent = MessageList.intentDisplaySearch(this, (SearchSpecification) account, true, true);
+ shortcutIntent = MessageList.intentDisplaySearch(this, (SearchSpecification) account,
+ false, true, true);
} else {
shortcutIntent = FolderList.actionHandleAccountIntent(this, (Account) account, null,
true);
diff --git a/src/com/fsck/k9/activity/MessageList.java b/src/com/fsck/k9/activity/MessageList.java
index 020e07110..67f065da8 100644
--- a/src/com/fsck/k9/activity/MessageList.java
+++ b/src/com/fsck/k9/activity/MessageList.java
@@ -49,22 +49,28 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
// for this activity
private static final String EXTRA_SEARCH = "search";
+ private static final String EXTRA_NO_THREADING = "no_threading";
// used for remote search
private static final String EXTRA_SEARCH_ACCOUNT = "com.fsck.k9.search_account";
private static final String EXTRA_SEARCH_FOLDER = "com.fsck.k9.search_folder";
- public static void actionDisplaySearch(Context context, SearchSpecification search, boolean newTask) {
- actionDisplaySearch(context, search, newTask, true);
+ public static void actionDisplaySearch(Context context, SearchSpecification search,
+ boolean noThreading, boolean newTask) {
+ actionDisplaySearch(context, search, noThreading, newTask, true);
}
- public static void actionDisplaySearch(Context context, SearchSpecification search, boolean newTask, boolean clearTop) {
- context.startActivity(intentDisplaySearch(context, search, newTask, clearTop));
+ public static void actionDisplaySearch(Context context, SearchSpecification search,
+ boolean noThreading, boolean newTask, boolean clearTop) {
+ context.startActivity(
+ intentDisplaySearch(context, search, noThreading, newTask, clearTop));
}
- public static Intent intentDisplaySearch(Context context, SearchSpecification search, boolean newTask, boolean clearTop) {
+ public static Intent intentDisplaySearch(Context context, SearchSpecification search,
+ boolean noThreading, boolean newTask, boolean clearTop) {
Intent intent = new Intent(context, MessageList.class);
intent.putExtra(EXTRA_SEARCH, search);
+ intent.putExtra(EXTRA_NO_THREADING, noThreading);
if (clearTop) {
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
@@ -95,6 +101,13 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
private boolean mIsRemote;
private boolean mThreadViewEnabled = true; //TODO: this should be a setting
+ /**
+ * {@code true} if the message list should be displayed as flat list (i.e. no threading)
+ * regardless whether or not message threading was enabled in the settings. This is used for
+ * filtered views, e.g. when only displaying the unread messages in a folder.
+ */
+ private boolean mNoThreading;
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -115,8 +128,8 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
if (mMessageListFragment == null) {
FragmentTransaction ft = fragmentManager.beginTransaction();
- mMessageListFragment = MessageListFragment.newInstance(mSearch, mThreadViewEnabled,
- mIsRemote);
+ mMessageListFragment = MessageListFragment.newInstance(mSearch,
+ (mThreadViewEnabled && !mNoThreading), mIsRemote);
ft.add(R.id.message_list_container, mMessageListFragment);
ft.commit();
}
@@ -143,6 +156,7 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
} else {
// regular LocalSearch object was passed
mSearch = intent.getParcelableExtra(EXTRA_SEARCH);
+ mNoThreading = intent.getBooleanExtra(EXTRA_NO_THREADING, false);
}
String[] accounts = mSearch.getAccountUuids();
diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java
index 2c3ee24ea..4d0dcf2cc 100644
--- a/src/com/fsck/k9/controller/MessagingController.java
+++ b/src/com/fsck/k9/controller/MessagingController.java
@@ -3053,7 +3053,7 @@ public class MessagingController implements Runnable {
LocalSearch search = new LocalSearch(account.getInboxFolderName());
search.addAllowedFolder(account.getInboxFolderName());
search.addAccountUuid(account.getUuid());
- Intent intent = MessageList.intentDisplaySearch(mApplication, search, true, true);
+ Intent intent = MessageList.intentDisplaySearch(mApplication, search, false, true, true);
PendingIntent pi = PendingIntent.getActivity(mApplication, 0, intent, 0);
builder.setContentIntent(pi);
@@ -3139,7 +3139,7 @@ public class MessagingController implements Runnable {
LocalSearch search = new LocalSearch(account.getInboxFolderName());
search.addAllowedFolder(account.getInboxFolderName());
search.addAccountUuid(account.getUuid());
- Intent intent = MessageList.intentDisplaySearch(mApplication, search, true, true);
+ Intent intent = MessageList.intentDisplaySearch(mApplication, search, false, true, true);
PendingIntent pi = PendingIntent.getActivity(mApplication, 0, intent, 0);
builder.setContentIntent(pi);
diff --git a/src/com/fsck/k9/provider/UnreadWidgetProvider.java b/src/com/fsck/k9/provider/UnreadWidgetProvider.java
index 180b8c22a..75acd1073 100644
--- a/src/com/fsck/k9/provider/UnreadWidgetProvider.java
+++ b/src/com/fsck/k9/provider/UnreadWidgetProvider.java
@@ -66,7 +66,8 @@ public class UnreadWidgetProvider extends AppWidgetProvider {
LocalSearch search = new LocalSearch(account.getAutoExpandFolderName());
search.addAllowedFolder(account.getAutoExpandFolderName());
search.addAccountUuid(account.getUuid());
- clickIntent = MessageList.intentDisplaySearch(context, search, true, true);
+ clickIntent = MessageList.intentDisplaySearch(context, search, false, true,
+ true);
}
clickIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
}
From 613ef6ced253561508ae287a449a18cac02950ae Mon Sep 17 00:00:00 2001
From: cketti
Date: Wed, 24 Oct 2012 05:35:45 +0200
Subject: [PATCH 34/74] "Select all" now won't start selection mode when there
are no messages
---
src/com/fsck/k9/fragment/MessageListFragment.java | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/com/fsck/k9/fragment/MessageListFragment.java b/src/com/fsck/k9/fragment/MessageListFragment.java
index a87c08ed8..8a1c5e03b 100644
--- a/src/com/fsck/k9/fragment/MessageListFragment.java
+++ b/src/com/fsck/k9/fragment/MessageListFragment.java
@@ -1810,6 +1810,11 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
private void setSelectionState(boolean selected) {
if (selected) {
mSelectedCount = mAdapter.getCount();
+ if (mSelectedCount == 0) {
+ // Nothing to do if there are no messages
+ return;
+ }
+
for (int i = 0, end = mSelectedCount; i < end; i++) {
mSelected.put(i, true);
}
From d703286833a03b692ec55f115fe3a14fde2561c2 Mon Sep 17 00:00:00 2001
From: cketti
Date: Wed, 24 Oct 2012 06:01:26 +0200
Subject: [PATCH 35/74] Make LocalStore.searchForMessages() skip empty messages
---
src/com/fsck/k9/mail/store/LocalStore.java | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java
index 459015064..db9e52495 100644
--- a/src/com/fsck/k9/mail/store/LocalStore.java
+++ b/src/com/fsck/k9/mail/store/LocalStore.java
@@ -976,8 +976,10 @@ public class LocalStore extends Store implements Serializable {
}
// build sql query
- String sqlQuery = "SELECT " + GET_MESSAGES_COLS + "FROM messages WHERE deleted = 0 "
- + (search.getConditions() != null ? "AND (" + search.getConditions() + ")" : "") + " ORDER BY date DESC";
+ ConditionsTreeNode conditions = search.getConditions();
+ String sqlQuery = "SELECT " + GET_MESSAGES_COLS + "FROM messages WHERE " +
+ "((empty IS NULL OR empty != 1) AND deleted = 0)" +
+ ((conditions != null) ? " AND (" + conditions + ")" : "") + " ORDER BY date DESC";
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Query = " + sqlQuery);
From a4585657d115f46e506cbdb73643c01933b83408 Mon Sep 17 00:00:00 2001
From: cketti
Date: Wed, 24 Oct 2012 06:57:14 +0200
Subject: [PATCH 36/74] Fix global search
---
src/com/fsck/k9/activity/MessageList.java | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/src/com/fsck/k9/activity/MessageList.java b/src/com/fsck/k9/activity/MessageList.java
index 67f065da8..92ab7d2e4 100644
--- a/src/com/fsck/k9/activity/MessageList.java
+++ b/src/com/fsck/k9/activity/MessageList.java
@@ -140,17 +140,22 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
if (intent.getStringExtra(SearchManager.QUERY) != null) {
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
//Query was received from Search Dialog
+ String query = intent.getStringExtra(SearchManager.QUERY);
+
+ mSearch = new LocalSearch(getString(R.string.search_results));
+ mNoThreading = true;
+
+ mSearch.or(new SearchCondition(Searchfield.SENDER, Attribute.CONTAINS, query));
+ mSearch.or(new SearchCondition(Searchfield.SUBJECT, Attribute.CONTAINS, query));
+
Bundle appData = getIntent().getBundleExtra(SearchManager.APP_DATA);
if (appData != null) {
- mSearch = new LocalSearch();
mSearch.addAccountUuid(appData.getString(EXTRA_SEARCH_ACCOUNT));
mSearch.addAllowedFolder(appData.getString(EXTRA_SEARCH_FOLDER));
- String query = intent.getStringExtra(SearchManager.QUERY);
- mSearch.or(new SearchCondition(Searchfield.SENDER, Attribute.CONTAINS, query));
- mSearch.or(new SearchCondition(Searchfield.SUBJECT, Attribute.CONTAINS, query));
-
mIsRemote = true;
+ } else {
+ mSearch.addAccountUuid(LocalSearch.ALL_ACCOUNTS);
}
}
} else {
From 49a5a3b7ff733aa945e4efa4c035a5b447288857 Mon Sep 17 00:00:00 2001
From: cketti
Date: Thu, 25 Oct 2012 21:51:14 +0200
Subject: [PATCH 37/74] Fix message sorting for message lists of a single
account
To support sorting of message lists spanning multiple accounts (e.g.
Unified Inbox) we need a MergeCursor that also does sorting.
---
.../fsck/k9/fragment/MessageListFragment.java | 77 +++++++++++++------
src/com/fsck/k9/provider/EmailProvider.java | 12 +--
2 files changed, 61 insertions(+), 28 deletions(-)
diff --git a/src/com/fsck/k9/fragment/MessageListFragment.java b/src/com/fsck/k9/fragment/MessageListFragment.java
index 8a1c5e03b..d61de81b0 100644
--- a/src/com/fsck/k9/fragment/MessageListFragment.java
+++ b/src/com/fsck/k9/fragment/MessageListFragment.java
@@ -413,17 +413,11 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
* perform the operation in the calling thread.
*/
class MessageListHandler extends Handler {
- private static final int ACTION_SORT_MESSAGES = 1;
- private static final int ACTION_FOLDER_LOADING = 2;
- private static final int ACTION_REFRESH_TITLE = 3;
- private static final int ACTION_PROGRESS = 4;
+ private static final int ACTION_FOLDER_LOADING = 1;
+ private static final int ACTION_REFRESH_TITLE = 2;
+ private static final int ACTION_PROGRESS = 3;
- public void sortMessages() {
- android.os.Message msg = android.os.Message.obtain(this, ACTION_SORT_MESSAGES);
- sendMessage(msg);
- }
-
public void folderLoading(String folder, boolean loading) {
android.os.Message msg = android.os.Message.obtain(this, ACTION_FOLDER_LOADING,
(loading) ? 1 : 0, 0, folder);
@@ -454,10 +448,6 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
@Override
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
- case ACTION_SORT_MESSAGES: {
- mAdapter.sortMessages();
- break;
- }
case ACTION_FOLDER_LOADING: {
String folder = (String) msg.obj;
boolean loading = (msg.arg1 == 1);
@@ -1024,7 +1014,10 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
Toast toast = Toast.makeText(getActivity(), toastString, Toast.LENGTH_SHORT);
toast.show();
- mAdapter.sortMessages();
+ LoaderManager loaderManager = getLoaderManager();
+ for (int i = 0, len = mAccountUuids.length; i < len; i++) {
+ loaderManager.restartLoader(i, null, this);
+ }
}
public void onCycleSort() {
@@ -1451,7 +1444,6 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
if (updateForMe(account, folder)) {
mHandler.progress(false);
mHandler.folderLoading(folder, false);
- mHandler.sortMessages();
}
super.synchronizeMailboxFinished(account, folder, totalMessagesInMailbox, numNewMessages);
}
@@ -1462,7 +1454,6 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
if (updateForMe(account, folder)) {
mHandler.progress(false);
mHandler.folderLoading(folder, false);
- mHandler.sortMessages();
}
super.synchronizeMailboxFailed(account, folder, message);
}
@@ -1513,12 +1504,6 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
//notifyDataSetChanged();
}
- public void sortMessages() {
- //TODO: implement
-
- //notifyDataSetChanged();
- }
-
private String recipientSigil(boolean toMe, boolean ccMe) {
if (toMe) {
return getString(R.string.messagelist_sent_to_me_sigil);
@@ -2667,8 +2652,54 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
String selection = query.toString();
String[] selectionArgs = queryArgs.toArray(new String[0]);
+ String sortColumn = MessageColumns.ID;
+ switch (mSortType) {
+ case SORT_ARRIVAL: {
+ sortColumn = MessageColumns.INTERNAL_DATE;
+ break;
+ }
+ case SORT_ATTACHMENT: {
+ sortColumn = "(" + MessageColumns.ATTACHMENT_COUNT + " < 1)";
+ break;
+ }
+ case SORT_FLAGGED: {
+ sortColumn = "(" + MessageColumns.FLAGS + " NOT LIKE '%FLAGGED%')";
+ break;
+ }
+ case SORT_SENDER: {
+ //FIXME
+ sortColumn = MessageColumns.SENDER_LIST;
+ break;
+ }
+ case SORT_SUBJECT: {
+ sortColumn = MessageColumns.SUBJECT;
+ break;
+ }
+ case SORT_UNREAD: {
+ sortColumn = "(" + MessageColumns.FLAGS + " LIKE '%SEEN%')";
+ break;
+ }
+ case SORT_DATE:
+ default: {
+ sortColumn = MessageColumns.DATE;
+ }
+ }
+
+ String sortDirection;
+ String secondarySort;
+ if (mSortType == SortType.SORT_DATE) {
+ sortDirection = (mSortDateAscending) ? " ASC" : " DESC";
+ secondarySort = "";
+ } else {
+ sortDirection = (mSortAscending) ? " ASC" : " DESC";
+ secondarySort = MessageColumns.DATE + " DESC, ";
+ }
+
+ String sortOrder = sortColumn + sortDirection + ", " + secondarySort +
+ MessageColumns.ID + " DESC";
+
return new CursorLoader(getActivity(), uri, projection, selection, selectionArgs,
- MessageColumns.DATE + " DESC");
+ sortOrder);
}
private void buildQuery(Account account, ConditionsTreeNode node, StringBuilder query,
diff --git a/src/com/fsck/k9/provider/EmailProvider.java b/src/com/fsck/k9/provider/EmailProvider.java
index 55fff0e96..75f3a3b06 100644
--- a/src/com/fsck/k9/provider/EmailProvider.java
+++ b/src/com/fsck/k9/provider/EmailProvider.java
@@ -219,12 +219,14 @@ public class EmailProvider extends ContentProvider {
String where;
if (StringUtils.isNullOrEmpty(selection)) {
- where = InternalMessageColumns.DELETED + "=0 AND " +
- InternalMessageColumns.EMPTY + "!=1";
+ where = InternalMessageColumns.DELETED + "=0 AND (" +
+ InternalMessageColumns.EMPTY + " IS NULL OR " +
+ InternalMessageColumns.EMPTY + "!=1)";
} else {
where = "(" + selection + ") AND " +
- InternalMessageColumns.DELETED + "=0 AND " +
- InternalMessageColumns.EMPTY + "!=1";
+ InternalMessageColumns.DELETED + "=0 AND (" +
+ InternalMessageColumns.EMPTY + " IS NULL OR " +
+ InternalMessageColumns.EMPTY + "!=1)";
}
return db.query(MESSAGES_TABLE, projection, where, selectionArgs, null, null,
@@ -288,7 +290,7 @@ public class EmailProvider extends ContentProvider {
if (!StringUtils.isNullOrEmpty(sortOrder)) {
query.append(" ORDER BY ");
- query.append(sortOrder);
+ query.append(addPrefixToSelection(MESSAGES_COLUMNS, "m.", sortOrder));
}
return db.rawQuery(query.toString(), selectionArgs);
From 08fba5468fca507e003e7698feab55cbb0bb5f1f Mon Sep 17 00:00:00 2001
From: cketti
Date: Thu, 25 Oct 2012 23:14:28 +0200
Subject: [PATCH 38/74] Switch MergeCursor from using a List to an array
---
.../fsck/k9/fragment/MessageListFragment.java | 17 +-
src/com/fsck/k9/helper/MergeCursor.java | 187 ++++++++++--------
.../k9/helper/MergeCursorWithUniqueId.java | 6 +-
3 files changed, 114 insertions(+), 96 deletions(-)
diff --git a/src/com/fsck/k9/fragment/MessageListFragment.java b/src/com/fsck/k9/fragment/MessageListFragment.java
index d61de81b0..c58fd1df7 100644
--- a/src/com/fsck/k9/fragment/MessageListFragment.java
+++ b/src/com/fsck/k9/fragment/MessageListFragment.java
@@ -338,7 +338,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
private String[] mAccountUuids;
private int mUnreadMessageCount = 0;
- private Map mCursors = new HashMap();
+ private Cursor[] mCursors;
/**
* Stores the name of the folder that we want to open as soon as possible
@@ -700,7 +700,9 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
initializeMessageList();
LoaderManager loaderManager = getLoaderManager();
- for (int i = 0, len = mAccountUuids.length; i < len; i++) {
+ int len = mAccountUuids.length;
+ mCursors = new Cursor[len];
+ for (int i = 0; i < len; i++) {
loaderManager.initLoader(i, null, this);
}
}
@@ -2757,16 +2759,9 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
@Override
public void onLoadFinished(Loader loader, Cursor data) {
- mCursors.put(loader.getId(), data);
+ mCursors[loader.getId()] = data;
- List list = new LinkedList(mCursors.keySet());
- Collections.sort(list);
- List cursors = new ArrayList(list.size());
- for (Integer id : list) {
- cursors.add(mCursors.get(id));
- }
-
- MergeCursorWithUniqueId cursor = new MergeCursorWithUniqueId(cursors);
+ MergeCursorWithUniqueId cursor = new MergeCursorWithUniqueId(mCursors);
mSelected = new SparseBooleanArray(cursor.getCount());
//TODO: use the (stable) IDs as index and reuse the old mSelected
diff --git a/src/com/fsck/k9/helper/MergeCursor.java b/src/com/fsck/k9/helper/MergeCursor.java
index efba70d23..1edc6360c 100644
--- a/src/com/fsck/k9/helper/MergeCursor.java
+++ b/src/com/fsck/k9/helper/MergeCursor.java
@@ -1,7 +1,23 @@
+/*
+ * Copyright (C) 2012 The K-9 Dog Walkers
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.fsck.k9.helper;
-import java.util.List;
-
+import android.annotation.TargetApi;
import android.content.ContentResolver;
import android.database.CharArrayBuffer;
import android.database.ContentObserver;
@@ -18,7 +34,7 @@ public class MergeCursor implements Cursor {
/**
* List of the cursors combined in this object.
*/
- protected final List mCursors;
+ protected final Cursor[] mCursors;
/**
* The currently active cursor.
@@ -32,6 +48,8 @@ public class MergeCursor implements Cursor {
*/
protected int mActiveCursorIndex;
+ protected int mPosition;
+
/**
* Used to cache the value of {@link #getCount()}
*/
@@ -44,16 +62,24 @@ public class MergeCursor implements Cursor {
* @param cursors
* The list of cursors this {@code MultiCursor} should combine.
*/
- public MergeCursor(List cursors) {
- mCursors = cursors;
- mActiveCursorIndex = 0;
- mActiveCursor = cursors.get(0);
+ public MergeCursor(Cursor[] cursors) {
+ mCursors = cursors.clone();
+
+ for (int i = 0, len = mCursors.length; i < len; i++) {
+ if (mCursors[i] != null) {
+ mActiveCursorIndex = i;
+ mActiveCursor = mCursors[mActiveCursorIndex];
+ }
+ }
+ mPosition = -1;
}
@Override
public void close() {
for (Cursor cursor : mCursors) {
- cursor.close();
+ if (cursor != null) {
+ cursor.close();
+ }
}
}
@@ -65,7 +91,9 @@ public class MergeCursor implements Cursor {
@Override
public void deactivate() {
for (Cursor cursor : mCursors) {
- cursor.deactivate();
+ if (cursor != null) {
+ cursor.deactivate();
+ }
}
}
@@ -105,7 +133,9 @@ public class MergeCursor implements Cursor {
if (mCount == -1) {
int count = 0;
for (Cursor cursor : mCursors) {
- count += cursor.getCount();
+ if (cursor != null) {
+ count += cursor.getCount();
+ }
}
mCount = count;
@@ -136,12 +166,7 @@ public class MergeCursor implements Cursor {
@Override
public int getPosition() {
- int pos = 0;
- for (int i = 0; i < mActiveCursorIndex; i++) {
- pos += mCursors.get(i).getCount();
- }
-
- return pos + mActiveCursor.getPosition();
+ return mPosition;
}
@Override
@@ -154,6 +179,7 @@ public class MergeCursor implements Cursor {
return mActiveCursor.getString(columnIndex);
}
+ @TargetApi(11)
@Override
public int getType(int columnIndex) {
return mActiveCursor.getType(columnIndex);
@@ -166,20 +192,21 @@ public class MergeCursor implements Cursor {
@Override
public boolean isAfterLast() {
- if (mActiveCursorIndex == mCursors.size() - 1) {
- return mActiveCursor.isAfterLast();
+ int count = getCount();
+ if (count == 0) {
+ return true;
}
- return false;
+ return (mPosition == count);
}
@Override
public boolean isBeforeFirst() {
- if (mActiveCursorIndex == 0) {
- return mActiveCursor.isBeforeFirst();
+ if (getCount() == 0) {
+ return true;
}
- return false;
+ return (mPosition == -1);
}
@Override
@@ -189,20 +216,21 @@ public class MergeCursor implements Cursor {
@Override
public boolean isFirst() {
- if (mActiveCursorIndex == 0) {
- return mActiveCursor.isFirst();
+ if (getCount() == 0) {
+ return false;
}
- return false;
+ return (mPosition == 0);
}
@Override
public boolean isLast() {
- if (mActiveCursorIndex == mCursors.size() - 1) {
- return mActiveCursor.isLast();
+ int count = getCount();
+ if (count == 0) {
+ return false;
}
- return false;
+ return (mPosition == (count - 1));
}
@Override
@@ -212,81 +240,78 @@ public class MergeCursor implements Cursor {
@Override
public boolean move(int offset) {
- int ofs = offset;
- int pos = mActiveCursor.getPosition();
- if (offset >= 0) {
- while (pos + ofs > mActiveCursor.getCount() &
- mActiveCursorIndex < mCursors.size() - 1) {
-
- // Adjust the "move offset"
- ofs -= mActiveCursor.getCount() - pos;
-
- // Move to the next cursor
- mActiveCursor = mCursors.get(++mActiveCursorIndex);
-
- // Move the new cursor to the first position
- mActiveCursor.moveToFirst();
- pos = 0;
- }
- } else {
- while (pos + ofs < 0 && mActiveCursorIndex > 0) {
- // Adjust the "move offset"
- ofs += pos;
-
- // Move to the next cursor
- mActiveCursor = mCursors.get(--mActiveCursorIndex);
-
- // Move the new cursor to the first position
- mActiveCursor.moveToLast();
- pos = mActiveCursor.getPosition();
- }
- }
-
- return mActiveCursor.move(ofs);
+ return moveToPosition(mPosition + offset);
}
@Override
public boolean moveToFirst() {
- mActiveCursorIndex = 0;
- mActiveCursor = mCursors.get(mActiveCursorIndex);
- return mActiveCursor.moveToFirst();
+ return moveToPosition(0);
}
@Override
public boolean moveToLast() {
- mActiveCursorIndex = mCursors.size() - 1;
- mActiveCursor = mCursors.get(mActiveCursorIndex);
- return mActiveCursor.moveToLast();
+ return moveToPosition(getCount() - 1);
}
@Override
public boolean moveToNext() {
- return move(1);
+ return moveToPosition(mPosition + 1);
}
@Override
public boolean moveToPosition(int position) {
- // Start at the beginning
- mActiveCursorIndex = 0;
- mActiveCursor = mCursors.get(mActiveCursorIndex);
-
- int pos = position;
- while (pos > mActiveCursor.getCount() - 1 &&
- mActiveCursorIndex < mCursors.size() - 1) {
-
- // Adjust the position
- pos -= mActiveCursor.getCount();
-
- // Move to the next cursor
- mActiveCursor = mCursors.get(++mActiveCursorIndex);
+ // Make sure position isn't past the end of the cursor
+ final int count = getCount();
+ if (position >= count) {
+ mPosition = count;
+ return false;
}
- return mActiveCursor.moveToPosition(pos);
+ // Make sure position isn't before the beginning of the cursor
+ if (position < 0) {
+ mPosition = -1;
+ return false;
+ }
+
+ // Check for no-op moves, and skip the rest of the work for them
+ if (position == mPosition) {
+ return true;
+ }
+
+ /* Find the right cursor */
+ mActiveCursor = null;
+ mActiveCursorIndex = -1;
+ mPosition = -1;
+ int cursorStartPos = 0;
+
+ for (int i = 0, len = mCursors.length; i < len; i++) {
+ if (mCursors[i] == null) {
+ continue;
+ }
+
+ if (position < (cursorStartPos + mCursors[i].getCount())) {
+ mActiveCursorIndex = i;
+ mActiveCursor = mCursors[mActiveCursorIndex];
+ break;
+ }
+
+ cursorStartPos += mCursors[i].getCount();
+ }
+
+ /* Move it to the right position */
+ if (mActiveCursor != null) {
+ boolean success = mActiveCursor.moveToPosition(position - cursorStartPos);
+ mPosition = (success) ? position : -1;
+
+ return success;
+ }
+
+ return false;
}
@Override
public boolean moveToPrevious() {
- return move(-1);
+ return moveToPosition(mPosition - 1);
}
@Override
diff --git a/src/com/fsck/k9/helper/MergeCursorWithUniqueId.java b/src/com/fsck/k9/helper/MergeCursorWithUniqueId.java
index 284fcf9f4..7cad2b084 100644
--- a/src/com/fsck/k9/helper/MergeCursorWithUniqueId.java
+++ b/src/com/fsck/k9/helper/MergeCursorWithUniqueId.java
@@ -1,7 +1,5 @@
package com.fsck.k9.helper;
-import java.util.List;
-
import android.database.Cursor;
@@ -14,10 +12,10 @@ public class MergeCursorWithUniqueId extends MergeCursor {
private int mIdColumnIndex = -1;
- public MergeCursorWithUniqueId(List cursors) {
+ public MergeCursorWithUniqueId(Cursor[] cursors) {
super(cursors);
- if (cursors.size() > MAX_CURSORS) {
+ if (cursors.length > MAX_CURSORS) {
throw new IllegalArgumentException("This class only supports up to " +
MAX_CURSORS + " cursors");
}
From d74ca8c8ce869ad63465551507e12668ef8faee8 Mon Sep 17 00:00:00 2001
From: cketti
Date: Fri, 26 Oct 2012 02:44:40 +0200
Subject: [PATCH 39/74] Move MessageInfoHolder comparator to MessageProvider
The MessageInfoHolder comparators aren't used in MessageListFragment
anymore.
---
src/com/fsck/k9/provider/MessageProvider.java | 18 ++++++++++++++----
1 file changed, 14 insertions(+), 4 deletions(-)
diff --git a/src/com/fsck/k9/provider/MessageProvider.java b/src/com/fsck/k9/provider/MessageProvider.java
index d40ed23e0..967fe79cf 100644
--- a/src/com/fsck/k9/provider/MessageProvider.java
+++ b/src/com/fsck/k9/provider/MessageProvider.java
@@ -23,10 +23,8 @@ import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
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.fragment.MessageListFragment;
import com.fsck.k9.helper.MessageHelper;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
@@ -38,6 +36,7 @@ import com.fsck.k9.search.SearchAccount;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.concurrent.BlockingQueue;
@@ -308,8 +307,7 @@ public class MessageProvider extends ContentProvider {
final List holders = queue.take();
// TODO add sort order parameter
- Collections.sort(holders, new MessageListFragment.ReverseComparator(
- new MessageListFragment.DateComparator()));
+ Collections.sort(holders, new ReverseDateComparator());
final String[] projectionToUse;
if (projection == null) {
@@ -1123,4 +1121,16 @@ public class MessageProvider extends ContentProvider {
mUriMatcher.addURI(AUTHORITY, handler.getPath(), code);
}
+ public static class ReverseDateComparator implements Comparator {
+ @Override
+ public int compare(MessageInfoHolder object2, MessageInfoHolder object1) {
+ if (object1.compareDate == null) {
+ return (object2.compareDate == null ? 0 : 1);
+ } else if (object2.compareDate == null) {
+ return -1;
+ } else {
+ return object1.compareDate.compareTo(object2.compareDate);
+ }
+ }
+ }
}
From faa666394ce0bf9e4239869ee194991d2f3308f1 Mon Sep 17 00:00:00 2001
From: cketti
Date: Sat, 27 Oct 2012 02:15:30 +0200
Subject: [PATCH 40/74] Do sorting in MergeCursor when merging the query
results
Disabled "sort by sender" for now because the database can't sort by
contact names from the contacts database. We probably have to
special-case that and do in-memory sorting.
---
res/menu/message_list_option.xml | 2 +
src/com/fsck/k9/Account.java | 2 +-
src/com/fsck/k9/activity/MessageList.java | 8 +-
.../fsck/k9/fragment/MessageListFragment.java | 250 ++++++++++--------
src/com/fsck/k9/helper/MergeCursor.java | 157 ++++++++---
.../k9/helper/MergeCursorWithUniqueId.java | 6 +-
6 files changed, 270 insertions(+), 155 deletions(-)
diff --git a/res/menu/message_list_option.xml b/res/menu/message_list_option.xml
index 106dfab5a..ed0a09078 100644
--- a/res/menu/message_list_option.xml
+++ b/res/menu/message_list_option.xml
@@ -29,9 +29,11 @@
+
diff --git a/src/com/fsck/k9/Account.java b/src/com/fsck/k9/Account.java
index 6fe63d3ba..5ce0d3f96 100644
--- a/src/com/fsck/k9/Account.java
+++ b/src/com/fsck/k9/Account.java
@@ -94,7 +94,7 @@ public class Account implements BaseAccount {
SORT_DATE(R.string.sort_earliest_first, R.string.sort_latest_first, false),
SORT_ARRIVAL(R.string.sort_earliest_first, R.string.sort_latest_first, false),
SORT_SUBJECT(R.string.sort_subject_alpha, R.string.sort_subject_re_alpha, true),
- SORT_SENDER(R.string.sort_sender_alpha, R.string.sort_sender_re_alpha, true),
+// SORT_SENDER(R.string.sort_sender_alpha, R.string.sort_sender_re_alpha, true),
SORT_UNREAD(R.string.sort_unread_first, R.string.sort_unread_last, true),
SORT_FLAGGED(R.string.sort_flagged_first, R.string.sort_flagged_last, true),
SORT_ATTACHMENT(R.string.sort_attach_first, R.string.sort_unattached_first, true);
diff --git a/src/com/fsck/k9/activity/MessageList.java b/src/com/fsck/k9/activity/MessageList.java
index 92ab7d2e4..bf8f296a9 100644
--- a/src/com/fsck/k9/activity/MessageList.java
+++ b/src/com/fsck/k9/activity/MessageList.java
@@ -374,10 +374,10 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
mMessageListFragment.changeSort(SortType.SORT_SUBJECT);
return true;
}
- case R.id.set_sort_sender: {
- mMessageListFragment.changeSort(SortType.SORT_SENDER);
- return true;
- }
+// case R.id.set_sort_sender: {
+// mMessageListFragment.changeSort(SortType.SORT_SENDER);
+// return true;
+// }
case R.id.set_sort_flag: {
mMessageListFragment.changeSort(SortType.SORT_FLAGGED);
return true;
diff --git a/src/com/fsck/k9/fragment/MessageListFragment.java b/src/com/fsck/k9/fragment/MessageListFragment.java
index c58fd1df7..9b0dd87e0 100644
--- a/src/com/fsck/k9/fragment/MessageListFragment.java
+++ b/src/com/fsck/k9/fragment/MessageListFragment.java
@@ -7,8 +7,6 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.EnumMap;
-import java.util.HashMap;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
@@ -69,7 +67,6 @@ import com.fsck.k9.R;
import com.fsck.k9.activity.ActivityListener;
import com.fsck.k9.activity.ChooseFolder;
import com.fsck.k9.activity.FolderInfoHolder;
-import com.fsck.k9.activity.MessageInfoHolder;
import com.fsck.k9.activity.MessageReference;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.fragment.ConfirmationDialogFragment;
@@ -77,7 +74,6 @@ import com.fsck.k9.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmen
import com.fsck.k9.helper.MessageHelper;
import com.fsck.k9.helper.MergeCursorWithUniqueId;
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;
@@ -173,7 +169,6 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
// arg1 & 2 are mixed up, this is done on purpose
return mDelegate.compare(object2, object1);
}
-
}
/**
@@ -182,7 +177,6 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
* @param
*/
public static class ComparatorChain implements Comparator {
-
private List> mChain;
/**
@@ -204,89 +198,93 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
}
return result;
}
-
}
- public static class AttachmentComparator implements Comparator {
+ public static class ReverseIdComparator implements Comparator {
+ private int mIdColumn = -1;
@Override
- public int compare(MessageInfoHolder object1, MessageInfoHolder object2) {
- return (object1.message.hasAttachments() ? 0 : 1) - (object2.message.hasAttachments() ? 0 : 1);
+ public int compare(Cursor cursor1, Cursor cursor2) {
+ if (mIdColumn == -1) {
+ mIdColumn = cursor1.getColumnIndex("_id");
+ }
+ long o1Id = cursor1.getLong(mIdColumn);
+ long o2Id = cursor2.getLong(mIdColumn);
+ return (o1Id > o2Id) ? -1 : 1;
}
-
}
- public static class FlaggedComparator implements Comparator {
+ public static class AttachmentComparator implements Comparator {
@Override
- public int compare(MessageInfoHolder object1, MessageInfoHolder object2) {
- return (object1.flagged ? 0 : 1) - (object2.flagged ? 0 : 1);
+ public int compare(Cursor cursor1, Cursor cursor2) {
+ int o1HasAttachment = (cursor1.getInt(ATTACHMENT_COUNT_COLUMN) > 0) ? 0 : 1;
+ int o2HasAttachment = (cursor2.getInt(ATTACHMENT_COUNT_COLUMN) > 0) ? 0 : 1;
+ return o1HasAttachment - o2HasAttachment;
}
-
}
- public static class UnreadComparator implements Comparator {
+ public static class FlaggedComparator implements Comparator {
@Override
- public int compare(MessageInfoHolder object1, MessageInfoHolder object2) {
- return (object1.read ? 1 : 0) - (object2.read ? 1 : 0);
+ public int compare(Cursor cursor1, Cursor cursor2) {
+ int o1IsFlagged = (cursor1.getString(FLAGS_COLUMN).contains("FLAGGED")) ? 0 : 1;
+ int o2IsFlagged = (cursor2.getString(FLAGS_COLUMN).contains("FLAGGED")) ? 0 : 1;
+ return o1IsFlagged - o2IsFlagged;
}
-
}
- public static class SenderComparator implements Comparator {
+ public static class UnreadComparator implements Comparator {
@Override
- public int compare(MessageInfoHolder object1, MessageInfoHolder object2) {
- if (object1.compareCounterparty == null) {
- return (object2.compareCounterparty == null ? 0 : 1);
- } else if (object2.compareCounterparty == null) {
+ public int compare(Cursor cursor1, Cursor cursor2) {
+ int o1IsUnread = (cursor1.getString(FLAGS_COLUMN).contains("SEEN")) ? 1 : 0;
+ int o2IsUnread = (cursor2.getString(FLAGS_COLUMN).contains("SEEN")) ? 1 : 0;
+ return o1IsUnread - o2IsUnread;
+ }
+ }
+
+ public static class DateComparator implements Comparator {
+
+ @Override
+ public int compare(Cursor cursor1, Cursor cursor2) {
+ long o1Date = cursor1.getLong(DATE_COLUMN);
+ long o2Date = cursor2.getLong(DATE_COLUMN);
+ if (o1Date < o2Date) {
+ return -1;
+ } else if (o1Date == o2Date) {
+ return 0;
+ } else {
+ return 1;
+ }
+ }
+ }
+
+ public static class ArrivalComparator implements Comparator {
+
+ @Override
+ public int compare(Cursor cursor1, Cursor cursor2) {
+ long o1Date = cursor1.getLong(INTERNAL_DATE_COLUMN);
+ long o2Date = cursor2.getLong(INTERNAL_DATE_COLUMN);
+ if (o1Date == o2Date) {
+ return 0;
+ } else if (o1Date < o2Date) {
return -1;
} else {
- return object1.compareCounterparty.toLowerCase().compareTo(object2.compareCounterparty.toLowerCase());
+ return 1;
}
}
-
}
- public static class DateComparator implements Comparator {
+ public static class SubjectComparator implements Comparator {
@Override
- public int compare(MessageInfoHolder object1, MessageInfoHolder object2) {
- if (object1.compareDate == null) {
- return (object2.compareDate == null ? 0 : 1);
- } else if (object2.compareDate == null) {
- return -1;
- } else {
- return object1.compareDate.compareTo(object2.compareDate);
- }
+ public int compare(Cursor cursor1, Cursor cursor2) {
+ String subject1 = cursor1.getString(SUBJECT_COLUMN);
+ String subject2 = cursor2.getString(SUBJECT_COLUMN);
+
+ return subject1.compareToIgnoreCase(subject2);
}
-
- }
-
- public static class ArrivalComparator implements Comparator {
-
- @Override
- public int compare(MessageInfoHolder object1, MessageInfoHolder object2) {
- return object1.compareArrival.compareTo(object2.compareArrival);
- }
-
- }
-
- public static class SubjectComparator implements Comparator {
-
- @Override
- public int compare(MessageInfoHolder arg0, MessageInfoHolder arg1) {
- // XXX doesn't respect the Comparator contract since it alters the compared object
- if (arg0.compareSubject == null) {
- arg0.compareSubject = Utility.stripSubject(arg0.message.getSubject());
- }
- if (arg1.compareSubject == null) {
- arg1.compareSubject = Utility.stripSubject(arg1.message.getSubject());
- }
- return arg0.compareSubject.compareToIgnoreCase(arg1.compareSubject);
- }
-
}
@@ -301,17 +299,17 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
/**
* Maps a {@link SortType} to a {@link Comparator} implementation.
*/
- private static final Map> SORT_COMPARATORS;
+ private static final Map> SORT_COMPARATORS;
static {
// fill the mapping at class time loading
- final Map> map = new EnumMap>(SortType.class);
+ final Map> map =
+ new EnumMap>(SortType.class);
map.put(SortType.SORT_ATTACHMENT, new AttachmentComparator());
map.put(SortType.SORT_DATE, new DateComparator());
map.put(SortType.SORT_ARRIVAL, new ArrivalComparator());
map.put(SortType.SORT_FLAGGED, new FlaggedComparator());
- map.put(SortType.SORT_SENDER, new SenderComparator());
map.put(SortType.SORT_SUBJECT, new SubjectComparator());
map.put(SortType.SORT_UNREAD, new UnreadComparator());
@@ -471,35 +469,33 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
* @return The comparator to use to display messages in an ordered
* fashion. Never null
.
*/
- protected Comparator getComparator() {
- final List> chain = new ArrayList>(2 /* we add 2 comparators at most */);
+ protected Comparator getComparator() {
+ final List> chain =
+ new ArrayList>(3 /* we add 3 comparators at most */);
- {
- // add the specified comparator
- final Comparator comparator = SORT_COMPARATORS.get(mSortType);
- if (mSortAscending) {
- chain.add(comparator);
+ // Add the specified comparator
+ final Comparator comparator = SORT_COMPARATORS.get(mSortType);
+ if (mSortAscending) {
+ chain.add(comparator);
+ } else {
+ chain.add(new ReverseComparator(comparator));
+ }
+
+ // Add the date comparator if not already specified
+ if (mSortType != SortType.SORT_DATE && mSortType != SortType.SORT_ARRIVAL) {
+ final Comparator dateComparator = SORT_COMPARATORS.get(SortType.SORT_DATE);
+ if (mSortDateAscending) {
+ chain.add(dateComparator);
} else {
- chain.add(new ReverseComparator(comparator));
+ chain.add(new ReverseComparator(dateComparator));
}
}
- {
- // add the date comparator if not already specified
- if (mSortType != SortType.SORT_DATE && mSortType != SortType.SORT_ARRIVAL) {
- final Comparator comparator = SORT_COMPARATORS.get(SortType.SORT_DATE);
- if (mSortDateAscending) {
- chain.add(comparator);
- } else {
- chain.add(new ReverseComparator(comparator));
- }
- }
- }
+ // Add the id comparator
+ chain.add(new ReverseIdComparator());
- // build the comparator chain
- final Comparator chainComparator = new ComparatorChain(chain);
-
- return chainComparator;
+ // Build the comparator chain
+ return new ComparatorChain(chain);
}
private void folderLoading(String folder, boolean loading) {
@@ -699,6 +695,9 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
initializeMessageList();
+ // This needs to be done before initializing the cursor loader below
+ initializeSortSettings();
+
LoaderManager loaderManager = getLoaderManager();
int len = mAccountUuids.length;
mCursors = new Cursor[len];
@@ -707,6 +706,18 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
}
}
+ private void initializeSortSettings() {
+ if (mSingleAccountMode) {
+ mSortType = mAccount.getSortType();
+ mSortAscending = mAccount.isSortAscending(mSortType);
+ mSortDateAscending = mAccount.isSortAscending(SortType.SORT_DATE);
+ } else {
+ mSortType = K9.getSortType();
+ mSortAscending = K9.isSortAscending(mSortType);
+ mSortDateAscending = K9.isSortAscending(SortType.SORT_DATE);
+ }
+ }
+
private void decodeArguments() {
Bundle args = getArguments();
@@ -781,11 +792,24 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
}
private String getFolderNameById(Account account, long folderId) {
+ try {
+ Folder folder = getFolderById(account, folderId);
+ if (folder != null) {
+ return folder.getName();
+ }
+ } catch (Exception e) {
+ Log.e(K9.LOG_TAG, "getFolderNameById() failed.", e);
+ }
+
+ return null;
+ }
+
+ private Folder getFolderById(Account account, long folderId) {
try {
LocalStore localStore = account.getLocalStore();
LocalFolder localFolder = localStore.getFolderById(folderId);
localFolder.open(OpenMode.READ_ONLY);
- return localFolder.getName();
+ return localFolder;
} catch (Exception e) {
Log.e(K9.LOG_TAG, "getFolderNameById() failed.", e);
return null;
@@ -879,17 +903,10 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
Account[] accountsWithNotification;
Account account = mAccount;
-
if (account != null) {
accountsWithNotification = new Account[] { account };
- mSortType = account.getSortType();
- mSortAscending = account.isSortAscending(mSortType);
- mSortDateAscending = account.isSortAscending(SortType.SORT_DATE);
} else {
accountsWithNotification = mPreferences.getAccounts();
- mSortType = K9.getSortType();
- mSortAscending = K9.isSortAscending(mSortType);
- mSortDateAscending = K9.isSortAscending(SortType.SORT_DATE);
}
for (Account accountWithNotification : accountsWithNotification) {
@@ -1145,10 +1162,10 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
changeSort(SortType.SORT_SUBJECT);
return true;
}
- case R.id.set_sort_sender: {
- changeSort(SortType.SORT_SENDER);
- return true;
- }
+// case R.id.set_sort_sender: {
+// changeSort(SortType.SORT_SENDER);
+// return true;
+// }
case R.id.set_sort_flag: {
changeSort(SortType.SORT_FLAGGED);
return true;
@@ -2496,8 +2513,9 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
Cursor cursor = (Cursor) mAdapter.getItem(adapterPosition);
String uid = cursor.getString(UID_COLUMN);
- //TODO: get account and folder from cursor
- Folder folder = mCurrentFolder.folder;
+ Account account = getAccountFromCursor(cursor);
+ long folderId = cursor.getLong(FOLDER_ID_COLUMN);
+ Folder folder = getFolderById(account, folderId);
try {
return folder.getMessage(uid);
@@ -2668,13 +2686,13 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
sortColumn = "(" + MessageColumns.FLAGS + " NOT LIKE '%FLAGGED%')";
break;
}
- case SORT_SENDER: {
- //FIXME
- sortColumn = MessageColumns.SENDER_LIST;
- break;
- }
+// case SORT_SENDER: {
+// //FIXME
+// sortColumn = MessageColumns.SENDER_LIST;
+// break;
+// }
case SORT_SUBJECT: {
- sortColumn = MessageColumns.SUBJECT;
+ sortColumn = MessageColumns.SUBJECT + " COLLATE NOCASE";
break;
}
case SORT_UNREAD: {
@@ -2687,14 +2705,12 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
}
}
- String sortDirection;
+ String sortDirection = (mSortAscending) ? " ASC" : " DESC";
String secondarySort;
- if (mSortType == SortType.SORT_DATE) {
- sortDirection = (mSortDateAscending) ? " ASC" : " DESC";
+ if (mSortType == SortType.SORT_DATE || mSortType == SortType.SORT_ARRIVAL) {
secondarySort = "";
} else {
- sortDirection = (mSortAscending) ? " ASC" : " DESC";
- secondarySort = MessageColumns.DATE + " DESC, ";
+ secondarySort = MessageColumns.DATE + ((mSortDateAscending) ? " ASC, " : " DESC, ");
}
String sortOrder = sortColumn + sortDirection + ", " + secondarySort +
@@ -2759,9 +2775,13 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
@Override
public void onLoadFinished(Loader loader, Cursor data) {
- mCursors[loader.getId()] = data;
-
- MergeCursorWithUniqueId cursor = new MergeCursorWithUniqueId(mCursors);
+ Cursor cursor;
+ if (mCursors.length > 1) {
+ mCursors[loader.getId()] = data;
+ cursor = new MergeCursorWithUniqueId(mCursors, getComparator());
+ } else {
+ cursor = data;
+ }
mSelected = new SparseBooleanArray(cursor.getCount());
//TODO: use the (stable) IDs as index and reuse the old mSelected
diff --git a/src/com/fsck/k9/helper/MergeCursor.java b/src/com/fsck/k9/helper/MergeCursor.java
index 1edc6360c..39776e646 100644
--- a/src/com/fsck/k9/helper/MergeCursor.java
+++ b/src/com/fsck/k9/helper/MergeCursor.java
@@ -17,6 +17,8 @@
package com.fsck.k9.helper;
+import java.util.Comparator;
+
import android.annotation.TargetApi;
import android.content.ContentResolver;
import android.database.CharArrayBuffer;
@@ -48,30 +50,53 @@ public class MergeCursor implements Cursor {
*/
protected int mActiveCursorIndex;
+ /**
+ * The cursor's current position.
+ */
protected int mPosition;
/**
- * Used to cache the value of {@link #getCount()}
+ * Used to cache the value of {@link #getCount()}.
*/
private int mCount = -1;
+ /**
+ * The comparator that is used to decide how the individual cursors are merged.
+ */
+ private final Comparator mComparator;
+
/**
* Constructor
*
* @param cursors
* The list of cursors this {@code MultiCursor} should combine.
+ * @param comparator
+ * A comparator that is used to decide in what order the individual cursors are merged.
*/
- public MergeCursor(Cursor[] cursors) {
+ public MergeCursor(Cursor[] cursors, Comparator