From f3c285d4a30dfd1ab6b8f1963f6ae8d936447d35 Mon Sep 17 00:00:00 2001 From: Andrew Stadler Date: Fri, 28 May 2010 12:54:56 -0700 Subject: [PATCH] Refactor AccountsAdapter into its own class. * Extract AccountAdapter from AccountFolderList * Use callback instead of hardcoded launch of MailboxList * Unit tests Change-Id: Icafce1ef73a99fb61985c649620440656f9b51a3 --- .../email/activity/AccountFolderList.java | 337 +--------------- .../email/activity/AccountFolderListItem.java | 4 +- .../email/activity/AccountsAdapter.java | 381 ++++++++++++++++++ .../email/activity/AccountsAdapterTest.java | 262 ++++++++++++ 4 files changed, 657 insertions(+), 327 deletions(-) create mode 100644 src/com/android/email/activity/AccountsAdapter.java create mode 100644 tests/src/com/android/email/activity/AccountsAdapterTest.java diff --git a/src/com/android/email/activity/AccountFolderList.java b/src/com/android/email/activity/AccountFolderList.java index b18c955aa..8316440b6 100644 --- a/src/com/android/email/activity/AccountFolderList.java +++ b/src/com/android/email/activity/AccountFolderList.java @@ -44,7 +44,6 @@ 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; @@ -52,16 +51,13 @@ 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; @@ -69,9 +65,8 @@ import android.widget.TextView; import android.widget.Toast; import android.widget.AdapterView.OnItemClickListener; -import java.util.ArrayList; - -public class AccountFolderList extends ListActivity implements OnItemClickListener { +public class AccountFolderList extends ListActivity + implements OnItemClickListener, AccountsAdapter.Callback { private static final int DIALOG_REMOVE_ACCOUNT = 1; /** * Key codes used to open a debug settings screen. @@ -95,24 +90,6 @@ public class AccountFolderList extends ListActivity implements OnItemClickListen 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"; @@ -126,15 +103,6 @@ public class AccountFolderList extends ListActivity implements OnItemClickListen "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. @@ -233,6 +201,13 @@ public class AccountFolderList extends ListActivity implements OnItemClickListen } } + /** + * Implements AccountsAdapter.Controller + */ + public void onClickAccountFolders(long accountId) { + MailboxList.actionHandleAccount(this, accountId); + } + private static int getUnreadCountByMailboxType(Context context, int type) { int count = 0; Cursor c = context.getContentResolver().query(Mailbox.CONTENT_URI, @@ -290,7 +265,7 @@ public class AccountFolderList extends ListActivity implements OnItemClickListen * TODO use narrower account projection (see LoadAccountsTask) */ private MatrixCursor getSummaryChildCursor() { - MatrixCursor childCursor = new MatrixCursor(MAILBOX_PROJECTION); + MatrixCursor childCursor = new MatrixCursor(AccountsAdapter.MAILBOX_PROJECTION); int count; RowBuilder row; // TYPE_INBOX @@ -365,7 +340,7 @@ public class AccountFolderList extends ListActivity implements OnItemClickListen } // Now create a new list adapter and install it mListAdapter = AccountsAdapter.getInstance((Cursor)params[0], (Cursor)params[1], - AccountFolderList.this, (Long)params[2]); + AccountFolderList.this, (Long)params[2], AccountFolderList.this); mListView.setAdapter(mListAdapter); } } @@ -517,7 +492,7 @@ public class AccountFolderList extends ListActivity implements OnItemClickListen if (mListAdapter.isMailbox(menuInfo.position)) { Cursor c = (Cursor) mListView.getItemAtPosition(menuInfo.position); - long id = c.getLong(MAILBOX_COLUMN_ID); + long id = c.getLong(AccountsAdapter.MAILBOX_COLUMN_ID); switch (item.getItemId()) { case R.id.open_folder: MessageList.actionHandleMailbox(this, id); @@ -694,294 +669,6 @@ public class AccountFolderList extends ListActivity implements OnItemClickListen } } } - - /* 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 mOnDeletingAccounts = new ArrayList(); - - 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)); - } - } } diff --git a/src/com/android/email/activity/AccountFolderListItem.java b/src/com/android/email/activity/AccountFolderListItem.java index 1703bc038..047911700 100644 --- a/src/com/android/email/activity/AccountFolderListItem.java +++ b/src/com/android/email/activity/AccountFolderListItem.java @@ -32,7 +32,7 @@ public class AccountFolderListItem extends LinearLayout { public long mAccountId; - private AccountFolderList.AccountsAdapter mAdapter; + private AccountsAdapter mAdapter; private boolean mHasFolderButton; private boolean mDownEvent; @@ -54,7 +54,7 @@ public class AccountFolderListItem extends LinearLayout { * * @param adapter the adapter that creates this view */ - public void bindViewInit(AccountFolderList.AccountsAdapter adapter, boolean hasFolderButton) { + public void bindViewInit(AccountsAdapter adapter, boolean hasFolderButton) { mAdapter = adapter; mCachedViewPositions = false; mHasFolderButton = hasFolderButton; diff --git a/src/com/android/email/activity/AccountsAdapter.java b/src/com/android/email/activity/AccountsAdapter.java new file mode 100644 index 000000000..59e630e20 --- /dev/null +++ b/src/com/android/email/activity/AccountsAdapter.java @@ -0,0 +1,381 @@ +/* + * Copyright (C) 2010 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.Email; +import com.android.email.R; +import com.android.email.Utility; +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 android.content.Context; +import android.database.Cursor; +import android.database.MergeCursor; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CursorAdapter; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; + +import java.util.ArrayList; + +/** + * Adapter that presents a combined list of smart mailboxes (e.g. combined inbox, all drafts, etc), + * a non-selectable separator, and the list of accounts. + */ +public class AccountsAdapter extends CursorAdapter { + + /** + * Reduced mailbox projection used by AccountsAdapter + */ + 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 + }; + 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; + + private static final String[] MAILBOX_UNREAD_COUNT_PROJECTION = new String [] { + MailboxColumns.UNREAD_COUNT + }; + private static final int MAILBOX_UNREAD_COUNT_COLUMN_UNREAD_COUNT = 0; + + private static final String MAILBOX_INBOX_SELECTION = + MailboxColumns.ACCOUNT_KEY + " =?" + " AND " + MailboxColumns.TYPE +" = " + + Mailbox.TYPE_INBOX; + + private final LayoutInflater mInflater; + private final int mMailboxesCount; + private final int mSeparatorPosition; + private final long mDefaultAccountId; + private final ArrayList mOnDeletingAccounts = new ArrayList(); + private Callback mCallback; + + public static AccountsAdapter getInstance(Cursor mailboxesCursor, Cursor accountsCursor, + Context context, long defaultAccountId, Callback callback) { + Cursor[] cursors = new Cursor[] { mailboxesCursor, accountsCursor }; + Cursor mc = new MergeCursor(cursors); + return new AccountsAdapter(mc, context, mailboxesCursor.getCount(), defaultAccountId, + callback); + } + + private AccountsAdapter(Cursor c, Context context, int mailboxesCount, long defaultAccountId, + Callback callback) { + super(context, c, true); + mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mMailboxesCount = mailboxesCount; + mSeparatorPosition = mailboxesCount; + mDefaultAccountId = defaultAccountId; + mCallback = callback; + } + + /** + * When changeCursor(null) is called, drop reference(s) to make sure we don't leak the activity + */ + @Override + public void changeCursor(Cursor cursor) { + if (cursor == null) { + mCallback = null; + } + } + + /** + * Callback interface used to report clicks other than the basic list item click or longpress. + */ + public interface Callback { + /** + * Callback for clicks on the "folder" icon (to open MailboxList) + */ + public void onClickAccountFolders(long accountId); + } + + 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 an entry point called by the list item for clicks in the folder "button" + * + * @param itemView the item in which the click occurred + */ + public void onClickFolder(AccountFolderListItem itemView) { + if (mCallback != null) { + mCallback.onClickAccountFolders(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); + } + + // TODO: We should not be doing a query inside bindAccountItem + 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)); + } +} + diff --git a/tests/src/com/android/email/activity/AccountsAdapterTest.java b/tests/src/com/android/email/activity/AccountsAdapterTest.java new file mode 100644 index 000000000..9f31369aa --- /dev/null +++ b/tests/src/com/android/email/activity/AccountsAdapterTest.java @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2010 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.provider.EmailContent.Account; +import com.android.email.provider.EmailContent.Mailbox; + +import android.database.Cursor; +import android.database.MatrixCursor; +import android.database.MergeCursor; +import android.database.MatrixCursor.RowBuilder; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +/** + * Basic unit tests of AccountsAdapter + */ +@SmallTest +public class AccountsAdapterTest extends AndroidTestCase { + + private Cursor mUpperCursor = null; + private Cursor mLowerCursor = null; + + /** + * Make an empty set of magic mailboxes in mUpperCursor + */ + private void setupUpperCursor() { + MatrixCursor childCursor = new MatrixCursor(AccountsAdapter.MAILBOX_PROJECTION); + mUpperCursor = childCursor; + } + + /** + * Make a simple set of magic mailboxes in mUpperCursor + */ + private void populateUpperCursor() { + MatrixCursor childCursor = (MatrixCursor) mUpperCursor; + + int count; + RowBuilder row; + // TYPE_INBOX + count = 10; + row = childCursor.newRow(); + row.add(Long.valueOf(Mailbox.QUERY_ALL_INBOXES)); // MAILBOX_COLUMN_ID = 0; + row.add("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 = 20; + row = childCursor.newRow(); + row.add(Long.valueOf(Mailbox.QUERY_ALL_FAVORITES)); // MAILBOX_COLUMN_ID = 0; + row.add("Favorites"); // MAILBOX_DISPLAY_NAME + 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 = 30; + row = childCursor.newRow(); + row.add(Long.valueOf(Mailbox.QUERY_ALL_DRAFTS)); // MAILBOX_COLUMN_ID = 0; + row.add("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 = 40; + row = childCursor.newRow(); + row.add(Long.valueOf(Mailbox.QUERY_ALL_OUTBOX)); // MAILBOX_COLUMN_ID = 0; + row.add("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; + } + + /** + * Make an empty set of accounts in mLowerCursor + */ + private void setupLowerCursor() { + MatrixCursor childCursor = new MatrixCursor(Account.CONTENT_PROJECTION); + mLowerCursor = childCursor; + } + + /** + * Make a simple set of "accounts". + * Note: We don't fill in the entire width of the projection because the accounts adapter + * only looks at a few of the columns anyway. + */ + private void populateLowerCursor() { + MatrixCursor childCursor = (MatrixCursor) mLowerCursor; + + RowBuilder row; + // Account #1 + row = childCursor.newRow(); + row.add(Long.valueOf(1)); // CONTENT_ID_COLUMN = 0; + row.add("Account 1"); // CONTENT_DISPLAY_NAME_COLUMN = 1; + row.add("account1@android.com"); // CONTENT_EMAIL_ADDRESS_COLUMN = 2; + // Account #2 + row = childCursor.newRow(); + row.add(Long.valueOf(2)); // CONTENT_ID_COLUMN = 0; + row.add("Account 2"); // CONTENT_DISPLAY_NAME_COLUMN = 1; + row.add("account2@android.com"); // CONTENT_EMAIL_ADDRESS_COLUMN = 2; + } + + /** + * Test: General handling of separator + */ + public void testSeparator() { + // Test with fully populated upper and lower sections + setupUpperCursor(); + populateUpperCursor(); + setupLowerCursor(); + populateLowerCursor(); + AccountsAdapter adapter = AccountsAdapter.getInstance(mUpperCursor, mLowerCursor, + getContext(), -1, null); + checkAdapter("fully populated", adapter, mUpperCursor, mLowerCursor); + + // Test with empty upper and populated lower + setupUpperCursor(); + setupLowerCursor(); + populateLowerCursor(); + adapter = AccountsAdapter.getInstance(mUpperCursor, mLowerCursor, getContext(), -1, null); + checkAdapter("lower populated", adapter, mUpperCursor, mLowerCursor); + + // Test with both empty + setupUpperCursor(); + setupLowerCursor(); + adapter = AccountsAdapter.getInstance(mUpperCursor, mLowerCursor, getContext(), -1, null); + checkAdapter("both empty", adapter, mUpperCursor, mLowerCursor); + } + + /** + * Helper to check the various APIs related to the upper/lower separator + */ + private void checkAdapter(String tag, AccountsAdapter adapter, Cursor upper, Cursor lower) { + // Check total count + int expectedCount = 0; + if (upper != null) expectedCount += upper.getCount(); + if (lower != null) expectedCount += lower.getCount(); + // If one or more items are shown, the adapter inserts the separator as well + if (expectedCount > 0) expectedCount++; + assertEquals(tag, expectedCount, adapter.getCount()); + + // Check separator-related APIs + int separatorIndex = -1; + if (upper != null) { + separatorIndex = upper.getCount(); + } + + // Get the MergeCursor that the adapter created + MergeCursor mc = (MergeCursor) adapter.getCursor(); + + // Check APIs for the position above the separator index + // This will be the last entry in the "upper" cursor + if (separatorIndex > 0) { + int checkIndex = separatorIndex - 1; + assertTrue(tag, adapter.isMailbox(checkIndex)); + assertFalse(tag, adapter.isAccount(checkIndex)); + assertTrue(tag, adapter.isEnabled(checkIndex)); + Cursor c = (Cursor) adapter.getItem(checkIndex); + assertEquals(tag, mc, c); + assertEquals(tag, checkIndex, c.getPosition()); + upper.moveToLast(); + long id = upper.getLong(0); + assertEquals(tag, id, adapter.getItemId(checkIndex)); + } + + // Check APIs for position at the separator index + if (separatorIndex >= 0) { + int checkIndex = separatorIndex; + assertFalse(tag, adapter.isMailbox(checkIndex)); + assertFalse(tag, adapter.isAccount(checkIndex)); + assertFalse(tag, adapter.isEnabled(checkIndex)); + // getItem and getItemId should never be called because it should not be enabled + } + + // Check APIs for the position below the separator index + // This will be the first entry in the "lower" cursor + if (lower != null && lower.getCount() > 0) { + int checkIndex = separatorIndex + 1; + assertFalse(tag, adapter.isMailbox(checkIndex)); + assertTrue(tag, adapter.isAccount(checkIndex)); + assertTrue(tag, adapter.isEnabled(checkIndex)); + Cursor c = (Cursor) adapter.getItem(checkIndex); + assertEquals(tag, mc, c); + assertEquals(tag, separatorIndex, c.getPosition()); + lower.moveToFirst(); + long id = lower.getLong(0); + assertEquals(tag, id, adapter.getItemId(checkIndex)); + } + } + + /** + * Test: isOnDeletingAccountView + */ + public void testDeletingAccount() { + // Test with fully populated upper and lower sections + setupUpperCursor(); + populateUpperCursor(); + setupLowerCursor(); + populateLowerCursor(); + AccountsAdapter adapter = AccountsAdapter.getInstance(mUpperCursor, mLowerCursor, + getContext(), -1, null); + + // Check enabled state - all should be enabled except for separator + for (int i = 0; i < adapter.getCount(); i++) { + boolean expectEnabled = adapter.isMailbox(i) || adapter.isAccount(i); + assertEquals(expectEnabled, adapter.isEnabled(i)); + } + + // "Delete" the first account + adapter.addOnDeletingAccount(1); // account Id of 1st account + int account1Position = mUpperCursor.getCount() + 1; // first entry after separator + for (int i = 0; i < adapter.getCount(); i++) { + boolean isNotSeparator = adapter.isMailbox(i) || adapter.isAccount(i); + boolean expectEnabled = isNotSeparator && (i != account1Position); + assertEquals(expectEnabled, adapter.isEnabled(i)); + } + } + + /** + * Test: callback(s) + */ + public void testCallbacks() { + // Test with fully populated upper and lower sections + setupUpperCursor(); + populateUpperCursor(); + setupLowerCursor(); + populateLowerCursor(); + Callback cb = new Callback(); + AccountsAdapter adapter = AccountsAdapter.getInstance(mUpperCursor, mLowerCursor, + getContext(), -1, cb); + + AccountFolderListItem itemView = new AccountFolderListItem(mContext); + itemView.mAccountId = 1; + adapter.onClickFolder(itemView); + assertTrue(cb.called); + assertEquals(1, cb.id); + } + + private static class Callback implements AccountsAdapter.Callback { + public boolean called = false; + public long id = -1; + + public void onClickAccountFolders(long accountId) { + called = true; + id = accountId; + } + } +}