Rework/cleanup of "refresh".

Added RefreshManager, which is responsible for getting refresh requests
from UI and keeping track of what is being refreshed.

Conceptually it's a part of Controller, but extracted for easier testing.

- Now sendPendingMessagesForAllAccounts() is owned by RefreshManager
  rather than Controller.
- Also updateMailboxRefreshTime/mailboxRequiresRefresh have been moved
  in from the Email class.
- Now MessagingException implements a method to return an error message
  for the UI.

The refresh button on 2-pane doesn't work as intended yet, because the
spec is a bit too complicated (as described in the TODO in
MessageListXLFragmentManager.onRefhres()).

This change touches many file mostly because it cleans up a lot
of code duplication.

Change-Id: I058ab745ccff10f6e574f6ec4569c84ac4a3e10e
This commit is contained in:
Makoto Onuki 2010-08-16 14:14:40 -07:00
parent f513fbd8cb
commit 21efedb67f
20 changed files with 1237 additions and 198 deletions

View File

@ -0,0 +1,32 @@
/*
* 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;
/**
* A class provide the current time (like {@link System#currentTimeMillis()}).
* It's intended to be mocked out for unit tests.
*/
public class Clock {
public static final Clock INSTANCE = new Clock();
protected Clock() {
}
public long getTime() {
return System.currentTimeMillis();
}
}

View File

@ -571,26 +571,6 @@ public class Controller {
}
}
/**
* Call {@link #sendPendingMessages} for all accounts.
*/
public void sendPendingMessagesForAllAccounts(final Context context) {
Utility.runAsync(new Runnable() {
public void run() {
Cursor c = context.getContentResolver().query(Account.CONTENT_URI,
Account.ID_PROJECTION, null, null, null);
try {
while (c.moveToNext()) {
long accountId = c.getLong(Account.ID_PROJECTION_COLUMN);
sendPendingMessages(accountId);
}
} finally {
c.close();
}
}
});
}
/**
* Reset visible limits for all accounts.
* For each account:

View File

@ -24,6 +24,9 @@ import android.os.Handler;
/**
* A {@link Result} that wraps another {@link Result} and makes sure methods gets called back
* on the UI thread.
*
* <p>Optionally it supports the "synchronous" mode, if you pass null for the {@code handler}
* parameter, which allows unit tests to run synchronously.
*/
public class ControllerResultUiThreadWrapper<T extends Result> extends Result {
private final Handler mHandler;
@ -38,10 +41,18 @@ public class ControllerResultUiThreadWrapper<T extends Result> extends Result {
return mWrappee;
}
private void run(Runnable runnable) {
if (mHandler == null) {
runnable.run();
} else {
mHandler.post(runnable);
}
}
@Override
public void loadAttachmentCallback(final MessagingException result, final long messageId,
final long attachmentId, final int progress) {
mHandler.post(new Runnable() {
run(new Runnable() {
public void run() {
mWrappee.loadAttachmentCallback(result, messageId, attachmentId, progress);
}
@ -51,7 +62,7 @@ public class ControllerResultUiThreadWrapper<T extends Result> extends Result {
@Override
public void loadMessageForViewCallback(final MessagingException result,
final long messageId, final int progress) {
mHandler.post(new Runnable() {
run(new Runnable() {
public void run() {
mWrappee.loadMessageForViewCallback(result, messageId, progress);
}
@ -61,7 +72,7 @@ public class ControllerResultUiThreadWrapper<T extends Result> extends Result {
@Override
public void sendMailCallback(final MessagingException result, final long accountId,
final long messageId, final int progress) {
mHandler.post(new Runnable() {
run(new Runnable() {
public void run() {
mWrappee.sendMailCallback(result, accountId, messageId, progress);
}
@ -71,7 +82,7 @@ public class ControllerResultUiThreadWrapper<T extends Result> extends Result {
@Override
public void serviceCheckMailCallback(final MessagingException result, final long accountId,
final long mailboxId, final int progress, final long tag) {
mHandler.post(new Runnable() {
run(new Runnable() {
public void run() {
mWrappee.serviceCheckMailCallback(result, accountId, mailboxId, progress, tag);
}
@ -81,7 +92,7 @@ public class ControllerResultUiThreadWrapper<T extends Result> extends Result {
@Override
public void updateMailboxCallback(final MessagingException result, final long accountId,
final long mailboxId, final int progress, final int numNewMessages) {
mHandler.post(new Runnable() {
run(new Runnable() {
public void run() {
mWrappee.updateMailboxCallback(result, accountId, mailboxId, progress,
numNewMessages);
@ -92,7 +103,7 @@ public class ControllerResultUiThreadWrapper<T extends Result> extends Result {
@Override
public void updateMailboxListCallback(final MessagingException result, final long accountId,
final int progress) {
mHandler.post(new Runnable() {
run(new Runnable() {
public void run() {
mWrappee.updateMailboxListCallback(result, accountId, progress);
}
@ -101,7 +112,7 @@ public class ControllerResultUiThreadWrapper<T extends Result> extends Result {
@Override
public void deleteAccountCallback(final long accountId) {
mHandler.post(new Runnable() {
run(new Runnable() {
public void run() {
mWrappee.deleteAccountCallback(accountId);
}

View File

@ -141,9 +141,6 @@ public class Email extends Application {
*/
public static final int MAX_ATTACHMENT_UPLOAD_SIZE = (5 * 1024 * 1024);
private static HashMap<Long, Long> sMailboxSyncTimes = new HashMap<Long, Long>();
private static final long UPDATE_INTERVAL = 5 * DateUtils.MINUTE_IN_MILLIS;
/**
* This is used to force stacked UI to return to the "welcome" screen any time we change
* the accounts list (e.g. deleting accounts in the Account Manager preferences.)
@ -271,6 +268,8 @@ public class Email extends Application {
DEBUG = prefs.getEnableDebugLogging();
setTempDirectory(this);
// Tie MailRefreshManager to the Controller.
RefreshManager.getInstance(this);
// Reset all accounts to default visible window
Controller.getInstance(this).resetVisibleLimits();
@ -286,30 +285,6 @@ public class Email extends Application {
Log.d(LOG_TAG, message);
}
/**
* Update the time when the mailbox is refreshed
* @param mailboxId mailbox which need to be updated
*/
public static void updateMailboxRefreshTime(long mailboxId) {
synchronized (sMailboxSyncTimes) {
sMailboxSyncTimes.put(mailboxId, System.currentTimeMillis());
}
}
/**
* Check if the mailbox is need to be refreshed
* @param mailboxId mailbox checked the need of refreshing
* @return the need of refreshing
*/
public static boolean mailboxRequiresRefresh(long mailboxId) {
synchronized (sMailboxSyncTimes) {
return
!sMailboxSyncTimes.containsKey(mailboxId)
|| (System.currentTimeMillis() - sMailboxSyncTimes.get(mailboxId)
> UPDATE_INTERVAL);
}
}
/**
* Called by the accounts reconciler to notify that accounts have changed, or by "Welcome"
* to clear the flag.

View File

@ -2026,14 +2026,13 @@ public class MessagingController implements Runnable {
}
/**
* Checks mail for one or multiple accounts. If account is null all accounts
* are checked. This entry point is for use by the mail checking service only, because it
* Checks mail for an account.
* This entry point is for use by the mail checking service only, because it
* gives slightly different callbacks (so the service doesn't get confused by callbacks
* triggered by/for the foreground UI.
*
* TODO clean up the execution model which is unnecessarily threaded due to legacy code
*
* @param context
* @param accountId the account to check
* @param listener
*/

View File

@ -0,0 +1,443 @@
/*
* 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;
import com.android.email.mail.MessagingException;
import com.android.email.provider.EmailContent;
import android.content.Context;
import android.database.Cursor;
import android.os.Handler;
import android.util.Log;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
/**
* Class that handles "refresh" (and "send pending messages" for outboxes) related functionalities.
*
* <p>This class is responsible for two things:
* <ul>
* <li>Taking refresh requests of mailbox-lists and message-lists and the "send outgoing
* messages" requests from UI, and calls appropriate methods of {@link Controller}.
* Note at this point the timer-based refresh
* (by {@link com.android.email.service.MailService}) uses {@link Controller} directly.
* <li>Keeping track of which mailbox list/message list is actually being refreshed.
* </ul>
* Refresh requests will be ignored if a request to the same target is already requested, or is
* already being refreshed.
*
* <p>Conceptually it can be a part of {@link Controller}, but extracted for easy testing.
*/
public class RefreshManager {
private static final boolean DEBUG_CALLBACK_LOG = true;
private static final long MAILBOX_AUTO_REFRESH_INTERVAL = 5 * 60 * 1000; // in milliseconds
private static RefreshManager sInstance;
private final Clock mClock;
private final Context mContext;
private final Controller mController;
private final Controller.Result mControllerResult;
/** Last error message */
private String mErrorMessage;
public interface Listener {
public void onRefreshStatusChanged(long accountId, long mailboxId);
public void onMessagingError(long accountId, long mailboxId, String message);
}
private final ArrayList<Listener> mListeners = new ArrayList<Listener>();
/**
* Status of a mailbox list/message list.
*/
/* package */ static class Status {
/**
* True if a refresh of the mailbox is requested, and not finished yet.
*/
private boolean mIsRefreshRequested;
/**
* True if the mailbox is being refreshed.
*
* Set true when {@link #onRefreshRequested} is called, i.e. refresh is requested by UI.
* Note refresh can occur without a request from UI as well (e.g. timer based refresh).
* In which case, {@link #mIsRefreshing} will be true with {@link #mIsRefreshRequested}
* being false.
*/
private boolean mIsRefreshing;
private long mLastRefreshTime;
public boolean isRefreshing() {
return mIsRefreshRequested || mIsRefreshing;
}
public boolean canRefresh() {
return !isRefreshing();
}
public void onRefreshRequested() {
mIsRefreshRequested = true;
}
public long getLastRefreshTime() {
return mLastRefreshTime;
}
public void onCallback(MessagingException exception, int progress, Clock clock) {
if (exception == null && progress == 0) {
// Refresh started
mIsRefreshing = true;
} else if (exception != null || progress == 100) {
// Refresh finished
mIsRefreshing = false;
mIsRefreshRequested = false;
mLastRefreshTime = clock.getTime();
}
}
}
/**
* Map of accounts/mailboxes to {@link Status}.
*/
private static class RefreshStatusMap {
private final HashMap<Long, Status> mMap = new HashMap<Long, Status>();
public Status get(long id) {
Status s = mMap.get(id);
if (s == null) {
s = new Status();
mMap.put(id, s);
}
return s;
}
public boolean isRefreshingAny() {
for (Status s : mMap.values()) {
if (s.isRefreshing()) {
return true;
}
}
return false;
}
}
private final RefreshStatusMap mMailboxListStatus = new RefreshStatusMap();
private final RefreshStatusMap mMessageListStatus = new RefreshStatusMap();
private final RefreshStatusMap mOutboxStatus = new RefreshStatusMap();
/**
* @return the singleton instance.
*/
public static synchronized RefreshManager getInstance(Context context) {
if (sInstance == null) {
sInstance = new RefreshManager(context, Controller.getInstance(context),
Clock.INSTANCE, new Handler());
}
return sInstance;
}
/* package */ RefreshManager(Context context, Controller controller, Clock clock,
Handler handler) {
mClock = clock;
mContext = context.getApplicationContext();
mController = controller;
mControllerResult = new ControllerResultUiThreadWrapper<ControllerResult>(
handler, new ControllerResult());
mController.addResultCallback(mControllerResult);
}
public void registerListener(Listener listener) {
if (listener == null) {
throw new InvalidParameterException();
}
mListeners.add(listener);
}
public void unregisterListener(Listener listener) {
if (listener == null) {
throw new InvalidParameterException();
}
mListeners.remove(listener);
}
/**
* Refresh the mailbox list of an account.
*/
public boolean refreshMailboxList(long accountId) {
final Status status = mMailboxListStatus.get(accountId);
if (!status.canRefresh()) return false;
Log.i(Email.LOG_TAG, "refreshMailboxList " + accountId);
status.onRefreshRequested();
notifyRefreshStatusChanged(accountId, -1);
mController.updateMailboxList(accountId);
return true;
}
public boolean isMailboxStale(long mailboxId) {
return mClock.getTime() >= (mMessageListStatus.get(mailboxId).getLastRefreshTime()
+ MAILBOX_AUTO_REFRESH_INTERVAL);
}
/**
* Refresh messages in a mailbox.
*/
public boolean refreshMessageList(long accountId, long mailboxId) {
return refreshMessageList(accountId, mailboxId, false);
}
/**
* "load more messages" in a mailbox.
*/
public boolean loadMoreMessages(long accountId, long mailboxId) {
return refreshMessageList(accountId, mailboxId, true);
}
private boolean refreshMessageList(long accountId, long mailboxId, boolean loadMoreMessages) {
final Status status = mMessageListStatus.get(mailboxId);
if (!status.canRefresh()) return false;
Log.i(Email.LOG_TAG, "refreshMessageList " + accountId + ", " + mailboxId + ", "
+ loadMoreMessages);
status.onRefreshRequested();
notifyRefreshStatusChanged(accountId, mailboxId);
mController.updateMailbox(accountId, mailboxId);
return true;
}
/**
* Send pending messages.
*/
public boolean sendPendingMessages(long accountId) {
final Status status = mOutboxStatus.get(accountId);
if (!status.canRefresh()) return false;
Log.i(Email.LOG_TAG, "sendPendingMessages " + accountId);
status.onRefreshRequested();
notifyRefreshStatusChanged(accountId, -1);
mController.sendPendingMessages(accountId);
return true;
}
/**
* Call {@link #sendPendingMessages} for all accounts.
*/
public void sendPendingMessagesForAllAccounts() {
Log.i(Email.LOG_TAG, "sendPendingMessagesForAllAccounts");
Utility.runAsync(new Runnable() {
public void run() {
sendPendingMessagesForAllAccountsSync();
}
});
}
/**
* Synced internal method for {@link #sendPendingMessagesForAllAccounts} for testing.
*/
/* package */ void sendPendingMessagesForAllAccountsSync() {
Cursor c = mContext.getContentResolver().query(EmailContent.Account.CONTENT_URI,
EmailContent.Account.ID_PROJECTION, null, null, null);
try {
while (c.moveToNext()) {
sendPendingMessages(c.getLong(EmailContent.Account.ID_PROJECTION_COLUMN));
}
} finally {
c.close();
}
}
public boolean isMailboxListRefreshing(long accountId) {
return mMailboxListStatus.get(accountId).isRefreshing();
}
public boolean isMessageListRefreshing(long mailboxId) {
return mMessageListStatus.get(mailboxId).isRefreshing();
}
public boolean isSendingMessage(long accountId) {
return mOutboxStatus.get(accountId).isRefreshing();
}
public boolean isRefreshingAnyMailboxList() {
return mMailboxListStatus.isRefreshingAny();
}
public boolean isRefreshingAnyMessageList() {
return mMessageListStatus.isRefreshingAny();
}
public boolean isSendingAnyMessage() {
return mOutboxStatus.isRefreshingAny();
}
public boolean isRefreshingOrSendingAny() {
return isRefreshingAnyMailboxList() || isRefreshingAnyMessageList()
|| isSendingAnyMessage();
}
public String getErrorMessage() {
return mErrorMessage;
}
private void notifyRefreshStatusChanged(long accountId, long mailboxId) {
for (Listener l : mListeners) {
l.onRefreshStatusChanged(accountId, mailboxId);
}
}
private void reportError(long accountId, long mailboxId, String errorMessage) {
mErrorMessage = errorMessage;
for (Listener l : mListeners) {
l.onMessagingError(accountId, mailboxId, mErrorMessage);
}
}
/* package */ Collection<Listener> getListenersForTest() {
return mListeners;
}
/* package */ Status getMailboxListStatusForTest(long accountId) {
return mMailboxListStatus.get(accountId);
}
/* package */ Status getMessageListStatusForTest(long mailboxId) {
return mMessageListStatus.get(mailboxId);
}
/* package */ Status getOutboxStatusForTest(long acountId) {
return mOutboxStatus.get(acountId);
}
private class ControllerResult extends Controller.Result {
private boolean mSendMailExceptionReported = false;
private String exceptionToString(MessagingException exception) {
if (exception == null) {
return "(no exception)";
} else {
return exception.getUiErrorMessage(mContext);
}
}
/**
* Callback for mailbox list refresh.
*/
@Override
public void updateMailboxListCallback(MessagingException exception, long accountId,
int progress) {
if (Email.DEBUG && DEBUG_CALLBACK_LOG) {
Log.d(Email.LOG_TAG, "updateMailboxListCallback " + accountId + ", " + progress
+ ", " + exceptionToString(exception));
}
mMailboxListStatus.get(accountId).onCallback(exception, progress, mClock);
if (exception != null) {
reportError(accountId, -1, exception.getUiErrorMessage(mContext));
}
notifyRefreshStatusChanged(accountId, -1);
}
/**
* Callback for explicit (user-driven) mailbox refresh.
*/
@Override
public void updateMailboxCallback(MessagingException exception, long accountId,
long mailboxId, int progress, int dontUseNumNewMessages) {
if (Email.DEBUG && DEBUG_CALLBACK_LOG) {
Log.d(Email.LOG_TAG, "updateMailboxCallback " + accountId + ", "
+ mailboxId + ", " + progress + ", " + exceptionToString(exception));
}
updateMailboxCallbackInternal(exception, accountId, mailboxId, progress, 0);
}
/**
* Callback for implicit (timer-based) mailbox refresh.
*
* Do the same as {@link #updateMailboxCallback}.
* TODO: Figure out if it's really okay to do the same as updateMailboxCallback.
* If both the explicit refresh and the implicit refresh can run at the same time,
* we need to keep track of their status separately.
*/
@Override
public void serviceCheckMailCallback(
MessagingException exception, long accountId, long mailboxId, int progress,
long tag) {
if (Email.DEBUG && DEBUG_CALLBACK_LOG) {
Log.d(Email.LOG_TAG, "serviceCheckMailCallback " + accountId + ", "
+ mailboxId + ", " + progress + ", " + exceptionToString(exception));
}
updateMailboxCallbackInternal(exception, accountId, mailboxId, progress, 0);
}
private void updateMailboxCallbackInternal(MessagingException exception, long accountId,
long mailboxId, int progress, int dontUseNumNewMessages) {
// Don't use dontUseNumNewMessages. serviceCheckMailCallback() don't set it.
mMessageListStatus.get(mailboxId).onCallback(exception, progress, mClock);
if (exception != null) {
reportError(accountId, mailboxId, exception.getUiErrorMessage(mContext));
}
notifyRefreshStatusChanged(accountId, mailboxId);
}
/**
* Send message progress callback.
*
* This callback is overly overloaded:
*
* First, we get this.
* result == null, messageId == -1, progress == 0: start batch send
*
* Then we get these callbacks per message.
* (Exchange backend may skip "start sending one message".)
* result == null, messageId == xx, progress == 0: start sending one message
* result == xxxx, messageId == xx, progress == 0; failed sending one message
*
* Finally we get this.
* result == null, messageId == -1, progres == 100; finish sending batch
*
* So, let's just report the first exception we get, and ignore the rest.
*/
@Override
public void sendMailCallback(MessagingException exception, long accountId, long messageId,
int progress) {
if (Email.DEBUG && DEBUG_CALLBACK_LOG) {
Log.d(Email.LOG_TAG, "sendMailCallback " + accountId + ", "
+ messageId + ", " + progress + ", " + exceptionToString(exception));
}
if (progress == 0 && messageId == -1) {
mSendMailExceptionReported = false;
}
if (messageId == -1) {
// Update the status only for the batch start/end.
// (i.e. don't report for each message.)
mOutboxStatus.get(accountId).onCallback(exception, progress, mClock);
notifyRefreshStatusChanged(accountId, -1);
}
if (exception != null && !mSendMailExceptionReported) {
// Only the first error in a batch will be reported.
mSendMailExceptionReported = true;
reportError(accountId, messageId, exception.getUiErrorMessage(mContext));
}
}
}
}

View File

@ -322,9 +322,6 @@ public class AccountFolderList extends Activity implements AccountFolderListFrag
@Override
public void updateMailboxCallback(MessagingException result, long accountKey,
long mailboxKey, int progress, int numNewMessages) {
if (result != null || progress == 100) {
Email.updateMailboxRefreshTime(mailboxKey);
}
updateProgress(result, progress);
}

View File

@ -22,8 +22,6 @@ import com.android.email.Email;
import com.android.email.R;
import com.android.email.Utility;
import com.android.email.activity.setup.AccountSettings;
import com.android.email.mail.AuthenticationFailedException;
import com.android.email.mail.CertificateValidationException;
import com.android.email.mail.MessagingException;
import com.android.email.provider.EmailContent.Account;
import com.android.email.provider.EmailContent.AccountColumns;
@ -189,7 +187,7 @@ public class MailboxList extends Activity implements MailboxListFragment.Callbac
onAccounts();
return true;
case R.id.refresh:
onRefresh(-1);
onRefresh();
return true;
case R.id.compose:
onCompose();
@ -210,17 +208,12 @@ public class MailboxList extends Activity implements MailboxListFragment.Callbac
}
/**
* Refresh the mailbox list, or a single mailbox
* @param mailboxId -1 for all
* Refresh the mailbox list
*/
private void onRefresh(long mailboxId) {
private void onRefresh() {
Controller controller = Controller.getInstance(getApplication());
showProgressIcon(true);
if (mailboxId >= 0) {
controller.updateMailbox(mAccountId, mailboxId);
} else {
controller.updateMailboxList(mAccountId);
}
mListFragment.onRefresh();
}
private void onAccounts() {
@ -274,7 +267,6 @@ public class MailboxList extends Activity implements MailboxListFragment.Callbac
*/
private class ControllerResults extends Controller.Result {
// TODO report errors into UI
@Override
public void updateMailboxListCallback(MessagingException result, long accountKey,
int progress) {
@ -284,13 +276,9 @@ public class MailboxList extends Activity implements MailboxListFragment.Callbac
}
}
// TODO report errors into UI
@Override
public void updateMailboxCallback(MessagingException result, long accountKey,
long mailboxKey, int progress, int numNewMessages) {
if (result != null || progress == 100) {
Email.updateMailboxRefreshTime(mailboxKey);
}
if (accountKey == mAccountId) {
updateBanner(result, progress);
updateProgress(result, progress);
@ -321,28 +309,7 @@ public class MailboxList extends Activity implements MailboxListFragment.Callbac
*/
private void updateBanner(MessagingException result, int progress) {
if (result != null) {
int id = R.string.status_network_error;
if (result instanceof AuthenticationFailedException) {
id = R.string.account_setup_failed_dlg_auth_message;
} else if (result instanceof CertificateValidationException) {
id = R.string.account_setup_failed_dlg_certificate_message;
} else {
switch (result.getExceptionType()) {
case MessagingException.IOERROR:
id = R.string.account_setup_failed_ioerror;
break;
case MessagingException.TLS_REQUIRED:
id = R.string.account_setup_failed_tls_required;
break;
case MessagingException.AUTH_REQUIRED:
id = R.string.account_setup_failed_auth_required;
break;
case MessagingException.GENERAL_SECURITY:
id = R.string.account_setup_failed_security;
break;
}
}
showErrorBanner(getString(id));
showErrorBanner(result.getUiErrorMessage(MailboxList.this));
} else if (progress > 0) {
showErrorBanner(null);
}

View File

@ -16,7 +16,9 @@
package com.android.email.activity;
import com.android.email.Controller;
import com.android.email.Email;
import com.android.email.RefreshManager;
import com.android.email.Utility;
import android.app.Activity;
@ -238,4 +240,10 @@ public class MailboxListFragment extends ListFragment implements OnItemClickList
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
mCallback.onMailboxSelected(mAccountId, id);
}
public void onRefresh() {
if (mAccountId != -1) {
RefreshManager.getInstance(getActivity()).refreshMailboxList(mAccountId);
}
}
}

View File

@ -145,7 +145,6 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
private TextView mRightTitle;
private Controller mController;
private Listener mListener;
private boolean mDraftNeedsSaving;
private boolean mMessageLoaded;
private AsyncTask mLoadAttachmentsTask;
@ -270,7 +269,6 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.list_title);
mController = Controller.getInstance(getApplication());
mListener = new Listener();
initViews();
setDraftNeedsSaving(false);
@ -342,7 +340,6 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
@Override
public void onResume() {
super.onResume();
mController.addResultCallback(mListener);
// Exit immediately if the accounts list has changed (e.g. externally deleted)
if (Email.getNotifyUiAccountsChanged()) {
@ -356,7 +353,6 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
public void onPause() {
super.onPause();
saveIfNeeded();
mController.removeResultCallback(mListener);
}
/**
@ -1523,14 +1519,4 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
setMessageContentSelection((mAccount != null) ? mAccount.mSignature : null);
}
}
private class Listener extends Controller.Result {
@Override
public void updateMailboxCallback(MessagingException result, long accountId,
long mailboxId, int progress, int numNewMessages) {
if (result != null || progress == 100) {
Email.updateMailboxRefreshTime(mailboxId);
}
}
}
}

View File

@ -23,8 +23,6 @@ import com.android.email.R;
import com.android.email.Utility;
import com.android.email.activity.setup.AccountSecurity;
import com.android.email.activity.setup.AccountSettings;
import com.android.email.mail.AuthenticationFailedException;
import com.android.email.mail.CertificateValidationException;
import com.android.email.mail.MessagingException;
import com.android.email.provider.EmailContent;
import com.android.email.provider.EmailContent.Account;
@ -542,7 +540,6 @@ public class MessageList extends Activity implements OnClickListener,
private void showProgressIcon(boolean show) {
int visibility = show ? View.VISIBLE : View.GONE;
mProgressIcon.setVisibility(visibility);
mListFragment.showProgressIcon(show);
}
private void showErrorBanner(String message) {
@ -579,9 +576,6 @@ public class MessageList extends Activity implements OnClickListener,
public void updateMailboxCallback(MessagingException result, long accountKey,
long mailboxKey, int progress, int numNewMessages) {
updateBanner(result, progress, mailboxKey);
if (result != null || progress == 100) {
Email.updateMailboxRefreshTime(mailboxKey);
}
updateProgress(result, progress);
}
@ -632,33 +626,7 @@ public class MessageList extends Activity implements OnClickListener,
return;
}
if (result != null) {
int id = R.string.status_network_error;
if (result instanceof AuthenticationFailedException) {
id = R.string.account_setup_failed_dlg_auth_message;
} else if (result instanceof CertificateValidationException) {
id = R.string.account_setup_failed_dlg_certificate_message;
} else {
switch (result.getExceptionType()) {
case MessagingException.IOERROR:
id = R.string.account_setup_failed_ioerror;
break;
case MessagingException.TLS_REQUIRED:
id = R.string.account_setup_failed_tls_required;
break;
case MessagingException.AUTH_REQUIRED:
id = R.string.account_setup_failed_auth_required;
break;
case MessagingException.GENERAL_SECURITY:
id = R.string.account_setup_failed_security;
break;
// TODO Generate a unique string for this case, which is the case
// where the security policy needs to be updated.
case MessagingException.SECURITY_POLICIES_REQUIRED:
id = R.string.account_setup_failed_security;
break;
}
}
showErrorBanner(getString(id));
showErrorBanner(result.getUiErrorMessage(MessageList.this));
} else if (progress > 0) {
showErrorBanner(null);
}

View File

@ -18,6 +18,7 @@ package com.android.email.activity;
import com.android.email.Controller;
import com.android.email.Email;
import com.android.email.RefreshManager;
import com.android.email.R;
import com.android.email.Utility;
import com.android.email.data.MailboxAccountLoader;
@ -62,6 +63,8 @@ import java.util.Set;
* </ul>
* We run them sequentially. i.e. First starts {@link MailboxAccountLoader}, and when it finishes
* starts the other.
*
* TODO Add "send all messages" button to outboxes
*/
public class MessageListFragment extends ListFragment
implements OnItemClickListener, OnItemLongClickListener, MessagesAdapter.Callback {
@ -90,6 +93,8 @@ public class MessageListFragment extends ListFragment
// Controller access
private Controller mController;
private RefreshManager mRefreshManager;
private RefreshListener mRefreshListener = new RefreshListener();
// Misc members
private boolean mDoAutoRefresh;
@ -151,6 +156,8 @@ public class MessageListFragment extends ListFragment
super.onCreate(savedInstanceState);
mActivity = getActivity();
mController = Controller.getInstance(mActivity);
mRefreshManager = RefreshManager.getInstance(mActivity);
mRefreshManager.registerListener(mRefreshListener);
}
@Override
@ -218,7 +225,7 @@ public class MessageListFragment extends ListFragment
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
Log.d(Email.LOG_TAG, "MessageListFragment onDestroy");
}
mRefreshManager.unregisterListener(mRefreshListener);
super.onDestroy();
}
@ -391,9 +398,9 @@ public class MessageListFragment extends ListFragment
* Refresh the list. NOOP for special mailboxes (e.g. combined inbox).
*/
public void onRefresh() {
final long accountId = getAccountId();
long accountId = getAccountId();
if (accountId != -1) {
mController.updateMailbox(accountId, mMailboxId);
mRefreshManager.refreshMessageList(accountId, mMailboxId);
}
}
@ -410,16 +417,18 @@ public class MessageListFragment extends ListFragment
* Load more messages. NOOP for special mailboxes (e.g. combined inbox).
*/
private void onLoadMoreMessages() {
if (!isMagicMailbox()) {
mController.loadMoreMessages(mMailboxId);
long accountId = getAccountId();
if (accountId != -1) {
mRefreshManager.loadMoreMessages(accountId, mMailboxId);
}
}
public void onSendPendingMessages() {
RefreshManager rm = RefreshManager.getInstance(mActivity);
if (getMailboxId() == Mailbox.QUERY_ALL_OUTBOX) {
mController.sendPendingMessagesForAllAccounts(mActivity);
rm.sendPendingMessagesForAllAccounts();
} else if (!isMagicMailbox()) { // Magic boxes don't have a specific account id.
mController.sendPendingMessages(getAccountId());
rm.sendPendingMessages(getAccountId());
}
}
@ -618,24 +627,12 @@ public class MessageListFragment extends ListFragment
return;
}
mDoAutoRefresh = false;
if (!Email.mailboxRequiresRefresh(mMailboxId)) {
if (!mRefreshManager.isMailboxStale(mMailboxId)) {
return;
}
onRefresh();
}
/**
* 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
* itself, rather than letting the activity controll this.
*/
public void showProgressIcon(boolean show) {
if (mListFooterProgress != null) {
mListFooterProgress.setVisibility(show ? View.VISIBLE : View.GONE);
}
updateListFooterText(show);
}
/** Implements {@link MessagesAdapter.Callback} */
@Override
public void onAdapterFavoriteChanged(MessageListItem itemView, boolean newFavorite) {
@ -651,6 +648,10 @@ public class MessageListFragment extends ListFragment
private void determineFooterMode() {
mListFooterMode = LIST_FOOTER_MODE_NONE;
if ((mMailbox == null) || (mMailbox.mType == Mailbox.TYPE_OUTBOX)
|| (mMailbox.mType == Mailbox.TYPE_DRAFTS)) {
return; // No footer
}
if (mAccount != null && !mAccount.isEasAccount()) {
// IMAP, POP has "load more"
mListFooterMode = LIST_FOOTER_MODE_MORE;
@ -658,12 +659,12 @@ public class MessageListFragment extends ListFragment
}
private void addFooterView() {
ListView lv = getListView();
if (mListFooterView != null) {
lv.removeFooterView(mListFooterView);
}
determineFooterMode();
if (mListFooterMode != LIST_FOOTER_MODE_NONE) {
ListView lv = getListView();
if (mListFooterView != null) {
lv.removeFooterView(mListFooterView);
}
lv.addFooterView(mListFooterView);
lv.setAdapter(mListAdapter);
@ -671,22 +672,22 @@ public class MessageListFragment extends ListFragment
mListFooterProgress = mListFooterView.findViewById(R.id.progress);
mListFooterText = (TextView) mListFooterView.findViewById(R.id.main_text);
// TODO We don't know if it's really "inactive". Someone has to
// remember all sync status.
updateListFooterText(false);
updateListFooter();
}
}
/**
* Set the list footer text based on mode and "network active" status
* Set the list footer text based on mode and the current "network active" status
*/
private void updateListFooterText(boolean networkActive) {
private void updateListFooter() {
if (mListFooterMode != LIST_FOOTER_MODE_NONE) {
int footerTextId = 0;
switch (mListFooterMode) {
case LIST_FOOTER_MODE_MORE:
footerTextId = networkActive ? R.string.status_loading_messages
boolean active = mRefreshManager.isMessageListRefreshing(mMailboxId);
footerTextId = active ? R.string.status_loading_messages
: R.string.message_list_load_more_messages_action;
mListFooterProgress.setVisibility(active ? View.VISIBLE : View.GONE);
break;
}
mListFooterText.setText(footerTextId);
@ -914,4 +915,15 @@ public class MessageListFragment extends ListFragment
mSelectionMode = null;
}
}
}
private class RefreshListener implements RefreshManager.Listener {
@Override
public void onMessagingError(long accountId, long mailboxId, String message) {
}
@Override
public void onRefreshStatusChanged(long accountId, long mailboxId) {
updateListFooter();
}
}
}

View File

@ -17,6 +17,7 @@
package com.android.email.activity;
import com.android.email.Email;
import com.android.email.RefreshManager;
import com.android.email.R;
import com.android.email.Utility;
import com.android.email.activity.setup.AccountSettings;
@ -54,6 +55,9 @@ public class MessageListXL extends Activity implements View.OnClickListener,
private static final int LOADER_ID_ACCOUNT_LIST = 0;
private Context mContext;
private RefreshManager mRefreshManager;
private final RefreshListener mMailRefreshManagerListener
= new RefreshListener();
private View mMessageViewButtonPanel;
private View mMoveToNewerButton;
@ -91,6 +95,8 @@ public class MessageListXL extends Activity implements View.OnClickListener,
final boolean isRestoring = (savedInstanceState != null);
mContext = getApplicationContext();
mRefreshManager = RefreshManager.getInstance(this);
mRefreshManager.registerListener(mMailRefreshManagerListener);
mFragmentManager.setMailboxListFragmentCallback(new MailboxListFragmentCallback());
mFragmentManager.setMessageListFragmentCallback(new MessageListFragmentCallback());
@ -171,6 +177,7 @@ public class MessageListXL extends Activity implements View.OnClickListener,
@Override
protected void onDestroy() {
if (Email.DEBUG_LIFECYCLE && Email.DEBUG) Log.d(Email.LOG_TAG, "MessageListXL onDestroy");
mRefreshManager.unregisterListener(mMailRefreshManagerListener);
super.onDestroy();
}
@ -492,6 +499,26 @@ public class MessageListXL extends Activity implements View.OnClickListener,
}
}
private class RefreshListener
implements RefreshManager.Listener {
@Override
public void onMessagingError(long accountId, long mailboxId, String message) {
Utility.showToast(MessageListXL.this, message); // STOPSHIP temporary UI
invalidateOptionsMenu();
}
@Override
public void onRefreshStatusChanged(long accountId, long mailboxId) {
invalidateOptionsMenu();
}
}
private boolean isProgressActive() {
final long mailboxId = mFragmentManager.getMailboxId();
return (mailboxId >= 0) && mRefreshManager.isMessageListRefreshing(mailboxId);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
@ -499,13 +526,26 @@ public class MessageListXL extends Activity implements View.OnClickListener,
return true;
}
// STOPSHIP - this is a placeholder if/until there's support for progress in actionbar
// Remove it, or replace with a better icon
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
MenuItem item = menu.findItem(R.id.refresh);
if (isProgressActive()) {
item.setIcon(android.R.drawable.progress_indeterminate_horizontal);
} else {
item.setIcon(R.drawable.ic_menu_refresh);
}
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.compose:
return onCompose();
case R.id.refresh:
// TODO Implement this
onRefresh();
return true;
case R.id.account_settings:
return onAccountSettings();
@ -539,6 +579,27 @@ public class MessageListXL extends Activity implements View.OnClickListener,
return true;
}
private void onRefresh() {
// Temporary implementation
if (mFragmentManager.isMailboxSelected()) {
mRefreshManager.refreshMessageList(mFragmentManager.getAccountId(),
mFragmentManager.getMailboxId());
}
// TODO implement this
// - Refresh mailbox list. But don't do that always; implement a min interval.
//
// - Refresh the selected mailbox, if it's supported.
// (regardless if the right-pane is MessageList or MessageView)
// - If not suppoted (e.g. outbox, draft, or push mailboxes), refresh the inbox of the
// current account.
// To do that, we need a way to tell the type of the currently selected mailbox.
// We can do this with MessageListFragment, but it's gone it if a message is being viewed.
// Maybe we should always have a MessageListFragment instance?
// That way it'll be easier to restore the scroll position.
}
/**
* STOPSHIP: Remove this.
* Rotate screen when the R key is pressed. Workaround for auto-orientation not working.

View File

@ -1024,14 +1024,6 @@ public abstract class MessageViewFragmentBase extends Fragment implements View.O
attachment.downloadButton.setEnabled(enable);
}
}
@Override
public void updateMailboxCallback(MessagingException result, long accountId,
long mailboxId, int progress, int numNewMessages) {
if (result != null || progress == 100) {
Email.updateMailboxRefreshTime(mailboxId);
}
}
}
public boolean isMessageLoadedForTest() {

View File

@ -16,6 +16,8 @@
package com.android.email.mail;
import com.android.email.R;
public class AuthenticationFailedException extends MessagingException {
public static final long serialVersionUID = -1;
@ -29,5 +31,10 @@ public class AuthenticationFailedException extends MessagingException {
public AuthenticationFailedException(String message, Throwable throwable) {
super(MessagingException.AUTHENTICATION_FAILED, message, throwable);
}
}
@Override
public int getUiErrorMessageResourceId() {
return R.string.account_setup_failed_dlg_auth_message;
}
}

View File

@ -16,6 +16,8 @@
package com.android.email.mail;
import com.android.email.R;
public class CertificateValidationException extends MessagingException {
public static final long serialVersionUID = -1;
@ -26,4 +28,9 @@ public class CertificateValidationException extends MessagingException {
public CertificateValidationException(String message, Throwable throwable) {
super(MessagingException.CERTIFICATE_VALIDATION_ERROR, message, throwable);
}
@Override
public int getUiErrorMessageResourceId() {
return R.string.account_setup_failed_dlg_certificate_message;
}
}

View File

@ -16,18 +16,22 @@
package com.android.email.mail;
import com.android.email.R;
import android.content.Context;
/**
* This exception is used for most types of failures that occur during server interactions.
*
*
* Data passed through this exception should be considered non-localized. Any strings should
* either be internal-only (for debugging) or server-generated.
*
*
* TO DO: Does it make sense to further collapse AuthenticationFailedException and
* CertificateValidationException and any others into this?
*/
public class MessagingException extends Exception {
public static final long serialVersionUID = -1;
public static final int NO_ERROR = -1;
/** Any exception that does not specify a specific issue */
public static final int UNSPECIFIED_EXCEPTION = 0;
@ -53,9 +57,9 @@ public class MessagingException extends Exception {
public static final int CERTIFICATE_VALIDATION_ERROR = 10;
/** Authentication failed during autodiscover */
public static final int AUTODISCOVER_AUTHENTICATION_FAILED = 11;
protected int mExceptionType;
public MessagingException(String message) {
super(message);
mExceptionType = UNSPECIFIED_EXCEPTION;
@ -70,7 +74,7 @@ public class MessagingException extends Exception {
super(message, throwable);
mExceptionType = exceptionType;
}
/**
* Constructs a MessagingException with an exceptionType and a null message.
* @param exceptionType The exception type to set for this exception.
@ -79,7 +83,7 @@ public class MessagingException extends Exception {
super();
mExceptionType = exceptionType;
}
/**
* Constructs a MessagingException with an exceptionType and a message.
* @param exceptionType The exception type to set for this exception.
@ -88,13 +92,41 @@ public class MessagingException extends Exception {
super(message);
mExceptionType = exceptionType;
}
/**
* Return the exception type. Will be OTHER_EXCEPTION if not explicitly set.
*
*
* @return Returns the exception type.
*/
public int getExceptionType() {
return mExceptionType;
}
}
/**
* @return the error message associated with this exception.
*/
public final String getUiErrorMessage(Context context) {
return context.getResources().getString(getUiErrorMessageResourceId());
}
/**
* @return the resource ID of the error message associated with this exception.
*/
public int getUiErrorMessageResourceId() {
switch (getExceptionType()) {
case MessagingException.IOERROR:
return R.string.account_setup_failed_ioerror;
case MessagingException.TLS_REQUIRED:
return R.string.account_setup_failed_tls_required;
case MessagingException.AUTH_REQUIRED:
return R.string.account_setup_failed_auth_required;
case MessagingException.GENERAL_SECURITY:
return R.string.account_setup_failed_security;
// TODO Generate a unique string for this case, which is the case
// where the security policy needs to be updated.
case MessagingException.SECURITY_POLICIES_REQUIRED:
return R.string.account_setup_failed_security;
}
return R.string.status_network_error; // default
}
}

View File

@ -655,8 +655,6 @@ public class MailService extends Service {
updateAccountReport(accountId, -1);
}
}
// Call the global refresh tracker for all mailboxes
Email.updateMailboxRefreshTime(mailboxId);
}
}

View File

@ -0,0 +1,32 @@
/*
* 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;
public class MockClock extends Clock {
public static final long DEFAULT_TIME = 10000; // Arbitrary value
public long mTime = DEFAULT_TIME;
@Override
public long getTime() {
return mTime;
}
public void advance() {
mTime++;
}
}

View File

@ -0,0 +1,532 @@
/*
* 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;
import com.android.email.mail.MessagingException;
import com.android.email.provider.EmailContent.Account;
import com.android.email.provider.EmailProvider;
import com.android.email.provider.ProviderTestUtils;
import android.content.Context;
import android.test.AndroidTestCase;
import android.test.MoreAsserts;
import android.util.Log;
import java.util.ArrayList;
import junit.framework.Assert;
public class RefreshManagerTest extends AndroidTestCase {
private MockClock mClock;
private MockController mController;
private RefreshManager mTarget;
private RefreshListener mListener;
// Isolated Context for providers.
private Context mProviderContext;
private static final MessagingException EXCEPTION = new MessagingException("test");
// Looks silly, but it'll make it more readable.
private static final long ACCOUNT_1 = 1;
private static final long ACCOUNT_2 = 2;
private static final long MAILBOX_1 = 3;
private static final long MAILBOX_2 = 4;
@Override
protected void setUp() throws Exception {
super.setUp();
mClock = new MockClock();
mController = new MockController(getContext());
mListener = new RefreshListener();
mProviderContext = DBTestHelper.ProviderContextSetupHelper.getProviderContext(
mContext, EmailProvider.class);
mTarget = new RefreshManager(mProviderContext, mController, mClock, null);
mTarget.registerListener(mListener);
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
mController.cleanupForTest();
}
public void testRegisterUnregisterListener() {
// mListener is already registered
assertEquals(1, mTarget.getListenersForTest().size());
mTarget.unregisterListener(mListener);
assertEquals(0, mTarget.getListenersForTest().size());
}
public void testRefreshStatus() {
RefreshManager.Status s = new RefreshManager.Status();
assertFalse(s.isRefreshing());
assertTrue(s.canRefresh());
assertEquals(0, s.getLastRefreshTime());
// Request refresh
s.onRefreshRequested();
assertTrue(s.isRefreshing());
assertFalse(s.canRefresh());
assertEquals(0, s.getLastRefreshTime());
// Refresh start
s.onCallback(null, 0, mClock);
assertTrue(s.isRefreshing());
assertFalse(s.canRefresh());
assertEquals(0, s.getLastRefreshTime());
// Refresh 50% done -- nothing changes
s.onCallback(null, 50, mClock);
assertTrue(s.isRefreshing());
assertFalse(s.canRefresh());
assertEquals(0, s.getLastRefreshTime());
// Refresh finish
s.onCallback(null, 100, mClock);
assertFalse(s.isRefreshing());
assertTrue(s.canRefresh());
assertEquals(mClock.mTime, s.getLastRefreshTime());
// Refresh start without request
s.onCallback(null, 0, mClock);
assertTrue(s.isRefreshing());
assertFalse(s.canRefresh());
assertEquals(mClock.mTime, s.getLastRefreshTime());
mClock.advance();
// Refresh finish with error.
s.onCallback(EXCEPTION, 0, mClock);
assertFalse(s.isRefreshing());
assertTrue(s.canRefresh());
assertEquals(mClock.mTime, s.getLastRefreshTime());
}
public void testRefreshMailboxList() {
// request refresh for account 1
assertTrue(mTarget.refreshMailboxList(ACCOUNT_1));
assertTrue(mListener.mCalledOnRefreshStatusChanged);
assertFalse(mListener.mCalledOnConnectionError);
assertEquals(ACCOUNT_1, mListener.mAccountId);
assertEquals(-1, mListener.mMailboxId);
mListener.reset();
assertTrue(mController.mCalledUpdateMailboxList);
assertEquals(ACCOUNT_1, mController.mAccountId);
assertEquals(-1, mController.mMailboxId);
mController.reset();
assertTrue(mTarget.isMailboxListRefreshing(ACCOUNT_1));
assertTrue(mTarget.isRefreshingAnyMailboxList());
// Request again -- shouldn't be accepted.
assertFalse(mTarget.refreshMailboxList(ACCOUNT_1));
assertFalse(mListener.mCalledOnRefreshStatusChanged);
assertFalse(mListener.mCalledOnConnectionError);
mListener.reset();
assertFalse(mController.mCalledUpdateMailboxList);
mController.reset();
// request refresh for account 2
assertTrue(mTarget.refreshMailboxList(ACCOUNT_2));
assertTrue(mListener.mCalledOnRefreshStatusChanged);
assertFalse(mListener.mCalledOnConnectionError);
assertEquals(ACCOUNT_2, mListener.mAccountId);
assertEquals(-1, mListener.mMailboxId);
mListener.reset();
assertTrue(mController.mCalledUpdateMailboxList);
assertEquals(ACCOUNT_2, mController.mAccountId);
assertEquals(-1, mController.mMailboxId);
mController.reset();
assertTrue(mTarget.isMailboxListRefreshing(ACCOUNT_2));
assertTrue(mTarget.isRefreshingAnyMailboxList());
// Refreshing for account 1...
mController.mListener.updateMailboxListCallback(null, ACCOUNT_1, 0);
assertTrue(mListener.mCalledOnRefreshStatusChanged);
assertFalse(mListener.mCalledOnConnectionError);
assertEquals(ACCOUNT_1, mListener.mAccountId);
assertEquals(-1, mListener.mMailboxId);
mListener.reset();
assertTrue(mTarget.isMailboxListRefreshing(ACCOUNT_1));
assertEquals(0, mTarget.getMailboxListStatusForTest(ACCOUNT_1).getLastRefreshTime());
// Done.
Log.w(Email.LOG_TAG, "" + mController.mListener.getClass());
mController.mListener.updateMailboxListCallback(null, ACCOUNT_1, 100);
assertTrue(mListener.mCalledOnRefreshStatusChanged);
assertFalse(mListener.mCalledOnConnectionError);
assertEquals(ACCOUNT_1, mListener.mAccountId);
assertEquals(-1, mListener.mMailboxId);
mListener.reset();
assertFalse(mTarget.isMailboxListRefreshing(ACCOUNT_1));
assertEquals(mClock.mTime, mTarget.getMailboxListStatusForTest(ACCOUNT_1)
.getLastRefreshTime());
// Check "any" method.
assertTrue(mTarget.isRefreshingAnyMailboxList()); // still refreshing account 2
// Refreshing for account 2...
mClock.advance();
mController.mListener.updateMailboxListCallback(null, ACCOUNT_2, 0);
assertTrue(mListener.mCalledOnRefreshStatusChanged);
assertFalse(mListener.mCalledOnConnectionError);
assertEquals(ACCOUNT_2, mListener.mAccountId);
assertEquals(-1, mListener.mMailboxId);
mListener.reset();
assertTrue(mTarget.isMailboxListRefreshing(ACCOUNT_2));
assertEquals(0, mTarget.getMailboxListStatusForTest(ACCOUNT_2).getLastRefreshTime());
// Done with exception.
mController.mListener.updateMailboxListCallback(EXCEPTION, ACCOUNT_2, 0);
assertTrue(mListener.mCalledOnRefreshStatusChanged);
assertTrue(mListener.mCalledOnConnectionError);
assertEquals(ACCOUNT_2, mListener.mAccountId);
assertEquals(-1, mListener.mMailboxId);
assertEquals(EXCEPTION.getUiErrorMessage(mContext), mListener.mMessage);
mListener.reset();
assertFalse(mTarget.isMailboxListRefreshing(ACCOUNT_2));
assertEquals(mClock.mTime, mTarget.getMailboxListStatusForTest(ACCOUNT_2)
.getLastRefreshTime());
// Check "any" method.
assertFalse(mTarget.isRefreshingAnyMailboxList());
}
public void testRefreshMessageList() {
// request refresh mailbox 1
assertTrue(mTarget.refreshMessageList(ACCOUNT_1, MAILBOX_1));
assertTrue(mListener.mCalledOnRefreshStatusChanged);
assertFalse(mListener.mCalledOnConnectionError);
assertEquals(ACCOUNT_1, mListener.mAccountId);
assertEquals(MAILBOX_1, mListener.mMailboxId);
mListener.reset();
assertTrue(mController.mCalledUpdateMailbox);
assertEquals(ACCOUNT_1, mController.mAccountId);
assertEquals(MAILBOX_1, mController.mMailboxId);
mController.reset();
assertTrue(mTarget.isMessageListRefreshing(MAILBOX_1));
assertTrue(mTarget.isRefreshingAnyMessageList());
// Request again -- shouldn't be accepted.
assertFalse(mTarget.refreshMessageList(ACCOUNT_1, MAILBOX_1));
assertFalse(mListener.mCalledOnRefreshStatusChanged);
assertFalse(mListener.mCalledOnConnectionError);
mListener.reset();
assertFalse(mController.mCalledUpdateMailbox);
mController.reset();
// request refresh mailbox 2
assertTrue(mTarget.refreshMessageList(ACCOUNT_2, MAILBOX_2));
assertTrue(mListener.mCalledOnRefreshStatusChanged);
assertFalse(mListener.mCalledOnConnectionError);
assertEquals(ACCOUNT_2, mListener.mAccountId);
assertEquals(MAILBOX_2, mListener.mMailboxId);
mListener.reset();
assertTrue(mController.mCalledUpdateMailbox);
assertEquals(ACCOUNT_2, mController.mAccountId);
assertEquals(MAILBOX_2, mController.mMailboxId);
mController.reset();
assertTrue(mTarget.isMessageListRefreshing(MAILBOX_2));
assertTrue(mTarget.isRefreshingAnyMessageList());
// Refreshing mailbox 1...
mController.mListener.updateMailboxCallback(null, ACCOUNT_1, MAILBOX_1, 0, 0);
assertTrue(mListener.mCalledOnRefreshStatusChanged);
assertFalse(mListener.mCalledOnConnectionError);
assertEquals(ACCOUNT_1, mListener.mAccountId);
assertEquals(MAILBOX_1, mListener.mMailboxId);
mListener.reset();
assertTrue(mTarget.isMessageListRefreshing(MAILBOX_1));
assertEquals(0, mTarget.getMessageListStatusForTest(MAILBOX_1).getLastRefreshTime());
// Done.
Log.w(Email.LOG_TAG, "" + mController.mListener.getClass());
mController.mListener.updateMailboxCallback(null, ACCOUNT_1, MAILBOX_1, 100, 0);
assertTrue(mListener.mCalledOnRefreshStatusChanged);
assertFalse(mListener.mCalledOnConnectionError);
assertEquals(ACCOUNT_1, mListener.mAccountId);
assertEquals(MAILBOX_1, mListener.mMailboxId);
mListener.reset();
assertFalse(mTarget.isMessageListRefreshing(MAILBOX_1));
assertEquals(mClock.mTime, mTarget.getMessageListStatusForTest(MAILBOX_1)
.getLastRefreshTime());
// Check "any" method.
assertTrue(mTarget.isRefreshingAnyMessageList()); // still refreshing mailbox 2
// Refreshing mailbox 2...
mClock.advance();
mController.mListener.updateMailboxCallback(null, ACCOUNT_2, MAILBOX_2, 0, 0);
assertTrue(mListener.mCalledOnRefreshStatusChanged);
assertFalse(mListener.mCalledOnConnectionError);
assertEquals(ACCOUNT_2, mListener.mAccountId);
assertEquals(MAILBOX_2, mListener.mMailboxId);
mListener.reset();
assertTrue(mTarget.isMessageListRefreshing(MAILBOX_2));
assertEquals(0, mTarget.getMessageListStatusForTest(MAILBOX_2).getLastRefreshTime());
// Done with exception.
mController.mListener.updateMailboxCallback(EXCEPTION, ACCOUNT_2, MAILBOX_2, 0, 0);
assertTrue(mListener.mCalledOnRefreshStatusChanged);
assertTrue(mListener.mCalledOnConnectionError);
assertEquals(ACCOUNT_2, mListener.mAccountId);
assertEquals(MAILBOX_2, mListener.mMailboxId);
assertEquals(EXCEPTION.getUiErrorMessage(mContext), mListener.mMessage);
mListener.reset();
assertFalse(mTarget.isMessageListRefreshing(MAILBOX_2));
assertEquals(mClock.mTime, mTarget.getMessageListStatusForTest(MAILBOX_2)
.getLastRefreshTime());
// Check "any" method.
assertFalse(mTarget.isRefreshingAnyMessageList());
}
public void testSendPendingMessages() {
// request sending for account 1
assertTrue(mTarget.sendPendingMessages(ACCOUNT_1));
assertTrue(mListener.mCalledOnRefreshStatusChanged);
assertFalse(mListener.mCalledOnConnectionError);
assertEquals(ACCOUNT_1, mListener.mAccountId);
assertEquals(-1, mListener.mMailboxId);
mListener.reset();
assertTrue(mController.mCalledSendPendingMessages);
assertEquals(ACCOUNT_1, mController.mAccountId);
assertEquals(-1, mController.mMailboxId);
mController.reset();
assertTrue(mTarget.isSendingMessage(ACCOUNT_1));
assertTrue(mTarget.isSendingAnyMessage());
// Request again -- shouldn't be accepted.
assertFalse(mTarget.sendPendingMessages(ACCOUNT_1));
assertFalse(mListener.mCalledOnRefreshStatusChanged);
assertFalse(mListener.mCalledOnConnectionError);
mListener.reset();
assertFalse(mController.mCalledSendPendingMessages);
mController.reset();
// request sending for account 2
assertTrue(mTarget.sendPendingMessages(ACCOUNT_2));
assertTrue(mListener.mCalledOnRefreshStatusChanged);
assertFalse(mListener.mCalledOnConnectionError);
assertEquals(ACCOUNT_2, mListener.mAccountId);
assertEquals(-1, mListener.mMailboxId);
mListener.reset();
assertTrue(mController.mCalledSendPendingMessages);
assertEquals(ACCOUNT_2, mController.mAccountId);
assertEquals(-1, mController.mMailboxId);
mController.reset();
assertTrue(mTarget.isSendingMessage(ACCOUNT_2));
assertTrue(mTarget.isSendingAnyMessage());
// sending for account 1...
mController.mListener.sendMailCallback(null, ACCOUNT_1, -1, 0);
assertTrue(mListener.mCalledOnRefreshStatusChanged);
assertFalse(mListener.mCalledOnConnectionError);
assertEquals(ACCOUNT_1, mListener.mAccountId);
assertEquals(-1, mListener.mMailboxId);
mListener.reset();
assertTrue(mTarget.isSendingMessage(ACCOUNT_1));
assertEquals(0, mTarget.getOutboxStatusForTest(ACCOUNT_1).getLastRefreshTime());
// Per message callback (1)
mController.mListener.sendMailCallback(null, ACCOUNT_1, 100, 0);
mController.mListener.sendMailCallback(null, ACCOUNT_1, 101, 0);
// No callback per message
assertFalse(mListener.mCalledOnRefreshStatusChanged);
assertFalse(mListener.mCalledOnConnectionError);
mListener.reset();
// Exception -- first error will be reported.
mController.mListener.sendMailCallback(EXCEPTION, ACCOUNT_1, 102, 0);
assertFalse(mListener.mCalledOnRefreshStatusChanged);
assertTrue(mListener.mCalledOnConnectionError);
assertEquals(EXCEPTION.getUiErrorMessage(mContext), mListener.mMessage);
mListener.reset();
// Exception again -- no more error callbacks
mController.mListener.sendMailCallback(null, ACCOUNT_1, 103, 0);
mController.mListener.sendMailCallback(EXCEPTION, ACCOUNT_1, 104, 0);
assertFalse(mListener.mCalledOnRefreshStatusChanged);
assertFalse(mListener.mCalledOnConnectionError);
mListener.reset();
// Done.
Log.w(Email.LOG_TAG, "" + mController.mListener.getClass());
mController.mListener.sendMailCallback(null, ACCOUNT_1, -1, 100);
assertTrue(mListener.mCalledOnRefreshStatusChanged);
assertFalse(mListener.mCalledOnConnectionError);
assertEquals(ACCOUNT_1, mListener.mAccountId);
assertEquals(-1, mListener.mMailboxId);
mListener.reset();
assertFalse(mTarget.isSendingMessage(ACCOUNT_1));
assertEquals(mClock.mTime, mTarget.getOutboxStatusForTest(ACCOUNT_1)
.getLastRefreshTime());
// Check "any" method.
assertTrue(mTarget.isSendingAnyMessage()); // still sending for account 2
// sending for account 2...
mClock.advance();
mController.mListener.sendMailCallback(null, ACCOUNT_2, -1, 0);
assertTrue(mListener.mCalledOnRefreshStatusChanged);
assertFalse(mListener.mCalledOnConnectionError);
assertEquals(ACCOUNT_2, mListener.mAccountId);
assertEquals(-1, mListener.mMailboxId);
mListener.reset();
assertTrue(mTarget.isSendingMessage(ACCOUNT_2));
assertEquals(0, mTarget.getOutboxStatusForTest(ACCOUNT_2).getLastRefreshTime());
// Done with exception.
mController.mListener.sendMailCallback(EXCEPTION, ACCOUNT_2, -1, 0);
assertTrue(mListener.mCalledOnRefreshStatusChanged);
assertTrue(mListener.mCalledOnConnectionError);
assertEquals(ACCOUNT_2, mListener.mAccountId);
assertEquals(-1, mListener.mMailboxId);
assertEquals(EXCEPTION.getUiErrorMessage(mContext), mListener.mMessage);
mListener.reset();
assertFalse(mTarget.isSendingMessage(ACCOUNT_2));
assertEquals(mClock.mTime, mTarget.getOutboxStatusForTest(ACCOUNT_2)
.getLastRefreshTime());
// Check "any" method.
assertFalse(mTarget.isSendingAnyMessage());
}
public void testSendPendingMessagesForAllAccounts() {
Account acct1 = ProviderTestUtils.setupAccount("acct1", true, mProviderContext);
Account acct2 = ProviderTestUtils.setupAccount("acct2", true, mProviderContext);
mTarget.sendPendingMessagesForAllAccountsSync();
assertTrue(mController.mCalledSendPendingMessages);
MoreAsserts.assertEquals(new Long[] {acct1.mId, acct2.mId}, mListener.getAccountIds());
}
private static class MockController extends Controller {
public long mAccountId = -1;
public long mMailboxId = -1;
public boolean mCalledSendPendingMessages;
public boolean mCalledUpdateMailbox;
public boolean mCalledUpdateMailboxList;
public Result mListener;
protected MockController(Context context) {
super(context);
}
public void reset() {
mAccountId = -1;
mMailboxId = -1;
mCalledSendPendingMessages = false;
mCalledUpdateMailbox = false;
mCalledUpdateMailboxList = false;
}
@Override
public void sendPendingMessages(long accountId) {
mCalledSendPendingMessages = true;
mAccountId = accountId;
}
@Override
public void updateMailbox(long accountId, long mailboxId) {
mCalledUpdateMailbox = true;
mAccountId = accountId;
mMailboxId = mailboxId;
}
@Override
public void updateMailboxList(long accountId) {
mCalledUpdateMailboxList = true;
mAccountId = accountId;
}
@Override
public void addResultCallback(Result listener) {
Assert.assertTrue(mListener == null);
mListener = listener;
}
}
private static class RefreshListener implements RefreshManager.Listener {
public long mAccountId = -1;
public long mMailboxId = -1;
public String mMessage;
public boolean mCalledOnConnectionError;
public boolean mCalledOnRefreshStatusChanged;
private final ArrayList<Long> mAccountIds = new ArrayList<Long>();
public void reset() {
mAccountId = -1;
mMailboxId = -1;
mMessage = null;
mAccountIds.clear();
mCalledOnConnectionError = false;
mCalledOnRefreshStatusChanged = false;
}
@Override
public void onRefreshStatusChanged(long accountId, long mailboxId) {
mAccountId = accountId;
mMailboxId = mailboxId;
mAccountIds.add(mAccountId);
mCalledOnRefreshStatusChanged = true;
}
@Override
public void onMessagingError(long accountId, long mailboxId, String message) {
mAccountId = accountId;
mMailboxId = mailboxId;
mMessage = message;
mAccountIds.add(mAccountId);
mCalledOnConnectionError = true;
}
public Long[] getAccountIds() {
return mAccountIds.toArray(new Long[0]);
}
}
}