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:
parent
fd8ee7bc50
commit
2ac1eaf8c3
|
@ -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"
|
||||
/>
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue