Loading src/com/fsck/k9/activity/MessageList.java +8 −7 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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) { Loading @@ -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(); } Loading Loading @@ -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); } Loading Loading @@ -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); } Loading Loading @@ -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); } } src/com/fsck/k9/fragment/MessageListFragment.java +37 −20 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -99,7 +100,7 @@ import com.handmark.pulltorefresh.library.PullToRefreshListView; public class MessageListFragment extends SherlockFragment implements OnItemClickListener, ConfirmationDialogFragmentListener, LoaderCallbacks<Cursor> { private static final String[] PROJECTION = { private static final String[] THREADED_PROJECTION = { MessageColumns.ID, MessageColumns.UID, MessageColumns.INTERNAL_DATE, Loading @@ -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; Loading @@ -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; Loading Loading @@ -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"; Loading Loading @@ -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; Loading Loading @@ -631,16 +638,16 @@ 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); 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; Loading @@ -648,6 +655,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick onOpenMessage(ref); } } } @Override public void onAttach(Activity activity) { Loading Loading @@ -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(); Loading Loading @@ -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(","); Loading Loading @@ -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 { Loading Loading @@ -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<String> queryArgs = new ArrayList<String>(); Loading @@ -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"); } Loading src/com/fsck/k9/provider/EmailProvider.java +110 −8 Original line number Diff line number Diff line Loading @@ -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 { Loading @@ -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); } Loading @@ -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 { Loading Loading @@ -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<String> segments = uri.getPathSegments(); String accountUuid = segments.get(1); Loading @@ -145,8 +168,15 @@ public class EmailProvider extends ContentProvider { String[] dbProjection = dbColumnNames.toArray(new String[0]); 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); Loading Loading @@ -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<Cursor>() { @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(); Loading src/com/fsck/k9/search/LocalSearch.java +4 −2 Original line number Diff line number Diff line Loading @@ -176,7 +176,8 @@ public class LocalSearch implements SearchSpecification { return node; } return mConditions.and(node); mConditions = mConditions.and(node); return mConditions; } /** Loading Loading @@ -212,7 +213,8 @@ public class LocalSearch implements SearchSpecification { return node; } return mConditions.or(node); mConditions = mConditions.or(node); return mConditions; } /** Loading src/com/fsck/k9/search/SearchSpecification.java +2 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading
src/com/fsck/k9/activity/MessageList.java +8 −7 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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) { Loading @@ -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(); } Loading Loading @@ -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); } Loading Loading @@ -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); } Loading Loading @@ -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); } }
src/com/fsck/k9/fragment/MessageListFragment.java +37 −20 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -99,7 +100,7 @@ import com.handmark.pulltorefresh.library.PullToRefreshListView; public class MessageListFragment extends SherlockFragment implements OnItemClickListener, ConfirmationDialogFragmentListener, LoaderCallbacks<Cursor> { private static final String[] PROJECTION = { private static final String[] THREADED_PROJECTION = { MessageColumns.ID, MessageColumns.UID, MessageColumns.INTERNAL_DATE, Loading @@ -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; Loading @@ -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; Loading Loading @@ -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"; Loading Loading @@ -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; Loading Loading @@ -631,16 +638,16 @@ 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); 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; Loading @@ -648,6 +655,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick onOpenMessage(ref); } } } @Override public void onAttach(Activity activity) { Loading Loading @@ -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(); Loading Loading @@ -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(","); Loading Loading @@ -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 { Loading Loading @@ -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<String> queryArgs = new ArrayList<String>(); Loading @@ -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"); } Loading
src/com/fsck/k9/provider/EmailProvider.java +110 −8 Original line number Diff line number Diff line Loading @@ -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 { Loading @@ -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); } Loading @@ -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 { Loading Loading @@ -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<String> segments = uri.getPathSegments(); String accountUuid = segments.get(1); Loading @@ -145,8 +168,15 @@ public class EmailProvider extends ContentProvider { String[] dbProjection = dbColumnNames.toArray(new String[0]); 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); Loading Loading @@ -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<Cursor>() { @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(); Loading
src/com/fsck/k9/search/LocalSearch.java +4 −2 Original line number Diff line number Diff line Loading @@ -176,7 +176,8 @@ public class LocalSearch implements SearchSpecification { return node; } return mConditions.and(node); mConditions = mConditions.and(node); return mConditions; } /** Loading Loading @@ -212,7 +213,8 @@ public class LocalSearch implements SearchSpecification { return node; } return mConditions.or(node); mConditions = mConditions.or(node); return mConditions; } /** Loading
src/com/fsck/k9/search/SearchSpecification.java +2 −1 Original line number Diff line number Diff line Loading @@ -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; Loading