Refactor AccountsAdapter into its own class.

* Extract AccountAdapter from AccountFolderList
* Use callback instead of hardcoded launch of MailboxList
* Unit tests

Change-Id: Icafce1ef73a99fb61985c649620440656f9b51a3
This commit is contained in:
Andrew Stadler 2010-05-28 12:54:56 -07:00
parent 164c5004e7
commit f3c285d4a3
4 changed files with 657 additions and 327 deletions

View File

@ -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<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));
}
}
}

View File

@ -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;

View File

@ -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<Long> mOnDeletingAccounts = new ArrayList<Long>();
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));
}
}

View File

@ -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;
}
}
}