diff --git a/res/values/strings.xml b/res/values/strings.xml
index cf225e8dd..50baae45b 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -21,6 +21,14 @@
+
+
+
+ Sending messages\u2026
+
+ Send outgoing messages
+
Read Email attachments
@@ -109,9 +117,7 @@
Choose attachment
- Loading messages\u2026
-
- Sending messages\u2026
+ Loading messages\u2026
Connection error
@@ -200,9 +206,6 @@
Load more messages
-
- Send outgoing messages
To
@@ -721,5 +724,4 @@
Email 1 Pane
Change orientation
-
diff --git a/src/com/android/email/activity/AccountSelectorAdapter.java b/src/com/android/email/activity/AccountSelectorAdapter.java
index 3d1ebe0e6..ccfccbadd 100644
--- a/src/com/android/email/activity/AccountSelectorAdapter.java
+++ b/src/com/android/email/activity/AccountSelectorAdapter.java
@@ -16,6 +16,7 @@
package com.android.email.activity;
+import com.android.email.data.NoAutoRequeryCursorLoader;
import com.android.email.provider.EmailContent;
import android.content.Context;
@@ -77,20 +78,4 @@ public class AccountSelectorAdapter extends CursorAdapter {
public static long getAccountId(Cursor c) {
return c.getLong(ID_COLUMN);
}
-
- /**
- * Same as {@link CursorLoader} but it doesn't auto-requery when it gets content-changed
- * notifications.
- */
- private static class NoAutoRequeryCursorLoader extends CursorLoader {
- public NoAutoRequeryCursorLoader(Context context, Uri uri, String[] projection,
- String selection, String[] selectionArgs, String sortOrder) {
- super(context, uri, projection, selection, selectionArgs, sortOrder);
- }
-
- @Override
- public void onContentChanged() {
- // Don't reload.
- }
- }
}
diff --git a/src/com/android/email/activity/MessageList.java b/src/com/android/email/activity/MessageList.java
index 61b7291aa..6e9eb6d1d 100644
--- a/src/com/android/email/activity/MessageList.java
+++ b/src/com/android/email/activity/MessageList.java
@@ -206,7 +206,7 @@ public class MessageList extends Activity implements OnClickListener,
// Specific mailbox ID was provided - go directly to it
mSetTitleTask = new SetTitleTask(mailboxId);
mSetTitleTask.execute();
- mListFragment.openMailbox(-1, mailboxId);
+ mListFragment.openMailbox(mailboxId);
} else {
int mailboxType = intent.getIntExtra(EXTRA_MAILBOX_TYPE, Mailbox.TYPE_INBOX);
Uri uri = intent.getData();
@@ -308,7 +308,8 @@ public class MessageList extends Activity implements OnClickListener,
}
public void onAnimationEnd(Animation animation) {
- mListFragment.updateListPosition();
+ // TODO: If the button panel hides the only selected item, scroll the list to make it
+ // visible again.
}
public void onAnimationRepeat(Animation animation) {
@@ -661,7 +662,7 @@ public class MessageList extends Activity implements OnClickListener,
public void onMailboxFound(long accountId, long mailboxId) {
mSetTitleTask = new SetTitleTask(mailboxId);
mSetTitleTask.execute();
- mListFragment.openMailbox(accountId, mailboxId);
+ mListFragment.openMailbox(mailboxId);
}
@Override
diff --git a/src/com/android/email/activity/MessageListFragment.java b/src/com/android/email/activity/MessageListFragment.java
index 03d548acb..8bc2f7f8c 100644
--- a/src/com/android/email/activity/MessageListFragment.java
+++ b/src/com/android/email/activity/MessageListFragment.java
@@ -20,21 +20,18 @@ import com.android.email.Controller;
import com.android.email.Email;
import com.android.email.R;
import com.android.email.Utility;
+import com.android.email.data.MailboxAccountLoader;
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 com.android.email.provider.EmailContent.MessageColumns;
import com.android.email.service.MailService;
import android.app.Activity;
import android.app.ListFragment;
-import android.content.ContentResolver;
-import android.content.ContentUris;
+import android.app.LoaderManager;
import android.content.Context;
+import android.content.Loader;
import android.database.Cursor;
-import android.net.Uri;
-import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
@@ -50,57 +47,51 @@ import java.security.InvalidParameterException;
import java.util.HashSet;
import java.util.Set;
-public class MessageListFragment extends ListFragment implements OnItemClickListener,
- OnItemLongClickListener, MessagesAdapter.Callback {
- private static final String STATE_SELECTED_ITEM_TOP =
- "com.android.email.activity.MessageList.selectedItemTop";
- private static final String STATE_SELECTED_POSITION =
- "com.android.email.activity.MessageList.selectedPosition";
- private static final String STATE_CHECKED_ITEMS =
- "com.android.email.activity.MessageList.checkedItems";
+// TODO Better handling of restoring list position/adapter check status
+/**
+ * Message list.
+ *
+ *
This fragment uses two different loaders to load data.
+ *
+ * - One to load {@link Account} and {@link Mailbox}, with {@link MailboxAccountLoader}.
+ *
- The other to actually load messages.
+ *
+ * We run them sequentially. i.e. First starts {@link MailboxAccountLoader}, and when it finishes
+ * starts the other.
+ */
+public class MessageListFragment extends ListFragment
+ implements OnItemClickListener, OnItemLongClickListener, MessagesAdapter.Callback {
+
+ private static final int LOADER_ID_MAILBOX_LOADER = 1;
+ private static final int LOADER_ID_MESSAGES_LOADER = 2;
// UI Support
private Activity mActivity;
private Callback mCallback = EmptyCallback.INSTANCE;
+
private View mListFooterView;
private TextView mListFooterText;
private View mListFooterProgress;
private static final int LIST_FOOTER_MODE_NONE = 0;
- private static final int LIST_FOOTER_MODE_REFRESH = 1;
private static final int LIST_FOOTER_MODE_MORE = 2;
- private static final int LIST_FOOTER_MODE_SEND = 3;
private int mListFooterMode;
private MessagesAdapter mListAdapter;
- // DB access
- private ContentResolver mResolver;
- private long mAccountId = -1;
private long mMailboxId = -1;
- private LoadMessagesTask mLoadMessagesTask;
- private SetFooterTask mSetFooterTask;
-
- /* package */ static final String[] MESSAGE_PROJECTION = new String[] {
- EmailContent.RECORD_ID, MessageColumns.MAILBOX_KEY, MessageColumns.ACCOUNT_KEY,
- MessageColumns.DISPLAY_NAME, MessageColumns.SUBJECT, MessageColumns.TIMESTAMP,
- MessageColumns.FLAG_READ, MessageColumns.FLAG_FAVORITE, MessageColumns.FLAG_ATTACHMENT,
- MessageColumns.FLAGS,
- };
+ private long mLastLoadedMailboxId = -1;
+ private Account mAccount;
+ private Mailbox mMailbox;
// Controller access
private Controller mController;
// Misc members
- private Boolean mPushModeMailbox = null;
- private int mSavedItemTop = 0;
- private int mSavedItemPosition = -1;
- private int mFirstSelectedItemTop = 0;
- private int mFirstSelectedItemPosition = -1;
- private int mFirstSelectedItemHeight = -1;
- private boolean mCanAutoRefresh;
+ private boolean mDoAutoRefresh;
- private boolean mStarted;
+ /** true between {@link #onResume} and {@link #onPause}. */
+ private boolean mResumed;
/**
* Callback interface that owning activities must implement
@@ -158,9 +149,7 @@ public class MessageListFragment extends ListFragment implements OnItemClickList
}
super.onCreate(savedInstanceState);
mActivity = getActivity();
- mResolver = mActivity.getContentResolver();
mController = Controller.getInstance(mActivity);
- mCanAutoRefresh = true;
}
@Override
@@ -180,8 +169,6 @@ public class MessageListFragment extends ListFragment implements OnItemClickList
mListFooterView = getActivity().getLayoutInflater().inflate(
R.layout.message_list_item_footer, listView, false);
- // TODO extend this to properly deal with multiple mailboxes, cursor, etc.
-
if (savedInstanceState != null) {
// Fragment doesn't have this method. Call it manually.
loadState(savedInstanceState);
@@ -194,19 +181,6 @@ public class MessageListFragment extends ListFragment implements OnItemClickList
Log.d(Email.LOG_TAG, "MessageListFragment onStart");
}
super.onStart();
- mStarted = true;
- if (mMailboxId != -1) {
- startLoading();
- }
- }
-
- @Override
- public void onStop() {
- if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
- Log.d(Email.LOG_TAG, "MessageListFragment onStop");
- }
- mStarted = false;
- super.onStop();
}
@Override
@@ -215,8 +189,27 @@ public class MessageListFragment extends ListFragment implements OnItemClickList
Log.d(Email.LOG_TAG, "MessageListFragment onResume");
}
super.onResume();
- restoreListPosition();
- autoRefreshStaleMailbox();
+ mResumed = true;
+ if (mMailboxId != -1) {
+ startLoading();
+ }
+ }
+
+ @Override
+ public void onPause() {
+ if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
+ Log.d(Email.LOG_TAG, "MessageListFragment onPause");
+ }
+ mResumed = false;
+ super.onStop();
+ }
+
+ @Override
+ public void onStop() {
+ if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
+ Log.d(Email.LOG_TAG, "MessageListFragment onStop");
+ }
+ super.onStop();
}
@Override
@@ -224,14 +217,7 @@ public class MessageListFragment extends ListFragment implements OnItemClickList
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MessageListFragment onDestroy");
}
- Utility.cancelTaskInterrupt(mLoadMessagesTask);
- mLoadMessagesTask = null;
- Utility.cancelTaskInterrupt(mSetFooterTask);
- mSetFooterTask = null;
- if (mListAdapter != null) {
- mListAdapter.changeCursor(null);
- }
super.onDestroy();
}
@@ -241,27 +227,12 @@ public class MessageListFragment extends ListFragment implements OnItemClickList
Log.d(Email.LOG_TAG, "MessageListFragment onSaveInstanceState");
}
super.onSaveInstanceState(outState);
- saveListPosition();
- outState.putInt(STATE_SELECTED_POSITION, mSavedItemPosition);
- outState.putInt(STATE_SELECTED_ITEM_TOP, mSavedItemTop);
- Set checkedset = mListAdapter.getSelectedSet();
- long[] checkedarray = new long[checkedset.size()];
- int i = 0;
- for (Long l : checkedset) {
- checkedarray[i] = l;
- i++;
- }
- outState.putLongArray(STATE_CHECKED_ITEMS, checkedarray);
+ mListAdapter.onSaveInstanceState(outState);
}
// Unit tests use it
- /* package */ void loadState(Bundle savedInstanceState) {
- mSavedItemTop = savedInstanceState.getInt(STATE_SELECTED_ITEM_TOP, 0);
- mSavedItemPosition = savedInstanceState.getInt(STATE_SELECTED_POSITION, -1);
- Set checkedset = mListAdapter.getSelectedSet();
- for (long l: savedInstanceState.getLongArray(STATE_CHECKED_ITEMS)) {
- checkedset.add(l);
- }
+ /* package */void loadState(Bundle savedInstanceState) {
+ mListAdapter.loadState(savedInstanceState);
}
public void setCallback(Callback callback) {
@@ -271,46 +242,28 @@ public class MessageListFragment extends ListFragment implements OnItemClickList
/**
* Called by an Activity to open an mailbox.
*
- * @param accountId account id of the mailbox, if already known. Pass -1 if unknown or
- * {@code mailboxId} is of a special mailbox. If -1 is passed, this fragment will find it
- * using {@code mailboxId}, which the activity can get later with {@link #getAccountId()}.
- * Passing -1 is always safe, but we can skip a database lookup if specified.
- *
* @param mailboxId the ID of a mailbox, or one of "special" mailbox IDs like
* {@link Mailbox#QUERY_ALL_INBOXES}. -1 is not allowed.
*/
- public void openMailbox(long accountId, long mailboxId) {
+ public void openMailbox(long mailboxId) {
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MessageListFragment openMailbox");
}
if (mailboxId == -1) {
throw new InvalidParameterException();
}
- if ((mAccountId == accountId) && (mMailboxId == mailboxId)) {
+ if (mMailboxId == mailboxId) {
return;
}
- mAccountId = accountId;
mMailboxId = mailboxId;
- if (mStarted) {
+ if (mResumed) {
startLoading();
}
}
- private void startLoading() {
- // Clear the list. (ListFragment will show the "Loading" animation)
- getListView().removeFooterView(mListFooterView);
- setListAdapter(null);
- setListShown(false);
-
- // Start loading...
- Utility.cancelTaskInterrupt(mLoadMessagesTask);
- mLoadMessagesTask = new LoadMessagesTask(mMailboxId, mAccountId);
- mLoadMessagesTask.execute();
- }
-
- /* package */ MessagesAdapter getAdapterForTest() {
+ /* package */MessagesAdapter getAdapterForTest() {
return mListAdapter;
}
@@ -318,7 +271,7 @@ public class MessageListFragment extends ListFragment implements OnItemClickList
* @return the account id or -1 if it's unknown yet. It's also -1 if it's a magic mailbox.
*/
public long getAccountId() {
- return mAccountId;
+ return (mMailbox == null) ? -1 : mMailbox.mAccountKey;
}
/**
@@ -338,10 +291,16 @@ public class MessageListFragment extends ListFragment implements OnItemClickList
}
/**
- * @return if it's an outbox.
+ * @return true if it's an outbox. false otherwise, or the mailbox type is
+ * unknown yet.
+ * @deprecated It's used by MessageList to see if we should show a progress
+ * for sending messages. The logic here means we can't catch
+ * callbacks while the mailbox type isn't figured out yet. That
+ * show/hide progress logic isn't working in the way it should
+ * in the first place, so fix it and remove this method.
*/
public boolean isOutbox() {
- return mListFooterMode == LIST_FOOTER_MODE_SEND;
+ return mMailbox == null ? false : (mMailbox.mType == Mailbox.TYPE_OUTBOX);
}
/**
@@ -358,40 +317,6 @@ public class MessageListFragment extends ListFragment implements OnItemClickList
return getSelectedCount() > 0;
}
- /**
- * Save the focused list item.
- *
- * TODO It's not really working. Fix it.
- */
- private void saveListPosition() {
- mSavedItemPosition = getListView().getSelectedItemPosition();
- if (mSavedItemPosition >= 0 && getListView().isSelected()) {
- mSavedItemTop = getListView().getSelectedView().getTop();
- } else {
- mSavedItemPosition = getListView().getFirstVisiblePosition();
- if (mSavedItemPosition >= 0) {
- mSavedItemTop = 0;
- View topChild = getListView().getChildAt(0);
- if (topChild != null) {
- mSavedItemTop = topChild.getTop();
- }
- }
- }
- }
-
- /**
- * Restore the focused list item.
- *
- * TODO It's not really working. Fix it.
- */
- private void restoreListPosition() {
- if (mSavedItemPosition >= 0 && mSavedItemPosition < getListView().getCount()) {
- getListView().setSelectionFromTop(mSavedItemPosition, mSavedItemTop);
- mSavedItemPosition = -1;
- mSavedItemTop = 0;
- }
- }
-
/**
* Called when a message is clicked.
*/
@@ -425,33 +350,27 @@ public class MessageListFragment extends ListFragment implements OnItemClickList
}
private void onMessageOpen(final long mailboxId, final long messageId) {
- // Use asynctask to determine the type.
- new AsyncTask() {
- @Override
- protected Integer doInBackground(Void... params) {
- EmailContent.Mailbox mailbox = EmailContent.Mailbox.restoreMailboxWithId(
- getActivity(), mailboxId);
- if (mailbox == null) {
- return null;
- }
- switch (mailbox.mType) {
- case EmailContent.Mailbox.TYPE_DRAFTS:
- return Callback.TYPE_DRAFT;
- case EmailContent.Mailbox.TYPE_TRASH:
- return Callback.TYPE_TRASH;
- default:
- return Callback.TYPE_REGULAR;
- }
+ final int type;
+ if (mMailbox == null) { // Magic mailbox
+ if (mMailboxId == Mailbox.QUERY_ALL_DRAFTS) {
+ type = Callback.TYPE_DRAFT;
+ } else {
+ type = Callback.TYPE_REGULAR;
}
-
- @Override
- protected void onPostExecute(Integer type) {
- if (type == null) {
- return;
- }
- mCallback.onMessageOpen(messageId, mailboxId, getMailboxId(), type);
+ } else {
+ switch (mMailbox.mType) {
+ case EmailContent.Mailbox.TYPE_DRAFTS:
+ type = Callback.TYPE_DRAFT;
+ break;
+ case EmailContent.Mailbox.TYPE_TRASH:
+ type = Callback.TYPE_TRASH;
+ break;
+ default:
+ type = Callback.TYPE_REGULAR;
+ break;
}
- }.execute();
+ }
+ mCallback.onMessageOpen(messageId, mailboxId, getMailboxId(), type);
}
public void onMultiToggleRead() {
@@ -470,10 +389,9 @@ public class MessageListFragment extends ListFragment implements OnItemClickList
* Refresh the list. NOOP for special mailboxes (e.g. combined inbox).
*/
public void onRefresh() {
- if (!isMagicMailbox()) {
- // Note we can use mAccountId here because it's not a magic mailbox, which doesn't have
- // a specific account id.
- mController.updateMailbox(mAccountId, mMailboxId);
+ final long accountId = getAccountId();
+ if (accountId != -1) {
+ mController.updateMailbox(accountId, mMailboxId);
}
}
@@ -646,7 +564,7 @@ public class MessageListFragment extends ListFragment implements OnItemClickList
while (c.moveToNext()) {
long id = c.getInt(MessagesAdapter.COLUMN_ID);
if (selectedSet.contains(Long.valueOf(id))) {
- if (c.getInt(column_id) == (defaultflag? 1 : 0)) {
+ if (c.getInt(column_id) == (defaultflag ? 1 : 0)) {
return true;
}
}
@@ -669,6 +587,18 @@ public class MessageListFragment extends ListFragment implements OnItemClickList
return testMultiple(mListAdapter.getSelectedSet(), MessagesAdapter.COLUMN_READ, true);
}
+ /**
+ * Called by activity to indicate that the user explicitly opened the
+ * mailbox and it needs auto-refresh when it's first shown. TODO:
+ * {@link MessageList} needs to call this as well.
+ *
+ * TODO It's a bit ugly. We can remove this if this fragment "remembers" the current mailbox ID
+ * through configuration changes.
+ */
+ public void doAutoRefresh() {
+ mDoAutoRefresh = true;
+ }
+
/**
* Implements a timed refresh of "stale" mailboxes. This should only happen when
* multiple conditions are true, including:
@@ -677,29 +607,19 @@ public class MessageListFragment extends ListFragment implements OnItemClickList
* Only when the mailbox is "stale" (currently set to 5 minutes since last refresh)
*/
private void autoRefreshStaleMailbox() {
- if (!mCanAutoRefresh
- || (mListAdapter.getCursor() == null) // Check if messages info is loaded
- || (mPushModeMailbox != null && mPushModeMailbox) // Check the push mode
- || isMagicMailbox()) { // Check if this mailbox is synthetic/combined
+ if (!mDoAutoRefresh // Not explicitly open
+ || (mMailbox == null) // Magic inbox
+ || (mAccount.mSyncInterval == Account.CHECK_INTERVAL_PUSH) // Not push
+ ) {
return;
}
- mCanAutoRefresh = false;
+ mDoAutoRefresh = false;
if (!Email.mailboxRequiresRefresh(mMailboxId)) {
return;
}
onRefresh();
}
- public void updateListPosition() { // TODO give it a better name
- int listViewHeight = getListView().getHeight();
- if (mListAdapter.getSelectedSet().size() == 1 && mFirstSelectedItemPosition >= 0
- && mFirstSelectedItemPosition < getListView().getCount()
- && listViewHeight < mFirstSelectedItemTop) {
- getListView().setSelectionFromTop(mFirstSelectedItemPosition,
- listViewHeight - mFirstSelectedItemHeight);
- }
- }
-
/**
* Show/hide the progress icon on the list footer. It's called by the host activity.
* TODO: It might be cleaner if the fragment listen to the controller events and show it by
@@ -709,169 +629,60 @@ public class MessageListFragment extends ListFragment implements OnItemClickList
if (mListFooterProgress != null) {
mListFooterProgress.setVisibility(show ? View.VISIBLE : View.GONE);
}
- setListFooterText(show);
+ updateListFooterText(show);
}
- // Adapter callbacks
+ /** Implements {@link MessagesAdapter.Callback} */
+ @Override
public void onAdapterFavoriteChanged(MessageListItem itemView, boolean newFavorite) {
onSetMessageFavorite(itemView.mMessageId, newFavorite);
}
- public void onAdapterRequery() {
- // This updates the "multi-selection" button labels.
+ /** Implements {@link MessagesAdapter.Callback} */
+ @Override
+ public void onAdapterSelectedChanged(
+ MessageListItem itemView, boolean newSelected, int mSelectedCount) {
mCallback.onSelectionChanged();
}
- public void onAdapterSelectedChanged(MessageListItem itemView, boolean newSelected,
- int mSelectedCount) {
- if (mSelectedCount == 1 && newSelected) {
- mFirstSelectedItemPosition = getListView().getPositionForView(itemView);
- mFirstSelectedItemTop = itemView.getBottom();
- mFirstSelectedItemHeight = itemView.getHeight();
- } else {
- mFirstSelectedItemPosition = -1;
- }
- mCallback.onSelectionChanged();
- }
-
- /**
- * Add the fixed footer view if appropriate (not always - not all accounts & mailboxes).
- *
- * Here are some rules (finish this list):
- *
- * Any merged, synced box (except send): refresh
- * Any push-mode account: refresh
- * Any non-push-mode account: load more
- * Any outbox (send again):
- *
- * @param mailboxId the ID of the mailbox
- * @param accountId the ID of the account
- */
- private void addFooterView(long mailboxId, long accountId) {
- // first, look for shortcuts that don't need us to spin up a DB access task
- if (mailboxId == Mailbox.QUERY_ALL_INBOXES
- || mailboxId == Mailbox.QUERY_ALL_UNREAD
- || mailboxId == Mailbox.QUERY_ALL_FAVORITES) {
- finishFooterView(LIST_FOOTER_MODE_REFRESH);
- return;
- }
- if (mailboxId == Mailbox.QUERY_ALL_DRAFTS) {
- finishFooterView(LIST_FOOTER_MODE_NONE);
- return;
- }
- if (mailboxId == Mailbox.QUERY_ALL_OUTBOX) {
- finishFooterView(LIST_FOOTER_MODE_SEND);
- return;
- }
-
- // We don't know enough to select the footer command type (yet), so we'll
- // launch an async task to do the remaining lookups and decide what to do
- mSetFooterTask = new SetFooterTask();
- mSetFooterTask.execute(mailboxId, accountId);
- }
-
- private final static String[] MAILBOX_ACCOUNT_AND_TYPE_PROJECTION =
- new String[] { MailboxColumns.ACCOUNT_KEY, MailboxColumns.TYPE };
-
- private class SetFooterTask extends AsyncTask {
- /**
- * There are two operational modes here, requiring different lookup.
- * mailboxIs != -1: A specific mailbox - check its type, then look up its account
- * accountId != -1: A specific account - look up the account
- */
- @Override
- protected Integer doInBackground(Long... params) {
- long mailboxId = params[0];
- long accountId = params[1];
- int mailboxType = -1;
- if (mailboxId != -1) {
- try {
- Uri uri = ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId);
- Cursor c = mResolver.query(uri, MAILBOX_ACCOUNT_AND_TYPE_PROJECTION,
- null, null, null);
- if (c.moveToFirst()) {
- try {
- accountId = c.getLong(0);
- mailboxType = c.getInt(1);
- } finally {
- c.close();
- }
- }
- } catch (IllegalArgumentException iae) {
- // can't do any more here
- return LIST_FOOTER_MODE_NONE;
- }
- }
- switch (mailboxType) {
- case Mailbox.TYPE_OUTBOX:
- return LIST_FOOTER_MODE_SEND;
- case Mailbox.TYPE_DRAFTS:
- return LIST_FOOTER_MODE_NONE;
- }
- if (accountId != -1) {
- // This is inefficient but the best fix is not here but in isMessagingController
- Account account = Account.restoreAccountWithId(mActivity, accountId);
- if (account != null) {
- // TODO move this to more appropriate place
- // (don't change member fields on a worker thread.)
- mPushModeMailbox = account.mSyncInterval == Account.CHECK_INTERVAL_PUSH;
- if (mController.isMessagingController(account)) {
- return LIST_FOOTER_MODE_MORE; // IMAP or POP
- } else {
- return LIST_FOOTER_MODE_NONE; // EAS
- }
- }
- }
- return LIST_FOOTER_MODE_NONE;
- }
-
- @Override
- protected void onPostExecute(Integer listFooterMode) {
- if (isCancelled()) {
- return;
- }
- if (listFooterMode == null) {
- return;
- }
- finishFooterView(listFooterMode);
+ private void determineFooterMode() {
+ mListFooterMode = LIST_FOOTER_MODE_NONE;
+ if (mAccount != null && !mAccount.isEasAccount()) {
+ // IMAP, POP has "load more"
+ mListFooterMode = LIST_FOOTER_MODE_MORE;
}
}
- /**
- * Add the fixed footer view as specified, and set up the test as well.
- *
- * @param listFooterMode the footer mode we've determined should be used for this list
- */
- private void finishFooterView(int listFooterMode) {
- mListFooterMode = listFooterMode;
+ private void addFooterView() {
+ determineFooterMode();
if (mListFooterMode != LIST_FOOTER_MODE_NONE) {
- getListView().addFooterView(mListFooterView);
- getListView().setAdapter(mListAdapter);
+ ListView lv = getListView();
+ if (mListFooterView != null) {
+ lv.removeFooterView(mListFooterView);
+ }
+
+ lv.addFooterView(mListFooterView);
+ lv.setAdapter(mListAdapter);
mListFooterProgress = mListFooterView.findViewById(R.id.progress);
mListFooterText = (TextView) mListFooterView.findViewById(R.id.main_text);
- setListFooterText(false);
+
+ // TODO We don't know if it's really "inactive". Someone has to
+ // remember all sync status.
+ updateListFooterText(false);
}
}
/**
- * Set the list footer text based on mode and "active" status
+ * Set the list footer text based on mode and "network active" status
*/
- private void setListFooterText(boolean active) {
+ private void updateListFooterText(boolean networkActive) {
if (mListFooterMode != LIST_FOOTER_MODE_NONE) {
int footerTextId = 0;
switch (mListFooterMode) {
- case LIST_FOOTER_MODE_REFRESH:
- footerTextId = active ? R.string.status_loading_more
- : R.string.refresh_action;
- break;
case LIST_FOOTER_MODE_MORE:
- footerTextId = active ? R.string.status_loading_more
- : R.string.message_list_load_more_messages_action;
- break;
- case LIST_FOOTER_MODE_SEND:
- footerTextId = active ? R.string.status_sending_messages
- : R.string.message_list_send_pending_messages_action;
+ footerTextId = networkActive ? R.string.status_loading_messages
+ : R.string.message_list_load_more_messages_action;
break;
}
mListFooterText.setText(footerTextId);
@@ -883,96 +694,128 @@ public class MessageListFragment extends ListFragment implements OnItemClickList
*/
private void doFooterClick() {
switch (mListFooterMode) {
- case LIST_FOOTER_MODE_NONE: // should never happen
- break;
- case LIST_FOOTER_MODE_REFRESH:
- onRefresh();
+ case LIST_FOOTER_MODE_NONE: // should never happen
break;
case LIST_FOOTER_MODE_MORE:
onLoadMoreMessages();
break;
- case LIST_FOOTER_MODE_SEND:
- onSendPendingMessages();
- break;
+ }
+ }
+
+ private void startLoading() {
+ // Clear the list. (ListFragment will show the "Loading" animation)
+ setListAdapter(null);
+ setListShown(false);
+
+ // Start loading...
+ final LoaderManager lm = getLoaderManager();
+
+ // If we're loading a different mailbox, discard the previous result.
+ if ((mLastLoadedMailboxId != -1) && (mLastLoadedMailboxId != mMailboxId)) {
+ lm.stopLoader(LOADER_ID_MAILBOX_LOADER);
+ lm.stopLoader(LOADER_ID_MESSAGES_LOADER);
+ }
+ lm.initLoader(LOADER_ID_MAILBOX_LOADER, null, new MailboxAccountLoaderCallback());
+ }
+
+ /**
+ * Loader callbacks for {@link MailboxAccountLoader}.
+ */
+ private class MailboxAccountLoaderCallback implements LoaderManager.LoaderCallbacks<
+ MailboxAccountLoader.Result> {
+ @Override
+ public Loader onCreateLoader(int id, Bundle args) {
+ if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
+ Log.d(Email.LOG_TAG,
+ "MessageListFragment onCreateLoader(mailbox) mailboxId=" + mMailboxId);
+ }
+ return new MailboxAccountLoader(getActivity().getApplicationContext(), mMailboxId);
+ }
+
+ @Override
+ public void onLoadFinished(Loader loader,
+ MailboxAccountLoader.Result result) {
+ if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
+ Log.d(Email.LOG_TAG, "MessageListFragment onLoadFinished(mailbox) mailboxId="
+ + mMailboxId);
+ }
+ if (!isMagicMailbox() && !result.isFound()) {
+ mCallback.onMailboxNotFound();
+ return;
+ }
+
+ mLastLoadedMailboxId = mMailboxId;
+ mAccount = result.mAccount;
+ mMailbox = result.mMailbox;
+ getLoaderManager().initLoader(LOADER_ID_MESSAGES_LOADER, null,
+ new MessagesLoaderCallback());
}
}
/**
- * Async task for loading a single folder out of the UI thread
- *
- * The code here (for merged boxes) is a placeholder/hack and should be replaced. Some
- * specific notes:
- * TODO: Move the double query into a specialized URI that returns all inbox messages
- * and do the dirty work in raw SQL in the provider.
- * TODO: Generalize the query generation so we can reuse it in MessageView (for next/prev)
+ * Loader callbacks for message list.
*/
- private class LoadMessagesTask extends AsyncTask {
+ private class MessagesLoaderCallback implements LoaderManager.LoaderCallbacks {
+ @Override
+ public Loader onCreateLoader(int id, Bundle args) {
+ if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
+ Log.d(Email.LOG_TAG,
+ "MessageListFragment onCreateLoader(messages) mailboxId=" + mMailboxId);
+ }
- private final long mMailboxKey;
- private long mAccountKey;
-
- /**
- * Special constructor to cache some local info
- */
- public LoadMessagesTask(long mailboxKey, long accountKey) {
- mMailboxKey = mailboxKey;
- mAccountKey = accountKey;
+ // Reset new message count.
+ // TODO Do it in onLoadFinished(). Unfortunately
+ // resetNewMessageCount() ends up a
+ // db operation, which causes a onContentChanged notification, which
+ // makes cursor
+ // loaders to requery. Until we fix ContentProvider (don't notify
+ // unrelated cursors)
+ // we need to do it here.
+ resetNewMessageCount(mActivity, mMailboxId, getAccountId());
+ return MessagesAdapter.createLoader(getActivity(), mMailboxId);
}
@Override
- protected Cursor doInBackground(Void... params) {
- // First, determine account id, if unknown
- if (mAccountKey == -1) { // TODO Use constant instead of -1
- if (isMagicMailbox()) {
- // Magic mailbox. No accountid.
- } else {
- EmailContent.Mailbox mailbox =
- EmailContent.Mailbox.restoreMailboxWithId(mActivity, mMailboxKey);
- if (mailbox != null) {
- mAccountKey = mailbox.mAccountKey;
- } else {
- // Mailbox not found.
- // TODO We used to close the activity in this case, but what to do now??
- return null;
- }
- }
+ public void onLoadFinished(Loader loader, Cursor cursor) {
+ if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
+ Log.d(Email.LOG_TAG,
+ "MessageListFragment onLoadFinished(messages) mailboxId=" + mMailboxId);
}
- // Load messages
- String selection =
- Utility.buildMailboxIdSelection(mResolver, mMailboxKey);
- Cursor c = mActivity.getContentResolver().query(
- EmailContent.Message.CONTENT_URI, MESSAGE_PROJECTION,
- selection, null, EmailContent.MessageColumns.TIMESTAMP + " DESC");
- return c;
- }
-
- @Override
- protected void onPostExecute(Cursor cursor) {
- if (isCancelled()) {
- return;
- }
- if (cursor == null || cursor.isClosed()) {
- mCallback.onMailboxNotFound();
- return;
- }
- MessageListFragment.this.mAccountId = mAccountKey;
+ // Save list view state (primarily scroll position)
+ final ListView lv = getListView();
+ final Utility.ListStateSaver lss = new Utility.ListStateSaver(lv);
+ // Update the list
mListAdapter.changeCursor(cursor);
setListAdapter(mListAdapter);
+ setListShown(true);
- addFooterView(mMailboxKey, mAccountKey);
+ // Restore the state
+ lss.restore(lv);
- // changeCursor occurs the jumping of position in ListView, so it's need to restore
- // the position;
- restoreListPosition();
+ // Various post processing...
+ // (resetNewMessageCount should be here. See above.)
autoRefreshStaleMailbox();
- // Reset the "new messages" count in the service, since we're seeing them now
- if (mMailboxKey == Mailbox.QUERY_ALL_INBOXES) {
- MailService.resetNewMessageCount(mActivity, -1);
- } else if (mMailboxKey >= 0 && mAccountKey != -1) {
- MailService.resetNewMessageCount(mActivity, mAccountKey);
- }
+ addFooterView();
+ }
+ }
+
+ /**
+ * Reset the "new message" count.
+ *
+ * - If {@code mailboxId} is {@link Mailbox#QUERY_ALL_INBOXES}, reset the
+ * counts of all accounts.
+ *
- If {@code mailboxId} is not of a magic inbox (i.e. >= 0) and {@code
+ * accountId} is valid, reset the count of the specified account.
+ *
+ */
+ /* protected */static void resetNewMessageCount(
+ Context context, long mailboxId, long accountId) {
+ if (mailboxId == Mailbox.QUERY_ALL_INBOXES) {
+ MailService.resetNewMessageCount(context, -1);
+ } else if (mailboxId >= 0 && accountId != -1) {
+ MailService.resetNewMessageCount(context, accountId);
}
}
}
diff --git a/src/com/android/email/activity/MessageListXL.java b/src/com/android/email/activity/MessageListXL.java
index 479343645..721f50bf1 100644
--- a/src/com/android/email/activity/MessageListXL.java
+++ b/src/com/android/email/activity/MessageListXL.java
@@ -189,7 +189,7 @@ public class MessageListXL extends Activity implements View.OnClickListener,
// position.
// TODO: FragmentTransaction *does* support backstack, but the behavior isn't too clear
// at this point.
- mFragmentManager.selectMailbox(mFragmentManager.getMailboxId());
+ mFragmentManager.selectMailbox(mFragmentManager.getMailboxId(), false);
} else {
// Perform the default behavior == close the activity.
super.onBackPressed();
@@ -292,7 +292,7 @@ public class MessageListXL extends Activity implements View.OnClickListener,
// TODO Rename to onSelectMailbox
@Override
public void onMailboxSelected(long accountId, long mailboxId) {
- mFragmentManager.selectMailbox(mailboxId);
+ mFragmentManager.selectMailbox(mailboxId, true);
}
}
diff --git a/src/com/android/email/activity/MessageListXLFragmentManager.java b/src/com/android/email/activity/MessageListXLFragmentManager.java
index 7e348f290..3d019ab08 100644
--- a/src/com/android/email/activity/MessageListXLFragmentManager.java
+++ b/src/com/android/email/activity/MessageListXLFragmentManager.java
@@ -303,8 +303,12 @@ class MessageListXLFragmentManager {
*
* We assume the mailbox selected here belongs to the account selected with
* {@link #selectAccount}.
+ *
+ * @param mailboxId ID of mailbox
+ * @param byUserAction set true if the user is explicitly opening the mailbox, in which case
+ * we perform "auto-refresh".
*/
- public void selectMailbox(long mailboxId) {
+ public void selectMailbox(long mailboxId, boolean byUserAction) {
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "selectMailbox mMailboxId=" + mailboxId);
}
@@ -322,6 +326,9 @@ class MessageListXLFragmentManager {
// Update fragments.
if (mMessageListFragment == null) {
MessageListFragment f = new MessageListFragment();
+ if (byUserAction) {
+ f.doAutoRefresh();
+ }
mTargetActivity.openFragmentTransaction().replace(R.id.right_pane, f).commit();
if (mMessageViewFragment != null) {
@@ -330,6 +337,9 @@ class MessageListXLFragmentManager {
mTargetActivity.onMessageViewFragmentHidden(); // Don't forget to tell the activity.
}
} else {
+ if (byUserAction) {
+ mMessageListFragment.doAutoRefresh();
+ }
updateMessageListFragment(mMessageListFragment);
}
}
@@ -344,7 +354,7 @@ class MessageListXLFragmentManager {
mMessageListFragment = fragment;
fragment.setCallback(mMessageListFragmentCallback);
- fragment.openMailbox(mAccountId, mMailboxId);
+ fragment.openMailbox(mMailboxId);
}
/**
@@ -435,7 +445,7 @@ class MessageListXLFragmentManager {
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, " Found inbox");
}
- selectMailbox(mailboxId);
+ selectMailbox(mailboxId, true);
}
@Override
diff --git a/src/com/android/email/activity/MessagesAdapter.java b/src/com/android/email/activity/MessagesAdapter.java
index a7330ed41..b8088a1e7 100644
--- a/src/com/android/email/activity/MessagesAdapter.java
+++ b/src/com/android/email/activity/MessagesAdapter.java
@@ -19,18 +19,22 @@ package com.android.email.activity;
import com.android.email.Email;
import com.android.email.R;
import com.android.email.Utility;
+import com.android.email.data.ThrottlingCursorLoader;
+import com.android.email.provider.EmailContent;
import com.android.email.provider.EmailContent.Message;
+import com.android.email.provider.EmailContent.MessageColumns;
import android.content.Context;
+import android.content.Loader;
import android.content.res.ColorStateList;
import android.content.res.Resources;
-import android.content.res.TypedArray;
import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
import android.database.Cursor;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
+import android.os.Bundle;
import android.os.Handler;
-import android.os.SystemClock;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -42,14 +46,21 @@ import android.widget.TextView;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
-import java.util.Timer;
-import java.util.TimerTask;
/**
* This class implements the adapter for displaying messages based on cursors.
*/
/* package */ class MessagesAdapter extends CursorAdapter {
+ private static final String STATE_CHECKED_ITEMS =
+ "com.android.email.activity.MessagesAdapter.checkedItems";
+
+ /* package */ static final String[] MESSAGE_PROJECTION = new String[] {
+ EmailContent.RECORD_ID, MessageColumns.MAILBOX_KEY, MessageColumns.ACCOUNT_KEY,
+ MessageColumns.DISPLAY_NAME, MessageColumns.SUBJECT, MessageColumns.TIMESTAMP,
+ MessageColumns.FLAG_READ, MessageColumns.FLAG_FAVORITE, MessageColumns.FLAG_ATTACHMENT,
+ MessageColumns.FLAGS,
+ };
public static final int COLUMN_ID = 0;
public static final int COLUMN_MAILBOX_KEY = 1;
@@ -73,13 +84,9 @@ import java.util.TimerTask;
private final ColorStateList mTextColorPrimary;
private final ColorStateList mTextColorSecondary;
- // Timer to control the refresh rate of the list
- private final RefreshTimer mRefreshTimer = new RefreshTimer();
- // Last time we allowed a refresh of the list
- private long mLastRefreshTime = 0;
// How long we want to wait for refreshes (a good starting guess)
// I suspect this could be lowered down to even 1000 or so, but this seems ok for now
- private static final long REFRESH_INTERVAL_MS = 2500;
+ private static final int REFRESH_INTERVAL_MS = 2500;
private final java.text.DateFormat mDateFormat;
private final java.text.DateFormat mTimeFormat;
@@ -90,8 +97,6 @@ import java.util.TimerTask;
* Callback from MessageListAdapter. All methods are called on the UI thread.
*/
public interface Callback {
- /** Called when the adapter refreshes */
- void onAdapterRequery();
/** Called when the use starts/unstars a message */
void onAdapterFavoriteChanged(MessageListItem itemView, boolean newFavorite);
/** Called when the user selects/unselects a message */
@@ -107,7 +112,7 @@ import java.util.TimerTask;
private final Handler mHandler;
public MessagesAdapter(Context context, Handler handler, Callback callback) {
- super(context.getApplicationContext(), null, true);
+ super(context.getApplicationContext(), null, 0 /* no auto requery */);
mHandler = handler;
mCallback = callback;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
@@ -129,72 +134,22 @@ import java.util.TimerTask;
mTimeFormat = android.text.format.DateFormat.getTimeFormat(context); // 12/24 time
}
- /**
- * We override onContentChange to throttle the refresh, which can happen way too often
- * on syncing a large list (up to many times per second). This will prevent ANR's during
- * initial sync and potentially at other times as well.
- */
- @Override
- protected synchronized void onContentChanged() {
- final Cursor cursor = getCursor();
- if (cursor != null && !cursor.isClosed()) {
- long sinceRefresh = SystemClock.elapsedRealtime() - mLastRefreshTime;
- mRefreshTimer.schedule(REFRESH_INTERVAL_MS - sinceRefresh);
+ public void onSaveInstanceState(Bundle outState) {
+ Set checkedset = getSelectedSet();
+ long[] checkedarray = new long[checkedset.size()];
+ int i = 0;
+ for (Long l : checkedset) {
+ checkedarray[i] = l;
+ i++;
}
+ outState.putLongArray(STATE_CHECKED_ITEMS, checkedarray);
}
- /**
- * Called in UI thread only to complete the requery that we
- * intercepted in onContentChanged().
- */
- private void doRequery() {
- super.onContentChanged();
- }
-
- private class RefreshTimer extends Timer {
- private TimerTask timerTask = null;
-
- protected void clear() {
- timerTask = null;
+ public void loadState(Bundle savedInstanceState) {
+ Set checkedset = getSelectedSet();
+ for (long l: savedInstanceState.getLongArray(STATE_CHECKED_ITEMS)) {
+ checkedset.add(l);
}
-
- protected synchronized void schedule(long delay) {
- if (timerTask != null) return;
- if (delay < 0) {
- refreshList();
- } else {
- timerTask = new RefreshTimerTask();
- schedule(timerTask, delay);
- }
- }
- }
-
- private class RefreshTimerTask extends TimerTask {
- @Override
- public void run() {
- refreshList();
- }
- }
-
- /**
- * Do the work of requerying the list and notifying the UI of changed data
- * Make sure we call notifyDataSetChanged on the UI thread.
- */
- private synchronized void refreshList() {
- if (Email.LOGD) {
- Log.d("messageList", "refresh: "
- + (SystemClock.elapsedRealtime() - mLastRefreshTime) + "ms");
- }
- mHandler.post(new Runnable() {
- public void run() {
- doRequery();
- if (mCallback != null) {
- mCallback.onAdapterRequery();
- }
- }
- });
- mLastRefreshTime = SystemClock.elapsedRealtime();
- mRefreshTimer.clear();
}
public Set getSelectedSet() {
@@ -321,4 +276,16 @@ import java.util.TimerTask;
itemView.setBackgroundDrawable(null); // Change back to default.
}
}
+
+ public static Loader createLoader(Context context, long mailboxId) {
+ if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
+ Log.d(Email.LOG_TAG, "MessagesAdapter createLoader mailboxId=" + mailboxId);
+ }
+ String selection =
+ Utility.buildMailboxIdSelection(context.getContentResolver(), mailboxId);
+ return new ThrottlingCursorLoader(context, EmailContent.Message.CONTENT_URI,
+ MESSAGE_PROJECTION, selection, null,
+ EmailContent.MessageColumns.TIMESTAMP + " DESC", REFRESH_INTERVAL_MS);
+
+ }
}
diff --git a/src/com/android/email/data/MailboxAccountLoader.java b/src/com/android/email/data/MailboxAccountLoader.java
new file mode 100644
index 000000000..c00092998
--- /dev/null
+++ b/src/com/android/email/data/MailboxAccountLoader.java
@@ -0,0 +1,81 @@
+/*
+ * 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.data;
+
+import com.android.email.provider.EmailContent.Account;
+import com.android.email.provider.EmailContent.Mailbox;
+
+import android.content.AsyncTaskLoader;
+import android.content.Context;
+
+
+/**
+ * Loader to load {@link Mailbox} and {@link Account}.
+ */
+public class MailboxAccountLoader extends AsyncTaskLoader {
+ public static class Result {
+ public Account mAccount;
+ public Mailbox mMailbox;
+
+ public boolean isFound() {
+ return (mAccount != null) && (mMailbox != null);
+ }
+ }
+
+ private final Context mContext;
+ private final long mMailboxId;
+
+ public MailboxAccountLoader(Context context, long mailboxId) {
+ super(context);
+ mContext = context;
+ mMailboxId = mailboxId;
+ }
+
+ @Override
+ public Result loadInBackground() {
+ Result result = new Result();
+ if (mMailboxId < 0) {
+ // Magic mailbox.
+ } else {
+ result.mMailbox = Mailbox.restoreMailboxWithId(mContext, mMailboxId);
+ if (result.mMailbox != null) {
+ result.mAccount = Account.restoreAccountWithId(mContext,
+ result.mMailbox.mAccountKey);
+ }
+ if (result.mAccount == null) { // account removed??
+ result.mMailbox = null;
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public void startLoading() {
+ cancelLoad();
+ forceLoad();
+ }
+
+ @Override
+ public void stopLoading() {
+ cancelLoad();
+ }
+
+ @Override
+ public void destroy() {
+ stopLoading();
+ }
+}
diff --git a/src/com/android/email/data/NoAutoRequeryCursorLoader.java b/src/com/android/email/data/NoAutoRequeryCursorLoader.java
new file mode 100644
index 000000000..e1e024246
--- /dev/null
+++ b/src/com/android/email/data/NoAutoRequeryCursorLoader.java
@@ -0,0 +1,37 @@
+/*
+ * 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.data;
+
+import android.content.Context;
+import android.content.CursorLoader;
+import android.net.Uri;
+
+/**
+ * Same as {@link CursorLoader} but it doesn't do auto-requery when it gets content-changed
+ * notifications.
+ */
+public class NoAutoRequeryCursorLoader extends CursorLoader {
+ public NoAutoRequeryCursorLoader(Context context, Uri uri, String[] projection,
+ String selection, String[] selectionArgs, String sortOrder) {
+ super(context, uri, projection, selection, selectionArgs, sortOrder);
+ }
+
+ @Override
+ public void onContentChanged() {
+ // Don't reload.
+ }
+}
diff --git a/tests/src/com/android/email/activity/MessageListUnitTests.java b/tests/src/com/android/email/activity/MessageListUnitTests.java
deleted file mode 100644
index 98300bba2..000000000
--- a/tests/src/com/android/email/activity/MessageListUnitTests.java
+++ /dev/null
@@ -1,243 +0,0 @@
-/*
- * Copyright (C) 2009 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.provider.EmailContent;
-import com.android.email.provider.EmailContent.Message;
-import com.android.email.provider.EmailContent.MessageColumns;
-
-import android.content.Context;
-import android.content.Intent;
-import android.database.CursorIndexOutOfBoundsException;
-import android.database.AbstractCursor;
-import android.database.SQLException;
-import android.os.Bundle;
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.widget.CursorAdapter;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Various instrumentation tests for MessageList.
- *
- * It might be possible to convert these to ActivityUnitTest, which would be faster.
- */
-@LargeTest
-public class MessageListUnitTests
- extends ActivityInstrumentationTestCase2 {
-
- private static final String EXTRA_ACCOUNT_ID = "com.android.email.activity._ACCOUNT_ID";
- private static final String EXTRA_MAILBOX_TYPE = "com.android.email.activity.MAILBOX_TYPE";
- private static final String EXTRA_MAILBOX_ID = "com.android.email.activity.MAILBOX_ID";
- private static final String STATE_CHECKED_ITEMS =
- "com.android.email.activity.MessageList.checkedItems";
- private Context mContext;
- private MessageList mMessageList;
- private CursorAdapter mListAdapter;
- private HashMap> mRowsMap;
- private ArrayList mIDarray;
-
- public MessageListUnitTests() {
- super(MessageList.class);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- mContext = getInstrumentation().getTargetContext();
- Email.setServicesEnabled(mContext);
-
- Intent i = new Intent()
- .putExtra(EXTRA_ACCOUNT_ID, Long.MIN_VALUE)
- .putExtra(EXTRA_MAILBOX_TYPE, Long.MIN_VALUE)
- .putExtra(EXTRA_MAILBOX_ID, Long.MIN_VALUE);
- this.setActivityIntent(i);
- mMessageList = getActivity();
- }
-
- /**
- * Add a dummy message to the data map
- */
- private void addElement(long id, long mailboxKey, long accountKey, String displayName,
- String subject, long timestamp, int flagRead, int flagFavorite, int flagAttachment,
- int flags) {
- HashMap emap = new HashMap();
- emap.put(EmailContent.RECORD_ID, id);
- emap.put(MessageColumns.MAILBOX_KEY, mailboxKey);
- emap.put(MessageColumns.ACCOUNT_KEY, accountKey);
- emap.put(MessageColumns.DISPLAY_NAME, displayName);
- emap.put(MessageColumns.SUBJECT, subject);
- emap.put(MessageColumns.TIMESTAMP, timestamp);
- emap.put(MessageColumns.FLAG_READ, flagRead);
- emap.put(MessageColumns.FLAG_FAVORITE, flagFavorite);
- emap.put(MessageColumns.FLAG_ATTACHMENT, flagAttachment);
- emap.put(MessageColumns.FLAGS, flags);
- mRowsMap.put(id, emap);
- mIDarray.add(id);
- }
-
- /**
- * Create dummy messages
- */
- private void setUpCustomCursor() throws Throwable {
- runTestOnUiThread(new Runnable() {
- public void run() {
- mListAdapter = mMessageList.getListFragmentForTest().getAdapterForTest();
- mRowsMap = new HashMap>(0);
- mIDarray = new ArrayList(0);
- final int FIMI = Message.FLAG_INCOMING_MEETING_INVITE;
- addElement(0, Long.MIN_VALUE, Long.MIN_VALUE, "a", "A", 0, 0, 0, 0, 0);
- addElement(1, Long.MIN_VALUE, Long.MIN_VALUE, "b", "B", 0, 0, 0, 0, 0);
- addElement(2, Long.MIN_VALUE, Long.MIN_VALUE, "c", "C", 0, 0, 0, 0, 0);
- addElement(3, Long.MIN_VALUE, Long.MIN_VALUE, "d", "D", 0, 0, 0, 0, FIMI);
- addElement(4, Long.MIN_VALUE, Long.MIN_VALUE, "e", "E", 0, 0, 0, 0, 0);
- addElement(5, Long.MIN_VALUE, Long.MIN_VALUE, "f", "F", 0, 0, 0, 0, 0);
- addElement(6, Long.MIN_VALUE, Long.MIN_VALUE, "g", "G", 0, 0, 0, 0, 0);
- addElement(7, Long.MIN_VALUE, Long.MIN_VALUE, "h", "H", 0, 0, 0, 0, 0);
- addElement(8, Long.MIN_VALUE, Long.MIN_VALUE, "i", "I", 0, 0, 0, 0, 0);
- addElement(9, Long.MIN_VALUE, Long.MIN_VALUE, "j", "J", 0, 0, 0, 0, 0);
- CustomCursor cc = new CustomCursor(mIDarray, MessageListFragment.MESSAGE_PROJECTION,
- mRowsMap);
- mListAdapter.changeCursor(cc);
- }
- });
- }
-
- public void testRestoreAndSaveInstanceState() throws Throwable {
- setUpCustomCursor();
- Bundle bundle = new Bundle();
- mMessageList.getListFragmentForTest().onSaveInstanceState(bundle);
- long[] checkedarray = bundle.getLongArray(STATE_CHECKED_ITEMS);
- assertEquals(0, checkedarray.length);
- Set checkedset = ((MessagesAdapter)mListAdapter).getSelectedSet();
- checkedset.add(1L);
- checkedset.add(3L);
- checkedset.add(5L);
- mMessageList.getListFragmentForTest().onSaveInstanceState(bundle);
- checkedarray = bundle.getLongArray(STATE_CHECKED_ITEMS);
- java.util.Arrays.sort(checkedarray);
- assertEquals(3, checkedarray.length);
- assertEquals(1, checkedarray[0]);
- assertEquals(3, checkedarray[1]);
- assertEquals(5, checkedarray[2]);
- }
-
- public void testRestoreInstanceState() throws Throwable {
- setUpCustomCursor();
- Bundle bundle = new Bundle();
- long[] checkedarray = new long[3];
- checkedarray[0] = 1;
- checkedarray[1] = 3;
- checkedarray[2] = 5;
- Set checkedset = ((MessagesAdapter)mListAdapter).getSelectedSet();
- assertEquals(0, checkedset.size());
- bundle.putLongArray(STATE_CHECKED_ITEMS, checkedarray);
- mMessageList.getListFragmentForTest().loadState(bundle);
- checkedset = ((MessagesAdapter)mListAdapter).getSelectedSet();
- assertEquals(3, checkedset.size());
- assertTrue(checkedset.contains(1L));
- assertTrue(checkedset.contains(3L));
- assertTrue(checkedset.contains(5L));
- }
-
- /**
- * Mock Cursor for MessageList
- */
- static class CustomCursor extends AbstractCursor {
- private final ArrayList mSortedIdList;
- private final String[] mColumnNames;
-
- public CustomCursor(ArrayList sortedIdList,
- String[] columnNames,
- HashMap> rows) {
- mSortedIdList = sortedIdList;
- mColumnNames = columnNames;
- mUpdatedRows = rows;
- }
-
- @Override
- public void close() {
- super.close();
- }
-
- @Override
- public String[] getColumnNames() {
- return mColumnNames;
- }
-
- private Object getObject(int columnIndex) {
- if (isClosed()) {
- throw new SQLException("Already closed.");
- }
- int size = mSortedIdList.size();
- if (mPos < 0 || mPos >= size) {
- throw new CursorIndexOutOfBoundsException(mPos, size);
- }
- if (columnIndex < 0 || columnIndex >= getColumnCount()) {
- return null;
- }
- return mUpdatedRows.get(mSortedIdList.get(mPos)).get(mColumnNames[columnIndex]);
- }
-
- @Override
- public float getFloat(int columnIndex) {
- return Float.valueOf(getObject(columnIndex).toString());
- }
-
- @Override
- public double getDouble(int columnIndex) {
- return Double.valueOf(getObject(columnIndex).toString());
- }
-
- @Override
- public int getInt(int columnIndex) {
- return Integer.valueOf(getObject(columnIndex).toString());
- }
-
- @Override
- public long getLong(int columnIndex) {
- return Long.valueOf(getObject(columnIndex).toString());
- }
-
- @Override
- public short getShort(int columnIndex) {
- return Short.valueOf(getObject(columnIndex).toString());
- }
-
- @Override
- public String getString(int columnIndex) {
- return String.valueOf(getObject(columnIndex));
- }
-
- @Override
- public boolean isNull(int columnIndex) {
- return getObject(columnIndex) == null;
- }
-
- @Override
- public int getCount() {
- return mSortedIdList.size();
- }
- }
-}
-
diff --git a/tests/src/com/android/email/data/MailboxAccountLoaderTestCase.java b/tests/src/com/android/email/data/MailboxAccountLoaderTestCase.java
new file mode 100644
index 000000000..254a5370b
--- /dev/null
+++ b/tests/src/com/android/email/data/MailboxAccountLoaderTestCase.java
@@ -0,0 +1,102 @@
+/*
+ * 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.data;
+
+import com.android.email.DBTestHelper;
+import com.android.email.provider.EmailContent.Account;
+import com.android.email.provider.EmailContent.Mailbox;
+import com.android.email.provider.EmailProvider;
+import com.android.email.provider.ProviderTestUtils;
+
+import android.content.Context;
+import android.test.LoaderTestCase;
+
+public class MailboxAccountLoaderTestCase extends LoaderTestCase {
+ // Isolted Context for providers.
+ private Context mProviderContext;
+
+ @Override
+ protected void setUp() throws Exception {
+ mProviderContext = DBTestHelper.ProviderContextSetupHelper.getProviderContext(
+ getContext(), EmailProvider.class);
+ }
+
+ private long createAccount() {
+ Account acct = ProviderTestUtils.setupAccount("acct1", true, mProviderContext);
+ return acct.mId;
+ }
+
+ private long createMailbox(long accountId) {
+ Mailbox box = ProviderTestUtils.setupMailbox("name", accountId, true, mProviderContext);
+ return box.mId;
+ }
+
+ /**
+ * Test for {@link MailboxAccountLoader.Result#isFound()}
+ */
+ public void testIsFound() {
+ MailboxAccountLoader.Result result = new MailboxAccountLoader.Result();
+ assertFalse(result.isFound());
+
+ result.mAccount = new Account();
+ assertFalse(result.isFound());
+
+ result.mMailbox = new Mailbox();
+ assertTrue(result.isFound());
+
+ result.mAccount = null;
+ assertFalse(result.isFound());
+ }
+
+ /**
+ * Test for normal case. (account, mailbox found)
+ */
+ public void testLoad() {
+ final long accountId = createAccount();
+ final long mailboxId = createMailbox(accountId);
+
+ MailboxAccountLoader.Result result = getLoaderResultSynchronously(
+ new MailboxAccountLoader(mProviderContext, mailboxId));
+ assertTrue(result.isFound());
+ assertEquals(accountId, result.mAccount.mId);
+ assertEquals(mailboxId, result.mMailbox.mId);
+ }
+
+ /**
+ * Mailbox not found.
+ */
+ public void testMailboxNotFound() {
+ MailboxAccountLoader.Result result = getLoaderResultSynchronously(
+ new MailboxAccountLoader(mProviderContext, 123));
+ assertFalse(result.isFound());
+ assertNull(result.mAccount);
+ assertNull(result.mMailbox);
+ }
+
+ /**
+ * Account not found.
+ */
+ public void testAccountNotFound() {
+ final long mailboxId = createMailbox(1);
+
+ MailboxAccountLoader.Result result = getLoaderResultSynchronously(
+ new MailboxAccountLoader(mProviderContext, mailboxId));
+ assertFalse(result.isFound());
+ assertNull(result.mAccount);
+ assertNull(result.mMailbox);
+ }
+}