Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 95b39c71 authored by cketti's avatar cketti
Browse files

Add threading support to content provider

parent 05a25715
Loading
Loading
Loading
Loading
+8 −7
Original line number Diff line number Diff line
@@ -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);
    }
}
+37 −20
Original line number Diff line number Diff line
@@ -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,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;
@@ -648,6 +655,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick
                onOpenMessage(ref);
            }
        }
    }

    @Override
    public void onAttach(Activity activity) {
@@ -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");
    }

+110 −8
Original line number Diff line number Diff line
@@ -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]);

                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();
+4 −2
Original line number Diff line number Diff line
@@ -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;
    }

    /**
+2 −1
Original line number Diff line number Diff line
@@ -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;