From 2ac1eaf8c3f7122da7900f0ea5a1198264631d74 Mon Sep 17 00:00:00 2001 From: Makoto Onuki Date: Tue, 9 Nov 2010 15:20:02 -0800 Subject: [PATCH] Account selector rework. - Show email address - Show inbox unread message count Initially I thought of using a join to get accounts with their unread counts with one query, but there were enough subtle issues that I gave up on the idea. Instead it uses a MatrixCursor to build a completely new cursor, based on a regular accounts cursor. Change-Id: I09e8762f131cc2bd3637e1a3d302088a3b5b2479 --- res/layout/account_selector.xml | 28 ++++ res/layout/account_selector_dropdown.xml | 64 ++++++++ res/values/strings.xml | 6 + .../activity/AccountSelectorAdapter.java | 140 +++++++++++++++--- .../android/email/provider/EmailContent.java | 12 ++ .../android/email/provider/ProviderTests.java | 32 +++- 6 files changed, 257 insertions(+), 25 deletions(-) create mode 100644 res/layout/account_selector.xml create mode 100644 res/layout/account_selector_dropdown.xml diff --git a/res/layout/account_selector.xml b/res/layout/account_selector.xml new file mode 100644 index 000000000..b315126be --- /dev/null +++ b/res/layout/account_selector.xml @@ -0,0 +1,28 @@ + + + + diff --git a/res/layout/account_selector_dropdown.xml b/res/layout/account_selector_dropdown.xml new file mode 100644 index 000000000..b61a87e11 --- /dev/null +++ b/res/layout/account_selector_dropdown.xml @@ -0,0 +1,64 @@ + + + + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 77fe4e373..090d7aae4 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -206,6 +206,12 @@ %2$s + + + %1$d account + %1$d accounts + + Inbox diff --git a/src/com/android/email/activity/AccountSelectorAdapter.java b/src/com/android/email/activity/AccountSelectorAdapter.java index 5873c2d4e..93fc32c3b 100644 --- a/src/com/android/email/activity/AccountSelectorAdapter.java +++ b/src/com/android/email/activity/AccountSelectorAdapter.java @@ -20,12 +20,12 @@ import com.android.email.R; import com.android.email.data.ThrottlingCursorLoader; import com.android.email.provider.EmailContent; import com.android.email.provider.EmailContent.Account; +import com.android.email.provider.EmailContent.Mailbox; import android.content.Context; import android.content.Loader; import android.database.Cursor; import android.database.MatrixCursor; -import android.database.MergeCursor; import android.database.MatrixCursor.RowBuilder; import android.view.LayoutInflater; import android.view.View; @@ -42,13 +42,28 @@ import android.widget.TextView; * simpler for now.) Maybe we can just use SimpleCursorAdapter. */ public class AccountSelectorAdapter extends CursorAdapter { - private static final String[] PROJECTION = new String[] { + /** Projection used to query from Account */ + private static final String[] ACCOUNT_PROJECTION = new String[] { EmailContent.RECORD_ID, - EmailContent.Account.DISPLAY_NAME + EmailContent.Account.DISPLAY_NAME, + EmailContent.Account.EMAIL_ADDRESS, + }; + + /** + * Projection for the resulting MatrixCursor -- must be {@link #ACCOUNT_PROJECTION} + * with "UNREAD_COUNT". + */ + private static final String[] RESULT_PROJECTION = new String[] { + EmailContent.RECORD_ID, + EmailContent.Account.DISPLAY_NAME, + EmailContent.Account.EMAIL_ADDRESS, + "UNREAD_COUNT" }; private static final int ID_COLUMN = 0; private static final int DISPLAY_NAME_COLUMN = 1; + private static final int EMAIL_ADDRESS_COLUMN = 2; + private static final int UNREAD_COUNT_COLUMN = 3; /** Sort order. Show the default account first. */ private static final String ORDER_BY = @@ -67,21 +82,35 @@ public class AccountSelectorAdapter extends CursorAdapter { @Override public View getDropDownView(int position, View convertView, ViewGroup parent) { - View view = mInflater.inflate(android.R.layout.simple_spinner_dropdown_item, null); - TextView textView = (TextView) view.findViewById(android.R.id.text1); - textView.setText(getAccountDisplayName(position)); + final View view = mInflater.inflate(R.layout.account_selector_dropdown, null); + + final TextView displayNameView = (TextView) view.findViewById(R.id.display_name); + final TextView emailAddressView = (TextView) view.findViewById(R.id.email_address); + final TextView unreadCountView = (TextView) view.findViewById(R.id.unread_count); + + final String displayName = getAccountDisplayName(position); + final String emailAddress = getAccountEmailAddress(position); + + displayNameView.setText(displayName); + + // Show the email address only when it's different from the display name. + // If same, show " " instead of "", so that the text view won't get completely + // collapsed. (TextView's height will be 0px if it's "match_content" and the + // content is "".) + emailAddressView.setText(emailAddress.equals(displayName) ? " " : emailAddress); + unreadCountView.setText(Integer.toString(getAccountUnreadCount(position))); return view; } @Override public void bindView(View view, Context context, Cursor cursor) { - TextView textView = (TextView) view.findViewById(android.R.id.text1); + TextView textView = (TextView) view.findViewById(R.id.display_name); textView.setText(getAccountDisplayName(cursor)); } @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { - return mInflater.inflate(android.R.layout.simple_spinner_item, null); + return mInflater.inflate(R.layout.account_selector, null); } /** @return Account id extracted from a Cursor. */ @@ -94,37 +123,106 @@ public class AccountSelectorAdapter extends CursorAdapter { return c.moveToPosition(position) ? getAccountDisplayName(c) : null; } + private String getAccountEmailAddress(int position) { + final Cursor c = getCursor(); + return c.moveToPosition(position) ? getAccountEmailAddress(c) : null; + } + + private int getAccountUnreadCount(int position) { + final Cursor c = getCursor(); + return c.moveToPosition(position) ? getAccountUnreadCount(c) : 0; + } + /** @return Account name extracted from a Cursor. */ public static String getAccountDisplayName(Cursor cursor) { return cursor.getString(DISPLAY_NAME_COLUMN); } + /** @return Email address extracted from a Cursor. */ + public static String getAccountEmailAddress(Cursor cursor) { + return cursor.getString(EMAIL_ADDRESS_COLUMN); + } + + /** @return Unread count extracted from a Cursor. */ + public static int getAccountUnreadCount(Cursor cursor) { + return cursor.getInt(UNREAD_COUNT_COLUMN); + } + /** - * Load the account list. Also add the "Combined view" row if there's more than one account. + * Load the account list. The resulting cursor contains + * - Account info + * - # of unread messages in inbox + * - The "Combined view" row if there's more than one account. */ private static class AccountsLoader extends ThrottlingCursorLoader { private final Context mContext; public AccountsLoader(Context context) { - super(context, EmailContent.Account.CONTENT_URI, PROJECTION, null, null, ORDER_BY); + // Super class loads a regular account cursor, but we replace it in loadInBackground(). + super(context, EmailContent.Account.CONTENT_URI, ACCOUNT_PROJECTION, null, null, + ORDER_BY); mContext = context; } @Override public Cursor loadInBackground() { + // Fetch account list final Cursor accountsCursor = super.loadInBackground(); - if (accountsCursor.getCount() <= 1) { - return accountsCursor; - } - // If more than 1 account, add "Combined view". - final MatrixCursor combinedViewRow = new MatrixCursor(PROJECTION); - RowBuilder rb = combinedViewRow.newRow(); - // Add ID and display name - rb.add(Account.ACCOUNT_ID_COMBINED_VIEW); - rb.add(mContext.getResources().getString( - R.string.mailbox_list_account_selector_combined_view)); - return new MergeCursor(new Cursor[] {accountsCursor, combinedViewRow}); + // Cursor that's actually returned. + // Use ClosingMatrixCursor so that accountsCursor gets closed too when it's closed. + final MatrixCursor resultCursor = new ClosingMatrixCursor(RESULT_PROJECTION, + accountsCursor); + accountsCursor.moveToPosition(-1); + + // Build the cursor... + int totalUnread = 0; + while (accountsCursor.moveToNext()) { + // Add account, with its unread count. + final long accountId = accountsCursor.getLong(0); + final int unread = Mailbox.getUnreadCountByAccountAndMailboxType( + mContext, accountId, Mailbox.TYPE_INBOX); + + RowBuilder rb = resultCursor.newRow(); + rb.add(accountId); + rb.add(getAccountDisplayName(accountsCursor)); + rb.add(getAccountEmailAddress(accountsCursor)); + rb.add(unread); + totalUnread += unread; + } + // Add "combined view" + final int countAccounts = resultCursor.getCount(); + if (countAccounts > 0) { + RowBuilder rb = resultCursor.newRow(); + + // Add ID, display name, # of accounts, total unread count. + rb.add(Account.ACCOUNT_ID_COMBINED_VIEW); + rb.add(mContext.getResources().getString( + R.string.mailbox_list_account_selector_combined_view)); + rb.add(mContext.getResources().getQuantityString(R.plurals.number_of_accounts, + countAccounts, countAccounts)); + rb.add(totalUnread); + } + return resultCursor; + } + } + + /** + * {@link MatrixCursor} which takes an extra {@link Cursor} to the constructor, and close + * it when self is closed. + */ + private static class ClosingMatrixCursor extends MatrixCursor { + private final Cursor mInnerCursor; + + public ClosingMatrixCursor(String[] columnNames, Cursor innerCursor) { + super(columnNames); + mInnerCursor = innerCursor; + } + + @Override + public void close() { + mInnerCursor.close(); + super.close(); } } } diff --git a/src/com/android/email/provider/EmailContent.java b/src/com/android/email/provider/EmailContent.java index eece8a045..cb9795929 100644 --- a/src/com/android/email/provider/EmailContent.java +++ b/src/com/android/email/provider/EmailContent.java @@ -2184,6 +2184,9 @@ public abstract class EmailContent { MailboxColumns.SYNC_STATUS, MailboxColumns.MESSAGE_COUNT }; + private static final String ACCOUNT_AND_MAILBOX_TYPE_SELECTION = + MailboxColumns.ACCOUNT_KEY + " =? AND " + + MailboxColumns.TYPE + " =?"; private static final String MAILBOX_TYPE_SELECTION = MailboxColumns.TYPE + " =?"; private static final String[] MAILBOX_SUM_OF_UNREAD_COUNT_PROJECTION = new String [] { @@ -2374,6 +2377,15 @@ public abstract class EmailContent { return null; } + public static int getUnreadCountByAccountAndMailboxType(Context context, long accountId, + int type) { + return Utility.getFirstRowInt(context, Mailbox.CONTENT_URI, + MAILBOX_SUM_OF_UNREAD_COUNT_PROJECTION, + ACCOUNT_AND_MAILBOX_TYPE_SELECTION, + new String[] { String.valueOf(accountId), String.valueOf(type) }, + null, UNREAD_COUNT_COUNT_COLUMN, 0); + } + public static int getUnreadCountByMailboxType(Context context, int type) { return Utility.getFirstRowInt(context, Mailbox.CONTENT_URI, MAILBOX_SUM_OF_UNREAD_COUNT_PROJECTION, diff --git a/tests/src/com/android/email/provider/ProviderTests.java b/tests/src/com/android/email/provider/ProviderTests.java index 9086f2777..2e1229b10 100644 --- a/tests/src/com/android/email/provider/ProviderTests.java +++ b/tests/src/com/android/email/provider/ProviderTests.java @@ -1920,6 +1920,7 @@ public class ProviderTests extends ProviderTestCase2 { * * It also covers: * - {@link Mailbox#getMessageCountByMailboxType(Context, int)} + * - {@link Mailbox#getUnreadCountByAccountAndMailboxType(Context, long, int)} * - {@link Mailbox#getUnreadCountByMailboxType(Context, int)} * - {@link Message#getFavoriteMessageCount(Context)} */ @@ -1949,22 +1950,31 @@ public class ProviderTests extends ProviderTestCase2 { assertEquals(0, Mailbox.getMessageCountByMailboxType(c, Mailbox.TYPE_INBOX)); assertEquals(0, Mailbox.getMessageCountByMailboxType(c, Mailbox.TYPE_OUTBOX)); + assertEquals(0, Mailbox.getUnreadCountByAccountAndMailboxType(c, + a1.mId, Mailbox.TYPE_INBOX)); + assertEquals(0, Mailbox.getUnreadCountByAccountAndMailboxType(c, + a1.mId, Mailbox.TYPE_OUTBOX)); + assertEquals(0, Mailbox.getUnreadCountByAccountAndMailboxType(c, + a2.mId, Mailbox.TYPE_INBOX)); + assertEquals(0, Mailbox.getUnreadCountByAccountAndMailboxType(c, + a2.mId, Mailbox.TYPE_OUTBOX)); + // 1. Test for insert triggers. // Create some messages - // b1: 1 message + // b1 (account 1, inbox): 1 message Message m11 = createMessage(c, b1, true, false); - // b2: 2 message + // b2 (account 1, outbox): 2 message Message m21 = createMessage(c, b2, false, false); Message m22 = createMessage(c, b2, true, true); - // b3: 3 message + // b3 (account 2, inbox): 3 message Message m31 = createMessage(c, b3, false, false); Message m32 = createMessage(c, b3, false, false); Message m33 = createMessage(c, b3, true, true); - // b4 has no messages. + // b4 (account 2, outbox) has no messages. // Check message counts assertEquals(1, getMessageCount(b1.mId)); @@ -1979,6 +1989,15 @@ public class ProviderTests extends ProviderTestCase2 { assertEquals(4, Mailbox.getMessageCountByMailboxType(c, Mailbox.TYPE_INBOX)); assertEquals(2, Mailbox.getMessageCountByMailboxType(c, Mailbox.TYPE_OUTBOX)); + assertEquals(1, Mailbox.getUnreadCountByAccountAndMailboxType(c, + a1.mId, Mailbox.TYPE_INBOX)); + assertEquals(1, Mailbox.getUnreadCountByAccountAndMailboxType(c, + a1.mId, Mailbox.TYPE_OUTBOX)); + assertEquals(2, Mailbox.getUnreadCountByAccountAndMailboxType(c, + a2.mId, Mailbox.TYPE_INBOX)); + assertEquals(0, Mailbox.getUnreadCountByAccountAndMailboxType(c, + a2.mId, Mailbox.TYPE_OUTBOX)); + // 2. test for recalculateMessageCount. // First, invalidate the message counts. @@ -2030,7 +2049,12 @@ public class ProviderTests extends ProviderTestCase2 { // No such mailbox type. assertEquals(0, Mailbox.getMessageCountByMailboxType(c, 99999)); + assertEquals(0, Mailbox.getUnreadCountByAccountAndMailboxType(c, a1.mId, 99999)); assertEquals(0, Mailbox.getUnreadCountByMailboxType(c, 99999)); + + // No such account + assertEquals(0, Mailbox.getUnreadCountByAccountAndMailboxType(c, + 99999, Mailbox.TYPE_INBOX)); } private static Message createMessage(Context c, Mailbox b, boolean starred, boolean read) {