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
This commit is contained in:
Makoto Onuki 2010-11-09 15:20:02 -08:00
parent fd8ee7bc50
commit 2ac1eaf8c3
6 changed files with 257 additions and 25 deletions

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/display_name"
android:layout_width="200dip"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?android:attr/textColorPrimary"
/>

View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<!--
The width set here will be ignored. The actual width will be the same as the dropdown view.
-->
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/unread_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:singleLine="true"
android:paddingTop="12dip"
android:paddingRight="12dip"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?android:attr/textColorPrimary"
/>
<TextView
android:id="@+id/display_name"
android:layout_width="320dip"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_toLeftOf="@id/unread_count"
android:ellipsize="end"
android:singleLine="true"
android:paddingTop="12dip"
android:paddingLeft="12dip"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?android:attr/textColorPrimary"
/>
<TextView
android:id="@+id/email_address"
android:layout_width="320dip"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@id/display_name"
android:ellipsize="end"
android:singleLine="true"
android:paddingLeft="12dip"
android:paddingBottom="12dip"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorSecondary"
/>
</RelativeLayout>

View File

@ -206,6 +206,12 @@
<xliff:g id="account_name">%2$s</xliff:g></item>
</plurals>
<!-- The number of accounts configured. [CHAR LIMIT=16] -->
<plurals name="number_of_accounts">
<item quantity="one"><xliff:g id="num_accounts" example="1">%1$d</xliff:g> account</item>
<item quantity="other"><xliff:g id="num_accounts" example="2">%1$d</xliff:g> accounts</item>
</plurals>
<!-- The next set of strings are used server-side and must not be localized. -->
<!-- Do Not Translate. This is the name of the "inbox" folder, on the server. -->
<string name="mailbox_name_server_inbox" translatable="false">Inbox</string>

View File

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

View File

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

View File

@ -1920,6 +1920,7 @@ public class ProviderTests extends ProviderTestCase2<EmailProvider> {
*
* 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<EmailProvider> {
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<EmailProvider> {
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<EmailProvider> {
// 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) {