Add threading support to content provider

This commit is contained in:
cketti 2012-10-23 03:01:50 +02:00
parent 05a2571570
commit 95b39c71d2
5 changed files with 161 additions and 38 deletions

View file

@ -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);
}
}

View file

@ -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<Cursor> {
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<String> queryArgs = new ArrayList<String>();
@ -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");
}

View file

@ -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<String> 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<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();

View file

@ -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;
}
/**

View file

@ -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;