1
0
mirror of https://github.com/moparisthebest/k-9 synced 2024-08-13 17:03:48 -04:00
k-9/src/com/fsck/k9/fragment/MessageListFragment.java

3045 lines
109 KiB
Java
Raw Normal View History

package com.fsck.k9.fragment;
2012-10-05 21:41:32 -04:00
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
2012-10-05 21:41:32 -04:00
import android.app.Activity;
import android.app.SearchManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences.Editor;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
2012-10-05 21:41:32 -04:00
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.os.Handler;
2012-10-05 21:41:32 -04:00
import android.support.v4.app.DialogFragment;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
import android.util.Log;
import android.util.TypedValue;
2012-10-05 21:41:32 -04:00
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
2012-10-05 21:41:32 -04:00
import com.actionbarsherlock.app.SherlockFragment;
import com.actionbarsherlock.view.ActionMode;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;
import com.actionbarsherlock.view.Window;
import com.fsck.k9.Account;
2012-04-08 22:29:08 -04:00
import com.fsck.k9.Account.SortType;
import com.fsck.k9.AccountStats;
import com.fsck.k9.FontSizes;
import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
2012-10-05 21:41:32 -04:00
import com.fsck.k9.activity.ActivityListener;
import com.fsck.k9.activity.ChooseFolder;
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.controller.MessagingListener;
2012-10-05 21:41:32 -04:00
import com.fsck.k9.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener;
import com.fsck.k9.helper.MessageHelper;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
import com.handmark.pulltorefresh.library.PullToRefreshBase;
import com.handmark.pulltorefresh.library.PullToRefreshListView;
2012-10-05 21:41:32 -04:00
public class MessageListFragment extends SherlockFragment implements OnItemClickListener,
ConfirmationDialogFragmentListener {
public static MessageListFragment newInstance(Account account, String folderName) {
MessageListFragment fragment = new MessageListFragment();
Bundle args = new Bundle();
args.putString(ARG_ACCOUNT, account.getUuid());
args.putString(ARG_FOLDER, folderName);
fragment.setArguments(args);
return fragment;
}
public static MessageListFragment newInstance(String title, String[] accountUuids,
String[] folderNames, String queryString, Flag[] flags,
Flag[] forbiddenFlags, boolean integrate) {
MessageListFragment fragment = new MessageListFragment();
Bundle args = new Bundle();
args.putStringArray(ARG_ACCOUNT_UUIDS, accountUuids);
args.putStringArray(ARG_FOLDER_NAMES, folderNames);
args.putString(ARG_QUERY, queryString);
if (flags != null) {
args.putString(ARG_QUERY_FLAGS, Utility.combine(flags, ','));
}
if (forbiddenFlags != null) {
args.putString(ARG_FORBIDDEN_FLAGS, Utility.combine(forbiddenFlags, ','));
}
args.putBoolean(ARG_INTEGRATE, integrate);
args.putString(ARG_TITLE, title);
fragment.setArguments(args);
return fragment;
}
public static MessageListFragment newInstance(String searchAccount, String searchFolder,
String queryString, boolean remoteSearch) {
MessageListFragment fragment = new MessageListFragment();
Bundle args = new Bundle();
args.putString(ARG_SEARCH_ACCOUNT, searchAccount);
args.putString(ARG_SEARCH_FOLDER, searchFolder);
args.putString(ARG_QUERY, queryString);
args.putBoolean(ARG_REMOTE_SEARCH, remoteSearch);
fragment.setArguments(args);
return fragment;
}
/**
* Reverses the result of a {@link Comparator}.
*
* @param <T>
*/
public static class ReverseComparator<T> implements Comparator<T> {
private Comparator<T> mDelegate;
/**
* @param delegate
* Never <code>null</code>.
*/
public ReverseComparator(final Comparator<T> delegate) {
mDelegate = delegate;
}
@Override
public int compare(final T object1, final T object2) {
// arg1 & 2 are mixed up, this is done on purpose
return mDelegate.compare(object2, object1);
}
}
/**
* Chains comparator to find a non-0 result.
*
* @param <T>
*/
public static class ComparatorChain<T> implements Comparator<T> {
private List<Comparator<T>> mChain;
/**
* @param chain
* Comparator chain. Never <code>null</code>.
*/
public ComparatorChain(final List<Comparator<T>> chain) {
mChain = chain;
}
@Override
public int compare(T object1, T object2) {
int result = 0;
for (final Comparator<T> comparator : mChain) {
result = comparator.compare(object1, object2);
if (result != 0) {
break;
}
}
return result;
}
}
public static class AttachmentComparator implements Comparator<MessageInfoHolder> {
@Override
public int compare(MessageInfoHolder object1, MessageInfoHolder object2) {
2010-11-26 23:02:56 -05:00
return (object1.message.hasAttachments() ? 0 : 1) - (object2.message.hasAttachments() ? 0 : 1);
}
}
public static class FlaggedComparator implements Comparator<MessageInfoHolder> {
@Override
public int compare(MessageInfoHolder object1, MessageInfoHolder object2) {
return (object1.flagged ? 0 : 1) - (object2.flagged ? 0 : 1);
}
}
public static class UnreadComparator implements Comparator<MessageInfoHolder> {
@Override
public int compare(MessageInfoHolder object1, MessageInfoHolder object2) {
return (object1.read ? 1 : 0) - (object2.read ? 1 : 0);
}
}
public static class SenderComparator implements Comparator<MessageInfoHolder> {
@Override
public int compare(MessageInfoHolder object1, MessageInfoHolder object2) {
if (object1.compareCounterparty == null) {
return (object2.compareCounterparty == null ? 0 : 1);
} else if (object2.compareCounterparty == null) {
return -1;
} else {
return object1.compareCounterparty.toLowerCase().compareTo(object2.compareCounterparty.toLowerCase());
}
}
}
public static class DateComparator implements Comparator<MessageInfoHolder> {
@Override
public int compare(MessageInfoHolder object1, MessageInfoHolder object2) {
if (object1.compareDate == null) {
return (object2.compareDate == null ? 0 : 1);
} else if (object2.compareDate == null) {
return -1;
} else {
return object1.compareDate.compareTo(object2.compareDate);
}
}
}
2012-03-11 18:48:56 -04:00
public static class ArrivalComparator implements Comparator<MessageInfoHolder> {
@Override
public int compare(MessageInfoHolder object1, MessageInfoHolder object2) {
return object1.compareArrival.compareTo(object2.compareArrival);
}
}
public static class SubjectComparator implements Comparator<MessageInfoHolder> {
@Override
public int compare(MessageInfoHolder arg0, MessageInfoHolder arg1) {
// XXX doesn't respect the Comparator contract since it alters the compared object
if (arg0.compareSubject == null) {
2010-11-26 23:03:06 -05:00
arg0.compareSubject = Utility.stripSubject(arg0.message.getSubject());
}
if (arg1.compareSubject == null) {
2010-11-26 23:03:06 -05:00
arg1.compareSubject = Utility.stripSubject(arg1.message.getSubject());
}
return arg0.compareSubject.compareToIgnoreCase(arg1.compareSubject);
}
}
/**
* Immutable empty {@link Message} array
*/
private static final Message[] EMPTY_MESSAGE_ARRAY = new Message[0];
private static final int ACTIVITY_CHOOSE_FOLDER_MOVE = 1;
private static final int ACTIVITY_CHOOSE_FOLDER_COPY = 2;
2012-10-05 21:41:32 -04:00
private static final String ARG_ACCOUNT = "account";
private static final String ARG_FOLDER = "folder";
private static final String ARG_REMOTE_SEARCH = "remote_search";
private static final String ARG_QUERY = "query";
private static final String ARG_SEARCH_ACCOUNT = "search_account";
private static final String ARG_SEARCH_FOLDER = "search_folder";
private static final String ARG_QUERY_FLAGS = "queryFlags";
private static final String ARG_FORBIDDEN_FLAGS = "forbiddenFlags";
private static final String ARG_INTEGRATE = "integrate";
private static final String ARG_ACCOUNT_UUIDS = "accountUuids";
private static final String ARG_FOLDER_NAMES = "folderNames";
private static final String ARG_TITLE = "title";
private static final String STATE_LIST_POSITION = "listPosition";
/**
2012-04-08 17:17:06 -04:00
* Maps a {@link SortType} to a {@link Comparator} implementation.
*/
2012-04-08 17:17:06 -04:00
private static final Map<SortType, Comparator<MessageInfoHolder>> SORT_COMPARATORS;
static {
// fill the mapping at class time loading
2012-04-08 17:17:06 -04:00
final Map<SortType, Comparator<MessageInfoHolder>> map = new EnumMap<SortType, Comparator<MessageInfoHolder>>(SortType.class);
map.put(SortType.SORT_ATTACHMENT, new AttachmentComparator());
map.put(SortType.SORT_DATE, new DateComparator());
map.put(SortType.SORT_ARRIVAL, new ArrivalComparator());
map.put(SortType.SORT_FLAGGED, new FlaggedComparator());
map.put(SortType.SORT_SENDER, new SenderComparator());
map.put(SortType.SORT_SUBJECT, new SubjectComparator());
map.put(SortType.SORT_UNREAD, new UnreadComparator());
// make it immutable to prevent accidental alteration (content is immutable already)
SORT_COMPARATORS = Collections.unmodifiableMap(map);
}
private ListView mListView;
private PullToRefreshListView mPullToRefreshView;
private int mPreviewLines = 0;
private MessageListAdapter mAdapter;
private View mFooterView;
private FolderInfoHolder mCurrentFolder;
private LayoutInflater mInflater;
private MessagingController mController;
private Account mAccount;
private int mUnreadMessageCount = 0;
/**
* Stores the name of the folder that we want to open as soon as possible
* after load.
*/
private String mFolderName;
/**
* If we're doing a search, this contains the query string.
*/
private String mQueryString;
private Flag[] mQueryFlags = null;
private Flag[] mForbiddenFlags = null;
private boolean mRemoteSearch = false;
private String mSearchAccount = null;
private String mSearchFolder = null;
private Future mRemoteSearchFuture = null;
private boolean mIntegrate = false;
private String[] mAccountUuids = null;
private String[] mFolderNames = null;
private String mTitle;
private MessageListHandler mHandler = new MessageListHandler();
2012-04-08 22:29:08 -04:00
private SortType mSortType = SortType.SORT_DATE;
private boolean mSortAscending = true;
private boolean mSortDateAscending = false;
private boolean mSenderAboveSubject = false;
private int mSelectedCount = 0;
private FontSizes mFontSizes = K9.getFontSizes();
private MenuItem mRefreshMenuItem;
private ActionMode mActionMode;
private View mActionBarProgressView;
private Bundle mState = null;
private Boolean mHasConnectivity;
/**
* Relevant messages for the current context when we have to remember the
* chosen messages between user interactions (eg. Selecting a folder for
* move operation)
*/
private List<MessageInfoHolder> mActiveMessages;
/* package visibility for faster inner class access */
2012-10-05 21:41:32 -04:00
MessageHelper mMessageHelper;
2012-10-05 21:41:32 -04:00
private ActionModeCallback mActionModeCallback = new ActionModeCallback();
Merge branch 'mail-on-sd' * mail-on-sd: (40 commits) Added more comments to explain how the locking mecanism works for LocalStore Fixed wrong method being called during experimental provider initialization (since provider isn't enabled, that didn't harm) Add more comments about how the various StorageProviders work and how they're enabled find src/com/fsck/ -name \*.java|xargs astyle --style=ansi --mode=java --indent-switches --indent=spaces=4 --convert-tabs French localization for storage related settings Remove unused SD card strings (replaced with storage indirection) Merge mail-on-sd branch from trunk Reset mail service on storage mount (even if no account uses the storage, to be improved) find src/com/fsck/ -name \*.java|xargs astyle --style=ansi --mode=java --indent-switches --indent=spaces=4 --convert-tabs Migraion -> Migration move the Storage location preference into preferences rather than the wizard. Made LocalStore log less verbose Added @Override compile checks Added ACTION_SHUTDOWN broadcast receiver to properly initiate shutdown sequence (not yet implemented) and cancel any scheduled Intent Be more consistent about which SQLiteDatabase variable is used (from instance variable to argument variable) to make code more refactoring-friendly (class is already big, code extraction should be easier if not referencing the instance variable). Added transaction timing logging Factorised storage lock/transaction handling code for regular operations. Use DB transactions to batch modifications (makes code more robust / could improve performances) Merge mail-on-sd branch from trunk Update issue 888 Added DB close on unmount / DB open on mount Update issue 888 Back to account list when underlying storage not available/unmounting in MessageView / MessageList ...
2010-11-13 16:40:56 -05:00
2012-10-05 21:41:32 -04:00
private MessageListFragmentListener mFragmentListener;
2012-09-13 00:27:58 -04:00
Merge branch 'mail-on-sd' * mail-on-sd: (40 commits) Added more comments to explain how the locking mecanism works for LocalStore Fixed wrong method being called during experimental provider initialization (since provider isn't enabled, that didn't harm) Add more comments about how the various StorageProviders work and how they're enabled find src/com/fsck/ -name \*.java|xargs astyle --style=ansi --mode=java --indent-switches --indent=spaces=4 --convert-tabs French localization for storage related settings Remove unused SD card strings (replaced with storage indirection) Merge mail-on-sd branch from trunk Reset mail service on storage mount (even if no account uses the storage, to be improved) find src/com/fsck/ -name \*.java|xargs astyle --style=ansi --mode=java --indent-switches --indent=spaces=4 --convert-tabs Migraion -> Migration move the Storage location preference into preferences rather than the wizard. Made LocalStore log less verbose Added @Override compile checks Added ACTION_SHUTDOWN broadcast receiver to properly initiate shutdown sequence (not yet implemented) and cancel any scheduled Intent Be more consistent about which SQLiteDatabase variable is used (from instance variable to argument variable) to make code more refactoring-friendly (class is already big, code extraction should be easier if not referencing the instance variable). Added transaction timing logging Factorised storage lock/transaction handling code for regular operations. Use DB transactions to batch modifications (makes code more robust / could improve performances) Merge mail-on-sd branch from trunk Update issue 888 Added DB close on unmount / DB open on mount Update issue 888 Back to account list when underlying storage not available/unmounting in MessageView / MessageList ...
2010-11-13 16:40:56 -05:00
2012-10-05 21:41:32 -04:00
private DateFormat mTimeFormat;
Merge branch 'mail-on-sd' * mail-on-sd: (40 commits) Added more comments to explain how the locking mecanism works for LocalStore Fixed wrong method being called during experimental provider initialization (since provider isn't enabled, that didn't harm) Add more comments about how the various StorageProviders work and how they're enabled find src/com/fsck/ -name \*.java|xargs astyle --style=ansi --mode=java --indent-switches --indent=spaces=4 --convert-tabs French localization for storage related settings Remove unused SD card strings (replaced with storage indirection) Merge mail-on-sd branch from trunk Reset mail service on storage mount (even if no account uses the storage, to be improved) find src/com/fsck/ -name \*.java|xargs astyle --style=ansi --mode=java --indent-switches --indent=spaces=4 --convert-tabs Migraion -> Migration move the Storage location preference into preferences rather than the wizard. Made LocalStore log less verbose Added @Override compile checks Added ACTION_SHUTDOWN broadcast receiver to properly initiate shutdown sequence (not yet implemented) and cancel any scheduled Intent Be more consistent about which SQLiteDatabase variable is used (from instance variable to argument variable) to make code more refactoring-friendly (class is already big, code extraction should be easier if not referencing the instance variable). Added transaction timing logging Factorised storage lock/transaction handling code for regular operations. Use DB transactions to batch modifications (makes code more robust / could improve performances) Merge mail-on-sd branch from trunk Update issue 888 Added DB close on unmount / DB open on mount Update issue 888 Back to account list when underlying storage not available/unmounting in MessageView / MessageList ...
2010-11-13 16:40:56 -05:00
/**
2012-09-04 21:50:02 -04:00
* This class is used to run operations that modify UI elements in the UI thread.
*
* <p>We are using convenience methods that add a {@link android.os.Message} instance or a
* {@link Runnable} to the message queue.</p>
*
* <p><strong>Note:</strong> If you add a method to this class make sure you don't accidentally
* perform the operation in the calling thread.</p>
*/
class MessageListHandler extends Handler {
2012-09-04 21:50:02 -04:00
private static final int ACTION_REMOVE_MESSAGE = 1;
private static final int ACTION_RESET_UNREAD_COUNT = 2;
private static final int ACTION_SORT_MESSAGES = 3;
private static final int ACTION_FOLDER_LOADING = 4;
private static final int ACTION_REFRESH_TITLE = 5;
private static final int ACTION_PROGRESS = 6;
2012-10-05 21:41:32 -04:00
2012-09-04 21:50:02 -04:00
public void removeMessage(MessageReference messageReference) {
android.os.Message msg = android.os.Message.obtain(this, ACTION_REMOVE_MESSAGE,
messageReference);
sendMessage(msg);
}
public void sortMessages() {
android.os.Message msg = android.os.Message.obtain(this, ACTION_SORT_MESSAGES);
sendMessage(msg);
}
public void folderLoading(String folder, boolean loading) {
android.os.Message msg = android.os.Message.obtain(this, ACTION_FOLDER_LOADING,
(loading) ? 1 : 0, 0, folder);
sendMessage(msg);
}
public void refreshTitle() {
android.os.Message msg = android.os.Message.obtain(this, ACTION_REFRESH_TITLE);
sendMessage(msg);
}
public void progress(final boolean progress) {
android.os.Message msg = android.os.Message.obtain(this, ACTION_PROGRESS,
(progress) ? 1 : 0, 0);
sendMessage(msg);
}
public void updateFooter(final String message, final boolean showProgress) {
2012-10-05 21:41:32 -04:00
//TODO: use message
post(new Runnable() {
@Override
public void run() {
2012-10-05 21:41:32 -04:00
updateFooter(message, showProgress);
}
});
}
public void changeMessageUid(final MessageReference ref, final String newUid) {
2012-09-04 21:50:02 -04:00
// Instead of explicitly creating a container to be able to pass both arguments in a
// Message we post a Runnable to the message queue.
post(new Runnable() {
@Override
public void run() {
mAdapter.changeMessageUid(ref, newUid);
}
});
}
public void addOrUpdateMessages(final Account account, final String folderName,
final List<Message> providedMessages, final boolean verifyAgainstSearch) {
// We copy the message list because it's later modified by MessagingController
final List<Message> messages = new ArrayList<Message>(providedMessages);
post(new Runnable() {
@Override
public void run() {
mAdapter.addOrUpdateMessages(account, folderName, messages,
verifyAgainstSearch);
}
});
}
@Override
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
2012-09-04 21:50:02 -04:00
case ACTION_REMOVE_MESSAGE: {
MessageReference messageReference = (MessageReference) msg.obj;
mAdapter.removeMessage(messageReference);
break;
}
case ACTION_RESET_UNREAD_COUNT: {
mAdapter.resetUnreadCount();
break;
}
case ACTION_SORT_MESSAGES: {
mAdapter.sortMessages();
break;
}
case ACTION_FOLDER_LOADING: {
String folder = (String) msg.obj;
boolean loading = (msg.arg1 == 1);
2012-10-05 21:41:32 -04:00
MessageListFragment.this.folderLoading(folder, loading);
break;
}
case ACTION_REFRESH_TITLE: {
2012-10-05 21:41:32 -04:00
MessageListFragment.this.refreshTitle();
break;
}
case ACTION_PROGRESS: {
boolean progress = (msg.arg1 == 1);
2012-10-05 21:41:32 -04:00
MessageListFragment.this.progress(progress);
break;
}
}
}
}
/**
* @return The comparator to use to display messages in an ordered
* fashion. Never <code>null</code>.
*/
protected Comparator<MessageInfoHolder> getComparator() {
final List<Comparator<MessageInfoHolder>> chain = new ArrayList<Comparator<MessageInfoHolder>>(2 /* we add 2 comparators at most */);
{
// add the specified comparator
final Comparator<MessageInfoHolder> comparator = SORT_COMPARATORS.get(mSortType);
if (mSortAscending) {
chain.add(comparator);
} else {
chain.add(new ReverseComparator<MessageInfoHolder>(comparator));
}
}
{
// add the date comparator if not already specified
if (mSortType != SortType.SORT_DATE && mSortType != SortType.SORT_ARRIVAL) {
final Comparator<MessageInfoHolder> comparator = SORT_COMPARATORS.get(SortType.SORT_DATE);
if (mSortDateAscending) {
chain.add(comparator);
} else {
chain.add(new ReverseComparator<MessageInfoHolder>(comparator));
}
}
}
2012-10-05 21:41:32 -04:00
// build the comparator chain
final Comparator<MessageInfoHolder> chainComparator = new ComparatorChain<MessageInfoHolder>(chain);
return chainComparator;
}
private void folderLoading(String folder, boolean loading) {
if (mCurrentFolder != null && mCurrentFolder.name.equals(folder)) {
mCurrentFolder.loading = loading;
}
updateFooterView();
}
private void refreshTitle() {
setWindowTitle();
if (!mRemoteSearch) {
setWindowProgress();
}
}
private void setWindowProgress() {
int level = Window.PROGRESS_END;
if (mCurrentFolder != null && mCurrentFolder.loading && mAdapter.mListener.getFolderTotal() > 0) {
int divisor = mAdapter.mListener.getFolderTotal();
if (divisor != 0) {
level = (Window.PROGRESS_END / divisor) * (mAdapter.mListener.getFolderCompleted()) ;
if (level > Window.PROGRESS_END) {
level = Window.PROGRESS_END;
}
}
}
2012-10-05 21:41:32 -04:00
mFragmentListener.setMessageListProgress(level);
}
private void setWindowTitle() {
2012-07-17 09:17:29 -04:00
// regular folder content display
if (mFolderName != null) {
2012-10-05 21:41:32 -04:00
Activity activity = getActivity();
String displayName = FolderInfoHolder.getDisplayName(activity, mAccount,
mFolderName);
2012-10-05 21:41:32 -04:00
mFragmentListener.setMessageListTitle(displayName);
2012-07-17 09:17:29 -04:00
2012-10-05 21:41:32 -04:00
String operation = mAdapter.mListener.getOperation(activity, getTimeFormat()).trim();
if (operation.length() < 1) {
2012-10-05 21:41:32 -04:00
mFragmentListener.setMessageListSubTitle(mAccount.getEmail());
} else {
2012-10-05 21:41:32 -04:00
mFragmentListener.setMessageListSubTitle(operation);
}
} else if (mQueryString != null) {
// query result display. This may be for a search folder as opposed to a user-initiated search.
if (mTitle != null) {
// This was a search folder; the search folder has overridden our title.
2012-10-05 21:41:32 -04:00
mFragmentListener.setMessageListTitle(mTitle);
} else {
// This is a search result; set it to the default search result line.
2012-10-05 21:41:32 -04:00
mFragmentListener.setMessageListTitle(getString(R.string.search_results));
}
2012-10-05 21:41:32 -04:00
mFragmentListener.setMessageListSubTitle(null);
}
// set unread count
if (mUnreadMessageCount == 0) {
2012-10-05 21:41:32 -04:00
mFragmentListener.setUnreadCount(0);
} else {
if (mQueryString != null && mTitle == null) {
// This is a search result. The unread message count is easily confused
// with total number of messages in the search result, so let's hide it.
2012-10-05 21:41:32 -04:00
mFragmentListener.setUnreadCount(0);
} else {
2012-10-05 21:41:32 -04:00
mFragmentListener.setUnreadCount(mUnreadMessageCount);
}
}
}
2012-10-05 21:41:32 -04:00
private void setupFormats() {
mTimeFormat = android.text.format.DateFormat.getTimeFormat(getActivity());
}
private DateFormat getTimeFormat() {
return mTimeFormat;
}
private void progress(final boolean progress) {
// Make sure we don't try this before the menu is initialized
// this could happen while the activity is initialized.
if (mRefreshMenuItem != null) {
if (progress) {
mRefreshMenuItem.setActionView(mActionBarProgressView);
} else {
mRefreshMenuItem.setActionView(null);
}
}
if (mPullToRefreshView != null && !progress) {
mPullToRefreshView.onRefreshComplete();
}
}
2010-08-03 01:46:35 -04:00
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (view == mFooterView) {
if (mCurrentFolder != null && !mRemoteSearch) {
mController.loadMoreMessages(mAccount, mFolderName, mAdapter.mListener);
} else if (mRemoteSearch && mAdapter.mExtraSearchResults != null && mAdapter.mExtraSearchResults.size() > 0 && mSearchAccount != null) {
int numResults = mAdapter.mExtraSearchResults.size();
2012-10-05 21:41:32 -04:00
Context appContext = getActivity().getApplicationContext();
Account account = Preferences.getPreferences(appContext).getAccount(mSearchAccount);
if (account == null) {
mHandler.updateFooter("", false);
return;
}
int limit = account.getRemoteSearchNumResults();
List<Message> toProcess = mAdapter.mExtraSearchResults;
if (limit > 0 && numResults > limit) {
toProcess = toProcess.subList(0, limit);
mAdapter.mExtraSearchResults = mAdapter.mExtraSearchResults.subList(limit, mAdapter.mExtraSearchResults.size());
} else {
mAdapter.mExtraSearchResults = null;
mHandler.updateFooter("", false);
}
mController.loadSearchResults(account, mSearchFolder, toProcess, mAdapter.mListener);
}
return;
}
final MessageInfoHolder message = (MessageInfoHolder) parent.getItemAtPosition(position);
if (mSelectedCount > 0) {
toggleMessageSelect(message);
} else {
onOpenMessage(message);
}
}
2012-10-05 21:41:32 -04:00
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mFragmentListener = (MessageListFragmentListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.getClass() +
" must implement MessageListFragmentListener");
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
2012-10-05 21:41:32 -04:00
mController = MessagingController.getInstance(getActivity().getApplication());
2012-10-05 21:41:32 -04:00
mPreviewLines = K9.messageListPreviewLines();
2012-10-05 21:41:32 -04:00
decodeArguments();
}
2012-10-05 21:41:32 -04:00
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
2012-10-05 21:41:32 -04:00
mInflater = inflater;
2012-10-05 21:41:32 -04:00
View view = inflater.inflate(R.layout.message_list_fragment, container, false);
Fix gesture detection This commit addresses 2 issues: 1) Before, a general GestureDetector was registered on the highest level in K9Activity This resulted in EVERY inherited activity to have a useless, unused gesture detector. But more than that, in MessageList, a second GestureDetector was assigned to the ListView. On every fling gesture, both detectors called the onSwipe() methods, which technically did the following: - The one directly assigned to the ListView would work corectly by mapping the (local) event coordinates to the right entry in the ListView - The global one worked on screen coordinates, so the onSwipe() method would likely select the wrong ListView entry (system menu bar offset). - For some reason this "worked" fine, and only the correct entry was selected, despite two detectors used. 2) The gesture detection for the MessageView caused problems when the message itself was scrollable, i.e. wide HTML mails. A fling gesture inside the WebView would scroll the message, but also switch the message. This commit fixes all those by doing the following: - Don't register the GestureDetector in K9Activity, instead make the member variable accessible by subclasses. - In the subclasses that need a detector register it - In K9Activity.dispatchTouchEvent() check for mGestureDetector being null - For MessageList: * Remove the duplicate gesture detector assigned to the ListView * in the handleSwipe() methods: calclulate pixel offset of the ListView to make it work using the global screen coordinates - For MessageView: Limit sensitive area to the message header, to prevent interference with the WebView scrolling - Respect current behavior: * Force-enable gestures for the MessageList * Respect user setting in MessageView - Make sure that after a successful swipe gesture, any pending action is cancelled, to prevent unwanted things to happen (such as expanding the header after changing the message, or a context menu popping up in the MessageList). See http://code.google.com/p/android/issues/detail?id=8497
2012-04-30 19:56:06 -04:00
2012-10-05 21:41:32 -04:00
mActionBarProgressView = inflater.inflate(R.layout.actionbar_indeterminate_progress_actionview, null);
2012-10-05 21:41:32 -04:00
mPullToRefreshView = (PullToRefreshListView) view.findViewById(R.id.message_list);
2012-10-05 21:41:32 -04:00
initializeLayout();
mListView.setVerticalFadingEdgeEnabled(false);
2012-10-05 21:41:32 -04:00
return view;
}
2012-10-05 21:41:32 -04:00
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
2012-10-05 21:41:32 -04:00
mMessageHelper = MessageHelper.getInstance(getActivity());
2012-10-05 21:41:32 -04:00
initializeMessageList();
}
2012-10-05 21:41:32 -04:00
private void decodeArguments() {
Bundle args = getArguments();
2012-10-05 21:41:32 -04:00
mQueryString = args.getString(SearchManager.QUERY);
mFolderName = args.getString(ARG_FOLDER);
mRemoteSearch = args.getBoolean(ARG_REMOTE_SEARCH, false);
mSearchAccount = args.getString(ARG_SEARCH_ACCOUNT);
mSearchFolder = args.getString(ARG_SEARCH_FOLDER);
2012-10-05 21:41:32 -04:00
String accountUuid = args.getString(ARG_ACCOUNT);
2012-10-05 21:41:32 -04:00
Context appContext = getActivity().getApplicationContext();
mAccount = Preferences.getPreferences(appContext).getAccount(accountUuid);
2012-10-05 21:41:32 -04:00
String queryFlags = args.getString(ARG_QUERY_FLAGS);
if (queryFlags != null) {
String[] flagStrings = queryFlags.split(",");
mQueryFlags = new Flag[flagStrings.length];
for (int i = 0; i < flagStrings.length; i++) {
mQueryFlags[i] = Flag.valueOf(flagStrings[i]);
}
}
2012-10-05 21:41:32 -04:00
String forbiddenFlags = args.getString(ARG_FORBIDDEN_FLAGS);
if (forbiddenFlags != null) {
String[] flagStrings = forbiddenFlags.split(",");
mForbiddenFlags = new Flag[flagStrings.length];
for (int i = 0; i < flagStrings.length; i++) {
mForbiddenFlags[i] = Flag.valueOf(flagStrings[i]);
}
}
2012-10-05 21:41:32 -04:00
mIntegrate = args.getBoolean(ARG_INTEGRATE, false);
mAccountUuids = args.getStringArray(ARG_ACCOUNT_UUIDS);
mFolderNames = args.getStringArray(ARG_FOLDER_NAMES);
mTitle = args.getString(ARG_TITLE);
}
2012-10-05 21:41:32 -04:00
private void initializeMessageList() {
mAdapter = new MessageListAdapter();
if (mFolderName != null) {
mCurrentFolder = mAdapter.getFolder(mFolderName, mAccount);
}
2012-10-05 21:41:32 -04:00
// Hide "Load up to x more" footer for search views
mFooterView.setVisibility((mQueryString != null) ? View.GONE : View.VISIBLE);
2012-10-05 21:41:32 -04:00
mController = MessagingController.getInstance(getActivity().getApplication());
mListView.setAdapter(mAdapter);
}
@Override
public void onPause() {
super.onPause();
mController.removeListener(mAdapter.mListener);
saveListState();
}
public void saveListState() {
mState = new Bundle();
2012-10-05 21:41:32 -04:00
mState.putInt(STATE_LIST_POSITION, mListView.getSelectedItemPosition());
}
public void restoreListState() {
if (mState == null) {
return;
}
2012-10-05 21:41:32 -04:00
int pos = mState.getInt(STATE_LIST_POSITION, ListView.INVALID_POSITION);
if (pos >= mListView.getCount()) {
pos = mListView.getCount() - 1;
}
if (pos == ListView.INVALID_POSITION) {
mListView.setSelected(false);
} else {
mListView.setSelection(pos);
}
}
/**
* On resume we refresh messages for the folder that is currently open.
* This guarantees that things like unread message count and read status
* are updated.
*/
@Override
public void onResume() {
super.onResume();
2011-04-12 08:16:22 -04:00
2012-10-05 21:41:32 -04:00
setupFormats();
2012-10-05 21:41:32 -04:00
Context appContext = getActivity().getApplicationContext();
mSenderAboveSubject = K9.messageListSenderAboveSubject();
Merge branch 'mail-on-sd' * mail-on-sd: (40 commits) Added more comments to explain how the locking mecanism works for LocalStore Fixed wrong method being called during experimental provider initialization (since provider isn't enabled, that didn't harm) Add more comments about how the various StorageProviders work and how they're enabled find src/com/fsck/ -name \*.java|xargs astyle --style=ansi --mode=java --indent-switches --indent=spaces=4 --convert-tabs French localization for storage related settings Remove unused SD card strings (replaced with storage indirection) Merge mail-on-sd branch from trunk Reset mail service on storage mount (even if no account uses the storage, to be improved) find src/com/fsck/ -name \*.java|xargs astyle --style=ansi --mode=java --indent-switches --indent=spaces=4 --convert-tabs Migraion -> Migration move the Storage location preference into preferences rather than the wizard. Made LocalStore log less verbose Added @Override compile checks Added ACTION_SHUTDOWN broadcast receiver to properly initiate shutdown sequence (not yet implemented) and cancel any scheduled Intent Be more consistent about which SQLiteDatabase variable is used (from instance variable to argument variable) to make code more refactoring-friendly (class is already big, code extraction should be easier if not referencing the instance variable). Added transaction timing logging Factorised storage lock/transaction handling code for regular operations. Use DB transactions to batch modifications (makes code more robust / could improve performances) Merge mail-on-sd branch from trunk Update issue 888 Added DB close on unmount / DB open on mount Update issue 888 Back to account list when underlying storage not available/unmounting in MessageView / MessageList ...
2010-11-13 16:40:56 -05:00
2012-10-05 21:41:32 -04:00
final Preferences prefs = Preferences.getPreferences(appContext);
boolean allowRemoteSearch = false;
if (mSearchAccount != null) {
final Account searchAccount = prefs.getAccount(mSearchAccount);
if (searchAccount != null) {
allowRemoteSearch = searchAccount.allowRemoteSearch();
}
}
// Check if we have connectivity. Cache the value.
if (mHasConnectivity == null) {
2012-10-05 21:41:32 -04:00
final ConnectivityManager connectivityManager =
(ConnectivityManager) getActivity().getApplication().getSystemService(
Context.CONNECTIVITY_SERVICE);
final NetworkInfo netInfo = connectivityManager.getActiveNetworkInfo();
if (netInfo != null && netInfo.getState() == NetworkInfo.State.CONNECTED) {
mHasConnectivity = true;
} else {
mHasConnectivity = false;
}
}
if (mQueryString == null) {
mPullToRefreshView.setOnRefreshListener(new PullToRefreshBase.OnRefreshListener<ListView>() {
@Override
public void onRefresh(PullToRefreshBase<ListView> refreshView) {
2012-10-05 21:41:32 -04:00
checkMail();
}
});
} else if (allowRemoteSearch && !mRemoteSearch && !mIntegrate && mHasConnectivity) {
// mQueryString != null is implied if we get this far.
mPullToRefreshView.setOnRefreshListener(new PullToRefreshBase.OnRefreshListener<ListView>() {
@Override
public void onRefresh(PullToRefreshBase<ListView> refreshView) {
mPullToRefreshView.onRefreshComplete();
onRemoteSearchRequested(true);
}
});
mPullToRefreshView.setPullLabel(getString(R.string.pull_to_refresh_remote_search_from_local_search_pull));
mPullToRefreshView.setReleaseLabel(getString(R.string.pull_to_refresh_remote_search_from_local_search_release));
} else {
mPullToRefreshView.setMode(PullToRefreshBase.Mode.DISABLED);
}
mController.addListener(mAdapter.mListener);
//Cancel pending new mail notifications when we open an account
2012-01-20 17:32:12 -05:00
Account[] accountsWithNotification;
Account account = getCurrentAccount(prefs);
if (account != null) {
accountsWithNotification = new Account[] { account };
mSortType = account.getSortType();
mSortAscending = account.isSortAscending(mSortType);
mSortDateAscending = account.isSortAscending(SortType.SORT_DATE);
} else {
accountsWithNotification = prefs.getAccounts();
mSortType = K9.getSortType();
mSortAscending = K9.isSortAscending(mSortType);
mSortDateAscending = K9.isSortAscending(SortType.SORT_DATE);
}
for (Account accountWithNotification : accountsWithNotification) {
2012-10-05 21:41:32 -04:00
mController.notifyAccountCancel(appContext, accountWithNotification);
2010-10-23 11:19:56 -04:00
}
if (mAdapter.isEmpty()) {
if (mRemoteSearch) {
//TODO: Support flag based search
mRemoteSearchFuture = mController.searchRemoteMessages(mSearchAccount, mSearchFolder, mQueryString, null, null, mAdapter.mListener);
} else if (mFolderName != null) {
mController.listLocalMessages(mAccount, mFolderName, mAdapter.mListener);
// Hide the archive button if we don't have an archive folder.
if (!mAccount.hasArchiveFolder()) {
// mBatchArchiveButton.setVisibility(View.GONE);
}
} else if (mQueryString != null) {
2010-10-23 11:19:56 -04:00
mController.searchLocalMessages(mAccountUuids, mFolderNames, null, mQueryString, mIntegrate, mQueryFlags, mForbiddenFlags, mAdapter.mListener);
// Don't show the archive button if this is a search.
// mBatchArchiveButton.setVisibility(View.GONE);
2010-10-23 11:19:56 -04:00
}
2012-10-05 21:41:32 -04:00
} else {
2011-04-12 08:16:22 -04:00
// reread the selected date format preference in case it has changed
mMessageHelper.refresh();
mAdapter.markAllMessagesAsDirty();
2012-10-05 21:41:32 -04:00
if (!mRemoteSearch) {
new Thread() {
@Override
public void run() {
if (mFolderName != null) {
2012-10-05 21:41:32 -04:00
mController.listLocalMessagesSynchronous(mAccount, mFolderName, mAdapter.mListener);
} else if (mQueryString != null) {
mController.searchLocalMessagesSynchronous(mAccountUuids, mFolderNames, null, mQueryString, mIntegrate, mQueryFlags, mForbiddenFlags, mAdapter.mListener);
2010-10-23 11:19:56 -04:00
}
2012-10-05 21:41:32 -04:00
mHandler.post(new Runnable() {
@Override
public void run() {
mAdapter.pruneDirtyMessages();
mAdapter.notifyDataSetChanged();
restoreListState();
}
});
}
2012-10-05 21:41:32 -04:00
}
.start();
2010-10-23 11:19:56 -04:00
}
}
2010-10-23 11:19:56 -04:00
if (mAccount != null && mFolderName != null && !mRemoteSearch) {
2010-10-23 11:19:56 -04:00
mController.getFolderUnreadMessageCount(mAccount, mFolderName, mAdapter.mListener);
}
refreshTitle();
}
private void initializeLayout() {
mListView = mPullToRefreshView.getRefreshableView();
mListView.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
mListView.setLongClickable(true);
mListView.setFastScrollEnabled(true);
mListView.setScrollingCacheEnabled(false);
mListView.setOnItemClickListener(this);
mListView.addFooterView(getFooterView(mListView));
registerForContextMenu(mListView);
}
private void onOpenMessage(MessageInfoHolder message) {
2012-10-05 21:41:32 -04:00
mFragmentListener.openMessage(message.message.makeMessageReference());
}
2012-10-05 21:41:32 -04:00
public void onCompose() {
if (mQueryString != null) {
/*
* If we have a query string, we don't have an account to let
* compose start the default action.
*/
2012-10-05 21:41:32 -04:00
mFragmentListener.onCompose(null);
} else {
2012-10-05 21:41:32 -04:00
mFragmentListener.onCompose(mAccount);
}
}
2012-10-05 21:41:32 -04:00
public void onReply(MessageInfoHolder holder) {
mFragmentListener.onReply(holder.message);
}
2012-10-05 21:41:32 -04:00
public void onReplyAll(MessageInfoHolder holder) {
mFragmentListener.onReplyAll(holder.message);
}
2012-10-05 21:41:32 -04:00
public void onForward(MessageInfoHolder holder) {
mFragmentListener.onForward(holder.message);
}
2012-10-05 21:41:32 -04:00
public void onResendMessage(MessageInfoHolder holder) {
mFragmentListener.onResendMessage(holder.message);
}
2012-10-05 21:41:32 -04:00
public void changeSort(SortType sortType) {
Boolean sortAscending = (mSortType == sortType) ? !mSortAscending : null;
changeSort(sortType, sortAscending);
}
/**
* User has requested a remote search. Setup the bundle and start the intent.
* @param fromLocalSearch true if this is being called from a local search result screen. This affects
* where we pull the account and folder info used for the next search.
*/
public void onRemoteSearchRequested(final boolean fromLocalSearch) {
2012-10-05 21:41:32 -04:00
String searchAccount;
String searchFolder;
2012-10-05 21:41:32 -04:00
if (fromLocalSearch) {
searchAccount = mSearchAccount;
searchFolder = mSearchFolder;
} else {
2012-10-05 21:41:32 -04:00
searchAccount = mAccount.getUuid();
searchFolder = mCurrentFolder.name;
}
2012-10-05 21:41:32 -04:00
mFragmentListener.remoteSearch(searchAccount, searchFolder, mQueryString);
}
/**
* Change the sort type and sort order used for the message list.
*
* @param sortType
* Specifies which field to use for sorting the message list.
* @param sortAscending
* Specifies the sort order. If this argument is {@code null} the default search order
* for the sort type is used.
*/
// FIXME: Don't save the changes in the UI thread
private void changeSort(SortType sortType, Boolean sortAscending) {
mSortType = sortType;
2012-10-05 21:41:32 -04:00
Preferences prefs = Preferences.getPreferences(getActivity().getApplicationContext());
Account account = getCurrentAccount(prefs);
if (account != null) {
account.setSortType(mSortType);
if (sortAscending == null) {
mSortAscending = account.isSortAscending(mSortType);
} else {
mSortAscending = sortAscending;
}
account.setSortAscending(mSortType, mSortAscending);
mSortDateAscending = account.isSortAscending(SortType.SORT_DATE);
account.save(prefs);
} else {
K9.setSortType(mSortType);
if (sortAscending == null) {
mSortAscending = K9.isSortAscending(mSortType);
} else {
mSortAscending = sortAscending;
}
K9.setSortAscending(mSortType, mSortAscending);
mSortDateAscending = K9.isSortAscending(SortType.SORT_DATE);
Editor editor = prefs.getPreferences().edit();
K9.save(editor);
editor.commit();
}
reSort();
}
private void reSort() {
2012-04-08 22:29:08 -04:00
int toastString = mSortType.getToast(mSortAscending);
2012-10-05 21:41:32 -04:00
Toast toast = Toast.makeText(getActivity(), toastString, Toast.LENGTH_SHORT);
toast.show();
mAdapter.sortMessages();
}
2012-10-05 21:41:32 -04:00
public void onCycleSort() {
2012-04-08 17:17:06 -04:00
SortType[] sorts = SortType.values();
int curIndex = 0;
for (int i = 0; i < sorts.length; i++) {
2012-04-08 22:29:08 -04:00
if (sorts[i] == mSortType) {
curIndex = i;
break;
}
}
curIndex++;
if (curIndex == sorts.length) {
curIndex = 0;
}
changeSort(sorts[curIndex]);
}
/**
* @param holders
* Never {@code null}.
*/
private void onDelete(final List<MessageInfoHolder> holders) {
final List<Message> messagesToRemove = new ArrayList<Message>();
for (MessageInfoHolder holder : holders) {
messagesToRemove.add(holder.message);
}
mAdapter.removeMessages(holders);
mController.deleteMessages(messagesToRemove.toArray(EMPTY_MESSAGE_ARRAY), null);
}
@Override
2012-10-05 21:41:32 -04:00
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != Activity.RESULT_OK) {
return;
}
switch (requestCode) {
case ACTIVITY_CHOOSE_FOLDER_MOVE:
case ACTIVITY_CHOOSE_FOLDER_COPY: {
if (data == null) {
return;
}
final String destFolderName = data.getStringExtra(ChooseFolder.EXTRA_NEW_FOLDER);
final List<MessageInfoHolder> holders = mActiveMessages;
if (destFolderName != null) {
mActiveMessages = null; // don't need it any more
final Account account = holders.get(0).message.getFolder().getAccount();
account.setLastSelectedFolderName(destFolderName);
switch (requestCode) {
case ACTIVITY_CHOOSE_FOLDER_MOVE:
move(holders, destFolderName);
break;
case ACTIVITY_CHOOSE_FOLDER_COPY:
copy(holders, destFolderName);
break;
}
}
break;
}
}
}
2012-10-05 21:41:32 -04:00
public void onExpunge() {
if (mCurrentFolder != null) {
onExpunge(mAccount, mCurrentFolder.name);
}
}
private void onExpunge(final Account account, String folderName) {
mController.expunge(account, folderName, null);
}
2012-10-05 21:41:32 -04:00
private void showDialog(int dialogId) {
DialogFragment fragment;
switch (dialogId) {
case R.id.dialog_confirm_spam: {
String title = getString(R.string.dialog_confirm_spam_title);
2012-10-05 21:41:32 -04:00
int selectionSize = mActiveMessages.size();
String message = getResources().getQuantityString(
R.plurals.dialog_confirm_spam_message, selectionSize,
Integer.valueOf(selectionSize));
2012-10-05 21:41:32 -04:00
String confirmText = getString(R.string.dialog_confirm_spam_confirm_button);
String cancelText = getString(R.string.dialog_confirm_spam_cancel_button);
fragment = ConfirmationDialogFragment.newInstance(dialogId, title, message,
confirmText, cancelText);
break;
}
default: {
throw new RuntimeException("Called showDialog(int) with unknown dialog id.");
}
}
2012-10-05 21:41:32 -04:00
fragment.setTargetFragment(this, dialogId);
fragment.show(getFragmentManager(), getDialogTag(dialogId));
}
2012-10-05 21:41:32 -04:00
private String getDialogTag(int dialogId) {
return String.format("dialog-%d", dialogId);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int itemId = item.getItemId();
switch (itemId) {
case R.id.set_sort_date: {
2012-04-08 17:17:06 -04:00
changeSort(SortType.SORT_DATE);
return true;
}
2012-03-11 18:48:56 -04:00
case R.id.set_sort_arrival: {
2012-04-08 17:17:06 -04:00
changeSort(SortType.SORT_ARRIVAL);
2012-03-11 18:48:56 -04:00
return true;
}
case R.id.set_sort_subject: {
2012-04-08 17:17:06 -04:00
changeSort(SortType.SORT_SUBJECT);
return true;
}
case R.id.set_sort_sender: {
2012-04-08 17:17:06 -04:00
changeSort(SortType.SORT_SENDER);
return true;
}
case R.id.set_sort_flag: {
2012-04-08 17:17:06 -04:00
changeSort(SortType.SORT_FLAGGED);
return true;
}
case R.id.set_sort_unread: {
2012-04-08 17:17:06 -04:00
changeSort(SortType.SORT_UNREAD);
return true;
}
case R.id.set_sort_attach: {
2012-04-08 17:17:06 -04:00
changeSort(SortType.SORT_ATTACHMENT);
return true;
}
2012-09-14 22:24:49 -04:00
case R.id.select_all: {
setSelectionState(true);
2012-09-14 22:24:49 -04:00
return true;
}
}
if (mQueryString != null) {
// None of the options after this point are "safe" for search results
//TODO: This is not true for "unread" and "starred" searches in regular folders
return false;
}
switch (itemId) {
case R.id.send_messages: {
2012-10-05 21:41:32 -04:00
onSendPendingMessages();
return true;
}
case R.id.expunge: {
if (mCurrentFolder != null) {
onExpunge(mAccount, mCurrentFolder.name);
}
return true;
}
default: {
return super.onOptionsItemSelected(item);
}
}
}
2012-10-05 21:41:32 -04:00
public void onSendPendingMessages() {
mController.sendPendingMessages(mAccount, mAdapter.mListener);
}
@Override
public boolean onContextItemSelected(android.view.MenuItem item) {
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
final MessageInfoHolder message = (MessageInfoHolder) mListView.getItemAtPosition(info.position);
final List<MessageInfoHolder> selection = getSelectionFromMessage(message);
switch (item.getItemId()) {
case R.id.reply: {
onReply(message);
break;
}
case R.id.reply_all: {
onReplyAll(message);
break;
}
case R.id.forward: {
onForward(message);
break;
}
case R.id.send_again: {
onResendMessage(message);
mSelectedCount = 0;
break;
}
case R.id.same_sender: {
2012-10-05 21:41:32 -04:00
mFragmentListener.showMoreFromSameSender(message.senderAddress);
break;
}
case R.id.delete: {
onDelete(selection);
break;
}
case R.id.mark_as_read: {
setFlag(selection, Flag.SEEN, true);
break;
}
case R.id.mark_as_unread: {
setFlag(selection, Flag.SEEN, false);
break;
}
case R.id.flag: {
setFlag(selection, Flag.FLAGGED, true);
break;
}
case R.id.unflag: {
setFlag(selection, Flag.FLAGGED, false);
break;
}
// only if the account supports this
case R.id.archive: {
onArchive(selection);
break;
}
case R.id.spam: {
onSpam(selection);
break;
}
case R.id.move: {
onMove(selection);
break;
}
case R.id.copy: {
onCopy(selection);
break;
}
}
return true;
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
MessageInfoHolder message = (MessageInfoHolder) mListView.getItemAtPosition(info.position);
if (message == null) {
return;
}
2012-10-05 21:41:32 -04:00
getActivity().getMenuInflater().inflate(R.menu.message_list_item_context, menu);
menu.setHeaderTitle(message.message.getSubject());
if (message.read) {
menu.findItem(R.id.mark_as_read).setVisible(false);
} else {
menu.findItem(R.id.mark_as_unread).setVisible(false);
}
if (message.flagged) {
menu.findItem(R.id.flag).setVisible(false);
} else {
menu.findItem(R.id.unflag).setVisible(false);
}
Account account = message.message.getFolder().getAccount();
if (!mController.isCopyCapable(account)) {
menu.findItem(R.id.copy).setVisible(false);
}
if (!mController.isMoveCapable(account)) {
menu.findItem(R.id.move).setVisible(false);
menu.findItem(R.id.archive).setVisible(false);
menu.findItem(R.id.spam).setVisible(false);
}
if (!account.hasArchiveFolder()) {
menu.findItem(R.id.archive).setVisible(false);
}
if (!account.hasSpamFolder()) {
menu.findItem(R.id.spam).setVisible(false);
}
}
public void onSwipeRightToLeft(final MotionEvent e1, final MotionEvent e2) {
// Handle right-to-left as an un-select
handleSwipe(e1, false);
}
public void onSwipeLeftToRight(final MotionEvent e1, final MotionEvent e2) {
// Handle left-to-right as a select.
handleSwipe(e1, true);
}
/**
* Handle a select or unselect swipe event
* @param downMotion Event that started the swipe
* @param selected true if this was an attempt to select (i.e. left to right).
*/
private void handleSwipe(final MotionEvent downMotion, final boolean selected) {
int[] listPosition = new int[2];
mListView.getLocationOnScreen(listPosition);
int position = mListView.pointToPosition((int) downMotion.getRawX() - listPosition[0], (int) downMotion.getRawY() - listPosition[1]);
if (position != AdapterView.INVALID_POSITION) {
final MessageInfoHolder message = (MessageInfoHolder) mListView.getItemAtPosition(position);
toggleMessageSelect(message);
}
}
class MessageListAdapter extends BaseAdapter {
private final List<MessageInfoHolder> mMessages =
Collections.synchronizedList(new ArrayList<MessageInfoHolder>());
public List<Message> mExtraSearchResults;
private final ActivityListener mListener = new ActivityListener() {
@Override
public void remoteSearchAddMessage(Account account, String folderName, Message message, final int numDone, final int numTotal) {
if (numTotal > 0 && numDone < numTotal) {
2012-10-05 21:41:32 -04:00
mFragmentListener.setMessageListProgress(Window.PROGRESS_END / numTotal * numDone);
} else {
2012-10-05 21:41:32 -04:00
mFragmentListener.setMessageListProgress(Window.PROGRESS_END);
}
mHandler.addOrUpdateMessages(account, folderName, Collections.singletonList(message), false);
}
@Override
public void remoteSearchFailed(Account acct, String folder, final String err) {
//TODO: Better error handling
2012-10-05 21:41:32 -04:00
mHandler.post(new Runnable() {
@Override
public void run() {
2012-10-05 21:41:32 -04:00
Toast.makeText(getActivity(), err, Toast.LENGTH_LONG).show();
}
});
}
@Override
public void remoteSearchStarted(Account acct, String folder) {
mHandler.progress(true);
mHandler.updateFooter(getString(R.string.remote_search_sending_query), true);
}
@Override
public void remoteSearchFinished(Account acct, String folder, int numResults, List<Message> extraResults) {
mHandler.progress(false);
if (extraResults != null && extraResults.size() > 0) {
mExtraSearchResults = extraResults;
mHandler.updateFooter(String.format(getString(R.string.load_more_messages_fmt), acct.getRemoteSearchNumResults()), false);
} else {
mHandler.updateFooter("", false);
}
2012-10-05 21:41:32 -04:00
mFragmentListener.setMessageListProgress(Window.PROGRESS_END);
}
@Override
public void remoteSearchServerQueryComplete(Account account, String folderName, int numResults) {
mHandler.progress(true);
if (account != null && account.getRemoteSearchNumResults() != 0 && numResults > account.getRemoteSearchNumResults()) {
mHandler.updateFooter(getString(R.string.remote_search_downloading_limited, account.getRemoteSearchNumResults(), numResults), true);
} else {
mHandler.updateFooter(getString(R.string.remote_search_downloading, numResults), true);
}
2012-10-05 21:41:32 -04:00
mFragmentListener.setMessageListProgress(Window.PROGRESS_START);
}
@Override
public void informUserOfStatus() {
mHandler.refreshTitle();
}
@Override
public void synchronizeMailboxStarted(Account account, String folder) {
if (updateForMe(account, folder)) {
mHandler.progress(true);
mHandler.folderLoading(folder, true);
}
super.synchronizeMailboxStarted(account, folder);
}
@Override
public void synchronizeMailboxFinished(Account account, String folder,
int totalMessagesInMailbox, int numNewMessages) {
if (updateForMe(account, folder)) {
mHandler.progress(false);
mHandler.folderLoading(folder, false);
mHandler.sortMessages();
}
super.synchronizeMailboxFinished(account, folder, totalMessagesInMailbox, numNewMessages);
}
@Override
public void synchronizeMailboxFailed(Account account, String folder, String message) {
if (updateForMe(account, folder)) {
mHandler.progress(false);
mHandler.folderLoading(folder, false);
mHandler.sortMessages();
}
super.synchronizeMailboxFailed(account, folder, message);
}
@Override
public void synchronizeMailboxAddOrUpdateMessage(Account account, String folder, Message message) {
mHandler.addOrUpdateMessages(account, folder, Collections.singletonList(message), true);
}
@Override
public void synchronizeMailboxRemovedMessage(Account account, String folder, Message message) {
mHandler.removeMessage(message.makeMessageReference());
}
@Override
public void listLocalMessagesStarted(Account account, String folder) {
if ((mQueryString != null && folder == null) || (account != null && account.equals(mAccount))) {
mHandler.progress(true);
if (folder != null) {
mHandler.folderLoading(folder, true);
}
}
}
@Override
public void listLocalMessagesFailed(Account account, String folder, String message) {
if ((mQueryString != null && folder == null) || (account != null && account.equals(mAccount))) {
mHandler.sortMessages();
mHandler.progress(false);
if (folder != null) {
mHandler.folderLoading(folder, false);
}
}
}
@Override
public void listLocalMessagesFinished(Account account, String folder) {
if ((mQueryString != null && folder == null) || (account != null && account.equals(mAccount))) {
mHandler.sortMessages();
mHandler.progress(false);
if (folder != null) {
mHandler.folderLoading(folder, false);
}
}
}
@Override
public void listLocalMessagesRemoveMessage(Account account, String folder, Message message) {
mHandler.removeMessage(message.makeMessageReference());
}
@Override
public void listLocalMessagesAddMessages(Account account, String folder, List<Message> messages) {
mHandler.addOrUpdateMessages(account, folder, messages, false);
}
@Override
public void listLocalMessagesUpdateMessage(Account account, String folder, Message message) {
mHandler.addOrUpdateMessages(account, folder, Collections.singletonList(message), false);
}
@Override
public void searchStats(AccountStats stats) {
mUnreadMessageCount = stats.unreadMessageCount;
super.searchStats(stats);
}
@Override
public void folderStatusChanged(Account account, String folder, int unreadMessageCount) {
if (updateForMe(account, folder)) {
mUnreadMessageCount = unreadMessageCount;
}
super.folderStatusChanged(account, folder, unreadMessageCount);
}
@Override
public void messageUidChanged(Account account, String folder, String oldUid, String newUid) {
MessageReference ref = new MessageReference();
ref.accountUuid = account.getUuid();
ref.folderName = folder;
ref.uid = oldUid;
mHandler.changeMessageUid(ref, newUid);
}
};
private boolean updateForMe(Account account, String folder) {
if ((account.equals(mAccount) && mFolderName != null && folder.equals(mFolderName))) {
return true;
} else {
return false;
}
}
public List<MessageInfoHolder> getMessages() {
return mMessages;
}
public void restoreMessages(List<MessageInfoHolder> messages) {
mMessages.addAll(messages);
}
private Drawable mAttachmentIcon;
2012-08-20 22:09:34 -04:00
private Drawable mForwardedIcon;
private Drawable mAnsweredIcon;
2012-08-20 22:09:34 -04:00
private Drawable mForwardedAnsweredIcon;
MessageListAdapter() {
mAttachmentIcon = getResources().getDrawable(R.drawable.ic_email_attachment_small);
mAnsweredIcon = getResources().getDrawable(R.drawable.ic_email_answered_small);
2012-08-20 22:09:34 -04:00
mForwardedIcon = getResources().getDrawable(R.drawable.ic_email_forwarded_small);
2012-08-30 11:47:58 -04:00
mForwardedAnsweredIcon = getResources().getDrawable(R.drawable.ic_email_forwarded_answered_small);
}
public void markAllMessagesAsDirty() {
for (MessageInfoHolder holder : mMessages) {
holder.dirty = true;
}
}
public void pruneDirtyMessages() {
List<MessageInfoHolder> messagesToRemove = new ArrayList<MessageInfoHolder>();
for (MessageInfoHolder holder : mMessages) {
if (holder.dirty) {
messagesToRemove.add(holder);
}
2010-11-04 22:59:21 -04:00
}
removeMessages(messagesToRemove);
}
public void removeMessage(MessageReference messageReference) {
MessageInfoHolder holder = getMessage(messageReference);
if (holder == null) {
Log.w(K9.LOG_TAG, "Got callback to remove non-existent message with UID " +
messageReference.uid);
} else {
removeMessages(Collections.singletonList(holder));
}
}
public void removeMessages(final List<MessageInfoHolder> messages) {
if (messages.isEmpty()) {
return;
}
for (MessageInfoHolder message : messages) {
if (message != null && (mFolderName == null || (
message.folder != null &&
message.folder.name.equals(mFolderName)))) {
if (message.selected && mSelectedCount > 0) {
mSelectedCount--;
}
mMessages.remove(message);
}
}
resetUnreadCount();
notifyDataSetChanged();
2012-09-13 00:27:58 -04:00
computeSelectAllVisibility();
}
/**
* Set the selection state for all messages at once.
* @param selected Selection state to set.
*/
public void setSelectionForAllMesages(final boolean selected) {
for (MessageInfoHolder message : mMessages) {
message.selected = selected;
}
notifyDataSetChanged();
}
public void addMessages(final List<MessageInfoHolder> messages) {
if (messages.isEmpty()) {
return;
}
final boolean wasEmpty = mMessages.isEmpty();
for (final MessageInfoHolder message : messages) {
if (mFolderName == null || (message.folder != null && message.folder.name.equals(mFolderName))) {
int index = Collections.binarySearch(mMessages, message, getComparator());
if (index < 0) {
index = (index * -1) - 1;
}
mMessages.add(index, message);
}
}
if (wasEmpty) {
mListView.setSelection(0);
}
resetUnreadCount();
notifyDataSetChanged();
2012-09-13 00:27:58 -04:00
computeSelectAllVisibility();
}
public void changeMessageUid(MessageReference ref, String newUid) {
MessageInfoHolder holder = getMessage(ref);
if (holder != null) {
holder.uid = newUid;
holder.message.setUid(newUid);
}
}
public void resetUnreadCount() {
if (mQueryString != null) {
int unreadCount = 0;
for (MessageInfoHolder holder : mMessages) {
unreadCount += holder.read ? 0 : 1;
}
mUnreadMessageCount = unreadCount;
refreshTitle();
}
}
public void sortMessages() {
final Comparator<MessageInfoHolder> chainComparator = getComparator();
Collections.sort(mMessages, chainComparator);
notifyDataSetChanged();
}
public void addOrUpdateMessages(final Account account, final String folderName,
final List<Message> messages, final boolean verifyAgainstSearch) {
2010-10-21 16:49:20 -04:00
boolean needsSort = false;
final List<MessageInfoHolder> messagesToAdd = new ArrayList<MessageInfoHolder>();
List<MessageInfoHolder> messagesToRemove = new ArrayList<MessageInfoHolder>();
List<Message> messagesToSearch = new ArrayList<Message>();
2010-10-21 16:49:20 -04:00
// cache field into local variable for faster access for JVM without JIT
final MessageHelper messageHelper = mMessageHelper;
for (Message message : messages) {
2010-10-21 16:49:20 -04:00
MessageInfoHolder m = getMessage(message);
if (message.isSet(Flag.DELETED)) {
if (m != null) {
2010-10-21 16:49:20 -04:00
messagesToRemove.add(m);
}
} else {
2010-10-21 16:49:20 -04:00
final Folder messageFolder = message.getFolder();
final Account messageAccount = messageFolder.getAccount();
if (m == null) {
if (updateForMe(account, folderName)) {
2010-10-21 16:49:20 -04:00
m = new MessageInfoHolder();
FolderInfoHolder folderInfoHolder = new FolderInfoHolder(
2012-10-05 21:41:32 -04:00
getActivity(), messageFolder, messageAccount);
messageHelper.populate(m, message, folderInfoHolder, messageAccount);
2010-10-21 16:49:20 -04:00
messagesToAdd.add(m);
} else {
if (mQueryString != null) {
if (verifyAgainstSearch) {
2010-10-21 16:49:20 -04:00
messagesToSearch.add(message);
} else {
2010-10-21 16:49:20 -04:00
m = new MessageInfoHolder();
FolderInfoHolder folderInfoHolder = new FolderInfoHolder(
2012-10-05 21:41:32 -04:00
getActivity(), messageFolder, messageAccount);
messageHelper.populate(m, message, folderInfoHolder,
messageAccount);
2010-10-21 16:49:20 -04:00
messagesToAdd.add(m);
2010-08-30 23:58:33 -04:00
}
}
}
} else {
2010-10-21 16:49:20 -04:00
m.dirty = false; // as we reload the message, unset its dirty flag
2012-10-05 21:41:32 -04:00
FolderInfoHolder folderInfoHolder = new FolderInfoHolder(getActivity(),
messageFolder, account);
messageHelper.populate(m, message, folderInfoHolder, account);
2010-10-21 16:49:20 -04:00
needsSort = true;
}
2010-10-21 16:49:20 -04:00
}
}
2011-10-06 12:28:14 -04:00
if (!messagesToSearch.isEmpty()) {
mController.searchLocalMessages(mAccountUuids, mFolderNames,
messagesToSearch.toArray(EMPTY_MESSAGE_ARRAY), mQueryString, mIntegrate,
mQueryFlags, mForbiddenFlags,
new MessagingListener() {
2010-10-21 16:49:20 -04:00
@Override
public void listLocalMessagesAddMessages(Account account, String folder,
List<Message> messages) {
mHandler.addOrUpdateMessages(account, folder, messages, false);
2010-08-30 23:58:33 -04:00
}
2010-10-21 16:49:20 -04:00
});
}
2011-10-06 12:28:14 -04:00
if (!messagesToRemove.isEmpty()) {
removeMessages(messagesToRemove);
2010-10-21 16:49:20 -04:00
}
2011-10-06 12:28:14 -04:00
if (!messagesToAdd.isEmpty()) {
addMessages(messagesToAdd);
2010-10-21 16:49:20 -04:00
}
if (needsSort) {
sortMessages();
resetUnreadCount();
2010-10-21 16:49:20 -04:00
}
}
/**
* Find a specific message in the message list.
*
* <p><strong>Note:</strong>
* This method was optimized because it is called a lot. Don't change it unless you know
* what you are doing.</p>
*
* @param message
* A {@link Message} instance describing the message to look for.
*
* @return The corresponding {@link MessageInfoHolder} instance if the message was found in
* the message list. {@code null} otherwise.
*/
private MessageInfoHolder getMessage(Message message) {
String uid;
Folder folder;
for (MessageInfoHolder holder : mMessages) {
uid = message.getUid();
if (uid != null && (holder.uid == uid || uid.equals(holder.uid))) {
folder = message.getFolder();
if (holder.folder.name.equals(folder.getName()) &&
holder.account.equals(folder.getAccount().getUuid())) {
return holder;
}
}
}
return null;
}
/**
* Find a specific message in the message list.
*
* <p><strong>Note:</strong>
* This method was optimized because it is called a lot. Don't change it unless you know
* what you are doing.</p>
*
* @param messageReference
* A {@link MessageReference} instance describing the message to look for.
*
* @return The corresponding {@link MessageInfoHolder} instance if the message was found in
* the message list. {@code null} otherwise.
*/
private MessageInfoHolder getMessage(MessageReference messageReference) {
String uid;
for (MessageInfoHolder holder : mMessages) {
uid = messageReference.uid;
if ((holder.uid == uid || uid.equals(holder.uid)) &&
holder.folder.name.equals(messageReference.folderName) &&
holder.account.equals(messageReference.accountUuid)) {
return holder;
}
}
return null;
}
public FolderInfoHolder getFolder(String folder, Account account) {
LocalFolder local_folder = null;
try {
LocalStore localStore = account.getLocalStore();
local_folder = localStore.getFolder(folder);
2012-10-05 21:41:32 -04:00
return new FolderInfoHolder(getActivity(), local_folder, account);
} catch (Exception e) {
Log.e(K9.LOG_TAG, "getFolder(" + folder + ") goes boom: ", e);
return null;
} finally {
if (local_folder != null) {
local_folder.close();
}
}
}
2012-09-12 20:58:44 -04:00
@Override
public int getCount() {
return mMessages.size();
}
2010-08-03 01:46:35 -04:00
@Override
public long getItemId(int position) {
try {
MessageInfoHolder messageHolder = (MessageInfoHolder) getItem(position);
if (messageHolder != null) {
2011-01-18 20:21:27 -05:00
return messageHolder.message.getId();
}
} catch (Exception e) {
Log.i(K9.LOG_TAG, "getItemId(" + position + ") ", e);
}
return -1;
}
2010-08-03 01:46:35 -04:00
@Override
public Object getItem(int position) {
try {
if (position < mMessages.size()) {
return mMessages.get(position);
}
} catch (Exception e) {
Log.e(K9.LOG_TAG, "getItem(" + position + "), but folder.messages.size() = " + mMessages.size(), e);
}
return null;
}
2010-08-03 01:46:35 -04:00
@Override
public View getView(int position, View convertView, ViewGroup parent) {
MessageInfoHolder message = (MessageInfoHolder) getItem(position);
View view;
if ((convertView != null) && (convertView.getId() == R.layout.message_list_item)) {
view = convertView;
} else {
view = mInflater.inflate(R.layout.message_list_item, parent, false);
view.setId(R.layout.message_list_item);
}
MessageViewHolder holder = (MessageViewHolder) view.getTag();
if (holder == null) {
holder = new MessageViewHolder();
holder.date = (TextView) view.findViewById(R.id.date);
holder.chip = view.findViewById(R.id.chip);
holder.preview = (TextView) view.findViewById(R.id.preview);
if (mSenderAboveSubject) {
holder.from = (TextView) view.findViewById(R.id.subject);
holder.from.setTextSize(TypedValue.COMPLEX_UNIT_SP, mFontSizes.getMessageListSender());
} else {
holder.subject = (TextView) view.findViewById(R.id.subject);
holder.subject.setTextSize(TypedValue.COMPLEX_UNIT_SP, mFontSizes.getMessageListSubject());
}
holder.date.setTextSize(TypedValue.COMPLEX_UNIT_SP, mFontSizes.getMessageListDate());
holder.preview.setLines(mPreviewLines);
holder.preview.setTextSize(TypedValue.COMPLEX_UNIT_SP, mFontSizes.getMessageListPreview());
view.setTag(holder);
}
if (message != null) {
bindView(position, view, holder, message);
} else {
// This branch code is triggered when the local store
// hands us an invalid message
holder.chip.getBackground().setAlpha(0);
if (holder.subject != null) {
holder.subject.setText(getString(R.string.general_no_subject));
holder.subject.setTypeface(null, Typeface.NORMAL);
}
String noSender = getString(R.string.general_no_sender);
if (holder.preview != null) {
holder.preview.setText(noSender, TextView.BufferType.SPANNABLE);
Spannable str = (Spannable) holder.preview.getText();
str.setSpan(new StyleSpan(Typeface.NORMAL),
2011-01-06 11:55:08 -05:00
0,
noSender.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
str.setSpan(new AbsoluteSizeSpan(mFontSizes.getMessageListSender(), true),
0,
noSender.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} else {
holder.from.setText(noSender);
holder.from.setTypeface(null, Typeface.NORMAL);
holder.from.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
}
holder.date.setText(getString(R.string.general_no_date));
//WARNING: Order of the next 2 lines matter
holder.position = -1;
}
return view;
}
/**
* Associate model data to view object.
2010-08-17 22:48:55 -04:00
*
* @param position
* The position of the item within the adapter's data set of
* the item whose view we want.
* @param view
* Main view component to alter. Never <code>null</code>.
* @param holder
* Convenience view holder - eases access to <tt>view</tt>
* child views. Never <code>null</code>.
* @param message
* Never <code>null</code>.
*/
private void bindView(final int position, final View view, final MessageViewHolder holder,
final MessageInfoHolder message) {
int maybeBoldTypeface = message.read ? Typeface.NORMAL : Typeface.BOLD;
// So that the mSelectedCount is only incremented/decremented
// when a user checks the checkbox (vs code)
holder.position = -1;
if (message.selected) {
holder.chip.setBackgroundDrawable(message.message.getFolder().getAccount().getCheckmarkChip().drawable());
}
else {
holder.chip.setBackgroundDrawable(message.message.getFolder().getAccount().generateColorChip(message.read,message.message.toMe(), message.message.ccMe(), message.message.fromMe(), message.flagged).drawable());
}
if (K9.useBackgroundAsUnreadIndicator()) {
int res = (message.read) ? R.attr.messageListReadItemBackgroundColor :
R.attr.messageListUnreadItemBackgroundColor;
TypedValue outValue = new TypedValue();
2012-10-05 21:41:32 -04:00
getActivity().getTheme().resolveAttribute(res, outValue, true);
view.setBackgroundColor(outValue.data);
}
String subject = null;
if ((message.message.getSubject() == null) || message.message.getSubject().equals("")) {
subject = (String) getText(R.string.general_no_subject);
} else {
subject = message.message.getSubject();
}
// We'll get badge support soon --jrv
// if (holder.badge != null) {
// String email = message.counterpartyAddress;
// holder.badge.assignContactFromEmail(email, true);
// if (email != null) {
// mContactsPictureLoader.loadContactPicture(email, holder.badge);
// }
// }
if (holder.preview != null) {
/*
* In the touchable UI, we have previews. Otherwise, we
* have just a "from" line.
* Because text views can't wrap around each other(?) we
* compose a custom view containing the preview and the
* from.
*/
CharSequence beforePreviewText = null;
if (mSenderAboveSubject) {
beforePreviewText = subject;
} else {
beforePreviewText = message.sender;
}
holder.preview.setText(new SpannableStringBuilder(recipientSigil(message))
.append(beforePreviewText).append(" ").append(message.message.getPreview()),
2011-01-06 11:55:08 -05:00
TextView.BufferType.SPANNABLE);
Spannable str = (Spannable)holder.preview.getText();
// Create a span section for the sender, and assign the correct font size and weight.
str.setSpan(new AbsoluteSizeSpan((mSenderAboveSubject ? mFontSizes.getMessageListSubject(): mFontSizes.getMessageListSender()), true),
0, beforePreviewText.length() + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
int color = (K9.getK9Theme() == K9.THEME_LIGHT) ?
Color.rgb(105, 105, 105) :
Color.rgb(160, 160, 160);
// set span for preview message.
str.setSpan(new ForegroundColorSpan(color), // How do I can specify the android.R.attr.textColorTertiary
beforePreviewText.length() + 1,
str.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
if (holder.from != null ) {
holder.from.setTypeface(null, maybeBoldTypeface);
if (mSenderAboveSubject) {
holder.from.setCompoundDrawablesWithIntrinsicBounds(
message.answered ? mAnsweredIcon : null, // left
null, // top
message.message.hasAttachments() ? mAttachmentIcon : null, // right
null); // bottom
holder.from.setText(message.sender);
} else {
holder.from.setText(new SpannableStringBuilder(recipientSigil(message)).append(message.sender));
}
}
if (holder.subject != null ) {
if (!mSenderAboveSubject) {
holder.subject.setCompoundDrawablesWithIntrinsicBounds(
message.answered ? mAnsweredIcon : null, // left
null, // top
message.message.hasAttachments() ? mAttachmentIcon : null, // right
null); // bottom
}
2012-08-20 22:09:34 -04:00
holder.subject.setTypeface(null, maybeBoldTypeface);
holder.subject.setText(subject);
}
holder.date.setText(message.getDate(mMessageHelper));
holder.position = position;
}
private String recipientSigil(MessageInfoHolder message) {
if (message.message.toMe()) {
return getString(R.string.messagelist_sent_to_me_sigil);
} else if (message.message.ccMe()) {
return getString(R.string.messagelist_sent_cc_me_sigil);
} else {
return "";
}
}
@Override
public boolean hasStableIds() {
return true;
}
}
class MessageViewHolder
implements OnCheckedChangeListener {
public TextView subject;
public TextView preview;
public TextView from;
public TextView time;
public TextView date;
public View chip;
public int position = -1;
2010-08-03 01:46:35 -04:00
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (position != -1) {
MessageInfoHolder message = (MessageInfoHolder) mAdapter.getItem(position);
toggleMessageSelect(message);
}
}
}
private View getFooterView(ViewGroup parent) {
if (mFooterView == null) {
mFooterView = mInflater.inflate(R.layout.message_list_item_footer, parent, false);
mFooterView.setId(R.layout.message_list_item_footer);
FooterViewHolder holder = new FooterViewHolder();
holder.progress = (ProgressBar) mFooterView.findViewById(R.id.message_list_progress);
holder.progress.setIndeterminate(true);
holder.main = (TextView) mFooterView.findViewById(R.id.main_text);
mFooterView.setTag(holder);
}
return mFooterView;
}
private void updateFooterView() {
if (mCurrentFolder != null && mAccount != null) {
if (mCurrentFolder.loading) {
final boolean showProgress = true;
updateFooter(getString(R.string.status_loading_more), showProgress);
} else {
String message;
if (!mCurrentFolder.lastCheckFailed) {
if (mAccount.getDisplayCount() == 0) {
message = getString(R.string.message_list_load_more_messages_action);
} else {
message = String.format(getString(R.string.load_more_messages_fmt), mAccount.getDisplayCount());
}
} else {
message = getString(R.string.status_loading_more_failed);
}
final boolean showProgress = false;
updateFooter(message, showProgress);
}
} else {
final boolean showProgress = false;
updateFooter(null, showProgress);
}
}
public void updateFooter(final String text, final boolean progressVisible) {
FooterViewHolder holder = (FooterViewHolder) mFooterView.getTag();
holder.progress.setVisibility(progressVisible ? ProgressBar.VISIBLE : ProgressBar.INVISIBLE);
if (text != null) {
holder.main.setText(text);
}
if (progressVisible || holder.main.getText().length() > 0) {
holder.main.setVisibility(View.VISIBLE);
} else {
holder.main.setVisibility(View.GONE);
}
}
static class FooterViewHolder {
public ProgressBar progress;
public TextView main;
}
private void setAllSelected(boolean isSelected) {
mSelectedCount = 0;
for (MessageInfoHolder holder : mAdapter.getMessages()) {
holder.selected = isSelected;
mSelectedCount += (isSelected ? 1 : 0);
}
computeBatchDirection();
mAdapter.notifyDataSetChanged();
if (isSelected) {
updateActionModeTitle();
computeSelectAllVisibility();
}
}
/**
* Set selection state for all messages.
*
* @param selected
* If {@code true} all messages get selected. Otherwise, all messages get deselected and
* action mode is finished.
*/
private void setSelectionState(boolean selected) {
mAdapter.setSelectionForAllMesages(selected);
if (selected) {
mSelectedCount = mAdapter.getCount();
2012-10-05 21:41:32 -04:00
mActionMode = getSherlockActivity().startActionMode(mActionModeCallback);
updateActionModeTitle();
2012-09-13 00:27:58 -04:00
computeSelectAllVisibility();
computeBatchDirection();
} else {
mSelectedCount = 0;
if (mActionMode != null) {
mActionMode.finish();
}
}
}
private void toggleMessageSelect(final MessageInfoHolder holder){
if (mActionMode != null) {
if (mSelectedCount == 1 && holder.selected) {
mActionMode.finish();
return;
}
} else {
2012-10-05 21:41:32 -04:00
mActionMode = getSherlockActivity().startActionMode(mActionModeCallback);
}
2012-09-13 00:27:58 -04:00
if (holder.selected) {
holder.selected = false;
mSelectedCount -= 1;
} else {
holder.selected = true;
mSelectedCount += 1;
}
mAdapter.notifyDataSetChanged();
computeBatchDirection();
updateActionModeTitle();
// make sure the onPrepareActionMode is called
mActionMode.invalidate();
2012-09-13 00:27:58 -04:00
computeSelectAllVisibility();
}
private void updateActionModeTitle() {
mActionMode.setTitle(String.format(getString(R.string.actionbar_selected), mSelectedCount));
}
2012-09-13 00:27:58 -04:00
private void computeSelectAllVisibility() {
mActionModeCallback.showSelectAll(mSelectedCount != mAdapter.getCount());
}
private void computeBatchDirection() {
boolean isBatchFlag = false;
boolean isBatchRead = false;
for (MessageInfoHolder holder : mAdapter.getMessages()) {
if (holder.selected) {
if (!holder.flagged) {
isBatchFlag = true;
}
if (!holder.read) {
isBatchRead = true;
}
if (isBatchFlag && isBatchRead) {
break;
}
}
}
mActionModeCallback.showMarkAsRead(isBatchRead);
mActionModeCallback.showFlag(isBatchFlag);
}
/**
* @param holders
* Messages to update. Never {@code null}.
* @param flag
* Flag to be updated on the specified messages. Never
* {@code null}.
* @param newState
* State to set for the given flag.
*/
private void setFlag(final List<MessageInfoHolder> holders, final Flag flag, final boolean newState) {
if (holders.isEmpty()) {
return;
}
final Message[] messageList = new Message[holders.size()];
int i = 0;
for (final Iterator<MessageInfoHolder> iterator = holders.iterator(); iterator.hasNext(); i++) {
final MessageInfoHolder holder = iterator.next();
messageList[i] = holder.message;
if (flag == Flag.SEEN) {
holder.read = newState;
} else if (flag == Flag.FLAGGED) {
holder.flagged = newState;
}
}
mController.setFlag(messageList, flag, newState);
mAdapter.sortMessages();
2012-10-05 21:41:32 -04:00
computeBatchDirection();
}
/**
* Display the message move activity.
2011-06-13 19:49:06 -04:00
*
* @param holders
* Never {@code null}.
*/
private void onMove(final List<MessageInfoHolder> holders) {
if (!checkCopyOrMovePossible(holders, FolderOperation.MOVE)) {
return;
}
final Folder folder = holders.size() == 1 ? holders.get(0).message.getFolder() : mCurrentFolder.folder;
displayFolderChoice(ACTIVITY_CHOOSE_FOLDER_MOVE, folder, holders);
}
/**
* Display the message copy activity.
2011-06-13 19:49:06 -04:00
*
* @param holders
* Never {@code null}.
*/
private void onCopy(final List<MessageInfoHolder> holders) {
if (!checkCopyOrMovePossible(holders, FolderOperation.COPY)) {
return;
}
final Folder folder = holders.size() == 1 ? holders.get(0).message.getFolder() : mCurrentFolder.folder;
displayFolderChoice(ACTIVITY_CHOOSE_FOLDER_COPY, folder, holders);
}
/**
* Helper method to manage the invocation of
* {@link #startActivityForResult(Intent, int)} for a folder operation
* ({@link ChooseFolder} activity), while saving a list of associated
* messages.
*
* @param requestCode
* If >= 0, this code will be returned in onActivityResult() when
* the activity exits.
* @param folder
* Never {@code null}.
* @param holders
* Messages to be affected by the folder operation. Never
* {@code null}.
* @see #startActivityForResult(Intent, int)
*/
private void displayFolderChoice(final int requestCode, final Folder folder, final List<MessageInfoHolder> holders) {
2012-10-05 21:41:32 -04:00
final Intent intent = new Intent(getActivity(), ChooseFolder.class);
intent.putExtra(ChooseFolder.EXTRA_ACCOUNT, folder.getAccount().getUuid());
intent.putExtra(ChooseFolder.EXTRA_CUR_FOLDER, folder.getName());
intent.putExtra(ChooseFolder.EXTRA_SEL_FOLDER, folder.getAccount().getLastSelectedFolderName());
// remember the selected messages for #onActivityResult
mActiveMessages = holders;
startActivityForResult(intent, requestCode);
}
/**
* @param holders
* Never {@code null}.
*/
private void onArchive(final List<MessageInfoHolder> holders) {
final String folderName = holders.get(0).message.getFolder().getAccount().getArchiveFolderName();
if (K9.FOLDER_NONE.equalsIgnoreCase(folderName)) {
return;
}
// TODO one should separate messages by account and call move afterwards
// (because each account might have a specific Archive folder name)
move(holders, folderName);
}
/**
* @param holders
* Never {@code null}.
*/
private void onSpam(final List<MessageInfoHolder> holders) {
if (K9.confirmSpam()) {
// remember the message selection for #onCreateDialog(int)
mActiveMessages = holders;
showDialog(R.id.dialog_confirm_spam);
} else {
onSpamConfirmed(holders);
}
}
/**
* @param holders
* Never {@code null}.
*/
private void onSpamConfirmed(final List<MessageInfoHolder> holders) {
final String folderName = holders.get(0).message.getFolder().getAccount().getSpamFolderName();
if (K9.FOLDER_NONE.equalsIgnoreCase(folderName)) {
return;
}
// TODO one should separate messages by account and call move afterwards
// (because each account might have a specific Spam folder name)
move(holders, folderName);
}
private static enum FolderOperation {
COPY, MOVE
}
/**
* Display an Toast message if any message isn't synchronized
2011-06-13 19:49:06 -04:00
*
* @param holders
* Never <code>null</code>.
2011-06-04 17:17:47 -04:00
* @param operation
* Never {@code null}.
2011-06-13 19:49:06 -04:00
*
* @return <code>true</code> if operation is possible
*/
private boolean checkCopyOrMovePossible(final List<MessageInfoHolder> holders, final FolderOperation operation) {
if (holders.isEmpty()) {
return false;
}
boolean first = true;
for (final MessageInfoHolder holder : holders) {
final Message message = holder.message;
if (first) {
first = false;
// account check
final Account account = message.getFolder().getAccount();
if ((operation == FolderOperation.MOVE && !mController.isMoveCapable(account)) || (operation == FolderOperation.COPY && !mController.isCopyCapable(account))) {
return false;
}
}
// message check
if ((operation == FolderOperation.MOVE && !mController.isMoveCapable(message)) || (operation == FolderOperation.COPY && !mController.isCopyCapable(message))) {
2012-10-05 21:41:32 -04:00
final Toast toast = Toast.makeText(getActivity(), R.string.move_copy_cannot_copy_unsynced_message,
2011-06-13 19:49:06 -04:00
Toast.LENGTH_LONG);
toast.show();
return false;
}
}
return true;
}
/**
* Helper method to get a List of message ready to be processed. This implementation will return a list containing the sole argument.
2011-06-13 19:49:06 -04:00
*
* @param holder Never {@code null}.
* @return Never {@code null}.
*/
private List<MessageInfoHolder> getSelectionFromMessage(final MessageInfoHolder holder) {
final List<MessageInfoHolder> selection = Collections.singletonList(holder);
return selection;
}
/**
* Helper method to get a List of message ready to be processed. This implementation will iterate over messages and choose the checked ones.
2011-06-13 19:49:06 -04:00
*
* @return Never {@code null}.
*/
private List<MessageInfoHolder> getSelectionFromCheckboxes() {
final List<MessageInfoHolder> selection = new ArrayList<MessageInfoHolder>();
for (final MessageInfoHolder holder : mAdapter.getMessages()) {
if (holder.selected) {
selection.add(holder);
}
}
return selection;
}
/**
* Copy the specified messages to the specified folder.
*
* @param holders Never {@code null}.
* @param destination Never {@code null}.
*/
private void copy(final List<MessageInfoHolder> holders, final String destination) {
copyOrMove(holders, destination, FolderOperation.COPY);
}
/**
* Move the specified messages to the specified folder.
*
* @param holders Never {@code null}.
* @param destination Never {@code null}.
*/
private void move(final List<MessageInfoHolder> holders, final String destination) {
copyOrMove(holders, destination, FolderOperation.MOVE);
}
/**
* The underlying implementation for {@link #copy(List, String)} and
* {@link #move(List, String)}. This method was added mainly because those 2
* methods share common behavior.
2011-06-13 19:49:06 -04:00
*
* Note: Must be called from the UI thread!
*
* @param holders
* Never {@code null}.
* @param destination
* Never {@code null}.
* @param operation
* Never {@code null}.
*/
private void copyOrMove(final List<MessageInfoHolder> holders, final String destination, final FolderOperation operation) {
if (K9.FOLDER_NONE.equalsIgnoreCase(destination)) {
return;
}
boolean first = true;
Account account = null;
String folderName = null;
final List<Message> messages = new ArrayList<Message>(holders.size());
for (final MessageInfoHolder holder : holders) {
final Message message = holder.message;
if (first) {
first = false;
folderName = message.getFolder().getName();
account = message.getFolder().getAccount();
if ((operation == FolderOperation.MOVE && !mController.isMoveCapable(account)) || (operation == FolderOperation.COPY && !mController.isCopyCapable(account))) {
// account is not copy/move capable
return;
}
} else if (!account.equals(message.getFolder().getAccount())
2011-06-13 19:49:06 -04:00
|| !folderName.equals(message.getFolder().getName())) {
// make sure all messages come from the same account/folder?
return;
}
if ((operation == FolderOperation.MOVE && !mController.isMoveCapable(message)) || (operation == FolderOperation.COPY && !mController.isCopyCapable(message))) {
2012-10-05 21:41:32 -04:00
final Toast toast = Toast.makeText(getActivity(), R.string.move_copy_cannot_copy_unsynced_message,
2011-06-13 19:49:06 -04:00
Toast.LENGTH_LONG);
toast.show();
// XXX return meaningful error value?
// message isn't synchronized
return;
}
messages.add(message);
}
if (operation == FolderOperation.MOVE) {
mController.moveMessages(account, folderName, messages.toArray(new Message[messages.size()]), destination,
2011-06-13 19:49:06 -04:00
null);
mAdapter.removeMessages(holders);
} else {
mController.copyMessages(account, folderName, messages.toArray(new Message[messages.size()]), destination,
2011-06-13 19:49:06 -04:00
null);
}
}
Merge branch 'mail-on-sd' * mail-on-sd: (40 commits) Added more comments to explain how the locking mecanism works for LocalStore Fixed wrong method being called during experimental provider initialization (since provider isn't enabled, that didn't harm) Add more comments about how the various StorageProviders work and how they're enabled find src/com/fsck/ -name \*.java|xargs astyle --style=ansi --mode=java --indent-switches --indent=spaces=4 --convert-tabs French localization for storage related settings Remove unused SD card strings (replaced with storage indirection) Merge mail-on-sd branch from trunk Reset mail service on storage mount (even if no account uses the storage, to be improved) find src/com/fsck/ -name \*.java|xargs astyle --style=ansi --mode=java --indent-switches --indent=spaces=4 --convert-tabs Migraion -> Migration move the Storage location preference into preferences rather than the wizard. Made LocalStore log less verbose Added @Override compile checks Added ACTION_SHUTDOWN broadcast receiver to properly initiate shutdown sequence (not yet implemented) and cancel any scheduled Intent Be more consistent about which SQLiteDatabase variable is used (from instance variable to argument variable) to make code more refactoring-friendly (class is already big, code extraction should be easier if not referencing the instance variable). Added transaction timing logging Factorised storage lock/transaction handling code for regular operations. Use DB transactions to batch modifications (makes code more robust / could improve performances) Merge mail-on-sd branch from trunk Update issue 888 Added DB close on unmount / DB open on mount Update issue 888 Back to account list when underlying storage not available/unmounting in MessageView / MessageList ...
2010-11-13 16:40:56 -05:00
/**
* Return the currently "open" account if available.
*
* @param prefs
* A {@link Preferences} instance that might be used to retrieve the current
* {@link Account}.
*
* @return The {@code Account} all displayed messages belong to.
*/
private Account getCurrentAccount(Preferences prefs) {
Account account = null;
if (mQueryString != null && !mIntegrate && mAccountUuids != null &&
mAccountUuids.length == 1) {
String uuid = mAccountUuids[0];
account = prefs.getAccount(uuid);
} else if (mAccount != null) {
account = mAccount;
}
return account;
}
2012-09-13 00:27:58 -04:00
class ActionModeCallback implements ActionMode.Callback {
private MenuItem mSelectAll;
private MenuItem mMarkAsRead;
private MenuItem mMarkAsUnread;
private MenuItem mFlag;
private MenuItem mUnflag;
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
2012-09-13 00:27:58 -04:00
mSelectAll = menu.findItem(R.id.select_all);
mMarkAsRead = menu.findItem(R.id.mark_as_read);
mMarkAsUnread = menu.findItem(R.id.mark_as_unread);
mFlag = menu.findItem(R.id.flag);
mUnflag = menu.findItem(R.id.unflag);
if (mQueryString != null) {
// show all
menu.findItem(R.id.move).setVisible(true);
menu.findItem(R.id.archive).setVisible(true);
menu.findItem(R.id.spam).setVisible(true);
menu.findItem(R.id.copy).setVisible(true);
// hide uncapable
/*
* TODO think of a better way then looping over all
* messages.
*/
final List<MessageInfoHolder> selection = getSelectionFromCheckboxes();
Account account;
for (MessageInfoHolder holder : selection) {
account = holder.message.getFolder().getAccount();
setContextCapabilities(account, menu);
}
}
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
mActionMode = null;
2012-09-13 00:27:58 -04:00
mSelectAll = null;
mMarkAsRead = null;
mMarkAsUnread = null;
mFlag = null;
mUnflag = null;
setAllSelected(false);
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.message_list_context, menu);
// check capabilities
if (mQueryString == null) {
setContextCapabilities(mAccount, menu);
}
return true;
}
/**
* Disables menu options based on if the account supports it or not.
* It also checks the controller and for now the 'mode' the messagelist
* is operation in ( query or not ).
*
* @param mAccount Account to check capabilities of.
* @param menu Menu to adapt.
*/
private void setContextCapabilities(Account mAccount, Menu menu) {
/*
* TODO get rid of this when we finally split the messagelist into
* a folder content display and a search result display
*/
if (mQueryString != null) {
menu.findItem(R.id.move).setVisible(false);
menu.findItem(R.id.copy).setVisible(false);
menu.findItem(R.id.archive).setVisible(false);
menu.findItem(R.id.spam).setVisible(false);
return;
}
// hide unsupported
if (!mController.isCopyCapable(mAccount)) {
menu.findItem(R.id.copy).setVisible(false);
}
if (!mController.isMoveCapable(mAccount)) {
menu.findItem(R.id.move).setVisible(false);
menu.findItem(R.id.archive).setVisible(false);
menu.findItem(R.id.spam).setVisible(false);
}
if (!mAccount.hasArchiveFolder()) {
menu.findItem(R.id.archive).setVisible(false);
}
if (!mAccount.hasSpamFolder()) {
menu.findItem(R.id.spam).setVisible(false);
}
}
2012-09-13 00:27:58 -04:00
public void showSelectAll(boolean show) {
if (mActionMode != null) {
mSelectAll.setVisible(show);
}
}
public void showMarkAsRead(boolean show) {
if (mActionMode != null) {
mMarkAsRead.setVisible(show);
mMarkAsUnread.setVisible(!show);
}
}
public void showFlag(boolean show) {
if (mActionMode != null) {
mFlag.setVisible(show);
mUnflag.setVisible(!show);
}
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
final List<MessageInfoHolder> selection = getSelectionFromCheckboxes();
/*
* In the following we assume that we can't move or copy
* mails to the same folder. Also that spam isn't available if we are
* in the spam folder,same for archive.
*
* This is the case currently so safe assumption.
*/
switch (item.getItemId()) {
case R.id.delete: {
onDelete(selection);
mSelectedCount = 0;
break;
}
case R.id.mark_as_read: {
setFlag(selection, Flag.SEEN, true);
break;
}
case R.id.mark_as_unread: {
setFlag(selection, Flag.SEEN, false);
break;
}
case R.id.flag: {
setFlag(selection, Flag.FLAGGED, true);
break;
}
case R.id.unflag: {
setFlag(selection, Flag.FLAGGED, false);
break;
}
2012-09-13 00:27:58 -04:00
case R.id.select_all: {
setAllSelected(true);
break;
}
// only if the account supports this
case R.id.archive: {
onArchive(selection);
mSelectedCount = 0;
break;
}
case R.id.spam: {
onSpam(selection);
mSelectedCount = 0;
break;
}
case R.id.move: {
onMove(selection);
mSelectedCount = 0;
break;
}
case R.id.copy: {
onCopy(selection);
mSelectedCount = 0;
break;
}
}
if (mSelectedCount == 0) {
mActionMode.finish();
}
return true;
}
}
2012-09-12 20:58:44 -04:00
2012-10-05 21:41:32 -04:00
@Override
public void doPositiveClick(int dialogId) {
switch (dialogId) {
case R.id.dialog_confirm_spam: {
onSpamConfirmed(mActiveMessages);
// No further need for this reference
mActiveMessages = null;
break;
}
}
}
@Override
public void doNegativeClick(int dialogId) {
switch (dialogId) {
case R.id.dialog_confirm_spam: {
// No further need for this reference
mActiveMessages = null;
break;
}
}
}
@Override
public void dialogCancelled(int dialogId) {
doNegativeClick(dialogId);
}
public void checkMail() {
mController.synchronizeMailbox(mAccount, mFolderName, mAdapter.mListener, null);
mController.sendPendingMessages(mAccount, mAdapter.mListener);
}
/**
* We need to do some special clean up when leaving a remote search result screen. If no remote search is
* in progress, this method does nothing special.
*/
@Override
public void onStop() {
// If we represent a remote search, then kill that before going back.
if (mSearchAccount != null && mSearchFolder != null && mRemoteSearchFuture != null) {
try {
Log.i(K9.LOG_TAG, "Remote search in progress, attempting to abort...");
// Canceling the future stops any message fetches in progress.
final boolean cancelSuccess = mRemoteSearchFuture.cancel(true); // mayInterruptIfRunning = true
if (!cancelSuccess) {
Log.e(K9.LOG_TAG, "Could not cancel remote search future.");
}
// Closing the folder will kill off the connection if we're mid-search.
Context appContext = getActivity().getApplicationContext();
final Account searchAccount = Preferences.getPreferences(appContext).getAccount(mSearchAccount);
final Store remoteStore = searchAccount.getRemoteStore();
final Folder remoteFolder = remoteStore.getFolder(mSearchFolder);
remoteFolder.close();
// Send a remoteSearchFinished() message for good measure.
mAdapter.mListener.remoteSearchFinished(searchAccount, mSearchFolder, 0, null);
} catch (Exception e) {
// Since the user is going back, log and squash any exceptions.
Log.e(K9.LOG_TAG, "Could not abort remote search before going back", e);
}
}
super.onStop();
}
public ArrayList<MessageReference> getMessageReferences() {
ArrayList<MessageReference> messageRefs = new ArrayList<MessageReference>();
for (MessageInfoHolder holder : mAdapter.getMessages()) {
MessageReference ref = holder.message.makeMessageReference();
messageRefs.add(ref);
}
return messageRefs;
}
public void selectAll() {
setSelectionState(true);
}
public void onMoveUp() {
int currentPosition = mListView.getSelectedItemPosition();
if (currentPosition == AdapterView.INVALID_POSITION || mListView.isInTouchMode()) {
currentPosition = mListView.getFirstVisiblePosition();
}
if (currentPosition > 0) {
mListView.setSelection(currentPosition - 1);
}
}
public void onMoveDown() {
int currentPosition = mListView.getSelectedItemPosition();
if (currentPosition == AdapterView.INVALID_POSITION || mListView.isInTouchMode()) {
currentPosition = mListView.getFirstVisiblePosition();
}
if (currentPosition < mListView.getCount()) {
mListView.setSelection(currentPosition + 1);
}
}
public interface MessageListFragmentListener {
void setMessageListProgress(int level);
void remoteSearch(String searchAccount, String searchFolder, String queryString);
void showMoreFromSameSender(String senderAddress);
void onResendMessage(Message message);
void onForward(Message message);
void onReply(Message message);
void onReplyAll(Message message);
void openMessage(MessageReference messageReference);
void setMessageListTitle(String title);
void setMessageListSubTitle(String subTitle);
void setUnreadCount(int unread);
void onCompose(Account account);
boolean startSearch(Account account, String folderName);
}
public void onReverseSort() {
changeSort(mSortType);
}
private MessageInfoHolder getSelection() {
return (MessageInfoHolder) mListView.getSelectedItem();
}
public void onDelete() {
MessageInfoHolder message = getSelection();
if (message != null) {
onDelete(Collections.singletonList(message));
}
}
public void toggleMessageSelect() {
MessageInfoHolder message = getSelection();
if (message != null) {
toggleMessageSelect(message);
}
}
public void onToggleFlag() {
MessageInfoHolder message = getSelection();
if (message != null) {
setFlag(Collections.singletonList(message), Flag.FLAGGED, !message.flagged);
}
}
public void onMove() {
MessageInfoHolder message = getSelection();
if (message != null) {
onMove(Collections.singletonList(message));
}
}
public void onArchive() {
MessageInfoHolder message = getSelection();
if (message != null) {
onArchive(Collections.singletonList(message));
}
}
public void onCopy() {
MessageInfoHolder message = getSelection();
if (message != null) {
onCopy(Collections.singletonList(message));
}
}
public void onToggleRead() {
MessageInfoHolder message = getSelection();
if (message != null) {
setFlag(Collections.singletonList(message), Flag.SEEN, !message.read);
}
}
public boolean isSearchQuery() {
return (mQueryString != null || mIntegrate);
}
public boolean isOutbox() {
return (mFolderName != null && mFolderName.equals(mAccount.getOutboxFolderName()));
}
public boolean isErrorFolder() {
return K9.ERROR_FOLDER_NAME.equals(mFolderName);
}
public boolean isRemoteFolder() {
if (isSearchQuery() || isOutbox() || isErrorFolder()) {
return false;
}
if (!mController.isMoveCapable(mAccount)) {
// For POP3 accounts only the Inbox is a remote folder.
return (mFolderName != null && !mFolderName.equals(mAccount.getInboxFolderName()));
}
return true;
}
public boolean isAccountExpungeCapable() {
try {
return (mAccount != null && mAccount.getRemoteStore().isExpungeCapable());
} catch (Exception e) {
return false;
}
}
public void onRemoteSearch() {
// Remote search is useless without the network.
if (mHasConnectivity) {
onRemoteSearchRequested(true);
} else {
Toast.makeText(getActivity(), getText(R.string.remote_search_unavailable_no_network),
Toast.LENGTH_SHORT).show();
}
}
public boolean isRemoteSearch() {
return mRemoteSearch;
}
public boolean isRemoteSearchAllowed() {
if (!isSearchQuery() || mRemoteSearch || mSearchFolder == null || mSearchAccount == null) {
return false;
}
Context appContext = getActivity().getApplicationContext();
final Preferences prefs = Preferences.getPreferences(appContext);
boolean allowRemoteSearch = false;
final Account searchAccount = prefs.getAccount(mSearchAccount);
if (searchAccount != null) {
allowRemoteSearch = searchAccount.allowRemoteSearch();
}
return allowRemoteSearch;
}
public boolean onSearchRequested() {
String folderName = (mCurrentFolder != null) ? mCurrentFolder.name : null;
return mFragmentListener.startSearch(mAccount, folderName);
}
}