FileDocCategorySizeDatePackage
FolderMessageList.javaAPI DocAndroid 1.5 API52441Wed May 06 22:42:46 BST 2009com.android.email.activity

FolderMessageList

public class FolderMessageList extends android.app.ExpandableListActivity
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.

Fields Summary
private static final String
EXTRA_ACCOUNT
private static final String
EXTRA_CLEAR_NOTIFICATION
private static final String
EXTRA_INITIAL_FOLDER
private static final String
STATE_KEY_LIST
private static final String
STATE_KEY_EXPANDED_GROUP
private static final String
STATE_KEY_EXPANDED_GROUP_SELECTION
private static final int
UPDATE_FOLDER_ON_EXPAND_INTERVAL_MS
private static final int[]
colorChipResIds
private android.widget.ExpandableListView
mListView
private int
colorChipResId
private FolderMessageListAdapter
mAdapter
private android.view.LayoutInflater
mInflater
private com.android.email.Account
mAccount
private String
mInitialFolder
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 DateFormat
mDateFormat
private DateFormat
mTimeFormat
private int
mExpandedGroup
private boolean
mRestoringState
private boolean
mRefreshRemote
private FolderMessageListHandler
mHandler
Constructors Summary
Methods Summary
public static voidactionHandleAccount(android.content.Context context, com.android.email.Account account, java.lang.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 voidactionHandleAccount(android.content.Context context, com.android.email.Account account)

        actionHandleAccount(context, account, null);
    
public static android.content.IntentactionHandleAccountIntent(android.content.Context context, com.android.email.Account account, java.lang.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 android.content.IntentactionHandleAccountUriIntent(android.content.Context context, com.android.email.Account account, java.lang.String initialFolder)
This should be used for generating lightweight (Uri-only) intents. It probably makes sense to move entirely to this, and stop passing entire account structs through Intents.

param
context Calling context for building the intent
param
account The account of interest
param
initialFolder If non-null, can set the folder name to open (typically Email.INBOX)
return
an Intent which can be used to view that account

        Intent i = actionHandleAccountIntent(context, account, initialFolder);
        i.removeExtra(EXTRA_ACCOUNT);
        i.setData(account.getContentUri());
        return i;
    
private voidonAccounts()

        Accounts.actionShowAccounts(this);
        finish();
    
public booleanonChildClick(android.widget.ExpandableListView parent, android.view.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 voidonCompose()

        MessageCompose.actionCompose(this, mAccount);
    
public booleanonContextItemSelected(android.view.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);
    
public voidonCreate(android.os.Bundle savedInstanceState)

        super.onCreate(savedInstanceState);

        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
        
        mDateFormat = android.text.format.DateFormat.getDateFormat(this);   // short format
        mTimeFormat = android.text.format.DateFormat.getTimeFormat(this);   // 12/24 date format

        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);
        if (mAccount == null) {
            Uri uri = intent.getData();
            if (uri != null) {
                mAccount = Preferences.getPreferences(this).getAccountByContentUri(uri);
            }
        }
        // If no useable account was specified, just go to the accounts list screen instead
        if (mAccount == null) {
            Accounts.actionShowAccounts(this);
            finish();
            return;
        }

        // 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());
    
public voidonCreateContextMenu(android.view.ContextMenu menu, android.view.View v, android.view.ContextMenu.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);
                }
            }
        }
    
public booleanonCreateOptionsMenu(android.view.Menu menu)

        super.onCreateOptionsMenu(menu);
        getMenuInflater().inflate(R.menu.folder_message_list_option, menu);
        return true;
    
private voidonDelete(com.android.email.activity.FolderMessageList$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 voidonEditAccount()

        AccountSettings.actionSettings(this, mAccount);
    
private voidonForward(com.android.email.activity.FolderMessageList$MessageInfoHolder holder)

        MessageCompose.actionForward(this, mAccount, holder.message);
    
public voidonGroupCollapse(int groupPosition)

        super.onGroupCollapse(groupPosition);
        mExpandedGroup = -1;
    
public voidonGroupExpand(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, null, mAccount, 
                    MessagingController.getInstance(getApplication())))
                    .start();
        }
    
private voidonOpenMessage(com.android.email.activity.FolderMessageList$FolderInfoHolder folder, com.android.email.activity.FolderMessageList$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);
        }
    
public booleanonOptionsItemSelected(android.view.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);
        }
    
public voidonPause()

        super.onPause();
        MessagingController.getInstance(getApplication()).removeListener(mAdapter.mListener);
    
private voidonRefresh(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 voidonReply(com.android.email.activity.FolderMessageList$MessageInfoHolder holder)

        MessageCompose.actionReply(this, mAccount, holder.message, false);
    
private voidonReplyAll(com.android.email.activity.FolderMessageList$MessageInfoHolder holder)

        MessageCompose.actionReply(this, mAccount, holder.message, true);
    
private voidonRestoreListState(android.os.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));
    
public voidonResume()
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.

        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);
    
public java.lang.ObjectonRetainNonConfigurationInstance()

        return mAdapter.mFolders;
    
public voidonSaveInstanceState(android.os.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());
    
private voidonToggleRead(com.android.email.activity.FolderMessageList$MessageInfoHolder holder)

        MessagingController.getInstance(getApplication()).markMessageRead(
                mAccount,
                holder.message.getFolder().getName(),
                holder.uid,
                !holder.read);
        holder.read = !holder.read;
        onRefresh(false);