1
0
mirror of https://github.com/moparisthebest/k-9 synced 2024-11-27 19:52:17 -05:00
k-9/src/com/fsck/k9/activity/FolderMessageList.java
Brock Tice 15ba4d0f39 Made "Load more messages" cue a little more informative.
Now pulls k9.VISIBLE_LIMIT_INCREMENT and says "Load up to (increment value) more".
Also, the build number was auto-updated by my build script. This shows up in the debug menu. It would be nice to have ant handle this, but I'm not sure how to make it do that.
2008-11-04 19:40:44 +00:00

1297 lines
49 KiB
Java

package com.fsck.k9.activity;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import android.app.ExpandableListActivity;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Process;
import android.util.Config;
import android.util.Log;
import android.view.ContextMenu;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.ContextMenu.ContextMenuInfo;
import android.widget.BaseExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
import com.fsck.k9.Account;
import com.fsck.k9.k9;
import com.fsck.k9.MessagingController;
import com.fsck.k9.MessagingListener;
import com.fsck.k9.R;
import com.fsck.k9.Utility;
import com.fsck.k9.Preferences;
import com.fsck.k9.activity.FolderMessageList.FolderMessageListAdapter.FolderInfoHolder;
import com.fsck.k9.activity.FolderMessageList.FolderMessageListAdapter.MessageInfoHolder;
import com.fsck.k9.activity.setup.AccountSettings;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mail.store.LocalStore.LocalMessage;
import com.fsck.k9.mail.store.LocalStore;
/**
* FolderMessageList is the primary user interface for the program. This Activity shows
* a two level list of the Account's folders and each folder's messages. From this
* Activity the user can perform all standard message operations.
*
*
* TODO some things that are slowing us down:
* Need a way to remove state such as progress bar and per folder progress on
* resume if the command has completed.
*
* TODO
* Break out seperate functions for:
* refresh local folders
* refresh remote folders
* refresh open folder local messages
* refresh open folder remote messages
*
* And don't refresh remote folders ever unless the user runs a refresh. Maybe not even then.
*/
public class FolderMessageList extends ExpandableListActivity {
private static final String EXTRA_ACCOUNT = "account";
private static final String EXTRA_CLEAR_NOTIFICATION = "clearNotification";
private static final String EXTRA_INITIAL_FOLDER = "initialFolder";
private static final String STATE_KEY_LIST =
"com.fsck.k9.activity.folderlist_expandableListState";
private static final String STATE_KEY_EXPANDED_GROUP =
"com.fsck.k9.activity.folderlist_expandedGroup";
private static final String STATE_KEY_EXPANDED_GROUP_SELECTION =
"com.fsck.k9.activity.folderlist_expandedGroupSelection";
private static final int UPDATE_FOLDER_ON_EXPAND_INTERVAL_MS = (1000 * 60 * 3);
private static final int[] colorChipResIds = new int[] {
R.drawable.appointment_indicator_leftside_1,
R.drawable.appointment_indicator_leftside_2,
R.drawable.appointment_indicator_leftside_3,
R.drawable.appointment_indicator_leftside_4,
R.drawable.appointment_indicator_leftside_5,
R.drawable.appointment_indicator_leftside_6,
R.drawable.appointment_indicator_leftside_7,
R.drawable.appointment_indicator_leftside_8,
R.drawable.appointment_indicator_leftside_9,
R.drawable.appointment_indicator_leftside_10,
R.drawable.appointment_indicator_leftside_11,
R.drawable.appointment_indicator_leftside_12,
R.drawable.appointment_indicator_leftside_13,
R.drawable.appointment_indicator_leftside_14,
R.drawable.appointment_indicator_leftside_15,
R.drawable.appointment_indicator_leftside_16,
R.drawable.appointment_indicator_leftside_17,
R.drawable.appointment_indicator_leftside_18,
R.drawable.appointment_indicator_leftside_19,
R.drawable.appointment_indicator_leftside_20,
R.drawable.appointment_indicator_leftside_21,
};
private ExpandableListView mListView;
private int colorChipResId;
private FolderMessageListAdapter mAdapter;
private LayoutInflater mInflater;
private Account mAccount;
/**
* Stores the name of the folder that we want to open as soon as possible after load. It is
* set to null once the folder has been opened once.
*/
private String mInitialFolder;
private DateFormat mDateFormat = DateFormat.getDateInstance(DateFormat.SHORT);
private DateFormat mTimeFormat = DateFormat.getTimeInstance(DateFormat.SHORT);
private int mExpandedGroup = -1;
private boolean mRestoringState;
private boolean mRefreshRemote;
private FolderMessageListHandler mHandler = new FolderMessageListHandler();
class FolderMessageListHandler extends Handler {
private static final int MSG_PROGRESS = 2;
private static final int MSG_DATA_CHANGED = 3;
private static final int MSG_EXPAND_GROUP = 5;
private static final int MSG_FOLDER_LOADING = 7;
private static final int MSG_REMOVE_MESSAGE = 11;
private static final int MSG_SYNC_MESSAGES = 13;
private static final int MSG_FOLDER_STATUS = 17;
@Override
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case MSG_PROGRESS:
setProgressBarIndeterminateVisibility(msg.arg1 != 0);
break;
case MSG_DATA_CHANGED:
mAdapter.notifyDataSetChanged();
break;
case MSG_EXPAND_GROUP:
mListView.expandGroup(msg.arg1);
break;
/*
* The following functions modify the state of the adapter's underlying list and
* must be run here, in the main thread, so that notifyDataSetChanged is run
* before any further requests are made to the adapter.
*/
case MSG_FOLDER_LOADING: {
FolderInfoHolder folder = mAdapter.getFolder((String) msg.obj);
if (folder != null) {
folder.loading = msg.arg1 != 0;
mAdapter.notifyDataSetChanged();
}
break;
}
case MSG_REMOVE_MESSAGE: {
FolderInfoHolder folder = (FolderInfoHolder) ((Object[]) msg.obj)[0];
MessageInfoHolder message = (MessageInfoHolder) ((Object[]) msg.obj)[1];
folder.messages.remove(message);
mAdapter.notifyDataSetChanged();
break;
}
case MSG_SYNC_MESSAGES: {
FolderInfoHolder folder = (FolderInfoHolder) ((Object[]) msg.obj)[0];
Message[] messages = (Message[]) ((Object[]) msg.obj)[1];
folder.messages.clear();
for (Message message : messages) {
mAdapter.addOrUpdateMessage(folder, message, false, false);
}
Collections.sort(folder.messages);
mAdapter.notifyDataSetChanged();
break;
}
case MSG_FOLDER_STATUS: {
String folderName = (String) ((Object[]) msg.obj)[0];
String status = (String) ((Object[]) msg.obj)[1];
FolderInfoHolder folder = mAdapter.getFolder(folderName);
if (folder != null) {
folder.status = status;
mAdapter.notifyDataSetChanged();
}
break;
}
default:
super.handleMessage(msg);
}
}
public void synchronizeMessages(FolderInfoHolder folder, Message[] messages) {
android.os.Message msg = new android.os.Message();
msg.what = MSG_SYNC_MESSAGES;
msg.obj = new Object[] { folder, messages };
sendMessage(msg);
}
public void removeMessage(FolderInfoHolder folder, MessageInfoHolder message) {
android.os.Message msg = new android.os.Message();
msg.what = MSG_REMOVE_MESSAGE;
msg.obj = new Object[] { folder, message };
sendMessage(msg);
}
public void folderLoading(String folder, boolean loading) {
android.os.Message msg = new android.os.Message();
msg.what = MSG_FOLDER_LOADING;
msg.arg1 = loading ? 1 : 0;
msg.obj = folder;
sendMessage(msg);
}
public void progress(boolean progress) {
android.os.Message msg = new android.os.Message();
msg.what = MSG_PROGRESS;
msg.arg1 = progress ? 1 : 0;
sendMessage(msg);
}
public void dataChanged() {
sendEmptyMessage(MSG_DATA_CHANGED);
}
public void expandGroup(int groupPosition) {
android.os.Message msg = new android.os.Message();
msg.what = MSG_EXPAND_GROUP;
msg.arg1 = groupPosition;
sendMessage(msg);
}
public void folderStatus(String folder, String status) {
android.os.Message msg = new android.os.Message();
msg.what = MSG_FOLDER_STATUS;
msg.obj = new String[] { folder, status };
sendMessage(msg);
}
}
/**
* This class is responsible for reloading the list of local messages for a given folder,
* notifying the adapter that the message have been loaded and queueing up a remote
* update of the folder.
*/
class FolderUpdateWorker implements Runnable {
String mFolder;
boolean mSynchronizeRemote;
/**
* Create a worker for the given folder and specifying whether the
* worker should synchronize the remote folder or just the local one.
* @param folder
* @param synchronizeRemote
*/
public FolderUpdateWorker(String folder, boolean synchronizeRemote) {
mFolder = folder;
mSynchronizeRemote = synchronizeRemote;
}
public void run() {
// Lower our priority
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// Synchronously load the list of local messages
MessagingController.getInstance(getApplication()).listLocalMessages(
mAccount,
mFolder,
mAdapter.mListener);
if (mSynchronizeRemote) {
// Tell the MessagingController to run a remote update of this folder
// at it's leisure
MessagingController.getInstance(getApplication()).synchronizeMailbox(
mAccount,
mFolder,
mAdapter.mListener);
}
}
}
public static void actionHandleAccount(Context context, Account account, String initialFolder) {
Intent intent = new Intent(context, FolderMessageList.class);
intent.putExtra(EXTRA_ACCOUNT, account);
if (initialFolder != null) {
intent.putExtra(EXTRA_INITIAL_FOLDER, initialFolder);
}
context.startActivity(intent);
}
public static void actionHandleAccount(Context context, Account account) {
actionHandleAccount(context, account, null);
}
public static Intent actionHandleAccountIntent(Context context, Account account, String initialFolder) {
Intent intent = new Intent(context, FolderMessageList.class);
intent.putExtra(EXTRA_ACCOUNT, account);
intent.putExtra(EXTRA_CLEAR_NOTIFICATION, true);
if (initialFolder != null) {
intent.putExtra(EXTRA_INITIAL_FOLDER, initialFolder);
}
return intent;
}
public static Intent actionHandleAccountIntent(Context context, Account account) {
return actionHandleAccountIntent(context, account, null);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
mListView = getExpandableListView();
mListView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_INSET);
mListView.setLongClickable(true);
registerForContextMenu(mListView);
/*
* We manually save and restore the list's state because our adapter is slow.
*/
mListView.setSaveEnabled(false);
getExpandableListView().setGroupIndicator(
getResources().getDrawable(R.drawable.expander_ic_folder));
mInflater = getLayoutInflater();
Intent intent = getIntent();
mAccount = (Account)intent.getSerializableExtra(EXTRA_ACCOUNT);
// Take the initial folder into account only if we are *not* restoring the activity already
if (savedInstanceState == null) {
mInitialFolder = intent.getStringExtra(EXTRA_INITIAL_FOLDER);
}
/*
* Since the color chip is always the same color for a given account we just cache the id
* of the chip right here.
*/
colorChipResId = colorChipResIds[mAccount.getAccountNumber() % colorChipResIds.length];
mAdapter = new FolderMessageListAdapter();
final Object previousData = getLastNonConfigurationInstance();
if (previousData != null) {
//noinspection unchecked
mAdapter.mFolders = (ArrayList<FolderInfoHolder>) previousData;
}
setListAdapter(mAdapter);
if (savedInstanceState != null) {
mRestoringState = true;
onRestoreListState(savedInstanceState);
mRestoringState = false;
}
setTitle(mAccount.getDescription());
}
private void onRestoreListState(Bundle savedInstanceState) {
final int expandedGroup = savedInstanceState.getInt(STATE_KEY_EXPANDED_GROUP, -1);
if (expandedGroup >= 0 && mAdapter.getGroupCount() > expandedGroup) {
mListView.expandGroup(expandedGroup);
long selectedChild = savedInstanceState.getLong(STATE_KEY_EXPANDED_GROUP_SELECTION, -1);
if (selectedChild != ExpandableListView.PACKED_POSITION_VALUE_NULL) {
mListView.setSelection(mListView.getFlatListPosition(selectedChild));
}
}
mListView.onRestoreInstanceState(savedInstanceState.getParcelable(STATE_KEY_LIST));
}
@Override
public Object onRetainNonConfigurationInstance() {
return mAdapter.mFolders;
}
@Override
public void onPause() {
super.onPause();
MessagingController.getInstance(getApplication()).removeListener(mAdapter.mListener);
}
/**
* On resume we refresh the folder list (in the background) and we refresh the messages
* for any folder that is currently open. This guarantees that things like unread message
* count and read status are updated.
*/
@Override
public void onResume() {
super.onResume();
NotificationManager notifMgr = (NotificationManager)
getSystemService(Context.NOTIFICATION_SERVICE);
notifMgr.cancel(1);
MessagingController.getInstance(getApplication()).addListener(mAdapter.mListener);
mAccount.refresh(Preferences.getPreferences(this));
onRefresh(false);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(STATE_KEY_LIST, mListView.onSaveInstanceState());
outState.putInt(STATE_KEY_EXPANDED_GROUP, mExpandedGroup);
outState.putLong(STATE_KEY_EXPANDED_GROUP_SELECTION, mListView.getSelectedPosition());
}
@Override
public void onGroupCollapse(int groupPosition) {
super.onGroupCollapse(groupPosition);
mExpandedGroup = -1;
}
@Override
public void onGroupExpand(int groupPosition) {
super.onGroupExpand(groupPosition);
if (mExpandedGroup != -1) {
mListView.collapseGroup(mExpandedGroup);
}
mExpandedGroup = groupPosition;
if (!mRestoringState) {
/*
* Scroll the selected item to the top of the screen.
*/
int position = mListView.getFlatListPosition(
ExpandableListView.getPackedPositionForGroup(groupPosition));
mListView.setSelectionFromTop(position, 0);
}
final FolderInfoHolder folder = (FolderInfoHolder) mAdapter.getGroup(groupPosition);
/*
* We'll only do a hard refresh of a particular folder every 3 minutes or if the user
* specifically asks for a refresh.
*/
if (System.currentTimeMillis() - folder.lastChecked
> UPDATE_FOLDER_ON_EXPAND_INTERVAL_MS) {
folder.lastChecked = System.currentTimeMillis();
// TODO: If the previous thread is already running, we should cancel it
new Thread(new FolderUpdateWorker(folder.name, true)).start();
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
int group = mListView.getPackedPositionGroup(mListView.getSelectedId());
int item =(mListView.getSelectedItemPosition() -1 );
// Guard against hitting delete on group names
//
try {
MessageInfoHolder message = (MessageInfoHolder) mAdapter.getChild(group, item);
switch (keyCode) {
case KeyEvent.KEYCODE_DEL: { onDelete(message); return true;}
case KeyEvent.KEYCODE_C: { onCompose(); return true;}
case KeyEvent.KEYCODE_Q: { onAccounts(); return true; }
case KeyEvent.KEYCODE_F: { onForward(message); return true;}
case KeyEvent.KEYCODE_A: { onReplyAll(message); return true; }
case KeyEvent.KEYCODE_R: { onReply(message); return true; }
}
}
finally {
return super.onKeyDown(keyCode, event);
}
}
@Override
public boolean onChildClick(ExpandableListView parent, View v, int groupPosition,
int childPosition, long id) {
FolderInfoHolder folder = (FolderInfoHolder) mAdapter.getGroup(groupPosition);
if (folder.outbox) {
return false;
}
if (childPosition == folder.messages.size() && !folder.loading) {
if (folder.status == null) {
MessagingController.getInstance(getApplication()).loadMoreMessages(
mAccount,
folder.name,
mAdapter.mListener);
return false;
}
else {
MessagingController.getInstance(getApplication()).synchronizeMailbox(
mAccount,
folder.name,
mAdapter.mListener);
return false;
}
}
else if (childPosition >= folder.messages.size()) {
return false;
}
MessageInfoHolder message = (MessageInfoHolder) mAdapter.getChild(groupPosition, childPosition);
onOpenMessage(folder, message);
return true;
}
private void onRefresh(final boolean forceRemote) {
if (forceRemote) {
mRefreshRemote = true;
}
new Thread() {
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
MessagingController.getInstance(getApplication()).listFolders(
mAccount,
forceRemote,
mAdapter.mListener);
if (forceRemote) {
MessagingController.getInstance(getApplication()).sendPendingMessages(
mAccount,
null);
}
}
}.start();
}
private void onOpenMessage(FolderInfoHolder folder, MessageInfoHolder message) {
/*
* We set read=true here for UI performance reasons. The actual value will get picked up
* on the refresh when the Activity is resumed but that may take a second or so and we
* don't want this to show and then go away.
* I've gone back and forth on this, and this gives a better UI experience, so I am
* putting it back in.
*/
if (!message.read) {
message.read = true;
mHandler.dataChanged();
}
if (folder.name.equals(mAccount.getDraftsFolderName())) {
MessageCompose.actionEditDraft(this, mAccount, message.message);
}
else {
ArrayList<String> folderUids = new ArrayList<String>();
for (MessageInfoHolder holder : folder.messages) {
folderUids.add(holder.uid);
}
MessageView.actionView(this, mAccount, folder.name, message.uid, folderUids);
}
}
private void onEditAccount() {
AccountSettings.actionSettings(this, mAccount);
}
private void onAccounts() {
startActivity(new Intent(this, Accounts.class));
finish();
}
private void onCompose() {
MessageCompose.actionCompose(this, mAccount);
}
private void onDelete(MessageInfoHolder holder) {
MessagingController.getInstance(getApplication()).deleteMessage(
mAccount,
holder.message.getFolder().getName(),
holder.message,
null);
mAdapter.removeMessage(holder.message.getFolder().getName(), holder.uid);
Toast.makeText(this, R.string.message_deleted_toast, Toast.LENGTH_SHORT).show();
}
private void onReply(MessageInfoHolder holder) {
MessageCompose.actionReply(this, mAccount, holder.message, false);
}
private void onReplyAll(MessageInfoHolder holder) {
MessageCompose.actionReply(this, mAccount, holder.message, true);
}
private void onForward(MessageInfoHolder holder) {
MessageCompose.actionForward(this, mAccount, holder.message);
}
private void onToggleRead(MessageInfoHolder holder) {
MessagingController.getInstance(getApplication()).markMessageRead(
mAccount,
holder.message.getFolder().getName(),
holder.uid,
!holder.read);
holder.read = !holder.read;
onRefresh(false);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.refresh:
onRefresh(true);
return true;
case R.id.accounts:
onAccounts();
return true;
case R.id.compose:
onCompose();
return true;
case R.id.account_settings:
onEditAccount();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.folder_message_list_option, menu);
return true;
}
@Override
public boolean onContextItemSelected(MenuItem item) {
ExpandableListContextMenuInfo info =
(ExpandableListContextMenuInfo) item.getMenuInfo();
int groupPosition =
ExpandableListView.getPackedPositionGroup(info.packedPosition);
int childPosition =
ExpandableListView.getPackedPositionChild(info.packedPosition);
FolderInfoHolder folder = (FolderInfoHolder) mAdapter.getGroup(groupPosition);
if (childPosition < mAdapter.getChildrenCount(groupPosition)) {
MessageInfoHolder holder =
(MessageInfoHolder) mAdapter.getChild(groupPosition, childPosition);
switch (item.getItemId()) {
case R.id.open:
onOpenMessage(folder, holder);
break;
case R.id.delete:
onDelete(holder);
break;
case R.id.reply:
onReply(holder);
break;
case R.id.reply_all:
onReplyAll(holder);
break;
case R.id.forward:
onForward(holder);
break;
case R.id.mark_as_read:
onToggleRead(holder);
break;
}
}
return super.onContextItemSelected(item);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuInfo;
if (ExpandableListView.getPackedPositionType(info.packedPosition) ==
ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
long packedPosition = info.packedPosition;
int groupPosition = ExpandableListView.getPackedPositionGroup(packedPosition);
int childPosition = ExpandableListView.getPackedPositionChild(packedPosition);
FolderInfoHolder folder = (FolderInfoHolder) mAdapter.getGroup(groupPosition);
if (folder.outbox) {
return;
}
if (childPosition < folder.messages.size()) {
getMenuInflater().inflate(R.menu.folder_message_list_context, menu);
MessageInfoHolder message =
(MessageInfoHolder) mAdapter.getChild(groupPosition, childPosition);
if (message.read) {
menu.findItem(R.id.mark_as_read).setTitle(R.string.mark_as_unread_action);
}
}
}
}
class FolderMessageListAdapter extends BaseExpandableListAdapter {
private ArrayList<FolderInfoHolder> mFolders = new ArrayList<FolderInfoHolder>();
private MessagingListener mListener = new MessagingListener() {
@Override
public void listFoldersStarted(Account account) {
if (!account.equals(mAccount)) {
return;
}
mHandler.progress(true);
}
@Override
public void listFoldersFailed(Account account, String message) {
if (!account.equals(mAccount)) {
return;
}
mHandler.progress(false);
if (Config.LOGV) {
Log.v(k9.LOG_TAG, "listFoldersFailed " + message);
}
}
@Override
public void listFoldersFinished(Account account) {
if (!account.equals(mAccount)) {
return;
}
mHandler.progress(false);
if (mInitialFolder != null) {
int groupPosition = getFolderPosition(mInitialFolder);
mInitialFolder = null;
if (groupPosition != -1) {
mHandler.expandGroup(groupPosition);
}
}
}
@Override
public void listFolders(Account account, Folder[] folders) {
if (!account.equals(mAccount)) {
return;
}
for (Folder folder : folders) {
FolderInfoHolder holder = getFolder(folder.getName());
if (holder == null) {
holder = new FolderInfoHolder();
mFolders.add(holder);
}
holder.name = folder.getName();
if (holder.name.equalsIgnoreCase(k9.INBOX)) {
holder.displayName = getString(R.string.special_mailbox_name_inbox);
// XXX TOOD nuke when we do this for all folders
try {
holder.unreadMessageCount = folder.getUnreadMessageCount();
}
catch (MessagingException me) {
Log.e(k9.LOG_TAG, "Folder.getUnreadMessageCount() failed", me);
}
}
else {
holder.displayName = folder.getName();
}
if (holder.name.equals(mAccount.getOutboxFolderName())) {
holder.outbox = true;
}
if (holder.messages == null) {
holder.messages = new ArrayList<MessageInfoHolder>();
}
/* TODO - once we're in a position to asynchronously list off
* unread message counts quckly, start doing this again.
* right now, they're not even displayed
try {
holder.unreadMessageCount = folder.getUnreadMessageCount();
}
catch (MessagingException me) {
Log.e(k9.LOG_TAG, "Folder.getUnreadMessageCount() failed", me);
}
*/
}
Collections.sort(mFolders);
mHandler.dataChanged();
/*
* We will do this eventually. This restores the state of the list in the
* case of a killed Activity but we have some message sync issues to take care of.
*/
// if (mRestoredState != null) {
// if (Config.LOGV) {
// Log.v(k9.LOG_TAG, "Attempting to restore list state");
// }
// Parcelable listViewState =
// mListView.onRestoreInstanceState(mListViewState);
// mListViewState = null;
// }
/*
* Now we need to refresh any folders that are currently expanded. We do this
* in case the status or number of messages has changed.
*/
for (int i = 0, count = getGroupCount(); i < count; i++) {
if (mListView.isGroupExpanded(i)) {
final FolderInfoHolder folder = (FolderInfoHolder) mAdapter.getGroup(i);
new Thread(new FolderUpdateWorker(folder.name, mRefreshRemote)).start();
}
}
mRefreshRemote = false;
}
@Override
public void listLocalMessagesStarted(Account account, String folder) {
if (!account.equals(mAccount)) {
return;
}
mHandler.progress(true);
mHandler.folderLoading(folder, true);
}
@Override
public void listLocalMessagesFailed(Account account, String folder, String message) {
if (!account.equals(mAccount)) {
return;
}
mHandler.progress(false);
mHandler.folderLoading(folder, false);
}
@Override
public void listLocalMessagesFinished(Account account, String folder) {
if (!account.equals(mAccount)) {
return;
}
mHandler.progress(false);
mHandler.folderLoading(folder, false);
}
@Override
public void listLocalMessages(Account account, String folder, Message[] messages) {
if (!account.equals(mAccount)) {
return;
}
synchronizeMessages(folder, messages);
}
@Override
public void synchronizeMailboxStarted(
Account account,
String folder) {
if (!account.equals(mAccount)) {
return;
}
mHandler.progress(true);
mHandler.folderLoading(folder, true);
mHandler.folderStatus(folder, null);
}
@Override
public void synchronizeMailboxFinished(
Account account,
String folder,
int totalMessagesInMailbox,
int numNewMessages) {
if (!account.equals(mAccount)) {
return;
}
mHandler.progress(false);
mHandler.folderLoading(folder, false);
mHandler.folderStatus(folder, null);
onRefresh(false);
}
@Override
public void synchronizeMailboxFailed(
Account account,
String folder,
String message) {
if (!account.equals(mAccount)) {
return;
}
mHandler.progress(false);
mHandler.folderLoading(folder, false);
mHandler.folderStatus(folder, getString(R.string.status_network_error));
FolderInfoHolder holder = getFolder(folder);
if (holder != null) {
/*
* Reset the last checked time to 0 so that the next expand will attempt to
* refresh this folder.
*/
holder.lastChecked = 0;
}
}
@Override
public void synchronizeMailboxNewMessage(
Account account,
String folder,
Message message) {
if (!account.equals(mAccount)) {
return;
}
addOrUpdateMessage(folder, message);
}
@Override
public void synchronizeMailboxRemovedMessage(
Account account,
String folder,
Message message) {
if (!account.equals(mAccount)) {
return;
}
removeMessage(folder, message.getUid());
}
@Override
public void emptyTrashCompleted(Account account) {
if (!account.equals(mAccount)) {
return;
}
onRefresh(false);
}
@Override
public void sendPendingMessagesCompleted(Account account) {
if (!account.equals(mAccount)) {
return;
}
onRefresh(false);
}
@Override
public void messageUidChanged(
Account account,
String folder,
String oldUid,
String newUid) {
if (mAccount.equals(account)) {
FolderInfoHolder holder = getFolder(folder);
if (folder != null) {
for (MessageInfoHolder message : holder.messages) {
if (message.uid.equals(oldUid)) {
message.uid = newUid;
message.message.setUid(newUid);
}
}
}
}
}
};
private Drawable mAttachmentIcon;
FolderMessageListAdapter() {
mAttachmentIcon = getResources().getDrawable(R.drawable.ic_mms_attachment_small);
}
public void removeMessage(String folder, String messageUid) {
FolderInfoHolder f = getFolder(folder);
if (f == null) {
return;
}
MessageInfoHolder m = getMessage(f, messageUid);
if (m == null) {
return;
}
mHandler.removeMessage(f, m);
}
public void synchronizeMessages(String folder, Message[] messages) {
FolderInfoHolder f = getFolder(folder);
if (f == null) {
return;
}
mHandler.synchronizeMessages(f, messages);
}
public void addOrUpdateMessage(String folder, Message message) {
addOrUpdateMessage(folder, message, true, true);
}
private void addOrUpdateMessage(FolderInfoHolder folder, Message message,
boolean sort, boolean notify) {
MessageInfoHolder m = getMessage(folder, message.getUid());
if (m == null) {
m = new MessageInfoHolder(message, folder);
folder.messages.add(m);
}
else {
m.populate(message, folder);
}
if (sort) {
Collections.sort(folder.messages);
}
if (notify) {
mHandler.dataChanged();
}
}
private void addOrUpdateMessage(String folder, Message message,
boolean sort, boolean notify) {
FolderInfoHolder f = getFolder(folder);
if (f == null) {
return;
}
addOrUpdateMessage(f, message, sort, notify);
}
public MessageInfoHolder getMessage(FolderInfoHolder folder, String messageUid) {
for (MessageInfoHolder message : folder.messages) {
if (message.uid.equals(messageUid)) {
return message;
}
}
return null;
}
public int getGroupCount() {
return mFolders.size();
}
public long getGroupId(int groupPosition) {
return groupPosition;
}
public Object getGroup(int groupPosition) {
return mFolders.get(groupPosition);
}
public FolderInfoHolder getFolder(String folder) {
FolderInfoHolder folderHolder = null;
for (int i = 0, count = getGroupCount(); i < count; i++) {
FolderInfoHolder holder = (FolderInfoHolder) getGroup(i);
if (holder.name.equals(folder)) {
folderHolder = holder;
}
}
return folderHolder;
}
/**
* Gets the group position of the given folder or returns -1 if the folder is not
* found.
* @param folder
* @return
*/
public int getFolderPosition(String folder) {
for (int i = 0, count = getGroupCount(); i < count; i++) {
FolderInfoHolder holder = (FolderInfoHolder) getGroup(i);
if (holder.name.equals(folder)) {
return i;
}
}
return -1;
}
public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
ViewGroup parent) {
FolderInfoHolder folder = (FolderInfoHolder) getGroup(groupPosition);
View view;
if (convertView != null) {
view = convertView;
} else {
view = mInflater.inflate(R.layout.folder_message_list_group, parent, false);
}
FolderViewHolder holder = (FolderViewHolder) view.getTag();
if (holder == null) {
holder = new FolderViewHolder();
holder.folderName = (TextView) view.findViewById(R.id.folder_name);
holder.newMessageCount = (TextView) view.findViewById(R.id.new_message_count);
holder.folderStatus = (TextView) view.findViewById(R.id.folder_status);
view.setTag(holder);
}
holder.folderName.setText(folder.displayName);
if (folder.status == null) {
holder.folderStatus.setVisibility(View.GONE);
}
else {
holder.folderStatus.setText(folder.status);
holder.folderStatus.setVisibility(View.VISIBLE);
}
if (folder.unreadMessageCount != 0) {
holder.newMessageCount.setText(Integer.toString(folder.unreadMessageCount));
holder.newMessageCount.setVisibility(View.VISIBLE);
}
else {
holder.newMessageCount.setVisibility(View.GONE);
}
return view;
}
public int getChildrenCount(int groupPosition) {
FolderInfoHolder folder = (FolderInfoHolder) getGroup(groupPosition);
return folder.messages.size() + 1;
}
public long getChildId(int groupPosition, int childPosition) {
FolderInfoHolder folder = (FolderInfoHolder) getGroup(groupPosition);
if (childPosition < folder.messages.size()) {
MessageInfoHolder holder = folder.messages.get(childPosition);
return ((LocalStore.LocalMessage) holder.message).getId();
} else {
return -1;
}
}
public Object getChild(int groupPosition, int childPosition) {
FolderInfoHolder folder = (FolderInfoHolder) getGroup(groupPosition);
return folder.messages.get(childPosition);
}
public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
View convertView, ViewGroup parent) {
FolderInfoHolder folder = (FolderInfoHolder) getGroup(groupPosition);
if (isLastChild) {
View view;
if ((convertView != null)
&& (convertView.getId()
== R.layout.folder_message_list_child_footer)) {
view = convertView;
}
else {
view = mInflater.inflate(R.layout.folder_message_list_child_footer,
parent, false);
view.setId(R.layout.folder_message_list_child_footer);
}
FooterViewHolder holder = (FooterViewHolder) view.getTag();
if (holder == null) {
holder = new FooterViewHolder();
holder.progress = (ProgressBar) view.findViewById(R.id.progress);
holder.main = (TextView) view.findViewById(R.id.main_text);
view.setTag(holder);
}
if (folder.loading) {
holder.main.setText(getString(R.string.status_loading_more));
holder.progress.setVisibility(View.VISIBLE);
}
else {
if (folder.status == null) {
// holder.main.setText(getString(R.string.message_list_load_more_messages_action));
holder.main.setText("Load up to " + k9.VISIBLE_LIMIT_INCREMENT + " more");
}
else {
holder.main.setText(getString(R.string.status_loading_more_failed));
}
holder.progress.setVisibility(View.GONE);
}
return view;
}
else {
MessageInfoHolder message =
(MessageInfoHolder) getChild(groupPosition, childPosition);
View view;
if ((convertView != null)
&& (convertView.getId() != R.layout.folder_message_list_child_footer)) {
view = convertView;
} else {
view = mInflater.inflate(R.layout.folder_message_list_child, parent, false);
}
MessageViewHolder holder = (MessageViewHolder) view.getTag();
if (holder == null) {
holder = new MessageViewHolder();
holder.subject = (TextView) view.findViewById(R.id.subject);
holder.from = (TextView) view.findViewById(R.id.from);
holder.date = (TextView) view.findViewById(R.id.date);
holder.chip = view.findViewById(R.id.chip);
/*
* TODO
* The line below and the commented lines a bit further down are work
* in progress for outbox status. They should not be removed.
*/
// holder.status = (TextView) view.findViewById(R.id.status);
/*
* This will need to move to below if we ever convert this whole thing
* to a combined inbox.
*/
holder.chip.setBackgroundResource(colorChipResId);
view.setTag(holder);
}
holder.chip.getBackground().setAlpha(message.read ? 0 : 255);
holder.subject.setText(message.subject);
holder.subject.setTypeface(null, message.read ? Typeface.NORMAL : Typeface.BOLD);
holder.from.setText(message.sender);
holder.from.setTypeface(null, message.read ? Typeface.NORMAL : Typeface.BOLD);
holder.date.setText(message.date);
holder.from.setCompoundDrawablesWithIntrinsicBounds(null, null,
message.hasAttachments ? mAttachmentIcon : null, null);
// if (folder.outbox) {
// holder.status.setText("Sending");
// }
// else {
// holder.status.setText("");
// }
return view;
}
}
public boolean hasStableIds() {
return true;
}
public boolean isChildSelectable(int groupPosition, int childPosition) {
return childPosition < getChildrenCount(groupPosition);
}
public class FolderInfoHolder implements Comparable<FolderInfoHolder> {
public String name;
public String displayName;
public ArrayList<MessageInfoHolder> messages;
public long lastChecked;
public int unreadMessageCount;
public boolean loading;
public String status;
public boolean lastCheckFailed;
/**
* Outbox is handled differently from any other folder.
*/
public boolean outbox;
public int compareTo(FolderInfoHolder o) {
String s1 = this.name;
String s2 = o.name;
if (k9.INBOX.equalsIgnoreCase(s1)) {
return -1;
} else if (k9.INBOX.equalsIgnoreCase(s2)) {
return 1;
} else
return s1.compareToIgnoreCase(s2);
}
}
public class MessageInfoHolder implements Comparable<MessageInfoHolder> {
public String subject;
public String date;
public Date compareDate;
public String sender;
public boolean hasAttachments;
public String uid;
public boolean read;
public Message message;
public MessageInfoHolder(Message m, FolderInfoHolder folder) {
populate(m, folder);
}
public void populate(Message m, FolderInfoHolder folder) {
try {
LocalMessage message = (LocalMessage) m;
Date date = message.getSentDate();
this.compareDate = date;
if (Utility.isDateToday(date)) {
this.date = mTimeFormat.format(date);
}
else {
this.date = mDateFormat.format(date);
}
this.hasAttachments = message.getAttachmentCount() > 0;
this.read = message.isSet(Flag.SEEN);
if (folder.outbox) {
this.sender = Address.toFriendly(
message.getRecipients(RecipientType.TO));
}
else {
this.sender = Address.toFriendly(message.getFrom());
}
this.subject = message.getSubject();
this.uid = message.getUid();
this.message = m;
}
catch (MessagingException me) {
if (Config.LOGV) {
Log.v(k9.LOG_TAG, "Unable to load message info", me);
}
}
}
public int compareTo(MessageInfoHolder o) {
return this.compareDate.compareTo(o.compareDate) * -1;
}
}
class FolderViewHolder {
public TextView folderName;
public TextView folderStatus;
public TextView newMessageCount;
}
class MessageViewHolder {
public TextView subject;
public TextView preview;
public TextView from;
public TextView date;
public View chip;
}
class FooterViewHolder {
public ProgressBar progress;
public TextView main;
}
}
}