From 1d655f5bc2ad129ec2ec36459da941969ff77acb Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 18 Oct 2012 05:15:40 +0200 Subject: [PATCH] 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); + } + } }