Aadd special boxes to mailbox list.

Change-Id: I36616f53555346ca7fbb6f3426a0c3196d7bacbc
This commit is contained in:
Makoto Onuki 2010-08-02 18:16:13 -07:00
parent 77c6ccdd61
commit 833fe73b99
9 changed files with 285 additions and 59 deletions

View File

@ -187,11 +187,11 @@
<!-- The summary section entry in the AccountFolder list to display all inboxes -->
<string name="account_folder_list_summary_inbox">Combined Inbox</string>
<!-- The summary section entry in the AccountFolder list to display all starred -->
<string name="account_folder_list_summary_starred">Starred</string>
<string name="account_folder_list_summary_starred">All Starred</string>
<!-- The summary section entry in the AccountFolder list to display all drafts -->
<string name="account_folder_list_summary_drafts">Drafts</string>
<string name="account_folder_list_summary_drafts">All Drafts</string>
<!-- The summary section entry in the AccountFolder list to display all outboxes -->
<string name="account_folder_list_summary_outbox">Outbox</string>
<string name="account_folder_list_summary_outbox">Combined Outbox</string>
<!-- Toast that appears when you select "Refresh" menu option -->
<string name="account_folder_list_refresh_toast">Please longpress an account to refresh it</string>

View File

@ -35,11 +35,13 @@ import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Parcelable;
import android.security.MessageDigest;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
import android.widget.AbsListView;
import android.widget.TextView;
import android.widget.Toast;
@ -316,18 +318,24 @@ public class Utility {
return selection.toString();
}
// TODO When the UI is settled, cache all strings/drawables
// TODO When the UI is settled, write up tests
// TODO When the UI is settled, remove backward-compatibility methods
public static class FolderProperties {
private static FolderProperties sInstance;
private final Context mContext;
// Caches for frequently accessed resources.
private String[] mSpecialMailbox = new String[] {};
private TypedArray mSpecialMailboxDrawable;
private Drawable mDefaultMailboxDrawable;
private Drawable mSummaryStarredMailboxDrawable;
private Drawable mSummaryCombinedInboxDrawable;
private final String[] mSpecialMailbox;
private final TypedArray mSpecialMailboxDrawable;
private final Drawable mDefaultMailboxDrawable;
private final Drawable mSummaryStarredMailboxDrawable;
private final Drawable mSummaryCombinedInboxDrawable;
private FolderProperties(Context context) {
mContext = context.getApplicationContext();
mSpecialMailbox = context.getResources().getStringArray(R.array.mailbox_display_names);
for (int i = 0; i < mSpecialMailbox.length; ++i) {
if ("".equals(mSpecialMailbox[i])) {
@ -352,12 +360,41 @@ public class Utility {
return sInstance;
}
// For backward compatibility.
public String getDisplayName(int type) {
return getDisplayName(type, -1);
}
// For backward compatibility.
public Drawable getSummaryMailboxIconIds(long id) {
return getIcon(-1, id);
}
public Drawable getIconIds(int type) {
return getIcon(type, -1);
}
/**
* Lookup names of localized special mailboxes
* @param type
* @return Localized strings
*/
public String getDisplayName(int type) {
public String getDisplayName(int type, long mailboxId) {
// Special combined mailboxes
int resId = 0;
// Can't use long for switch!?
if (mailboxId == Mailbox.QUERY_ALL_INBOXES) {
resId = R.string.account_folder_list_summary_inbox;
} else if (mailboxId == Mailbox.QUERY_ALL_FAVORITES) {
resId = R.string.account_folder_list_summary_starred;
} else if (mailboxId == Mailbox.QUERY_ALL_DRAFTS) {
resId = R.string.account_folder_list_summary_drafts;
} else if (mailboxId == Mailbox.QUERY_ALL_OUTBOX) {
resId = R.string.account_folder_list_summary_outbox;
}
if (resId != 0) {
return mContext.getString(resId);
}
if (type < mSpecialMailbox.length) {
return mSpecialMailbox[type];
}
@ -366,26 +403,20 @@ public class Utility {
/**
* Lookup icons of special mailboxes
* @param type
* @return icon's drawable
*/
public Drawable getIconIds(int type) {
if (type < mSpecialMailboxDrawable.length()) {
return mSpecialMailboxDrawable.getDrawable(type);
}
return mDefaultMailboxDrawable;
}
public Drawable getSummaryMailboxIconIds(long mailboxKey) {
if (mailboxKey == Mailbox.QUERY_ALL_INBOXES) {
public Drawable getIcon(int type, long mailboxId) {
if (mailboxId == Mailbox.QUERY_ALL_INBOXES) {
return mSummaryCombinedInboxDrawable;
} else if (mailboxKey == Mailbox.QUERY_ALL_FAVORITES) {
} else if (mailboxId == Mailbox.QUERY_ALL_FAVORITES) {
return mSummaryStarredMailboxDrawable;
} else if (mailboxKey == Mailbox.QUERY_ALL_DRAFTS) {
} else if (mailboxId == Mailbox.QUERY_ALL_DRAFTS) {
return mSpecialMailboxDrawable.getDrawable(Mailbox.TYPE_DRAFTS);
} else if (mailboxKey == Mailbox.QUERY_ALL_OUTBOX) {
} else if (mailboxId == Mailbox.QUERY_ALL_OUTBOX) {
return mSpecialMailboxDrawable.getDrawable(Mailbox.TYPE_OUTBOX);
}
if (0 <= type && type < mSpecialMailboxDrawable.length()) {
return mSpecialMailboxDrawable.getDrawable(type);
}
return mDefaultMailboxDrawable;
}
}
@ -773,4 +804,21 @@ public class Utility {
return getFirstRowLong(context, uri, projection, selection, selectionArgs,
sortOrder, column, null);
}
/**
* A class used to restore ListView state (e.g. scroll position) when changing adapter.
*
* TODO For some reason it doesn't always work. Investigate and fix it.
*/
public static class ListStateSaver {
private final Parcelable mState;
public ListStateSaver(AbsListView lv) {
mState = lv.onSaveInstanceState();
}
public void restore(AbsListView lv) {
lv.onRestoreInstanceState(mState);
}
}
}

View File

@ -17,6 +17,7 @@
package com.android.email.activity;
import com.android.email.Email;
import com.android.email.Utility;
import android.app.Activity;
import android.app.ListFragment;
@ -216,12 +217,17 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
Log.d(Email.LOG_TAG, "MailboxListFragment onLoadFinished");
}
// Save list view state (primarily scroll position)
final ListView lv = getListView();
final Parcelable listState = lv.onSaveInstanceState();
final Utility.ListStateSaver lss = new Utility.ListStateSaver(lv);
// Set the adapter.
mListAdapter.changeCursor(cursor);
setListAdapter(mListAdapter);
setListShown(true);
lv.onRestoreInstanceState(listState);
// Restore the state
lss.restore(lv);
}
}

View File

@ -21,11 +21,15 @@ import com.android.email.Utility;
import com.android.email.provider.EmailContent;
import com.android.email.provider.EmailContent.Mailbox;
import com.android.email.provider.EmailContent.MailboxColumns;
import com.android.email.provider.EmailContent.Message;
import android.content.Context;
import android.content.CursorLoader;
import android.content.Loader;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.MatrixCursor.RowBuilder;
import android.database.MergeCursor;
import android.graphics.Typeface;
import android.view.LayoutInflater;
import android.view.View;
@ -39,16 +43,18 @@ import android.widget.TextView;
*
* TODO Add "combined inbox/star/etc.".
* TODO Throttle auto-requery.
* TODO New UI will probably not distinguish unread counts from # of messages.
* i.e. we won't need two different viewes for them.
* TODO Unit test, when UI is settled.
*/
/* package */ class MailboxesAdapter extends CursorAdapter {
private static final String[] PROJECTION = new String[] { MailboxColumns.ID,
MailboxColumns.DISPLAY_NAME, MailboxColumns.UNREAD_COUNT, MailboxColumns.TYPE,
MailboxColumns.DISPLAY_NAME, MailboxColumns.TYPE, MailboxColumns.UNREAD_COUNT,
MailboxColumns.MESSAGE_COUNT};
private static final int COLUMN_ID = 0;
private static final int COLUMN_DISPLAY_NAME = 1;
private static final int COLUMN_UNREAD_COUNT = 2;
private static final int COLUMN_TYPE = 3;
private static final int COLUMN_TYPE = 2;
private static final int COLUMN_UNREAD_COUNT = 3;
private static final int COLUMN_MESSAGE_COUNT = 4;
private static final String MAILBOX_SELECTION = MailboxColumns.ACCOUNT_KEY + "=?" +
@ -65,11 +71,12 @@ import android.widget.TextView;
@Override
public void bindView(View view, Context context, Cursor cursor) {
final int type = cursor.getInt(COLUMN_TYPE);
final long mailboxId = cursor.getLong(COLUMN_ID);
// Set mailbox name
final TextView nameView = (TextView) view.findViewById(R.id.mailbox_name);
String mailboxName = Utility.FolderProperties.getInstance(context)
.getDisplayName(type);
.getDisplayName(type, mailboxId);
if (mailboxName == null) {
mailboxName = cursor.getString(COLUMN_DISPLAY_NAME);
}
@ -110,7 +117,7 @@ import android.widget.TextView;
// Set folder icon
((ImageView) view.findViewById(R.id.folder_icon))
.setImageDrawable(Utility.FolderProperties.getInstance(context)
.getIconIds(type));
.getIcon(type, mailboxId));
}
@Override
@ -122,11 +129,70 @@ import android.widget.TextView;
* @return mailboxes Loader for an account.
*/
public static Loader<Cursor> createLoader(Context context, long accountId) {
return new CursorLoader(context,
EmailContent.Mailbox.CONTENT_URI,
MailboxesAdapter.PROJECTION,
MAILBOX_SELECTION,
new String[] { String.valueOf(accountId) },
MailboxColumns.TYPE + "," + MailboxColumns.DISPLAY_NAME);
return new MailboxesLoader(context, accountId);
}
/**
* Loader for mailboxes. If there's more than 1 account set up, the result will also include
* special mailboxes. (e.g. combined inbox, etc)
*/
private static class MailboxesLoader extends CursorLoader {
private final Context mContext;
public MailboxesLoader(Context context, long accountId) {
super(context, EmailContent.Mailbox.CONTENT_URI,
MailboxesAdapter.PROJECTION,
MAILBOX_SELECTION,
new String[] { String.valueOf(accountId) },
MailboxColumns.TYPE + "," + MailboxColumns.DISPLAY_NAME);
mContext = context;
}
@Override
public Cursor loadInBackground() {
final Cursor mailboxes = super.loadInBackground();
return new MergeCursor(
new Cursor[] {getSpecialMailboxesCursor(mContext), mailboxes});
}
}
/* package */ static Cursor getSpecialMailboxesCursor(Context context) {
MatrixCursor cursor = new MatrixCursor(PROJECTION);
// TODO show combined boxes only if # accounts > 1 (wait for UI) but we always need starred.
// Combined inbox -- show unread count
addSummaryMailboxRow(context, cursor,
Mailbox.QUERY_ALL_INBOXES, Mailbox.TYPE_INBOX,
Mailbox.getUnreadCountByMailboxType(context, Mailbox.TYPE_INBOX));
// Favorite -- show # of favorites
addSummaryMailboxRow(context, cursor,
Mailbox.QUERY_ALL_FAVORITES, Mailbox.TYPE_MAIL,
Message.getFavoriteMessageCount(context));
// Drafts -- show # of drafts
addSummaryMailboxRow(context, cursor,
Mailbox.QUERY_ALL_DRAFTS, Mailbox.TYPE_DRAFTS,
Mailbox.getMessageCountByMailboxType(context, Mailbox.TYPE_DRAFTS));
// Outbox -- # of sent messages
addSummaryMailboxRow(context, cursor,
Mailbox.QUERY_ALL_OUTBOX, Mailbox.TYPE_OUTBOX,
Mailbox.getMessageCountByMailboxType(context, Mailbox.TYPE_OUTBOX));
return cursor;
}
private static void addSummaryMailboxRow(Context context, MatrixCursor cursor,
long id, int type, int count) {
if (count > 0) {
RowBuilder row = cursor.newRow();
row.add(id);
row.add(""); // Display name. We get it from FolderProperties.
row.add(type);
row.add(count);
row.add(count);
}
}
}

View File

@ -910,12 +910,12 @@ public class MessageListFragment extends ListFragment implements OnItemClickList
}
MessageListFragment.this.mAccountId = mAccountKey;
addFooterView(mMailboxKey, mAccountKey);
// TODO changeCursor(null)??
mListAdapter.changeCursor(cursor);
setListAdapter(mListAdapter);
addFooterView(mMailboxKey, mAccountKey);
// changeCursor occurs the jumping of position in ListView, so it's need to restore
// the position;
restoreListPosition();

View File

@ -504,6 +504,9 @@ public abstract class EmailContent {
public static final String[] ID_COLUMN_PROJECTION = new String[] { RECORD_ID };
private static final String FAVORITE_COUNT_SELECTION =
MessageColumns.FLAG_FAVORITE + "= 1";
// _id field is in AbstractContent
public String mDisplayName;
public long mTimeStamp;
@ -765,6 +768,15 @@ public abstract class EmailContent {
}
}
}
/**
* @return number of favorite (starred) messages throughout all accounts.
*
* TODO Add trigger to keep track. (index isn't efficient in this case.)
*/
public static int getFavoriteMessageCount(Context context) {
return count(context, Message.CONTENT_URI, FAVORITE_COUNT_SELECTION, null);
}
}
public interface AccountColumns {
@ -1996,6 +2008,18 @@ public abstract class EmailContent {
MailboxColumns.FLAG_VISIBLE, MailboxColumns.FLAGS, MailboxColumns.VISIBLE_LIMIT,
MailboxColumns.SYNC_STATUS, MailboxColumns.MESSAGE_COUNT
};
private static final String MAILBOX_TYPE_SELECTION =
MailboxColumns.TYPE + " =?";
private static final String[] MAILBOX_SUM_OF_UNREAD_COUNT_PROJECTION = new String [] {
"sum(" + MailboxColumns.UNREAD_COUNT + ")"
};
private static final int UNREAD_COUNT_COUNT_COLUMN = 0;
private static final String[] MAILBOX_SUM_OF_MESSAGE_COUNT_PROJECTION = new String [] {
"sum(" + MailboxColumns.MESSAGE_COUNT + ")"
};
private static final int MESSAGE_COUNT_COUNT_COLUMN = 0;
public static final long NO_MAILBOX = -1;
// Sentinel values for the mSyncInterval field of both Mailbox records
@ -2153,6 +2177,22 @@ public abstract class EmailContent {
}
return null;
}
public static int getUnreadCountByMailboxType(Context context, int type) {
return Utility.getFirstRowLong(context, Mailbox.CONTENT_URI,
MAILBOX_SUM_OF_UNREAD_COUNT_PROJECTION,
MAILBOX_TYPE_SELECTION,
new String[] { String.valueOf(type) }, null, UNREAD_COUNT_COUNT_COLUMN)
.intValue();
}
public static int getMessageCountByMailboxType(Context context, int type) {
return Utility.getFirstRowLong(context, Mailbox.CONTENT_URI,
MAILBOX_SUM_OF_MESSAGE_COUNT_PROJECTION,
MAILBOX_TYPE_SELECTION,
new String[] { String.valueOf(type) }, null, MESSAGE_COUNT_COUNT_COLUMN)
.intValue();
}
}
public interface HostAuthColumns {

View File

@ -23,12 +23,14 @@ import com.android.email.provider.EmailContent.Mailbox;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Environment;
import android.os.Parcelable;
import android.telephony.TelephonyManager;
import android.test.AndroidTestCase;
import android.test.MoreAsserts;
import android.test.mock.MockCursor;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
import android.widget.ListView;
import android.widget.TextView;
import java.io.File;
@ -401,4 +403,39 @@ public class UtilityUnitTests extends AndroidTestCase {
assertEquals(Long.valueOf(-1), actual);
assertTrue(cursor.mClosed);
}
public void testListStateSaver() {
MockListView lv = new MockListView(getContext());
Utility.ListStateSaver lss = new Utility.ListStateSaver(lv);
assertTrue(lv.mCalledOnSaveInstanceState);
assertFalse(lv.mCalledOnRestoreInstanceState);
lv.mCalledOnSaveInstanceState = false;
lss.restore(lv);
assertFalse(lv.mCalledOnSaveInstanceState);
assertTrue(lv.mCalledOnRestoreInstanceState);
}
private static class MockListView extends ListView {
public boolean mCalledOnSaveInstanceState;
public boolean mCalledOnRestoreInstanceState;
public MockListView(Context context) {
super(context);
}
@Override
public Parcelable onSaveInstanceState() {
mCalledOnSaveInstanceState = true;
return super.onSaveInstanceState();
}
@Override
public void onRestoreInstanceState(Parcelable state) {
mCalledOnRestoreInstanceState = true;
super.onRestoreInstanceState(state);
}
}
}

View File

@ -137,20 +137,26 @@ public class ProviderTestUtils extends Assert {
/**
* Create a message for test purposes
*
* TODO: body
* TODO: attachments
*/
public static Message setupMessage(String name, long accountId, long mailboxId,
boolean addBody, boolean saveIt, Context context) {
// Default starred, read, (backword compatibility)
return setupMessage(name, accountId, mailboxId, addBody, saveIt, context, true, true);
}
/**
* Create a message for test purposes
*/
public static Message setupMessage(String name, long accountId, long mailboxId,
boolean addBody, boolean saveIt, Context context, boolean starred, boolean read) {
Message message = new Message();
message.mDisplayName = name;
message.mTimeStamp = 100 + name.length();
message.mSubject = "subject " + name;
message.mFlagRead = true;
message.mFlagRead = read;
message.mFlagLoaded = Message.FLAG_LOADED_UNLOADED;
message.mFlagFavorite = true;
message.mFlagFavorite = starred;
message.mFlagAttachment = true;
message.mFlags = 0;

View File

@ -1824,8 +1824,13 @@ public class ProviderTests extends ProviderTestCase2<EmailProvider> {
}
/**
* Test for the message count trrigers (insert/delete/move mailbox), and also
* Test for the message count triggers (insert/delete/move mailbox), and also
* {@link EmailProvider#recalculateMessageCount}.
*
* It also covers:
* - {@link Mailbox#getMessageCountByMailboxType(Context, int)}
* - {@link Mailbox#getUnreadCountByMailboxType(Context, int)}
* - {@link Message#getFavoriteMessageCount(Context)}
*/
public void testMessageCount() {
final Context c = mMockContext;
@ -1835,10 +1840,10 @@ public class ProviderTests extends ProviderTestCase2<EmailProvider> {
Account a2 = ProviderTestUtils.setupAccount("holdflag-2", true, c);
// Create 2 mailboxes for each account
Mailbox b1 = ProviderTestUtils.setupMailbox("box1", a1.mId, true, c);
Mailbox b2 = ProviderTestUtils.setupMailbox("box2", a1.mId, true, c);
Mailbox b3 = ProviderTestUtils.setupMailbox("box3", a2.mId, true, c);
Mailbox b4 = ProviderTestUtils.setupMailbox("box4", a2.mId, true, c);
Mailbox b1 = ProviderTestUtils.setupMailbox("box1", a1.mId, true, c, Mailbox.TYPE_INBOX);
Mailbox b2 = ProviderTestUtils.setupMailbox("box2", a1.mId, true, c, Mailbox.TYPE_OUTBOX);
Mailbox b3 = ProviderTestUtils.setupMailbox("box3", a2.mId, true, c, Mailbox.TYPE_INBOX);
Mailbox b4 = ProviderTestUtils.setupMailbox("box4", a2.mId, true, c, Mailbox.TYPE_OUTBOX);
// 0. Check the initial values, just in case.
@ -1847,20 +1852,26 @@ public class ProviderTests extends ProviderTestCase2<EmailProvider> {
assertEquals(0, getMessageCount(b3.mId));
assertEquals(0, getMessageCount(b4.mId));
assertEquals(0, Message.getFavoriteMessageCount(c));
assertEquals(0, Mailbox.getUnreadCountByMailboxType(c, Mailbox.TYPE_INBOX));
assertEquals(0, Mailbox.getUnreadCountByMailboxType(c, Mailbox.TYPE_OUTBOX));
assertEquals(0, Mailbox.getMessageCountByMailboxType(c, Mailbox.TYPE_INBOX));
assertEquals(0, Mailbox.getMessageCountByMailboxType(c, Mailbox.TYPE_OUTBOX));
// 1. Test for insert triggers.
// Create some messages
Mailbox b = b1; // 1 message
Message m11 = ProviderTestUtils.setupMessage("1", b.mAccountKey, b.mId, true, true, c);
// b1: 1 message
Message m11 = createMessage(c, b1, true, false);
b = b2; // 2 messages
Message m21 = ProviderTestUtils.setupMessage("1", b.mAccountKey, b.mId, true, true, c);
Message m22 = ProviderTestUtils.setupMessage("1", b.mAccountKey, b.mId, true, true, c);
// b2: 2 message
Message m21 = createMessage(c, b2, false, false);
Message m22 = createMessage(c, b2, true, true);
b = b3; // 3 messages
Message m31 = ProviderTestUtils.setupMessage("1", b.mAccountKey, b.mId, true, true, c);
Message m32 = ProviderTestUtils.setupMessage("1", b.mAccountKey, b.mId, true, true, c);
Message m33 = ProviderTestUtils.setupMessage("1", b.mAccountKey, b.mId, true, true, c);
// b3: 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.
@ -1870,6 +1881,13 @@ public class ProviderTests extends ProviderTestCase2<EmailProvider> {
assertEquals(3, getMessageCount(b3.mId));
assertEquals(0, getMessageCount(b4.mId));
// Check the simple counting methods.
assertEquals(3, Message.getFavoriteMessageCount(c));
assertEquals(3, Mailbox.getUnreadCountByMailboxType(c, Mailbox.TYPE_INBOX));
assertEquals(1, Mailbox.getUnreadCountByMailboxType(c, Mailbox.TYPE_OUTBOX));
assertEquals(4, Mailbox.getMessageCountByMailboxType(c, Mailbox.TYPE_INBOX));
assertEquals(2, Mailbox.getMessageCountByMailboxType(c, Mailbox.TYPE_OUTBOX));
// 2. test for recalculateMessageCount.
// First, invalidate the message counts.
@ -1919,4 +1937,9 @@ public class ProviderTests extends ProviderTestCase2<EmailProvider> {
assertEquals(2, getMessageCount(b3.mId));
assertEquals(1, getMessageCount(b4.mId));
}
private static Message createMessage(Context c, Mailbox b, boolean starred, boolean read) {
return ProviderTestUtils.setupMessage("1", b.mAccountKey, b.mId, true, true, c, starred,
read);
}
}