c4cdb11d24
If we receive new messages, we may display a notification to the user. If those same messages are read elsewhere (i.e. via a web client), we will remove the notification. Change-Id: Iba09afe01942e0deaac8210fd6f9b315b1c8c93f
456 lines
16 KiB
Java
456 lines
16 KiB
Java
/*
|
|
* 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.emailcommon.Logging;
|
|
import com.android.emailcommon.mail.MessagingException;
|
|
import com.android.emailcommon.utility.Utility;
|
|
|
|
import android.content.Context;
|
|
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.
|
|
*
|
|
* (All public methods must be called on the UI thread. All callbacks will be called on the UI
|
|
* thread.)
|
|
*/
|
|
public class RefreshManager {
|
|
private static final boolean LOG_ENABLED = false; // DONT SUBMIT WITH TRUE
|
|
private static final long MAILBOX_AUTO_REFRESH_INTERVAL = 5 * 60 * 1000; // in milliseconds
|
|
private static final long MAILBOX_LIST_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 {
|
|
/**
|
|
* Refresh status of a mailbox list or a message list has changed.
|
|
*
|
|
* @param accountId ID of the account.
|
|
* @param mailboxId -1 if it's about the mailbox list, or the ID of the mailbox list in
|
|
* question.
|
|
*/
|
|
public void onRefreshStatusChanged(long accountId, long mailboxId);
|
|
|
|
/**
|
|
* Error callback.
|
|
*
|
|
* @param accountId ID of the account, or -1 if unknown.
|
|
* @param mailboxId ID of the mailbox, or -1 if unknown.
|
|
* @param message error message which can be shown to the user.
|
|
*/
|
|
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();
|
|
|
|
/**
|
|
* @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;
|
|
}
|
|
|
|
protected 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);
|
|
}
|
|
|
|
/**
|
|
* MUST be called for mock instances. (The actual instance is a singleton, so no cleanup
|
|
* is necessary.)
|
|
*/
|
|
public void cleanUpForTest() {
|
|
mController.removeResultCallback(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;
|
|
|
|
if (LOG_ENABLED) {
|
|
Log.d(Logging.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);
|
|
}
|
|
|
|
public boolean isMailboxListStale(long accountId) {
|
|
return mClock.getTime() >= (mMailboxListStatus.get(accountId).getLastRefreshTime()
|
|
+ MAILBOX_LIST_AUTO_REFRESH_INTERVAL);
|
|
}
|
|
|
|
/**
|
|
* Refresh messages in a mailbox.
|
|
*/
|
|
public boolean refreshMessageList(long accountId, long mailboxId, boolean userRequest) {
|
|
return refreshMessageList(accountId, mailboxId, false, userRequest);
|
|
}
|
|
|
|
/**
|
|
* "load more messages" in a mailbox.
|
|
*/
|
|
public boolean loadMoreMessages(long accountId, long mailboxId) {
|
|
return refreshMessageList(accountId, mailboxId, true, true);
|
|
}
|
|
|
|
private boolean refreshMessageList(long accountId, long mailboxId, boolean loadMoreMessages,
|
|
boolean userRequest) {
|
|
final Status status = mMessageListStatus.get(mailboxId);
|
|
if (!status.canRefresh()) return false;
|
|
|
|
if (LOG_ENABLED) {
|
|
Log.d(Logging.LOG_TAG, "refreshMessageList " + accountId + ", " + mailboxId + ", "
|
|
+ loadMoreMessages);
|
|
}
|
|
status.onRefreshRequested();
|
|
notifyRefreshStatusChanged(accountId, mailboxId);
|
|
if (loadMoreMessages) {
|
|
mController.loadMoreMessages(mailboxId);
|
|
} else {
|
|
mController.updateMailbox(accountId, mailboxId, userRequest);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Send pending messages.
|
|
*/
|
|
public boolean sendPendingMessages(long accountId) {
|
|
if (LOG_ENABLED) {
|
|
Log.d(Logging.LOG_TAG, "sendPendingMessages " + accountId);
|
|
}
|
|
notifyRefreshStatusChanged(accountId, -1);
|
|
mController.sendPendingMessages(accountId);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Call {@link #sendPendingMessages} for all accounts.
|
|
*/
|
|
public void sendPendingMessagesForAllAccounts() {
|
|
if (LOG_ENABLED) {
|
|
Log.d(Logging.LOG_TAG, "sendPendingMessagesForAllAccounts");
|
|
}
|
|
new SendPendingMessagesForAllAccountsImpl().execute();
|
|
}
|
|
|
|
private class SendPendingMessagesForAllAccountsImpl extends Utility.ForEachAccount {
|
|
public SendPendingMessagesForAllAccountsImpl() {
|
|
super(mContext);
|
|
}
|
|
|
|
@Override
|
|
protected void performAction(long accountId) {
|
|
sendPendingMessages(accountId);
|
|
}
|
|
}
|
|
|
|
public long getLastMailboxListRefreshTime(long accountId) {
|
|
return mMailboxListStatus.get(accountId).getLastRefreshTime();
|
|
}
|
|
|
|
public long getLastMessageListRefreshTime(long mailboxId) {
|
|
return mMessageListStatus.get(mailboxId).getLastRefreshTime();
|
|
}
|
|
|
|
public boolean isMailboxListRefreshing(long accountId) {
|
|
return mMailboxListStatus.get(accountId).isRefreshing();
|
|
}
|
|
|
|
public boolean isMessageListRefreshing(long mailboxId) {
|
|
return mMessageListStatus.get(mailboxId).isRefreshing();
|
|
}
|
|
|
|
public boolean isRefreshingAnyMailboxListForTest() {
|
|
return mMailboxListStatus.isRefreshingAny();
|
|
}
|
|
|
|
public boolean isRefreshingAnyMessageListForTest() {
|
|
return mMessageListStatus.isRefreshingAny();
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
private class ControllerResult extends Controller.Result {
|
|
private boolean mSendMailExceptionReported = false;
|
|
|
|
private String exceptionToString(MessagingException exception) {
|
|
if (exception == null) {
|
|
return "(no exception)";
|
|
} else {
|
|
return MessagingExceptionStrings.getErrorString(mContext, exception);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Callback for mailbox list refresh.
|
|
*/
|
|
@Override
|
|
public void updateMailboxListCallback(MessagingException exception, long accountId,
|
|
int progress) {
|
|
if (LOG_ENABLED) {
|
|
Log.d(Logging.LOG_TAG, "updateMailboxListCallback " + accountId + ", " + progress
|
|
+ ", " + exceptionToString(exception));
|
|
}
|
|
mMailboxListStatus.get(accountId).onCallback(exception, progress, mClock);
|
|
if (exception != null) {
|
|
reportError(accountId, -1,
|
|
MessagingExceptionStrings.getErrorString(mContext, exception));
|
|
}
|
|
notifyRefreshStatusChanged(accountId, -1);
|
|
}
|
|
|
|
/**
|
|
* Callback for explicit (user-driven) mailbox refresh.
|
|
*/
|
|
@Override
|
|
public void updateMailboxCallback(MessagingException exception, long accountId,
|
|
long mailboxId, int progress, int dontUseNumNewMessages,
|
|
ArrayList<Long> addedMessages) {
|
|
if (LOG_ENABLED) {
|
|
Log.d(Logging.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 (LOG_ENABLED) {
|
|
Log.d(Logging.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,
|
|
MessagingExceptionStrings.getErrorString(mContext, exception));
|
|
}
|
|
notifyRefreshStatusChanged(accountId, mailboxId);
|
|
}
|
|
|
|
|
|
/**
|
|
* Send message progress callback.
|
|
*
|
|
* We don't keep track of the status of outboxes, but we monitor this to catch
|
|
* errors.
|
|
*/
|
|
@Override
|
|
public void sendMailCallback(MessagingException exception, long accountId, long messageId,
|
|
int progress) {
|
|
if (LOG_ENABLED) {
|
|
Log.d(Logging.LOG_TAG, "sendMailCallback " + accountId + ", "
|
|
+ messageId + ", " + progress + ", " + exceptionToString(exception));
|
|
}
|
|
if (progress == 0 && messageId == -1) {
|
|
mSendMailExceptionReported = false;
|
|
}
|
|
if (exception != null && !mSendMailExceptionReported) {
|
|
// Only the first error in a batch will be reported.
|
|
mSendMailExceptionReported = true;
|
|
reportError(accountId, messageId,
|
|
MessagingExceptionStrings.getErrorString(mContext, exception));
|
|
}
|
|
if (progress == 100) {
|
|
mSendMailExceptionReported = false;
|
|
}
|
|
}
|
|
}
|
|
}
|