mirror of
https://github.com/moparisthebest/k-9
synced 2024-11-27 11:42:16 -05:00
Added ability to display special folders combining multiple accounts
This commit is contained in:
parent
20ed1ebe61
commit
1d655f5bc2
@ -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<Integer, Cursor> mCursors = new HashMap<Integer, Cursor>();
|
||||
|
||||
/**
|
||||
* 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<Cursor> 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<String> queryArgs = new ArrayList<String>();
|
||||
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<String> 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<Cursor> loader, Cursor data) {
|
||||
mSelected = new SparseBooleanArray(data.getCount());
|
||||
mAdapter.swapCursor(data);
|
||||
mCursors.put(loader.getId(), data);
|
||||
|
||||
List<Integer> list = new LinkedList<Integer>(mCursors.keySet());
|
||||
Collections.sort(list);
|
||||
List<Cursor> cursors = new ArrayList<Cursor>(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);
|
||||
}
|
||||
}
|
||||
|
347
src/com/fsck/k9/helper/MergeCursor.java
Normal file
347
src/com/fsck/k9/helper/MergeCursor.java
Normal file
@ -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<Cursor> 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<Cursor> 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");
|
||||
}
|
||||
}
|
83
src/com/fsck/k9/helper/MergeCursorWithUniqueId.java
Normal file
83
src/com/fsck/k9/helper/MergeCursorWithUniqueId.java
Normal file
@ -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<Cursor> 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);
|
||||
}
|
||||
}
|
@ -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<Long>() {
|
||||
@Override
|
||||
|
@ -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<String> segments = uri.getPathSegments();
|
||||
String accountUuid = segments.get(1);
|
||||
|
||||
cursor = getMessages(accountUuid, projection, selection, selectionArgs, sortOrder);
|
||||
List<String> dbColumnNames = new ArrayList<String>(projection.length);
|
||||
Map<String, String> specialColumns = new HashMap<String, String>();
|
||||
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<String, String> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user