Rework service to use provider accounts and controller.

* Rewrite service logic to select and update one account at a time
* Add checkmail API to Controller, and much rework/cleanup of existing
   callback API's
* Rewrite notification posting code
* Rewire connection to MessageList to be opened by notifications, to
   cancel notifications, and to reset the "new message" count whenever
   an account is viewed.
* Boilerplate cleanup to a lot of activities because they share the
   callbacks that have had minor changes.
* Remove old push controls from Store API

In progress:
* To provide notification mechanism for EAS pushed mail
This commit is contained in:
Andrew Stadler 2009-08-18 00:54:34 -07:00
parent d1fd72a44e
commit 46d7d7f1b6
15 changed files with 623 additions and 479 deletions

View File

@ -53,6 +53,7 @@ public class Controller {
private Context mContext; private Context mContext;
private Context mProviderContext; private Context mProviderContext;
private MessagingController mLegacyController; private MessagingController mLegacyController;
private LegacyListener mLegacyListener = new LegacyListener();
private ServiceCallback mServiceCallback = new ServiceCallback(); private ServiceCallback mServiceCallback = new ServiceCallback();
private HashSet<Result> mListeners = new HashSet<Result>(); private HashSet<Result> mListeners = new HashSet<Result>();
@ -66,6 +67,7 @@ public class Controller {
mContext = _context; mContext = _context;
mProviderContext = _context; mProviderContext = _context;
mLegacyController = MessagingController.getInstance(mContext); mLegacyController = MessagingController.getInstance(mContext);
mLegacyController.addListener(mLegacyListener);
} }
/** /**
@ -158,11 +160,39 @@ public class Controller {
new Thread() { new Thread() {
@Override @Override
public void run() { public void run() {
Account account = mLegacyController.listFolders(accountId, mLegacyListener);
EmailContent.Account.restoreAccountWithId(mProviderContext, accountId); }
MessagingListener listener = new LegacyListener(callback); }.start();
mLegacyController.addListener(listener); }
mLegacyController.listFolders(account, listener); }
/**
* Request a remote update of a mailbox. For use by the timed service.
*
* Functionally this is quite similar to updateMailbox(), but it's a separate API and
* separate callback in order to keep UI callbacks from affecting the service loop.
*/
public void serviceCheckMail(final long accountId, final long mailboxId, final long tag,
final Result callback) {
IEmailService service = getServiceForAccount(accountId);
if (service != null) {
// Service implementation
// try {
// TODO this isn't quite going to work, because we're going to get the
// generic (UI) callbacks and not the ones we need to restart the ol' service.
// service.startSync(mailboxId, tag);
callback.serviceCheckMailCallback(null, accountId, mailboxId, 100, tag);
// } catch (RemoteException e) {
// TODO Change exception handling to be consistent with however this method
// is implemented for other protocols
// Log.d("updateMailbox", "RemoteException" + e);
// }
} else {
// MessagingController implementation
new Thread() {
@Override
public void run() {
mLegacyController.checkMail(accountId, tag, mLegacyListener);
} }
}.start(); }.start();
} }
@ -175,14 +205,13 @@ public class Controller {
* a simple message list. We should also at this point queue up a background task of * a simple message list. We should also at this point queue up a background task of
* downloading some/all of the messages in this mailbox, but that should be interruptable. * downloading some/all of the messages in this mailbox, but that should be interruptable.
*/ */
public void updateMailbox(final long accountId, public void updateMailbox(final long accountId, final long mailboxId, final Result callback) {
final EmailContent.Mailbox mailbox, final Result callback) {
IEmailService service = getServiceForAccount(accountId); IEmailService service = getServiceForAccount(accountId);
if (service != null) { if (service != null) {
// Service implementation // Service implementation
try { try {
service.startSync(mailbox.mId); service.startSync(mailboxId);
} catch (RemoteException e) { } catch (RemoteException e) {
// TODO Change exception handling to be consistent with however this method // TODO Change exception handling to be consistent with however this method
// is implemented for other protocols // is implemented for other protocols
@ -193,11 +222,12 @@ public class Controller {
new Thread() { new Thread() {
@Override @Override
public void run() { public void run() {
// TODO shouldn't be passing fully-build accounts & mailboxes into APIs
Account account = Account account =
EmailContent.Account.restoreAccountWithId(mProviderContext, accountId); EmailContent.Account.restoreAccountWithId(mProviderContext, accountId);
MessagingListener listener = new LegacyListener(callback); Mailbox mailbox =
mLegacyController.addListener(listener); EmailContent.Mailbox.restoreMailboxWithId(mProviderContext, mailboxId);
mLegacyController.synchronizeMailbox(account, mailbox, listener); mLegacyController.synchronizeMailbox(account, mailbox, mLegacyListener);
} }
}.start(); }.start();
} }
@ -516,7 +546,6 @@ public class Controller {
* made from the UI thread, so you may need further handlers to safely make UI updates. * made from the UI thread, so you may need further handlers to safely make UI updates.
*/ */
public interface Result { public interface Result {
/** /**
* Callback for updateMailboxList * Callback for updateMailboxList
* *
@ -528,15 +557,17 @@ public class Controller {
int progress); int progress);
/** /**
* Callback for updateMailbox * Callback for updateMailbox. Note: This looks a lot like checkMailCallback, but
* it's a separate call used only by UI's, so we can keep things separate.
* *
* @param result If null, the operation completed without error * @param result If null, the operation completed without error
* @param accountId The account being operated on * @param accountId The account being operated on
* @param mailboxId The mailbox being operated on * @param mailboxId The mailbox being operated on
* @param progress 0 for "starting", 1..99 for updates (if needed in UI), 100 for complete * @param progress 0 for "starting", 1..99 for updates (if needed in UI), 100 for complete
* @param numNewMessages the number of new messages delivered
*/ */
public void updateMailboxCallback(MessagingException result, long accountId, public void updateMailboxCallback(MessagingException result, long accountId,
long mailboxId, int progress, int totalMessagesInMailbox, int numNewMessages); long mailboxId, int progress, int numNewMessages);
/** /**
* Callback for loadAttachment * Callback for loadAttachment
@ -548,6 +579,20 @@ public class Controller {
*/ */
public void loadAttachmentCallback(MessagingException result, long messageId, public void loadAttachmentCallback(MessagingException result, long messageId,
long attachmentId, int progress); long attachmentId, int progress);
/**
* Callback for checkmail. Note: This looks a lot like updateMailboxCallback, but
* it's a separate call used only by the automatic checker service, so we can keep
* things separate.
*
* @param result If null, the operation completed without error
* @param accountId The account being operated on
* @param mailboxId The mailbox being operated on (may be unknown at start)
* @param progress 0 for "starting", no updates, 100 for complete
* @param tag the same tag that was passed to serviceCheckMail()
*/
public void serviceCheckMailCallback(MessagingException result, long accountId,
long mailboxId, int progress, long tag);
} }
/** /**
@ -555,70 +600,87 @@ public class Controller {
* out of scope. * out of scope.
*/ */
private class LegacyListener extends MessagingListener { private class LegacyListener extends MessagingListener {
Result mResultCallback;
public LegacyListener(Result callback) {
mResultCallback = callback;
}
@Override @Override
public void listFoldersStarted(EmailContent.Account account) { public void listFoldersStarted(EmailContent.Account account) {
if (mResultCallback != null && isActiveResultCallback(mResultCallback)) { synchronized (mListeners) {
mResultCallback.updateMailboxListCallback(null, account.mId, 0); for (Result l : mListeners) {
l.updateMailboxListCallback(null, account.mId, 0);
}
} }
} }
@Override @Override
public void listFoldersFailed(EmailContent.Account account, String message) { public void listFoldersFailed(EmailContent.Account account, String message) {
if (mResultCallback != null && isActiveResultCallback(mResultCallback)) { synchronized (mListeners) {
mResultCallback.updateMailboxListCallback(new MessagingException(message), for (Result l : mListeners) {
account.mId, 0); l.updateMailboxListCallback(new MessagingException(message), account.mId, 0);
}
} }
mLegacyController.removeListener(this);
} }
@Override @Override
public void listFoldersFinished(EmailContent.Account account) { public void listFoldersFinished(EmailContent.Account account) {
if (mResultCallback != null && isActiveResultCallback(mResultCallback)) { synchronized (mListeners) {
mResultCallback.updateMailboxListCallback(null, account.mId, 100); for (Result l : mListeners) {
l.updateMailboxListCallback(null, account.mId, 100);
}
} }
mLegacyController.removeListener(this);
} }
@Override @Override
public void synchronizeMailboxStarted(EmailContent.Account account, public void synchronizeMailboxStarted(EmailContent.Account account,
EmailContent.Mailbox folder) { EmailContent.Mailbox folder) {
if (mResultCallback != null && isActiveResultCallback(mResultCallback)) { synchronized (mListeners) {
mResultCallback.updateMailboxCallback(null, account.mId, folder.mId, 0, -1, -1); for (Result l : mListeners) {
l.updateMailboxCallback(null, account.mId, folder.mId, 0, 0);
}
} }
} }
@Override @Override
public void synchronizeMailboxFinished(EmailContent.Account account, public void synchronizeMailboxFinished(EmailContent.Account account,
EmailContent.Mailbox folder, int totalMessagesInMailbox, int numNewMessages) { EmailContent.Mailbox folder, int totalMessagesInMailbox, int numNewMessages) {
if (mResultCallback != null && isActiveResultCallback(mResultCallback)) { synchronized (mListeners) {
mResultCallback.updateMailboxCallback(null, account.mId, folder.mId, 100, for (Result l : mListeners) {
totalMessagesInMailbox, numNewMessages); l.updateMailboxCallback(null, account.mId, folder.mId, 100, numNewMessages);
}
} }
mLegacyController.removeListener(this);
} }
@Override @Override
public void synchronizeMailboxFailed(EmailContent.Account account, public void synchronizeMailboxFailed(EmailContent.Account account,
EmailContent.Mailbox folder, Exception e) { EmailContent.Mailbox folder, Exception e) {
if (mResultCallback != null && isActiveResultCallback(mResultCallback)) { synchronized (mListeners) {
MessagingException me; for (Result l : mListeners) {
if (e instanceof MessagingException) { MessagingException me;
me = (MessagingException) e; if (e instanceof MessagingException) {
} else { me = (MessagingException) e;
me = new MessagingException(e.toString()); } else {
me = new MessagingException(e.toString());
}
l.updateMailboxCallback(me, account.mId, folder.mId, 0, 0);
} }
mResultCallback.updateMailboxCallback(me, account.mId, folder.mId, 0, -1, -1);
} }
mLegacyController.removeListener(this);
} }
@Override
public void checkMailStarted(Context context, long accountId, long tag) {
synchronized (mListeners) {
for (Result l : mListeners) {
l.serviceCheckMailCallback(null, accountId, -1, 0, tag);
}
}
}
@Override
public void checkMailFinished(Context context, long accountId, long folderId, long tag) {
synchronized (mListeners) {
for (Result l : mListeners) {
l.serviceCheckMailCallback(null, accountId, folderId, 100, tag);
}
}
}
} }
/** /**
@ -700,8 +762,7 @@ public class Controller {
result = new MessagingException(String.valueOf(statusCode)); result = new MessagingException(String.valueOf(statusCode));
break; break;
} }
// TODO can we get "number of new messages" back as well? // TODO where do we get "number of new messages" as well?
// TODO remove "total num messages" which can be looked up if needed
// TODO should pass this back instead of looking it up here // TODO should pass this back instead of looking it up here
// TODO smaller projection // TODO smaller projection
Mailbox mbx = Mailbox.restoreMailboxWithId(mContext, mailboxId); Mailbox mbx = Mailbox.restoreMailboxWithId(mContext, mailboxId);
@ -710,7 +771,7 @@ public class Controller {
long accountId = mbx.mAccountKey; long accountId = mbx.mAccountKey;
synchronized(mListeners) { synchronized(mListeners) {
for (Result listener : mListeners) { for (Result listener : mListeners) {
listener.updateMailboxCallback(result, accountId, mailboxId, progress, 0, 0); listener.updateMailboxCallback(result, accountId, mailboxId, progress, 0);
} }
} }
} }

View File

@ -138,25 +138,20 @@ public class GroupMessagingListener extends MessagingListener {
} }
@Override @Override
synchronized public void checkMailStarted(Context context, EmailContent.Account account) { synchronized public void checkMailStarted(Context context, long accountId, long tag) {
for (MessagingListener l : mListeners) { for (MessagingListener l : mListeners) {
l.checkMailStarted(context, account); l.checkMailStarted(context, accountId, tag);
} }
} }
@Override @Override
synchronized public void checkMailFinished(Context context, EmailContent.Account account) { synchronized public void checkMailFinished(Context context, long accountId, long folderId,
long tag) {
for (MessagingListener l : mListeners) { for (MessagingListener l : mListeners) {
l.checkMailFinished(context, account); l.checkMailFinished(context, accountId, folderId, tag);
} }
} }
@Override
synchronized public void checkMailFailed(Context context, EmailContent.Account account,
String reason) {
// TODO
}
@Override @Override
synchronized public void sendPendingMessagesCompleted(EmailContent.Account account) { synchronized public void sendPendingMessagesCompleted(EmailContent.Account account) {
for (MessagingListener l : mListeners) { for (MessagingListener l : mListeners) {

View File

@ -34,6 +34,7 @@ import com.android.email.mail.store.LocalStore.LocalFolder;
import com.android.email.mail.store.LocalStore.LocalMessage; import com.android.email.mail.store.LocalStore.LocalMessage;
import com.android.email.mail.store.LocalStore.PendingCommand; import com.android.email.mail.store.LocalStore.PendingCommand;
import com.android.email.provider.EmailContent; import com.android.email.provider.EmailContent;
import com.android.email.provider.EmailContent.Mailbox;
import com.android.email.provider.EmailContent.MailboxColumns; import com.android.email.provider.EmailContent.MailboxColumns;
import com.android.email.provider.EmailContent.MessageColumns; import com.android.email.provider.EmailContent.MessageColumns;
import com.android.email.provider.EmailContent.SyncColumns; import com.android.email.provider.EmailContent.SyncColumns;
@ -219,7 +220,9 @@ public class MessagingController implements Runnable {
* @param listener * @param listener
* @throws MessagingException * @throws MessagingException
*/ */
public void listFolders(final EmailContent.Account account, MessagingListener listener) { public void listFolders(long accountId, MessagingListener listener) {
final EmailContent.Account account =
EmailContent.Account.restoreAccountWithId(mContext, accountId);
mListeners.listFoldersStarted(account); mListeners.listFoldersStarted(account);
put("listFolders", listener, new Runnable() { put("listFolders", listener, new Runnable() {
public void run() { public void run() {
@ -493,6 +496,7 @@ public class MessagingController implements Runnable {
/** /**
* Start foreground synchronization of the specified folder. This is called by * Start foreground synchronization of the specified folder. This is called by
* synchronizeMailbox or checkMail. * synchronizeMailbox or checkMail.
* TODO this should use ID's instead of fully-restored objects
* @param account * @param account
* @param folder * @param folder
* @param listener * @param listener
@ -528,49 +532,6 @@ public class MessagingController implements Runnable {
mListeners.synchronizeMailboxFailed(account, folder, e); mListeners.synchronizeMailboxFailed(account, folder, e);
} }
} }
// TODO move all this to top
/*
public static final int CONTENT_ID_COLUMN = 0;
public static final int CONTENT_DISPLAY_NAME_COLUMN = 1;
public static final int CONTENT_TIMESTAMP_COLUMN = 2;
public static final int CONTENT_SUBJECT_COLUMN = 3;
public static final int CONTENT_PREVIEW_COLUMN = 4;
public static final int CONTENT_FLAG_READ_COLUMN = 5;
public static final int CONTENT_FLAG_LOADED_COLUMN = 6;
public static final int CONTENT_FLAG_FAVORITE_COLUMN = 7;
public static final int CONTENT_FLAG_ATTACHMENT_COLUMN = 8;
public static final int CONTENT_FLAGS_COLUMN = 9;
public static final int CONTENT_TEXT_INFO_COLUMN = 10;
public static final int CONTENT_HTML_INFO_COLUMN = 11;
public static final int CONTENT_BODY_ID_COLUMN = 12;
public static final int CONTENT_SERVER_ID_COLUMN = 13;
public static final int CONTENT_CLIENT_ID_COLUMN = 14;
public static final int CONTENT_MESSAGE_ID_COLUMN = 15;
public static final int CONTENT_THREAD_ID_COLUMN = 16;
public static final int CONTENT_MAILBOX_KEY_COLUMN = 17;
public static final int CONTENT_ACCOUNT_KEY_COLUMN = 18;
public static final int CONTENT_REFERENCE_KEY_COLUMN = 19;
public static final int CONTENT_SENDER_LIST_COLUMN = 20;
public static final int CONTENT_FROM_LIST_COLUMN = 21;
public static final int CONTENT_TO_LIST_COLUMN = 22;
public static final int CONTENT_CC_LIST_COLUMN = 23;
public static final int CONTENT_BCC_LIST_COLUMN = 24;
public static final int CONTENT_REPLY_TO_COLUMN = 25;
public static final int CONTENT_SERVER_VERSION_COLUMN = 26;
public static final String[] CONTENT_PROJECTION = new String[] {
RECORD_ID, MessageColumns.DISPLAY_NAME, MessageColumns.TIMESTAMP,
MessageColumns.SUBJECT, MessageColumns.PREVIEW, MessageColumns.FLAG_READ,
MessageColumns.FLAG_LOADED, MessageColumns.FLAG_FAVORITE,
MessageColumns.FLAG_ATTACHMENT, MessageColumns.FLAGS, MessageColumns.TEXT_INFO,
MessageColumns.HTML_INFO, MessageColumns.BODY_ID, SyncColumns.SERVER_ID,
MessageColumns.CLIENT_ID, MessageColumns.MESSAGE_ID, MessageColumns.THREAD_ID,
MessageColumns.MAILBOX_KEY, MessageColumns.ACCOUNT_KEY, MessageColumns.REFERENCE_KEY,
MessageColumns.SENDER_LIST, MessageColumns.FROM_LIST, MessageColumns.TO_LIST,
MessageColumns.CC_LIST, MessageColumns.BCC_LIST, MessageColumns.REPLY_TO_LIST,
SyncColumns.SERVER_VERSION
};
*/
/** /**
* Lightweight record for the first pass of message sync, where I'm just seeing if * Lightweight record for the first pass of message sync, where I'm just seeing if
@ -1135,8 +1096,7 @@ public class MessagingController implements Runnable {
return results; return results;
} }
return new StoreSynchronizer.SyncResults(0, 0); return new StoreSynchronizer.SyncResults(remoteMessageCount, newMessages.size());
} }
private void queuePendingCommand(EmailContent.Account account, PendingCommand command) { private void queuePendingCommand(EmailContent.Account account, PendingCommand command) {
@ -1710,6 +1670,9 @@ public class MessagingController implements Runnable {
/** /**
* Attempt to send any messages that are sitting in the Outbox. * Attempt to send any messages that are sitting in the Outbox.
* TODO rewrite for database not LocalStore
* TODO this should accept accountId, and probably be reworked in other ways
*
* @param account * @param account
* @param listener * @param listener
*/ */
@ -1846,51 +1809,36 @@ public class MessagingController implements Runnable {
/** /**
* Checks mail for one or multiple accounts. If account is null all accounts * Checks mail for one or multiple accounts. If account is null all accounts
* are checked. * are checked. 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: There is no use case for "check all accounts". Clean up this API to remove * TODO clean up the execution model which is unnecessarily threaded due to legacy code
* that case. Callers can supply the appropriate list.
*
* TODO: Better protection against a failure in account n, which should not prevent
* syncing account in accounts n+1 and beyond.
* *
* @param context * @param context
* @param accounts List of accounts to check, or null to check all accounts * @param accountId the account to check
* @param listener * @param listener
*/ */
public void checkMail(final Context context, EmailContent.Account[] accounts, public void checkMail(final long accountId, final long tag, final MessagingListener listener) {
final MessagingListener listener) { mListeners.checkMailStarted(mContext, accountId, tag);
/**
* Note: The somewhat tortured logic here is to guarantee proper ordering of events:
* listeners: checkMailStarted
* account 1: list folders
* account 1: sync messages
* account 2: list folders
* account 2: sync messages
* ...
* account n: list folders
* account n: sync messages
* listeners: checkMailFinished
*/
mListeners.checkMailStarted(context, null); // TODO this needs to pass the actual array
if (accounts == null) {
// TODO eliminate this use case, implement, or ...?
// accounts = Preferences.getPreferences(context).getAccounts();
}
for (final EmailContent.Account account : accounts) {
listFolders(account, null);
put("checkMail", listener, new Runnable() { // This puts the command on the queue (not synchronous)
public void run() { listFolders(accountId, null);
sendPendingMessagesSynchronous(account);
// TODO find mailbox # for inbox and sync it. // Put this on the queue as well so it follows listFolders
// synchronizeMailboxSynchronous(account, Email.INBOX); put("emptyTrash", listener, new Runnable() {
}
});
}
put("checkMailFinished", listener, new Runnable() {
public void run() { public void run() {
mListeners.checkMailFinished(context, null); // TODO this needs to pass actual array EmailContent.Account account =
EmailContent.Account.restoreAccountWithId(mContext, accountId);
sendPendingMessagesSynchronous(account);
// find mailbox # for inbox and sync it.
// TODO we already know this in Controller, can we pass it in?
long inboxId = Mailbox.findMailboxOfType(mContext, accountId, Mailbox.TYPE_INBOX);
EmailContent.Mailbox mailbox =
EmailContent.Mailbox.restoreMailboxWithId(mContext, inboxId);
synchronizeMailboxSynchronous(account, mailbox);
mListeners.checkMailFinished(mContext, accountId, tag, inboxId);
} }
}); });
} }

View File

@ -71,13 +71,10 @@ public class MessagingListener {
String message) { String message) {
} }
public void checkMailStarted(Context context, EmailContent.Account account) { public void checkMailStarted(Context context, long accountId, long tag) {
} }
public void checkMailFinished(Context context, EmailContent.Account account) { public void checkMailFinished(Context context, long accountId, long folderId, long tag) {
}
public void checkMailFailed(Context context, EmailContent.Account account, String reason) {
} }
public void sendPendingMessagesCompleted(EmailContent.Account account) { public void sendPendingMessagesCompleted(EmailContent.Account account) {

View File

@ -611,23 +611,34 @@ public class AccountFolderList extends ListActivity
} }
/** /**
* Callback for async Controller results. This is all a placeholder until we figure out the * Callback for async Controller results.
* final way to do this.
*/ */
private class ControllerResults implements Controller.Result { private class ControllerResults implements Controller.Result {
public void updateMailboxListCallback(MessagingException result, long accountKey, public void updateMailboxListCallback(MessagingException result, long accountKey,
int progress) { int progress) {
mHandler.progress(false); if (progress == 0) {
mHandler.progress(true);
} else if (result != null || progress == 100) {
mHandler.progress(false);
}
} }
public void updateMailboxCallback(MessagingException result, long accountKey, public void updateMailboxCallback(MessagingException result, long accountKey,
long mailboxKey, int progress, int totalMessagesInMailbox, int numNewMessages) { long mailboxKey, int progress, int numNewMessages) {
mHandler.progress(false); if (progress == 0) {
mHandler.progress(true);
} else if (result != null || progress == 100) {
mHandler.progress(false);
}
} }
public void loadAttachmentCallback(MessagingException result, long messageId, public void loadAttachmentCallback(MessagingException result, long messageId,
long attachmentId, int progress) { long attachmentId, int progress) {
} }
public void serviceCheckMailCallback(MessagingException result, long accountId,
long mailboxId, int progress, long tag) {
}
} }
/* package */ static class AccountsAdapter extends CursorAdapter { /* package */ static class AccountsAdapter extends CursorAdapter {

View File

@ -755,14 +755,14 @@ public class FolderMessageList extends ExpandableListActivity {
private void doRefreshOpenMailbox() { private void doRefreshOpenMailbox() {
if (this.mExpandedGroup != -1) { if (this.mExpandedGroup != -1) {
Cursor mailboxCursor = mNewAdapter.getGroup(mExpandedGroup); Cursor mailboxCursor = mNewAdapter.getGroup(mExpandedGroup);
EmailContent.Mailbox mailbox = // EmailContent.Mailbox mailbox =
EmailContent.getContent(mailboxCursor, EmailContent.Mailbox.class); // EmailContent.getContent(mailboxCursor, EmailContent.Mailbox.class);
//
if (mailbox != null) { // if (mailbox != null) {
mHandler.progress(true); // mHandler.progress(true);
Controller.getInstance(getApplication()). // Controller.getInstance(getApplication()).
updateMailbox(mAccount.mId, mailbox, mControllerCallback); // updateMailbox(mAccount.mId, mailbox, mControllerCallback);
} // }
} }
} }
@ -778,13 +778,17 @@ public class FolderMessageList extends ExpandableListActivity {
} }
public void updateMailboxCallback(MessagingException result, long accountKey, public void updateMailboxCallback(MessagingException result, long accountKey,
long mailboxKey, int progress, int totalMessagesInMailbox, int numNewMessages) { long mailboxKey, int progress, int numNewMessages) {
mHandler.progress(false); mHandler.progress(false);
} }
public void loadAttachmentCallback(MessagingException result, long messageId, public void loadAttachmentCallback(MessagingException result, long messageId,
long attachmentId, int progress) { long attachmentId, int progress) {
} }
public void serviceCheckMailCallback(MessagingException result, long accountId,
long mailboxId, int progress, long tag) {
}
} }
@Deprecated @Deprecated

View File

@ -275,8 +275,7 @@ public class MailboxList extends ListActivity implements OnItemClickListener, On
Controller controller = Controller.getInstance(getApplication()); Controller controller = Controller.getInstance(getApplication());
mHandler.progress(true); mHandler.progress(true);
if (mailboxId >= 0) { if (mailboxId >= 0) {
Mailbox mailbox = Mailbox.restoreMailboxWithId(this, mailboxId); controller.updateMailbox(mAccountId, mailboxId, mControllerCallback);
controller.updateMailbox(mAccountId, mailbox, mControllerCallback);
} else { } else {
controller.updateMailboxList(mAccountId, mControllerCallback); controller.updateMailboxList(mAccountId, mControllerCallback);
} }
@ -376,8 +375,7 @@ public class MailboxList extends ListActivity implements OnItemClickListener, On
if (accountKey == mAccountId) { if (accountKey == mAccountId) {
if (progress == 0) { if (progress == 0) {
mHandler.progress(true); mHandler.progress(true);
} } else if (result != null || progress == 100) {
else if (result != null || progress == 100) {
mHandler.progress(false); mHandler.progress(false);
} }
} }
@ -385,12 +383,11 @@ public class MailboxList extends ListActivity implements OnItemClickListener, On
// TODO report errors into UI // TODO report errors into UI
public void updateMailboxCallback(MessagingException result, long accountKey, public void updateMailboxCallback(MessagingException result, long accountKey,
long mailboxKey, int progress, int totalMessagesInMailbox, int numNewMessages) { long mailboxKey, int progress, int numNewMessages) {
if (accountKey == mAccountId) { if (accountKey == mAccountId) {
if (progress == 0) { if (progress == 0) {
mHandler.progress(true); mHandler.progress(true);
} } else if (result != null || progress == 100) {
else if (result != null || progress == 100) {
mHandler.progress(false); mHandler.progress(false);
} }
} }
@ -399,6 +396,10 @@ public class MailboxList extends ListActivity implements OnItemClickListener, On
public void loadAttachmentCallback(MessagingException result, long messageId, public void loadAttachmentCallback(MessagingException result, long messageId,
long attachmentId, int progress) { long attachmentId, int progress) {
} }
public void serviceCheckMailCallback(MessagingException result, long accountId,
long mailboxId, int progress, long tag) {
}
} }
/** /**

View File

@ -20,14 +20,10 @@ import com.android.email.Controller;
import com.android.email.Email; import com.android.email.Email;
import com.android.email.EmailAddressAdapter; import com.android.email.EmailAddressAdapter;
import com.android.email.EmailAddressValidator; import com.android.email.EmailAddressValidator;
import com.android.email.MessagingController;
import com.android.email.R; import com.android.email.R;
import com.android.email.Utility; import com.android.email.Utility;
import com.android.email.mail.Address; import com.android.email.mail.Address;
import com.android.email.mail.MessagingException; import com.android.email.mail.MessagingException;
import com.android.email.mail.Multipart;
import com.android.email.mail.Part;
import com.android.email.mail.Message.RecipientType;
import com.android.email.mail.internet.EmailHtmlUtil; import com.android.email.mail.internet.EmailHtmlUtil;
import com.android.email.mail.internet.MimeUtility; import com.android.email.mail.internet.MimeUtility;
import com.android.email.provider.EmailContent; import com.android.email.provider.EmailContent;
@ -56,7 +52,6 @@ import android.text.SpannableStringBuilder;
import android.text.Spanned; import android.text.Spanned;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.text.util.Rfc822Tokenizer; import android.text.util.Rfc822Tokenizer;
import android.util.Config;
import android.util.Log; import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
@ -72,7 +67,6 @@ import android.widget.LinearLayout;
import android.widget.MultiAutoCompleteTextView; import android.widget.MultiAutoCompleteTextView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import android.widget.AutoCompleteTextView.Validator;
import java.io.Serializable; import java.io.Serializable;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
@ -1232,12 +1226,16 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
} }
public void updateMailboxCallback(MessagingException result, long accountId, public void updateMailboxCallback(MessagingException result, long accountId,
long mailboxId, int progress, int totalMessagesInMailbox, int numNewMessages) { long mailboxId, int progress, int numNewMessages) {
} }
public void loadAttachmentCallback(MessagingException result, long messageId, public void loadAttachmentCallback(MessagingException result, long messageId,
long attachmentId, int progress) { long attachmentId, int progress) {
} }
public void serviceCheckMailCallback(MessagingException result, long accountId,
long mailboxId, int progress, long tag) {
}
} }
// class Listener extends MessagingListener { // class Listener extends MessagingListener {

View File

@ -28,8 +28,10 @@ import com.android.email.provider.EmailContent.Mailbox;
import com.android.email.provider.EmailContent.MailboxColumns; import com.android.email.provider.EmailContent.MailboxColumns;
import com.android.email.provider.EmailContent.Message; import com.android.email.provider.EmailContent.Message;
import com.android.email.provider.EmailContent.MessageColumns; import com.android.email.provider.EmailContent.MessageColumns;
import com.android.email.service.MailService;
import android.app.ListActivity; import android.app.ListActivity;
import android.app.NotificationManager;
import android.content.ContentUris; import android.content.ContentUris;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -180,13 +182,15 @@ public class MessageList extends ListActivity implements OnItemClickListener, On
* notifications. * notifications.
* *
* @param context The caller's context (for generating an intent) * @param context The caller's context (for generating an intent)
* @param accountId The account to open * @param accountId The account to open, or -1
* @param mailboxType the type of mailbox to open (e.g. @see EmailContent.Mailbox.TYPE_INBOX) * @param mailboxId the ID of the mailbox to open, or -1
* @param mailboxType the type of mailbox to open (e.g. @see Mailbox.TYPE_INBOX) or -1
*/ */
public static Intent actionHandleAccountIntent(Context context, long accountId, public static Intent actionHandleAccountIntent(Context context, long accountId,
int mailboxType) { long mailboxId, int mailboxType) {
Intent intent = new Intent(context, MessageList.class); Intent intent = new Intent(context, MessageList.class);
intent.putExtra(EXTRA_ACCOUNT_ID, accountId); intent.putExtra(EXTRA_ACCOUNT_ID, accountId);
intent.putExtra(EXTRA_MAILBOX_ID, mailboxId);
intent.putExtra(EXTRA_MAILBOX_TYPE, mailboxType); intent.putExtra(EXTRA_MAILBOX_TYPE, mailboxType);
return intent; return intent;
} }
@ -201,7 +205,7 @@ public class MessageList extends ListActivity implements OnItemClickListener, On
*/ */
public static Intent actionHandleAccountUriIntent(Context context, long accountId, public static Intent actionHandleAccountUriIntent(Context context, long accountId,
int mailboxType) { int mailboxType) {
Intent i = actionHandleAccountIntent(context, accountId, mailboxType); Intent i = actionHandleAccountIntent(context, accountId, -1, mailboxType);
i.removeExtra(EXTRA_ACCOUNT_ID); i.removeExtra(EXTRA_ACCOUNT_ID);
Uri uri = ContentUris.withAppendedId(Account.CONTENT_URI, accountId); Uri uri = ContentUris.withAppendedId(Account.CONTENT_URI, accountId);
i.setData(uri); i.setData(uri);
@ -241,7 +245,7 @@ public class MessageList extends ListActivity implements OnItemClickListener, On
// Specific mailbox ID was provided - go directly to it // Specific mailbox ID was provided - go directly to it
mSetTitleTask = new SetTitleTask(mMailboxId); mSetTitleTask = new SetTitleTask(mMailboxId);
mSetTitleTask.execute(); mSetTitleTask.execute();
mLoadMessagesTask = new LoadMessagesTask(mMailboxId); mLoadMessagesTask = new LoadMessagesTask(mMailboxId, -1);
mLoadMessagesTask.execute(); mLoadMessagesTask.execute();
} else { } else {
long accountId = -1; long accountId = -1;
@ -278,8 +282,11 @@ public class MessageList extends ListActivity implements OnItemClickListener, On
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
Controller.getInstance(getApplication()).addResultCallback(mControllerCallback); Controller.getInstance(getApplication()).addResultCallback(mControllerCallback);
// TODO: may need to clear notifications here // clear notifications here
NotificationManager notificationManager = (NotificationManager)
getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(MailService.NEW_MESSAGE_NOTIFICATION_ID);
} }
@Override @Override
@ -410,12 +417,11 @@ public class MessageList extends ListActivity implements OnItemClickListener, On
private void onRefresh() { private void onRefresh() {
// TODO: This needs to loop through all open mailboxes (there might be more than one) // TODO: This needs to loop through all open mailboxes (there might be more than one)
// TODO: Should not be reading from DB in UI thread // TODO: Should not be reading from DB in UI thread - need a cleaner way to get accountId
if (mMailboxId >= 0) { if (mMailboxId >= 0) {
EmailContent.Mailbox mailbox = Mailbox mailbox = Mailbox.restoreMailboxWithId(this, mMailboxId);
EmailContent.Mailbox.restoreMailboxWithId(this, mMailboxId);
Controller.getInstance(getApplication()).updateMailbox( Controller.getInstance(getApplication()).updateMailbox(
mailbox.mAccountKey, mailbox, mControllerCallback); mailbox.mAccountKey, mMailboxId, mControllerCallback);
} }
} }
@ -651,7 +657,7 @@ public class MessageList extends ListActivity implements OnItemClickListener, On
mMailboxId = mailboxId; mMailboxId = mailboxId;
mSetTitleTask = new SetTitleTask(mMailboxId); mSetTitleTask = new SetTitleTask(mMailboxId);
mSetTitleTask.execute(); mSetTitleTask.execute();
mLoadMessagesTask = new LoadMessagesTask(mMailboxId); mLoadMessagesTask = new LoadMessagesTask(mMailboxId, mAccountId);
mLoadMessagesTask.execute(); mLoadMessagesTask.execute();
} }
} }
@ -669,12 +675,14 @@ public class MessageList extends ListActivity implements OnItemClickListener, On
private class LoadMessagesTask extends AsyncTask<Void, Void, Cursor> { private class LoadMessagesTask extends AsyncTask<Void, Void, Cursor> {
private long mMailboxKey; private long mMailboxKey;
private long mAccountKey;
/** /**
* Special constructor to cache some local info * Special constructor to cache some local info
*/ */
public LoadMessagesTask(long mailboxKey) { public LoadMessagesTask(long mailboxKey, long accountKey) {
mMailboxKey = mailboxKey; mMailboxKey = mailboxKey;
mAccountKey = accountKey;
} }
@Override @Override
@ -743,6 +751,13 @@ public class MessageList extends ListActivity implements OnItemClickListener, On
if (cursor != null && cursor.getCount() == 0) { if (cursor != null && cursor.getCount() == 0) {
onRefresh(); onRefresh();
} }
// Reset the "new messages" count in the service, since we're seeing them now
if (mMailboxKey == QUERY_ALL_INBOXES) {
MailService.resetNewMessageCount(-1);
} else if (mMailboxKey >= 0 && mAccountKey != -1) {
MailService.resetNewMessageCount(mAccountKey);
}
} }
} }
@ -866,8 +881,7 @@ public class MessageList extends ListActivity implements OnItemClickListener, On
int progress) { int progress) {
if (progress == 0) { if (progress == 0) {
mHandler.progress(true); mHandler.progress(true);
} } else if (result != null || progress == 100) {
else if (result != null || progress == 100) {
mHandler.progress(false); mHandler.progress(false);
if (mWaitForMailboxType != -1) { if (mWaitForMailboxType != -1) {
if (result == null) { if (result == null) {
@ -880,11 +894,10 @@ public class MessageList extends ListActivity implements OnItemClickListener, On
// TODO report errors into UI // TODO report errors into UI
// TODO check accountKey and only react to relevant notifications // TODO check accountKey and only react to relevant notifications
public void updateMailboxCallback(MessagingException result, long accountKey, public void updateMailboxCallback(MessagingException result, long accountKey,
long mailboxKey, int progress, int totalMessagesInMailbox, int numNewMessages) { long mailboxKey, int progress, int numNewMessages) {
if (progress == 0) { if (progress == 0) {
mHandler.progress(true); mHandler.progress(true);
} } else if (result != null || progress == 100) {
else if (result != null || progress == 100) {
mHandler.progress(false); mHandler.progress(false);
} }
} }
@ -892,6 +905,10 @@ public class MessageList extends ListActivity implements OnItemClickListener, On
public void loadAttachmentCallback(MessagingException result, long messageId, public void loadAttachmentCallback(MessagingException result, long messageId,
long attachmentId, int progress) { long attachmentId, int progress) {
} }
public void serviceCheckMailCallback(MessagingException result, long accountId,
long mailboxId, int progress, long tag) {
}
} }
/** /**

View File

@ -25,10 +25,8 @@ import com.android.email.Utility;
import com.android.email.mail.Address; import com.android.email.mail.Address;
import com.android.email.mail.MessagingException; import com.android.email.mail.MessagingException;
import com.android.email.mail.Part; import com.android.email.mail.Part;
import com.android.email.mail.Message.RecipientType;
import com.android.email.mail.internet.EmailHtmlUtil; import com.android.email.mail.internet.EmailHtmlUtil;
import com.android.email.mail.internet.MimeUtility; import com.android.email.mail.internet.MimeUtility;
import com.android.email.mail.store.LocalStore.LocalMessage;
import com.android.email.provider.AttachmentProvider; import com.android.email.provider.AttachmentProvider;
import com.android.email.provider.EmailContent.Account; import com.android.email.provider.EmailContent.Account;
import com.android.email.provider.EmailContent.Attachment; import com.android.email.provider.EmailContent.Attachment;
@ -59,7 +57,6 @@ import android.provider.Contacts;
import android.provider.Contacts.Intents; import android.provider.Contacts.Intents;
import android.provider.Contacts.People; import android.provider.Contacts.People;
import android.text.util.Regex; import android.text.util.Regex;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
@ -1227,12 +1224,16 @@ public class MessageView extends Activity
} }
public void updateMailboxCallback(MessagingException result, long accountId, public void updateMailboxCallback(MessagingException result, long accountId,
long mailboxId, int progress, int totalMessagesInMailbox, int numNewMessages) { long mailboxId, int progress, int numNewMessages) {
} }
public void updateMailboxListCallback(MessagingException result, long accountId, public void updateMailboxListCallback(MessagingException result, long accountId,
int progress) { int progress) {
} }
public void serviceCheckMailCallback(MessagingException result, long accountId,
long mailboxId, int progress, long tag) {
}
} }
/** /**

View File

@ -242,27 +242,6 @@ public abstract class Store {
public abstract void checkSettings() throws MessagingException; public abstract void checkSettings() throws MessagingException;
/**
* Enable or disable push mode delivery for a given Store.
*
* <p>For protocols that do not support push mode, be sure that push="true" is not
* set by the stores.xml definition file(s). This function need not be implemented.
*
* <p>For protocols that do support push mode, this will be called at startup (boot) time
* so that the Store can launch its own underlying connection service. It will also be called
* any time the user changes the settings for the account (because the user may switch back
* to polling mode (or disable checking completely).
*
* <p>This API will be called repeatedly, even after push mode has already been started or
* stopped. Stores that support push mode should return quickly if the configuration has not
* changed.
*
* @param enablePushMode start or stop push mode delivery
*/
public void enablePushModeDelivery(boolean enablePushMode) {
// does nothing for non-push protocols
}
/** /**
* Delete Store and its corresponding resources. * Delete Store and its corresponding resources.
* @throws MessagingException * @throws MessagingException

View File

@ -49,8 +49,6 @@ public class ExchangeStoreExample extends Store {
private final ExchangeTransportExample mTransport; private final ExchangeTransportExample mTransport;
private final HashMap<String, Folder> mFolders = new HashMap<String, Folder>(); private final HashMap<String, Folder> mFolders = new HashMap<String, Folder>();
private boolean mPushModeRunning = false;
/** /**
* Factory method. * Factory method.
*/ */
@ -120,30 +118,7 @@ public class ExchangeStoreExample extends Store {
getFolder(ExchangeTransportExample.FOLDER_INBOX), getFolder(ExchangeTransportExample.FOLDER_INBOX),
}; };
} }
/**
* For a store that supports push mode, this is the API that enables it or disables it.
* The store should use this API to start or stop its persistent connection service or thread.
*
* <p>Note, may be called multiple times, even after push mode has been started or stopped.
*
* @param enablePushMode start or stop push mode delivery
*/
@Override
public void enablePushModeDelivery(boolean enablePushMode) {
if (Config.LOGD && Email.DEBUG) {
if (enablePushMode && !mPushModeRunning) {
Log.d(Email.LOG_TAG, "start push mode");
} else if (!enablePushMode && mPushModeRunning) {
Log.d(Email.LOG_TAG, "stop push mode");
} else {
Log.d(Email.LOG_TAG, enablePushMode ?
"push mode already started" : "push mode already stopped");
}
}
mPushModeRunning = enablePushMode;
}
/** /**
* Get class of SettingActivity for this Store class. * Get class of SettingActivity for this Store class.
* @return Activity class that has class method actionEditIncomingSettings(). * @return Activity class that has class method actionEditIncomingSettings().

View File

@ -41,7 +41,6 @@ import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.os.RemoteException; import android.os.RemoteException;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Config;
import android.util.Log; import android.util.Log;
import java.io.IOException; import java.io.IOException;
@ -64,8 +63,6 @@ public class ExchangeStore extends Store {
private final ExchangeTransport mTransport; private final ExchangeTransport mTransport;
private final HashMap<String, Folder> mFolders = new HashMap<String, Folder>(); private final HashMap<String, Folder> mFolders = new HashMap<String, Folder>();
private boolean mPushModeRunning = false;
/** /**
* Factory method. * Factory method.
*/ */
@ -164,29 +161,6 @@ public class ExchangeStore extends Store {
}; };
} }
/**
* For a store that supports push mode, this is the API that enables it or disables it.
* The store should use this API to start or stop its persistent connection service or thread.
*
* <p>Note, may be called multiple times, even after push mode has been started or stopped.
*
* @param enablePushMode start or stop push mode delivery
*/
@Override
public void enablePushModeDelivery(boolean enablePushMode) {
if (Config.LOGD && Email.DEBUG) {
if (enablePushMode && !mPushModeRunning) {
Log.d(Email.LOG_TAG, "start push mode");
} else if (!enablePushMode && mPushModeRunning) {
Log.d(Email.LOG_TAG, "stop push mode");
} else {
Log.d(Email.LOG_TAG, enablePushMode ?
"push mode already started" : "push mode already stopped");
}
}
mPushModeRunning = enablePushMode;
}
/** /**
* Get class of SettingActivity for this Store class. * Get class of SettingActivity for this Store class.
* @return Activity class that has class method actionEditIncomingSettings() * @return Activity class that has class method actionEditIncomingSettings()

View File

@ -16,52 +16,60 @@
package com.android.email.service; package com.android.email.service;
import com.android.email.Account; import com.android.email.Controller;
import com.android.email.Email; import com.android.email.Email;
import com.android.email.MessagingController;
import com.android.email.MessagingListener;
import com.android.email.R; import com.android.email.R;
import com.android.email.activity.AccountFolderList;
import com.android.email.activity.MessageList; import com.android.email.activity.MessageList;
import com.android.email.mail.MessagingException; import com.android.email.mail.MessagingException;
import com.android.email.mail.Store; import com.android.email.provider.EmailContent.Account;
import com.android.email.mail.store.LocalStore; import com.android.email.provider.EmailContent.Mailbox;
import com.android.email.provider.EmailContent;
import android.app.AlarmManager; import android.app.AlarmManager;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
import android.content.ContentUris;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.IBinder; import android.os.IBinder;
import android.os.SystemClock; import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Config; import android.util.Config;
import android.util.Log; import android.util.Log;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
/** /**
* Background service for refreshing non-push email accounts.
*/ */
public class MailService extends Service { public class MailService extends Service {
/** DO NOT CHECK IN "TRUE" */
private static final boolean DEBUG_FORCE_QUICK_REFRESH = false; // force 1-minute refresh
public static int NEW_MESSAGE_NOTIFICATION_ID = 1;
private static final String ACTION_CHECK_MAIL = private static final String ACTION_CHECK_MAIL =
"com.android.email.intent.action.MAIL_SERVICE_WAKEUP"; "com.android.email.intent.action.MAIL_SERVICE_WAKEUP";
private static final String ACTION_RESCHEDULE = private static final String ACTION_RESCHEDULE =
"com.android.email.intent.action.MAIL_SERVICE_RESCHEDULE"; "com.android.email.intent.action.MAIL_SERVICE_RESCHEDULE";
private static final String ACTION_CANCEL = private static final String ACTION_CANCEL =
"com.android.email.intent.action.MAIL_SERVICE_CANCEL"; "com.android.email.intent.action.MAIL_SERVICE_CANCEL";
private static final String EXTRA_CHECK_ACCOUNT = "com.android.email.intent.extra.ACCOUNT";
private Listener mListener = new Listener(); private static final String EXTRA_CHECK_ACCOUNT = "com.android.email.intent.extra.ACCOUNT";
private static final String EXTRA_ACCOUNT_INFO = "com.android.email.intent.extra.ACCOUNT_INFO";
private Controller.Result mControllerCallback = new ControllerResults();
private int mStartId; private int mStartId;
/**
* Access must be synchronized, because there are accesses from the Controller callback
*/
private static HashMap<Long,AccountSyncReport> mSyncReports =
new HashMap<Long,AccountSyncReport>();
public static void actionReschedule(Context context) { public static void actionReschedule(Context context) {
Intent i = new Intent(); Intent i = new Intent();
i.setClass(context, MailService.class); i.setClass(context, MailService.class);
@ -75,6 +83,23 @@ public class MailService extends Service {
i.setAction(MailService.ACTION_CANCEL); i.setAction(MailService.ACTION_CANCEL);
context.startService(i); context.startService(i);
} }
/**
* Reset new message counts for one or all accounts
*
* TODO what about EAS service new message counts, where are they reset?
*
* @param accountId account to clear, or -1 for all accounts
*/
public static void resetNewMessageCount(long accountId) {
synchronized (mSyncReports) {
for (AccountSyncReport report : mSyncReports.values()) {
if (accountId == -1 || accountId == report.accountId) {
report.numNewMessages = 0;
}
}
}
}
/** /**
* Entry point for asynchronous message services (e.g. push mode) to post notifications of new * Entry point for asynchronous message services (e.g. push mode) to post notifications of new
@ -82,66 +107,44 @@ public class MailService extends Service {
* which will attempt to load the new messages. So the Store should expect to be opened and * which will attempt to load the new messages. So the Store should expect to be opened and
* fetched from shortly after making this call. * fetched from shortly after making this call.
* *
* @param storeUri the Uri of the store that is reporting new messages * @param accountId the id of the account that is reporting new messages
*/ */
public static void actionNotifyNewMessages(Context context, String storeUri) { public static void actionNotifyNewMessages(Context context, long accountId) {
Intent i = new Intent(ACTION_CHECK_MAIL); Intent i = new Intent(ACTION_CHECK_MAIL);
i.setClass(context, MailService.class); i.setClass(context, MailService.class);
i.putExtra(EXTRA_CHECK_ACCOUNT, storeUri); i.putExtra(EXTRA_CHECK_ACCOUNT, accountId);
context.startService(i); context.startService(i);
} }
@Override @Override
public void onStart(Intent intent, int startId) { public void onStart(Intent intent, int startId) {
super.onStart(intent, startId); super.onStart(intent, startId);
// TODO this needs to be passed through the controller and back to us
this.mStartId = startId; this.mStartId = startId;
MessagingController controller = MessagingController.getInstance(getApplication()); Controller controller = Controller.getInstance(getApplication());
controller.addListener(mListener); controller.addResultCallback(mControllerCallback);
if (ACTION_CHECK_MAIL.equals(intent.getAction())) { if (ACTION_CHECK_MAIL.equals(intent.getAction())) {
if (Config.LOGD && Email.DEBUG) { if (Config.LOGD && Email.DEBUG) {
Log.d(Email.LOG_TAG, "*** MailService: checking mail"); Log.d(Email.LOG_TAG, "*** MailService: checking mail");
} }
// Only check mail for accounts that have enabled automatic checking. There is still // If we have the data, restore the last-sync-times for each account
// a bug here in that we check every enabled account, on every refresh - irrespective // These are cached in the wakeup intent in case the process was killed.
// of that account's refresh frequency - but this fixes the worst case of checking restoreSyncReports(intent);
// accounts that should not have been checked at all.
// Also note: Due to the organization of this service, you must gather the accounts
// and make a single call to controller.checkMail().
// TODO: Notification for single push account will fire up checks on all other // Sync a specific account if given
// accounts. This needs to be cleaned up for better efficiency. long checkAccountId = intent.getLongExtra(EXTRA_CHECK_ACCOUNT, -1);
String specificStoreUri = intent.getStringExtra(EXTRA_CHECK_ACCOUNT); if (checkAccountId != -1) {
// launch an account sync in the controller
ArrayList<EmailContent.Account> accountsToCheck = new ArrayList<EmailContent.Account>(); syncOneAccount(controller, checkAccountId, startId);
} else {
Cursor c = null; // Find next account to sync, and reschedule
try { AlarmManager alarmManager = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
c = this.getContentResolver().query( reschedule(alarmManager);
EmailContent.Account.CONTENT_URI, stopSelf(startId);
EmailContent.Account.CONTENT_PROJECTION,
null, null, null);
while (c.moveToNext()) {
EmailContent.Account account = EmailContent.getContent(c,
EmailContent.Account.class);
int interval = account.getSyncInterval();
String storeUri = account.getStoreUri(this);
if (interval > 0 || (storeUri != null && storeUri.equals(specificStoreUri))) {
accountsToCheck.add(account);
}
// For each account, switch pushmail on or off
enablePushMail(account, interval == EmailContent.Account.CHECK_INTERVAL_PUSH);
}
} finally {
if (c != null) {
c.close();
}
} }
EmailContent.Account[] accounts = accountsToCheck.toArray(
new EmailContent.Account[accountsToCheck.size()]);
controller.checkMail(this, accounts, mListener);
} }
else if (ACTION_CANCEL.equals(intent.getAction())) { else if (ACTION_CANCEL.equals(intent.getAction())) {
if (Config.LOGD && Email.DEBUG) { if (Config.LOGD && Email.DEBUG) {
@ -154,186 +157,366 @@ public class MailService extends Service {
if (Config.LOGD && Email.DEBUG) { if (Config.LOGD && Email.DEBUG) {
Log.d(Email.LOG_TAG, "*** MailService: reschedule"); Log.d(Email.LOG_TAG, "*** MailService: reschedule");
} }
reschedule(); AlarmManager alarmManager = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
reschedule(alarmManager);
stopSelf(startId); stopSelf(startId);
} }
} }
@Override
public void onDestroy() {
super.onDestroy();
MessagingController.getInstance(getApplication()).removeListener(mListener);
}
private void cancel() {
AlarmManager alarmMgr = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
Intent i = new Intent();
i.setClassName("com.android.email", "com.android.email.service.MailService");
i.setAction(ACTION_CHECK_MAIL);
PendingIntent pi = PendingIntent.getService(this, 0, i, 0);
alarmMgr.cancel(pi);
}
private void reschedule() {
AlarmManager alarmMgr = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
Intent i = new Intent();
i.setClassName("com.android.email", "com.android.email.service.MailService");
i.setAction(ACTION_CHECK_MAIL);
PendingIntent pi = PendingIntent.getService(this, 0, i, 0);
int shortestInterval = -1;
Cursor c = null;
try {
c = this.getContentResolver().query(
EmailContent.Account.CONTENT_URI,
EmailContent.Account.CONTENT_PROJECTION,
null, null, null);
while (c.moveToNext()) {
EmailContent.Account account = EmailContent.getContent(c,
EmailContent.Account.class);
int interval = account.getSyncInterval();
if (interval > 0 && (interval < shortestInterval || shortestInterval == -1)) {
shortestInterval = interval;
}
enablePushMail(account, interval == Account.CHECK_INTERVAL_PUSH);
}
} finally {
if (c != null) {
c.close();
}
}
if (shortestInterval == -1) {
alarmMgr.cancel(pi);
}
else {
alarmMgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime()
+ (shortestInterval * (60 * 1000)), pi);
}
}
public IBinder onBind(Intent intent) { public IBinder onBind(Intent intent) {
return null; return null;
} }
class Listener extends MessagingListener { @Override
HashMap<EmailContent.Account, Integer> accountsWithNewMail = public void onDestroy() {
new HashMap<EmailContent.Account, Integer>(); super.onDestroy();
Controller.getInstance(getApplication()).removeResultCallback(mControllerCallback);
}
// TODO this should be redone because account is usually null, not very interesting. private void cancel() {
// I think it would make more sense to pass Account[] here in case anyone uses it AlarmManager alarmMgr = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
// In any case, it should be noticed that this is called once per cycle PendingIntent pi = createAlarmIntent(-1, null);
@Override alarmMgr.cancel(pi);
public void checkMailStarted(Context context, EmailContent.Account account) { }
accountsWithNewMail.clear();
}
// Called once per checked account /**
@Override * Create and send an alarm with the entire list. This also sends a list of known last-sync
public void checkMailFailed(Context context, EmailContent.Account account, String reason) { * times with the alarm, so if we are killed between alarms, we don't lose this info.
if (Config.LOGD && Email.DEBUG) { *
Log.d(Email.LOG_TAG, "*** MailService: checkMailFailed: " + reason); * @param alarmMgr passed in so we can mock for testing.
} */
reschedule(); /* package */ void reschedule(AlarmManager alarmMgr) {
stopSelf(mStartId); // restore the reports if lost
} setupSyncReports(-1);
synchronized (mSyncReports) {
int numAccounts = mSyncReports.size();
long[] accountInfo = new long[numAccounts * 2]; // pairs of { accountId, lastSync }
int accountInfoIndex = 0;
// Called once per checked account long nextCheckTime = Long.MAX_VALUE;
@Override AccountSyncReport nextAccount = null;
public void synchronizeMailboxFinished(EmailContent.Account account, long timeNow = SystemClock.elapsedRealtime();
EmailContent.Mailbox folder, int totalMessagesInMailbox, int numNewMessages) {
if (Config.LOGD && Email.DEBUG) {
Log.d(Email.LOG_TAG, "*** MailService: synchronizeMailboxFinished: total=" +
totalMessagesInMailbox + " new=" + numNewMessages);
}
if (numNewMessages > 0 &&
((account.getFlags() & EmailContent.Account.FLAGS_NOTIFY_NEW_MAIL) != 0)) {
accountsWithNewMail.put(account, numNewMessages);
}
}
// TODO this should be redone because account is usually null, not very interesting. for (AccountSyncReport report : mSyncReports.values()) {
// I think it would make more sense to pass Account[] here in case anyone uses it if (report.syncInterval <= 0) { // no timed checks - skip
// In any case, it should be noticed that this is called once per cycle continue;
@Override
public void checkMailFinished(Context context, EmailContent.Account account) {
if (Config.LOGD && Email.DEBUG) {
Log.d(Email.LOG_TAG, "*** MailService: checkMailFinished");
}
NotificationManager notifMgr = (NotificationManager)context
.getSystemService(Context.NOTIFICATION_SERVICE);
if (accountsWithNewMail.size() > 0) {
Notification notif = new Notification(R.drawable.stat_notify_email_generic,
getString(R.string.notification_new_title), System.currentTimeMillis());
boolean vibrate = false;
String ringtone = null;
if (accountsWithNewMail.size() > 1) {
for (EmailContent.Account account1 : accountsWithNewMail.keySet()) {
if ((account1.getFlags() & EmailContent.Account.FLAGS_VIBRATE) != 0) {
vibrate = true;
}
ringtone = account1.getRingtone();
}
Intent i = new Intent(context, AccountFolderList.class);
PendingIntent pi = PendingIntent.getActivity(context, 0, i, 0);
notif.setLatestEventInfo(context, getString(R.string.notification_new_title),
getResources().
getQuantityString(R.plurals.notification_new_multi_account_fmt,
accountsWithNewMail.size(),
accountsWithNewMail.size()), pi);
} else {
EmailContent.Account account1 = accountsWithNewMail.keySet().iterator().next();
int totalNewMails = accountsWithNewMail.get(account1);
Intent i = MessageList.actionHandleAccountIntent(context,
account1.mId, EmailContent.Mailbox.TYPE_INBOX);
PendingIntent pi = PendingIntent.getActivity(context, 0, i, 0);
notif.setLatestEventInfo(context, getString(R.string.notification_new_title),
getResources().
getQuantityString(R.plurals.notification_new_one_account_fmt,
totalNewMails, totalNewMails,
account1.getDisplayName()), pi);
vibrate = ((account1.getFlags() & EmailContent.Account.FLAGS_VIBRATE) != 0);
ringtone = account1.getRingtone();
} }
notif.defaults = Notification.DEFAULT_LIGHTS; // select next account to sync
notif.sound = TextUtils.isEmpty(ringtone) ? null : Uri.parse(ringtone); if ((report.prevSyncTime == 0) // never checked
if (vibrate) { || (report.nextSyncTime < timeNow)) { // overdue
notif.defaults |= Notification.DEFAULT_VIBRATE; nextCheckTime = 0;
nextAccount = report;
} else if (report.nextSyncTime < nextCheckTime) { // next to be checked
nextCheckTime = report.nextSyncTime;
nextAccount = report;
} }
notifMgr.notify(1, notif); // collect last-sync-times for all accounts
// this is using pairs of {long,long} to simplify passing in a bundle
accountInfo[accountInfoIndex++] = report.accountId;
accountInfo[accountInfoIndex++] = report.prevSyncTime;
} }
reschedule(); // set/clear alarm as needed
stopSelf(mStartId); long idToCheck = (nextAccount == null) ? -1 : nextAccount.accountId;
PendingIntent pi = createAlarmIntent(idToCheck, accountInfo);
if (nextAccount == null) {
alarmMgr.cancel(pi);
Log.d(Email.LOG_TAG, "alarm cancel - no account to check");
} else {
alarmMgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextCheckTime, pi);
Log.d(Email.LOG_TAG, "alarm set at " + nextCheckTime + " for " + nextAccount);
}
} }
} }
/** /**
* For any account that wants push mail, get its Store and start the pushmail service. * Return a pending intent for use by this alarm. Most of the fields must be the same
* This function makes no attempt to optimize, so accounts may have push enabled (or disabled) * (in order for the intent to be recognized by the alarm manager) but the extras can
* repeatedly, and should handle this appropriately. * be different, and are passed in here as parameters.
*
* @param account the account that needs push delivery enabled
*/ */
private void enablePushMail(EmailContent.Account account, boolean enable) { /* package */ PendingIntent createAlarmIntent(long checkId, long[] accountInfo) {
try { Intent i = new Intent();
String localUri = account.getLocalStoreUri(this); i.setClassName("com.android.email", "com.android.email.service.MailService");
String storeUri = account.getStoreUri(this); i.setAction(ACTION_CHECK_MAIL);
if (localUri != null && storeUri != null) { i.putExtra(EXTRA_CHECK_ACCOUNT, checkId);
LocalStore localStore = (LocalStore) Store.getInstance( i.putExtra(EXTRA_ACCOUNT_INFO, accountInfo);
localUri, this.getBaseContext(), null); PendingIntent pi = PendingIntent.getService(this, 0, i, 0);
Store store = Store.getInstance(storeUri, this.getBaseContext(), return pi;
localStore.getPersistentCallbacks()); }
if (store != null) {
store.enablePushModeDelivery(enable); /**
* Start a controller sync for a specific account
*/
private void syncOneAccount(Controller controller, long checkAccountId, int startId) {
long inboxId = Mailbox.findMailboxOfType(this, checkAccountId, Mailbox.TYPE_INBOX);
if (inboxId == Mailbox.NO_MAILBOX) {
// no inbox?? sync mailboxes
} else {
controller.serviceCheckMail(checkAccountId, inboxId, startId, mControllerCallback);
}
}
/**
* Note: Times are relative to SystemClock.elapsedRealtime()
*/
private static class AccountSyncReport {
long accountId;
long prevSyncTime; // 0 == unknown
long nextSyncTime; // 0 == ASAP -1 == don't sync
int numNewMessages;
int syncInterval;
boolean notify;
boolean vibrate;
Uri ringtoneUri;
String displayName; // temporary, for debug logging
public String toString() {
return displayName + ": prevSync=" + prevSyncTime + " nextSync=" + nextSyncTime
+ " numNew=" + numNewMessages;
}
}
/**
* scan accounts to create a list of { acct, prev sync, next sync, #new }
* use this to create a fresh copy. assumes all accounts need sync
*
* @param accountId -1 will rebuild the list if empty. other values will force loading
* of a single account (e.g if it was created after the original list population)
*/
/* package */ void setupSyncReports(long accountId) {
synchronized (mSyncReports) {
if (accountId == -1) {
// -1 == reload the list if empty, otherwise exit immediately
if (mSyncReports.size() > 0) {
return;
}
} else {
// load a single account if it doesn't already have a sync record
if (mSyncReports.containsKey(accountId)) {
return;
} }
} }
} catch (MessagingException me) {
if (Config.LOGD && Email.DEBUG) { // setup to add a single account or all accounts
Log.d(Email.LOG_TAG, "Failed to enable push mail for account" + Uri uri;
account.getSenderName() + " with exception " + me.toString()); if (accountId == -1) {
uri = Account.CONTENT_URI;
} else {
uri = ContentUris.withAppendedId(Account.CONTENT_URI, accountId);
}
// TODO use a narrower projection here
Cursor c = getContentResolver().query(uri, Account.CONTENT_PROJECTION,
null, null, null);
try {
while (c.moveToNext()) {
AccountSyncReport report = new AccountSyncReport();
int syncInterval = c.getInt(Account.CONTENT_SYNC_INTERVAL_COLUMN);
int flags = c.getInt(Account.CONTENT_FLAGS_COLUMN);
String ringtoneString = c.getString(Account.CONTENT_RINGTONE_URI_COLUMN);
// For debugging only
if (DEBUG_FORCE_QUICK_REFRESH && syncInterval >= 0) {
syncInterval = 1;
}
report.accountId = c.getLong(Account.CONTENT_ID_COLUMN);
report.prevSyncTime = 0;
report.nextSyncTime = (syncInterval > 0) ? 0 : -1; // 0 == ASAP -1 == no sync
report.numNewMessages = 0;
report.syncInterval = syncInterval;
report.notify = (flags & Account.FLAGS_NOTIFY_NEW_MAIL) != 0;
report.vibrate = (flags & Account.FLAGS_VIBRATE) != 0;
report.ringtoneUri = (ringtoneString == null) ? null
: Uri.parse(ringtoneString);
report.displayName = c.getString(Account.CONTENT_DISPLAY_NAME_COLUMN);
// TODO lookup # new in inbox
mSyncReports.put(report.accountId, report);
}
} finally {
c.close();
} }
} }
} }
/**
* Update list with a single account's sync times and unread count
*
* @param accountId the account being udpated
* @param newCount the number of new messages, or -1 if not being reported (don't update)
* @return the report for the updated account, or null if it doesn't exist (e.g. deleted)
*/
/* package */ AccountSyncReport updateAccountReport(long accountId, int newCount) {
// restore the reports if lost
setupSyncReports(accountId);
synchronized (mSyncReports) {
AccountSyncReport report = mSyncReports.get(accountId);
if (report == null) {
// discard result - there is no longer an account with this id
Log.d(Email.LOG_TAG, "No account to update for id=" + Long.toString(accountId));
return null;
}
// report found - update it (note - editing the report while in-place in the hashmap)
report.prevSyncTime = SystemClock.elapsedRealtime();
if (report.syncInterval > 0) {
report.nextSyncTime = report.prevSyncTime + (report.syncInterval * 1000 * 60);
}
if (newCount != -1) {
report.numNewMessages = newCount;
}
Log.d(Email.LOG_TAG, "update account " + report.toString());
return report;
}
}
/**
* when we receive an alarm, update the account sync reports list if necessary
* this will be the case when if we have restarted the process and lost the data
* in the global.
*
* @param restoreIntent the intent with the list
*/
/* package */ void restoreSyncReports(Intent restoreIntent) {
// restore the reports if lost
setupSyncReports(-1);
synchronized (mSyncReports) {
long[] accountInfo = restoreIntent.getLongArrayExtra(EXTRA_ACCOUNT_INFO);
if (accountInfo == null) {
Log.d(Email.LOG_TAG, "no data in intent to restore");
return;
}
int accountInfoIndex = 0;
int accountInfoLimit = accountInfo.length;
while (accountInfoIndex < accountInfoLimit) {
long accountId = accountInfo[accountInfoIndex++];
long prevSync = accountInfo[accountInfoIndex++];
AccountSyncReport report = mSyncReports.get(accountId);
if (report != null) {
if (report.prevSyncTime == 0) {
report.prevSyncTime = prevSync;
Log.d(Email.LOG_TAG, "restore prev sync for account" + report);
}
}
}
}
}
class ControllerResults implements Controller.Result {
public void loadAttachmentCallback(MessagingException result, long messageId,
long attachmentId, int progress) {
}
public void updateMailboxCallback(MessagingException result, long accountId,
long mailboxId, int progress, int numNewMessages) {
if (result == null) {
updateAccountReport(accountId, numNewMessages);
if (numNewMessages > 0) {
notifyNewMessages(accountId);
}
} else {
updateAccountReport(accountId, -1);
}
}
public void updateMailboxListCallback(MessagingException result, long accountId,
int progress) {
}
public void serviceCheckMailCallback(MessagingException result, long accountId,
long mailboxId, int progress, long tag) {
if (progress == 100) {
AlarmManager alarmManager = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
reschedule(alarmManager);
int serviceId = MailService.this.mStartId;
if (tag != 0) {
serviceId = (int) tag;
}
stopSelf(serviceId);
}
}
}
/**
* Prepare notifications for a given new account having received mail
* The notification is organized around the account that has the new mail (e.g. selecting
* the alert preferences) but the notification will include a summary if other
* accounts also have new mail.
*/
private void notifyNewMessages(long accountId) {
boolean notify = false;
boolean vibrate = false;
Uri ringtone = null;
int accountsWithNewMessages = 0;
int numNewMessages = 0;
String reportName = null;
synchronized (mSyncReports) {
for (AccountSyncReport report : mSyncReports.values()) {
if (report.numNewMessages == 0) {
continue;
}
numNewMessages += report.numNewMessages;
accountsWithNewMessages += 1;
if (report.accountId == accountId) {
notify = report.notify;
vibrate = report.vibrate;
ringtone = report.ringtoneUri;
reportName = report.displayName;
}
}
}
if (!notify) {
return;
}
// set up to post a notification
Intent intent;
String reportString;
if (accountsWithNewMessages == 1) {
// Prepare a report for a single account
// "12 unread (gmail)"
reportString = getResources().getQuantityString(
R.plurals.notification_new_one_account_fmt, numNewMessages,
numNewMessages, reportName);
intent = MessageList.actionHandleAccountIntent(this,
accountId, -1, Mailbox.TYPE_INBOX);
} else {
// Prepare a report for multiple accounts
// "4 accounts"
reportString = getResources().getQuantityString(
R.plurals.notification_new_multi_account_fmt, accountsWithNewMessages,
accountsWithNewMessages);
intent = MessageList.actionHandleAccountIntent(this,
-1, MessageList.QUERY_ALL_INBOXES, -1);
}
// prepare appropriate pending intent, set up notification, and send
PendingIntent pending = PendingIntent.getActivity(this, 0, intent, 0);
Notification notification = new Notification(
R.drawable.stat_notify_email_generic,
getString(R.string.notification_new_title),
System.currentTimeMillis());
notification.setLatestEventInfo(this,
getString(R.string.notification_new_title),
reportString,
pending);
notification.sound = ringtone;
notification.defaults = vibrate
? Notification.DEFAULT_LIGHTS | Notification.DEFAULT_VIBRATE
: Notification.DEFAULT_LIGHTS;
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(NEW_MESSAGE_NOTIFICATION_ID, notification);
}
} }

View File

@ -472,7 +472,7 @@ public class EmailSyncAdapter extends AbstractSyncAdapter {
Notification notif = new Notification(R.drawable.stat_notify_email_generic, Notification notif = new Notification(R.drawable.stat_notify_email_generic,
mContext.getString(R.string.notification_new_title), mContext.getString(R.string.notification_new_title),
System.currentTimeMillis()); System.currentTimeMillis());
Intent i = MessageList.actionHandleAccountIntent(mContext, mAccount.mId, Intent i = MessageList.actionHandleAccountIntent(mContext, mAccount.mId, -1,
Mailbox.TYPE_INBOX); Mailbox.TYPE_INBOX);
PendingIntent pi = PendingIntent.getActivity(mContext, 0, i, 0); PendingIntent pi = PendingIntent.getActivity(mContext, 0, i, 0);
notif.setLatestEventInfo(mContext, notif.setLatestEventInfo(mContext,