/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.email.activity; import com.android.email.AccountBackupRestore; import com.android.email.Controller; import com.android.email.Email; import com.android.email.R; import com.android.email.SecurityPolicy; import com.android.email.Utility; import com.android.email.activity.setup.AccountSettings; import com.android.email.activity.setup.AccountSetupBasics; import com.android.email.mail.MessagingException; import com.android.email.mail.Store; import com.android.email.provider.EmailContent; import com.android.email.provider.EmailContent.Account; import com.android.email.provider.EmailContent.Mailbox; import com.android.email.provider.EmailContent.MailboxColumns; import com.android.email.provider.EmailContent.Message; import com.android.email.provider.EmailContent.MessageColumns; import com.android.email.service.MailService; import android.app.AlertDialog; import android.app.Dialog; import android.app.ListActivity; import android.app.NotificationManager; import android.content.ContentUris; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.database.Cursor; import android.database.MatrixCursor; import android.database.MergeCursor; import android.database.MatrixCursor.RowBuilder; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; 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.AdapterView; import android.widget.CursorAdapter; import android.widget.ImageView; import android.widget.ListAdapter; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import android.widget.AdapterView.OnItemClickListener; import java.util.ArrayList; public class AccountFolderList extends ListActivity implements OnItemClickListener { private static final int DIALOG_REMOVE_ACCOUNT = 1; /** * Key codes used to open a debug settings screen. */ private static int[] secretKeyCodes = { KeyEvent.KEYCODE_D, KeyEvent.KEYCODE_E, KeyEvent.KEYCODE_B, KeyEvent.KEYCODE_U, KeyEvent.KEYCODE_G }; private int mSecretKeyCodeIndex = 0; private static final String ICICLE_SELECTED_ACCOUNT = "com.android.email.selectedAccount"; private EmailContent.Account mSelectedContextAccount; private ListView mListView; private ProgressBar mProgressIcon; private AccountsAdapter mListAdapter; private LoadAccountsTask mLoadAccountsTask; private DeleteAccountTask mDeleteAccountTask; private MessageListHandler mHandler; private ControllerResults mControllerCallback; /** * Reduced mailbox projection used by AccountsAdapter */ public final static int MAILBOX_COLUMN_ID = 0; public final static int MAILBOX_DISPLAY_NAME = 1; public final static int MAILBOX_ACCOUNT_KEY = 2; public final static int MAILBOX_TYPE = 3; public final static int MAILBOX_UNREAD_COUNT = 4; public final static int MAILBOX_FLAG_VISIBLE = 5; public final static int MAILBOX_FLAGS = 6; public final static String[] MAILBOX_PROJECTION = new String[] { EmailContent.RECORD_ID, MailboxColumns.DISPLAY_NAME, MailboxColumns.ACCOUNT_KEY, MailboxColumns.TYPE, MailboxColumns.UNREAD_COUNT, MailboxColumns.FLAG_VISIBLE, MailboxColumns.FLAGS }; private static final String FAVORITE_COUNT_SELECTION = MessageColumns.FLAG_FAVORITE + "= 1"; private static final String MAILBOX_TYPE_SELECTION = MailboxColumns.TYPE + " =?"; private static final String MAILBOX_ID_SELECTION = MessageColumns.MAILBOX_KEY + " =?"; private static final String[] MAILBOX_SUM_OF_UNREAD_COUNT_PROJECTION = new String [] { "sum(" + MailboxColumns.UNREAD_COUNT + ")" }; private static final String MAILBOX_INBOX_SELECTION = MailboxColumns.ACCOUNT_KEY + " =?" + " AND " + MailboxColumns.TYPE +" = " + Mailbox.TYPE_INBOX; private static final int MAILBOX_UNREAD_COUNT_COLUMN_UNREAD_COUNT = 0; private static final String[] MAILBOX_UNREAD_COUNT_PROJECTION = new String [] { MailboxColumns.UNREAD_COUNT }; /** * Start the Accounts list activity. Uses the CLEAR_TOP flag which means that other stacked * activities may be killed in order to get back to Accounts. */ public static void actionShowAccounts(Context context) { Intent i = new Intent(context, AccountFolderList.class); i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); context.startActivity(i); } @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); setContentView(R.layout.account_folder_list); getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.list_title); mHandler = new MessageListHandler(); mControllerCallback = new ControllerResults(); mProgressIcon = (ProgressBar) findViewById(R.id.title_progress_icon); mListView = getListView(); mListView.setItemsCanFocus(false); mListView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_INSET); mListView.setOnItemClickListener(this); mListView.setLongClickable(true); registerForContextMenu(mListView); if (icicle != null && icicle.containsKey(ICICLE_SELECTED_ACCOUNT)) { mSelectedContextAccount = (Account) icicle.getParcelable(ICICLE_SELECTED_ACCOUNT); } ((TextView) findViewById(R.id.title_left_text)).setText(R.string.app_name); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); if (mSelectedContextAccount != null) { outState.putParcelable(ICICLE_SELECTED_ACCOUNT, mSelectedContextAccount); } } @Override public void onPause() { super.onPause(); Controller.getInstance(getApplication()).removeResultCallback(mControllerCallback); } @Override public void onResume() { super.onResume(); NotificationManager notifMgr = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); notifMgr.cancel(1); Controller.getInstance(getApplication()).addResultCallback(mControllerCallback); // Exit immediately if the accounts list has changed (e.g. externally deleted) if (Email.getNotifyUiAccountsChanged()) { Welcome.actionStart(this); finish(); return; } updateAccounts(); // TODO: What updates do we need to auto-trigger, now that we have mailboxes in view? } @Override protected void onDestroy() { super.onDestroy(); Utility.cancelTaskInterrupt(mLoadAccountsTask); mLoadAccountsTask = null; // TODO: We shouldn't call cancel() for DeleteAccountTask. If the task hasn't // started, this will mark it as "don't run", but we always want it to finish. // (But don't just remove this cancel() call. DeleteAccountTask.onPostExecute() checks if // it's been canceled to decided whether to update the UI.) Utility.cancelTask(mDeleteAccountTask, false); // Don't interrupt if it's running. mDeleteAccountTask = null; if (mListAdapter != null) { mListAdapter.changeCursor(null); } } public void onItemClick(AdapterView<?> parent, View view, int position, long id) { if (mListAdapter.isMailbox(position)) { MessageList.actionHandleMailbox(this, id); } else if (mListAdapter.isAccount(position)) { MessageList.actionHandleAccount(this, id, Mailbox.TYPE_INBOX); } } private static int getUnreadCountByMailboxType(Context context, int type) { int count = 0; Cursor c = context.getContentResolver().query(Mailbox.CONTENT_URI, MAILBOX_SUM_OF_UNREAD_COUNT_PROJECTION, MAILBOX_TYPE_SELECTION, new String[] { String.valueOf(type) }, null); try { if (c.moveToFirst()) { return c.getInt(0); } } finally { c.close(); } return count; } private static int getCountByMailboxType(Context context, int type) { int count = 0; Cursor c = context.getContentResolver().query(Mailbox.CONTENT_URI, EmailContent.ID_PROJECTION, MAILBOX_TYPE_SELECTION, new String[] { String.valueOf(type) }, null); try { c.moveToPosition(-1); while (c.moveToNext()) { count += EmailContent.count(context, Message.CONTENT_URI, MAILBOX_ID_SELECTION, new String[] { String.valueOf(c.getLong(EmailContent.ID_PROJECTION_COLUMN)) }); } } finally { c.close(); } return count; } /** * Build the group and child cursors that support the summary views (aka "at a glance"). * * This is a placeholder implementation with significant problems that need to be addressed: * * TODO: We should only show summary mailboxes if they are non-empty. So there needs to be * a more dynamic child-cursor here, probably listening for update notifications on a number * of other internally-held queries such as count-of-inbox, count-of-unread, etc. * * TODO: This simple list is incomplete. For example, we probably want drafts, outbox, and * (maybe) sent (again, these would be displayed only when non-empty). * * TODO: We need a way to count total unread in all inboxes (probably with some provider help) * * TODO: We need a way to count total # messages in all other summary boxes (probably with * some provider help). * * TODO use narrower account projection (see LoadAccountsTask) */ private MatrixCursor getSummaryChildCursor() { MatrixCursor childCursor = new MatrixCursor(MAILBOX_PROJECTION); int count; RowBuilder row; // TYPE_INBOX count = getUnreadCountByMailboxType(this, Mailbox.TYPE_INBOX); row = childCursor.newRow(); row.add(Long.valueOf(Mailbox.QUERY_ALL_INBOXES)); // MAILBOX_COLUMN_ID = 0; row.add(getString(R.string.account_folder_list_summary_inbox)); // MAILBOX_DISPLAY_NAME row.add(null); // MAILBOX_ACCOUNT_KEY = 2; row.add(Integer.valueOf(Mailbox.TYPE_INBOX)); // MAILBOX_TYPE = 3; row.add(Integer.valueOf(count)); // MAILBOX_UNREAD_COUNT = 4; // TYPE_MAIL (FAVORITES) count = EmailContent.count(this, Message.CONTENT_URI, FAVORITE_COUNT_SELECTION, null); if (count > 0) { row = childCursor.newRow(); row.add(Long.valueOf(Mailbox.QUERY_ALL_FAVORITES)); // MAILBOX_COLUMN_ID = 0; // MAILBOX_DISPLAY_NAME row.add(getString(R.string.account_folder_list_summary_starred)); row.add(null); // MAILBOX_ACCOUNT_KEY = 2; row.add(Integer.valueOf(Mailbox.TYPE_MAIL)); // MAILBOX_TYPE = 3; row.add(Integer.valueOf(count)); // MAILBOX_UNREAD_COUNT = 4; } // TYPE_DRAFTS count = getCountByMailboxType(this, Mailbox.TYPE_DRAFTS); if (count > 0) { row = childCursor.newRow(); row.add(Long.valueOf(Mailbox.QUERY_ALL_DRAFTS)); // MAILBOX_COLUMN_ID = 0; row.add(getString(R.string.account_folder_list_summary_drafts));// MAILBOX_DISPLAY_NAME row.add(null); // MAILBOX_ACCOUNT_KEY = 2; row.add(Integer.valueOf(Mailbox.TYPE_DRAFTS)); // MAILBOX_TYPE = 3; row.add(Integer.valueOf(count)); // MAILBOX_UNREAD_COUNT = 4; } // TYPE_OUTBOX count = getCountByMailboxType(this, Mailbox.TYPE_OUTBOX); if (count > 0) { row = childCursor.newRow(); row.add(Long.valueOf(Mailbox.QUERY_ALL_OUTBOX)); // MAILBOX_COLUMN_ID = 0; row.add(getString(R.string.account_folder_list_summary_outbox));// MAILBOX_DISPLAY_NAME row.add(null); // MAILBOX_ACCOUNT_KEY = 2; row.add(Integer.valueOf(Mailbox.TYPE_OUTBOX)); // MAILBOX_TYPE = 3; row.add(Integer.valueOf(count)); // MAILBOX_UNREAD_COUNT = 4; } return childCursor; } /** * Async task to handle the accounts query outside of the UI thread */ private class LoadAccountsTask extends AsyncTask<Void, Void, Object[]> { @Override protected Object[] doInBackground(Void... params) { // Create the summaries cursor Cursor c1 = getSummaryChildCursor(); // TODO use a custom projection and don't have to sample all of these columns Cursor c2 = getContentResolver().query( EmailContent.Account.CONTENT_URI, EmailContent.Account.CONTENT_PROJECTION, null, null, null); Long defaultAccount = Account.getDefaultAccountId(AccountFolderList.this); return new Object[] { c1, c2 , defaultAccount}; } @Override protected void onPostExecute(Object[] params) { if (isCancelled() || params == null || ((Cursor)params[1]).isClosed()) { return; } // Before writing a new list adapter into the listview, we need to // shut down the old one (if any). ListAdapter oldAdapter = mListView.getAdapter(); if (oldAdapter != null && oldAdapter instanceof CursorAdapter) { ((CursorAdapter)oldAdapter).changeCursor(null); } // Now create a new list adapter and install it mListAdapter = AccountsAdapter.getInstance((Cursor)params[0], (Cursor)params[1], AccountFolderList.this, (Long)params[2]); mListView.setAdapter(mListAdapter); } } private class DeleteAccountTask extends AsyncTask<Void, Void, Void> { private final long mAccountId; private final String mAccountUri; public DeleteAccountTask(long accountId, String accountUri) { mAccountId = accountId; mAccountUri = accountUri; } @Override protected Void doInBackground(Void... params) { try { // Delete Remote store at first. Store.getInstance(mAccountUri, getApplication(), null).delete(); // Remove the Store instance from cache. Store.removeInstance(mAccountUri); Uri uri = ContentUris.withAppendedId( EmailContent.Account.CONTENT_URI, mAccountId); AccountFolderList.this.getContentResolver().delete(uri, null, null); // Update the backup (side copy) of the accounts AccountBackupRestore.backupAccounts(AccountFolderList.this); // Release or relax device administration, if relevant SecurityPolicy.getInstance(AccountFolderList.this).reducePolicies(); } catch (Exception e) { // Ignore } Email.setServicesEnabled(AccountFolderList.this); return null; } @Override protected void onPostExecute(Void v) { if (!isCancelled()) { updateAccounts(); } } } private void updateAccounts() { Utility.cancelTaskInterrupt(mLoadAccountsTask); mLoadAccountsTask = (LoadAccountsTask) new LoadAccountsTask().execute(); } private void onAddNewAccount() { AccountSetupBasics.actionNewAccount(this); } private void onEditAccount(long accountId) { AccountSettings.actionSettings(this, accountId); } /** * Refresh one or all accounts * @param accountId A specific id to refresh folders only, or -1 to refresh everything */ private void onRefresh(long accountId) { if (accountId == -1) { // TODO implement a suitable "Refresh all accounts" / "check mail" comment in Controller // TODO this is temp Toast.makeText(this, getString(R.string.account_folder_list_refresh_toast), Toast.LENGTH_LONG).show(); } else { mHandler.progress(true); Controller.getInstance(getApplication()).updateMailboxList( accountId, mControllerCallback); } } private void onCompose(long accountId) { if (accountId == -1) { accountId = Account.getDefaultAccountId(this); } if (accountId != -1) { MessageCompose.actionCompose(this, accountId); } else { onAddNewAccount(); } } private void onDeleteAccount(long accountId) { mSelectedContextAccount = Account.restoreAccountWithId(this, accountId); showDialog(DIALOG_REMOVE_ACCOUNT); } @Override public Dialog onCreateDialog(int id) { switch (id) { case DIALOG_REMOVE_ACCOUNT: return createRemoveAccountDialog(); } return super.onCreateDialog(id); } private Dialog createRemoveAccountDialog() { return new AlertDialog.Builder(this) .setIcon(android.R.drawable.ic_dialog_alert) .setTitle(R.string.account_delete_dlg_title) .setMessage(getString(R.string.account_delete_dlg_instructions_fmt, mSelectedContextAccount.getDisplayName())) .setPositiveButton(R.string.okay_action, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { dismissDialog(DIALOG_REMOVE_ACCOUNT); // Clear notifications, which may become stale here NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.cancel(MailService.NOTIFICATION_ID_NEW_MESSAGES); int numAccounts = EmailContent.count(AccountFolderList.this, Account.CONTENT_URI, null, null); mListAdapter.addOnDeletingAccount(mSelectedContextAccount.mId); mDeleteAccountTask = (DeleteAccountTask) new DeleteAccountTask( mSelectedContextAccount.mId, mSelectedContextAccount.getStoreUri(AccountFolderList.this)).execute(); if (numAccounts == 1) { AccountSetupBasics.actionNewAccount(AccountFolderList.this); finish(); } } }) .setNegativeButton(R.string.cancel_action, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { dismissDialog(DIALOG_REMOVE_ACCOUNT); } }) .create(); } /** * Update a cached dialog with current values (e.g. account name) */ @Override public void onPrepareDialog(int id, Dialog dialog) { switch (id) { case DIALOG_REMOVE_ACCOUNT: AlertDialog alert = (AlertDialog) dialog; alert.setMessage(getString(R.string.account_delete_dlg_instructions_fmt, mSelectedContextAccount.getDisplayName())); } } @Override public boolean onContextItemSelected(MenuItem item) { AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); if (mListAdapter.isMailbox(menuInfo.position)) { Cursor c = (Cursor) mListView.getItemAtPosition(menuInfo.position); long id = c.getLong(MAILBOX_COLUMN_ID); switch (item.getItemId()) { case R.id.open_folder: MessageList.actionHandleMailbox(this, id); break; case R.id.check_mail: onRefresh(-1); break; } return false; } else if (mListAdapter.isAccount(menuInfo.position)) { Cursor c = (Cursor) mListView.getItemAtPosition(menuInfo.position); long accountId = c.getLong(Account.CONTENT_ID_COLUMN); switch (item.getItemId()) { case R.id.open_folder: MailboxList.actionHandleAccount(this, accountId); break; case R.id.compose: onCompose(accountId); break; case R.id.refresh_account: onRefresh(accountId); break; case R.id.edit_account: onEditAccount(accountId); break; case R.id.delete_account: onDeleteAccount(accountId); break; } return true; } return false; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.add_new_account: onAddNewAccount(); break; case R.id.check_mail: onRefresh(-1); break; case R.id.compose: onCompose(-1); break; default: return super.onOptionsItemSelected(item); } return true; } public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { return true; } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.account_folder_list_option, menu); return true; } @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo info) { super.onCreateContextMenu(menu, v, info); AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo) info; if (mListAdapter.isMailbox(menuInfo.position)) { Cursor c = (Cursor) mListView.getItemAtPosition(menuInfo.position); String displayName = c.getString(Account.CONTENT_DISPLAY_NAME_COLUMN); menu.setHeaderTitle(displayName); getMenuInflater().inflate(R.menu.account_folder_list_smart_folder_context, menu); } else if (mListAdapter.isAccount(menuInfo.position)) { Cursor c = (Cursor) mListView.getItemAtPosition(menuInfo.position); String accountName = c.getString(Account.CONTENT_DISPLAY_NAME_COLUMN); menu.setHeaderTitle(accountName); getMenuInflater().inflate(R.menu.account_folder_list_context, menu); } } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (event.getKeyCode() == secretKeyCodes[mSecretKeyCodeIndex]) { mSecretKeyCodeIndex++; if (mSecretKeyCodeIndex == secretKeyCodes.length) { mSecretKeyCodeIndex = 0; startActivity(new Intent(this, Debug.class)); } } else { mSecretKeyCodeIndex = 0; } return super.onKeyDown(keyCode, event); } /** * Handler for UI-thread operations (when called from callbacks or any other threads) */ private class MessageListHandler extends Handler { private static final int MSG_PROGRESS = 1; @Override public void handleMessage(android.os.Message msg) { switch (msg.what) { case MSG_PROGRESS: boolean showProgress = (msg.arg1 != 0); if (showProgress) { mProgressIcon.setVisibility(View.VISIBLE); } else { mProgressIcon.setVisibility(View.GONE); } break; default: super.handleMessage(msg); } } /** * Call from any thread to start/stop progress indicator(s) * @param progress true to start, false to stop */ public void progress(boolean progress) { android.os.Message msg = android.os.Message.obtain(); msg.what = MSG_PROGRESS; msg.arg1 = progress ? 1 : 0; sendMessage(msg); } } /** * Callback for async Controller results. */ private class ControllerResults implements Controller.Result { public void updateMailboxListCallback(MessagingException result, long accountKey, int progress) { updateProgress(result, progress); } public void updateMailboxCallback(MessagingException result, long accountKey, long mailboxKey, int progress, int numNewMessages) { if (result != null || progress == 100) { Email.updateMailboxRefreshTime(mailboxKey); } if (progress == 100) { updateAccounts(); } updateProgress(result, progress); } public void loadMessageForViewCallback(MessagingException result, long messageId, int progress) { } public void loadAttachmentCallback(MessagingException result, long messageId, long attachmentId, int progress) { } public void serviceCheckMailCallback(MessagingException result, long accountId, long mailboxId, int progress, long tag) { updateProgress(result, progress); } public void sendMailCallback(MessagingException result, long accountId, long messageId, int progress) { if (progress == 100) { updateAccounts(); } } private void updateProgress(MessagingException result, int progress) { if (result != null || progress == 100) { mHandler.progress(false); } else if (progress == 0) { mHandler.progress(true); } } } /* package */ static class AccountsAdapter extends CursorAdapter { private final Context mContext; private final LayoutInflater mInflater; private final int mMailboxesCount; private final int mSeparatorPosition; private final long mDefaultAccountId; private final ArrayList<Long> mOnDeletingAccounts = new ArrayList<Long>(); public static AccountsAdapter getInstance(Cursor mailboxesCursor, Cursor accountsCursor, Context context, long defaultAccountId) { Cursor[] cursors = new Cursor[] { mailboxesCursor, accountsCursor }; Cursor mc = new MergeCursor(cursors); return new AccountsAdapter(mc, context, mailboxesCursor.getCount(), defaultAccountId); } public AccountsAdapter(Cursor c, Context context, int mailboxesCount, long defaultAccountId) { super(context, c, true); mContext = context; mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mMailboxesCount = mailboxesCount; mSeparatorPosition = mailboxesCount; mDefaultAccountId = defaultAccountId; } public boolean isMailbox(int position) { return position < mMailboxesCount; } public boolean isAccount(int position) { return position >= mMailboxesCount; } public void addOnDeletingAccount(long accountId) { mOnDeletingAccounts.add(accountId); } public boolean isOnDeletingAccountView(long accountId) { return mOnDeletingAccounts.contains(accountId); } /** * This is used as a callback from the list items, for clicks in the folder "button" * * @param itemView the item in which the click occurred */ public void onClickFolder(AccountFolderListItem itemView) { MailboxList.actionHandleAccount(mContext, itemView.mAccountId); } @Override public void bindView(View view, Context context, Cursor cursor) { if (cursor.getPosition() < mMailboxesCount) { bindMailboxItem(view, context, cursor, false); } else { bindAccountItem(view, context, cursor, false); } } private void bindMailboxItem(View view, Context context, Cursor cursor, boolean isLastChild) { // Reset the view (in case it was recycled) and prepare for binding AccountFolderListItem itemView = (AccountFolderListItem) view; itemView.bindViewInit(this, false); // Invisible (not "gone") to maintain spacing view.findViewById(R.id.chip).setVisibility(View.INVISIBLE); String text = cursor.getString(MAILBOX_DISPLAY_NAME); if (text != null) { TextView nameView = (TextView) view.findViewById(R.id.name); nameView.setText(text); } // TODO get/track live folder status text = null; TextView statusView = (TextView) view.findViewById(R.id.status); if (text != null) { statusView.setText(text); statusView.setVisibility(View.VISIBLE); } else { statusView.setVisibility(View.GONE); } int count = -1; text = cursor.getString(MAILBOX_UNREAD_COUNT); if (text != null) { count = Integer.valueOf(text); } TextView unreadCountView = (TextView) view.findViewById(R.id.new_message_count); TextView allCountView = (TextView) view.findViewById(R.id.all_message_count); int id = cursor.getInt(MAILBOX_COLUMN_ID); // If the unread count is zero, not to show countView. if (count > 0) { if (id == Mailbox.QUERY_ALL_FAVORITES || id == Mailbox.QUERY_ALL_DRAFTS || id == Mailbox.QUERY_ALL_OUTBOX) { unreadCountView.setVisibility(View.GONE); allCountView.setVisibility(View.VISIBLE); allCountView.setText(text); } else { allCountView.setVisibility(View.GONE); unreadCountView.setVisibility(View.VISIBLE); unreadCountView.setText(text); } } else { allCountView.setVisibility(View.GONE); unreadCountView.setVisibility(View.GONE); } view.findViewById(R.id.folder_button).setVisibility(View.GONE); view.findViewById(R.id.folder_separator).setVisibility(View.GONE); view.findViewById(R.id.default_sender).setVisibility(View.GONE); view.findViewById(R.id.folder_icon).setVisibility(View.VISIBLE); ((ImageView)view.findViewById(R.id.folder_icon)).setImageDrawable( Utility.FolderProperties.getInstance(context).getSummaryMailboxIconIds(id)); } private void bindAccountItem(View view, Context context, Cursor cursor, boolean isExpanded) { // Reset the view (in case it was recycled) and prepare for binding AccountFolderListItem itemView = (AccountFolderListItem) view; itemView.bindViewInit(this, true); itemView.mAccountId = cursor.getLong(Account.CONTENT_ID_COLUMN); long accountId = cursor.getLong(Account.CONTENT_ID_COLUMN); View chipView = view.findViewById(R.id.chip); chipView.setBackgroundResource(Email.getAccountColorResourceId(accountId)); chipView.setVisibility(View.VISIBLE); String text = cursor.getString(Account.CONTENT_DISPLAY_NAME_COLUMN); if (text != null) { TextView descriptionView = (TextView) view.findViewById(R.id.name); descriptionView.setText(text); } text = cursor.getString(Account.CONTENT_EMAIL_ADDRESS_COLUMN); if (text != null) { TextView emailView = (TextView) view.findViewById(R.id.status); emailView.setText(text); emailView.setVisibility(View.VISIBLE); } int unreadMessageCount = 0; Cursor c = context.getContentResolver().query(Mailbox.CONTENT_URI, MAILBOX_UNREAD_COUNT_PROJECTION, MAILBOX_INBOX_SELECTION, new String[] { String.valueOf(accountId) }, null); try { if (c.moveToFirst()) { String count = c.getString(MAILBOX_UNREAD_COUNT_COLUMN_UNREAD_COUNT); if (count != null) { unreadMessageCount = Integer.valueOf(count); } } } finally { c.close(); } view.findViewById(R.id.all_message_count).setVisibility(View.GONE); TextView unreadCountView = (TextView) view.findViewById(R.id.new_message_count); if (unreadMessageCount > 0) { unreadCountView.setText(String.valueOf(unreadMessageCount)); unreadCountView.setVisibility(View.VISIBLE); } else { unreadCountView.setVisibility(View.GONE); } view.findViewById(R.id.folder_icon).setVisibility(View.GONE); view.findViewById(R.id.folder_button).setVisibility(View.VISIBLE); view.findViewById(R.id.folder_separator).setVisibility(View.VISIBLE); if (accountId == mDefaultAccountId) { view.findViewById(R.id.default_sender).setVisibility(View.VISIBLE); } else { view.findViewById(R.id.default_sender).setVisibility(View.GONE); } } @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return mInflater.inflate(R.layout.account_folder_list_item, parent, false); } /* * The following series of overrides insert the "Accounts" separator */ /** * Prevents the separator view from recycling into the other views */ @Override public int getItemViewType(int position) { if (position == mSeparatorPosition) { return IGNORE_ITEM_VIEW_TYPE; } return super.getItemViewType(position); } /** * Injects the separator view when required, and fudges the cursor for other views */ @Override public View getView(int position, View convertView, ViewGroup parent) { // The base class's getView() checks for mDataValid at the beginning, but we don't have // to do that, because if the cursor is invalid getCount() returns 0, in which case this // method wouldn't get called. // Handle the separator here - create & bind if (position == mSeparatorPosition) { TextView view; view = (TextView) mInflater.inflate(R.layout.list_separator, parent, false); view.setText(R.string.account_folder_list_separator_accounts); return view; } return super.getView(getRealPosition(position), convertView, parent); } /** * Forces navigation to skip over the separator */ @Override public boolean areAllItemsEnabled() { return false; } /** * Forces navigation to skip over the separator */ @Override public boolean isEnabled(int position) { if (position == mSeparatorPosition) { return false; } else if (isAccount(position)) { Long id = ((MergeCursor)getItem(position)).getLong(Account.CONTENT_ID_COLUMN); return !isOnDeletingAccountView(id); } else { return true; } } /** * Adjusts list count to include separator */ @Override public int getCount() { int count = super.getCount(); if (count > 0 && (mSeparatorPosition != ListView.INVALID_POSITION)) { // Increment for separator, if we have anything to show. count += 1; } return count; } /** * Converts list position to cursor position */ private int getRealPosition(int pos) { if (mSeparatorPosition == ListView.INVALID_POSITION) { // No separator, identity map return pos; } else if (pos <= mSeparatorPosition) { // Before or at the separator, identity map return pos; } else { // After the separator, remove 1 from the pos to get the real underlying pos return pos - 1; } } /** * Returns the item using external position numbering (no separator) */ @Override public Object getItem(int pos) { return super.getItem(getRealPosition(pos)); } /** * Returns the item id using external position numbering (no separator) */ @Override public long getItemId(int pos) { return super.getItemId(getRealPosition(pos)); } } }