summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTodd Kennedy <toddke@google.com>2011-05-03 14:42:26 -0700
committerTodd Kennedy <toddke@google.com>2011-05-05 12:00:27 -0700
commitc4cdb11d24c19428dd39f986b00c1a29e75e1505 (patch)
tree1f629694baded926959caf23853dd87eb06bbce6
parentc96cd4a8483798dc16010b0e7b9b3e95e3361615 (diff)
downloadandroid_packages_apps_Email-c4cdb11d24c19428dd39f986b00c1a29e75e1505.tar.gz
android_packages_apps_Email-c4cdb11d24c19428dd39f986b00c1a29e75e1505.tar.bz2
android_packages_apps_Email-c4cdb11d24c19428dd39f986b00c1a29e75e1505.zip
Remove notification if messages seen off device
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
-rw-r--r--CleanSpec.mk1
-rw-r--r--emailcommon/src/com/android/emailcommon/provider/EmailContent.java1
-rw-r--r--emailcommon/src/com/android/emailcommon/service/AccountServiceProxy.java21
-rw-r--r--emailcommon/src/com/android/emailcommon/service/IAccountService.aidl2
-rw-r--r--src/com/android/email/Controller.java14
-rw-r--r--src/com/android/email/ControllerResultUiThreadWrapper.java7
-rw-r--r--src/com/android/email/GroupMessagingListener.java5
-rw-r--r--src/com/android/email/MessagingController.java49
-rw-r--r--src/com/android/email/MessagingListener.java23
-rw-r--r--src/com/android/email/NotificationController.java235
-rw-r--r--src/com/android/email/RefreshManager.java3
-rw-r--r--src/com/android/email/activity/AccountFolderList.java5
-rw-r--r--src/com/android/email/activity/AccountFolderListFragment.java5
-rw-r--r--src/com/android/email/activity/EmailActivity.java4
-rw-r--r--src/com/android/email/activity/MailboxList.java4
-rw-r--r--src/com/android/email/mail/Store.java8
-rw-r--r--src/com/android/email/mail/StoreSynchronizer.java77
-rw-r--r--src/com/android/email/mail/store/ExchangeStore.java13
-rw-r--r--src/com/android/email/provider/EmailProvider.java10
-rw-r--r--src/com/android/email/service/AccountService.java25
-rw-r--r--src/com/android/email/service/MailService.java178
-rw-r--r--tests/src/com/android/email/NotificationControllerTest.java6
-rw-r--r--tests/src/com/android/email/RefreshManagerTest.java8
23 files changed, 413 insertions, 291 deletions
diff --git a/CleanSpec.mk b/CleanSpec.mk
index ebb255650..133cf331d 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -54,6 +54,7 @@ $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/Email_intermedia
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/com.android.emailcommon_intermediates)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/com.android.emailcommon_intermediates)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/com.android.emailcommon_intermediates)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/com.android.emailcommon_intermediates)
# ************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
diff --git a/emailcommon/src/com/android/emailcommon/provider/EmailContent.java b/emailcommon/src/com/android/emailcommon/provider/EmailContent.java
index b2e164c8c..9f0c855e0 100644
--- a/emailcommon/src/com/android/emailcommon/provider/EmailContent.java
+++ b/emailcommon/src/com/android/emailcommon/provider/EmailContent.java
@@ -97,6 +97,7 @@ public abstract class EmailContent {
public static final String FIELD_COLUMN_NAME = "field";
public static final String ADD_COLUMN_NAME = "add";
+ public static final String SET_COLUMN_NAME = "set";
// Newly created objects get this id
public static final int NOT_SAVED = -1;
diff --git a/emailcommon/src/com/android/emailcommon/service/AccountServiceProxy.java b/emailcommon/src/com/android/emailcommon/service/AccountServiceProxy.java
index 55e381908..0bf0d146a 100644
--- a/emailcommon/src/com/android/emailcommon/service/AccountServiceProxy.java
+++ b/emailcommon/src/com/android/emailcommon/service/AccountServiceProxy.java
@@ -22,6 +22,8 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
+import java.util.List;
+
public class AccountServiceProxy extends ServiceProxy implements IAccountService {
public static final String ACCOUNT_INTENT = "com.android.email.ACCOUNT_INTENT";
@@ -44,7 +46,7 @@ public class AccountServiceProxy extends ServiceProxy implements IAccountService
}
@Override
- public void notifyLoginFailed(final long accountId) throws RemoteException {
+ public void notifyLoginFailed(final long accountId) {
setTask(new ProxyTask() {
public void run() throws RemoteException {
mService.notifyLoginFailed(accountId);
@@ -53,7 +55,7 @@ public class AccountServiceProxy extends ServiceProxy implements IAccountService
}
@Override
- public void notifyLoginSucceeded(final long accountId) throws RemoteException {
+ public void notifyLoginSucceeded(final long accountId) {
setTask(new ProxyTask() {
public void run() throws RemoteException {
mService.notifyLoginSucceeded(accountId);
@@ -62,16 +64,17 @@ public class AccountServiceProxy extends ServiceProxy implements IAccountService
}
@Override
- public void notifyNewMessages(final long accountId) throws RemoteException {
+ @SuppressWarnings("unchecked")
+ public void notifyNewMessages(final long accountId, final List messageIdList) {
setTask(new ProxyTask() {
public void run() throws RemoteException {
- mService.notifyNewMessages(accountId);
+ mService.notifyNewMessages(accountId, messageIdList);
}
}, "notifyNewMessages");
}
@Override
- public void accountDeleted() throws RemoteException {
+ public void accountDeleted() {
setTask(new ProxyTask() {
public void run() throws RemoteException {
mService.accountDeleted();
@@ -81,7 +84,7 @@ public class AccountServiceProxy extends ServiceProxy implements IAccountService
// The following call is synchronous, and should not be made from the UI thread
@Override
- public void restoreAccountsIfNeeded() throws RemoteException {
+ public void restoreAccountsIfNeeded() {
setTask(new ProxyTask() {
public void run() throws RemoteException {
mService.restoreAccountsIfNeeded();
@@ -92,7 +95,7 @@ public class AccountServiceProxy extends ServiceProxy implements IAccountService
// The following call is synchronous, and should not be made from the UI thread
@Override
- public int getAccountColor(final long accountId) throws RemoteException {
+ public int getAccountColor(final long accountId) {
setTask(new ProxyTask() {
public void run() throws RemoteException{
mReturn = mService.getAccountColor(accountId);
@@ -107,7 +110,7 @@ public class AccountServiceProxy extends ServiceProxy implements IAccountService
}
// The following call is synchronous, and should not be made from the UI thread
- public Bundle getConfigurationData(final String accountType) throws RemoteException {
+ public Bundle getConfigurationData(final String accountType) {
setTask(new ProxyTask() {
public void run() throws RemoteException{
mReturn = mService.getConfigurationData(accountType);
@@ -122,7 +125,7 @@ public class AccountServiceProxy extends ServiceProxy implements IAccountService
}
// The following call is synchronous, and should not be made from the UI thread
- public String getDeviceId() throws RemoteException {
+ public String getDeviceId() {
setTask(new ProxyTask() {
public void run() throws RemoteException{
mReturn = mService.getDeviceId();
diff --git a/emailcommon/src/com/android/emailcommon/service/IAccountService.aidl b/emailcommon/src/com/android/emailcommon/service/IAccountService.aidl
index ea945c04e..db8908c0c 100644
--- a/emailcommon/src/com/android/emailcommon/service/IAccountService.aidl
+++ b/emailcommon/src/com/android/emailcommon/service/IAccountService.aidl
@@ -21,7 +21,7 @@ import android.os.Bundle;
interface IAccountService {
oneway void notifyLoginFailed(long accountId);
oneway void notifyLoginSucceeded(long accountId);
- oneway void notifyNewMessages(long accountId);
+ oneway void notifyNewMessages(long accountId, in List messageIdList);
void accountDeleted();
void restoreAccountsIfNeeded();
diff --git a/src/com/android/email/Controller.java b/src/com/android/email/Controller.java
index ed130e316..b2fc76de1 100644
--- a/src/com/android/email/Controller.java
+++ b/src/com/android/email/Controller.java
@@ -58,6 +58,7 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidParameterException;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.concurrent.ConcurrentHashMap;
@@ -1142,7 +1143,7 @@ public class Controller {
* @param numNewMessages the number of new messages delivered
*/
public void updateMailboxCallback(MessagingException result, long accountId,
- long mailboxId, int progress, int numNewMessages) {
+ long mailboxId, int progress, int numNewMessages, ArrayList<Long> addedMessages) {
}
/**
@@ -1292,17 +1293,18 @@ public class Controller {
public void synchronizeMailboxStarted(long accountId, long mailboxId) {
synchronized (mListeners) {
for (Result l : mListeners) {
- l.updateMailboxCallback(null, accountId, mailboxId, 0, 0);
+ l.updateMailboxCallback(null, accountId, mailboxId, 0, 0, null);
}
}
}
@Override
public void synchronizeMailboxFinished(long accountId, long mailboxId,
- int totalMessagesInMailbox, int numNewMessages) {
+ int totalMessagesInMailbox, int numNewMessages, ArrayList<Long> addedMessages) {
synchronized (mListeners) {
for (Result l : mListeners) {
- l.updateMailboxCallback(null, accountId, mailboxId, 100, numNewMessages);
+ l.updateMailboxCallback(null, accountId, mailboxId, 100, numNewMessages,
+ addedMessages);
}
}
}
@@ -1317,7 +1319,7 @@ public class Controller {
}
synchronized (mListeners) {
for (Result l : mListeners) {
- l.updateMailboxCallback(me, accountId, mailboxId, 0, 0);
+ l.updateMailboxCallback(me, accountId, mailboxId, 0, 0, null);
}
}
}
@@ -1566,7 +1568,7 @@ public class Controller {
long accountId = mbx.mAccountKey;
synchronized(mListeners) {
for (Result listener : mListeners) {
- listener.updateMailboxCallback(result, accountId, mailboxId, progress, 0);
+ listener.updateMailboxCallback(result, accountId, mailboxId, progress, 0, null);
}
}
}
diff --git a/src/com/android/email/ControllerResultUiThreadWrapper.java b/src/com/android/email/ControllerResultUiThreadWrapper.java
index 1f1bf25c6..9a33b929f 100644
--- a/src/com/android/email/ControllerResultUiThreadWrapper.java
+++ b/src/com/android/email/ControllerResultUiThreadWrapper.java
@@ -19,6 +19,8 @@ package com.android.email;
import com.android.email.Controller.Result;
import com.android.emailcommon.mail.MessagingException;
+import java.util.ArrayList;
+
import android.os.Handler;
/**
@@ -106,12 +108,13 @@ 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) {
+ final long mailboxId, final int progress, final int numNewMessages,
+ final ArrayList<Long> addedMessages) {
run(new Runnable() {
public void run() {
if (!isRegistered()) return;
mWrappee.updateMailboxCallback(result, accountId, mailboxId, progress,
- numNewMessages);
+ numNewMessages, addedMessages);
}
});
}
diff --git a/src/com/android/email/GroupMessagingListener.java b/src/com/android/email/GroupMessagingListener.java
index 06d162147..a7b87c288 100644
--- a/src/com/android/email/GroupMessagingListener.java
+++ b/src/com/android/email/GroupMessagingListener.java
@@ -20,6 +20,7 @@ import com.android.emailcommon.mail.MessagingException;
import android.content.Context;
+import java.util.ArrayList;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -80,10 +81,10 @@ public class GroupMessagingListener extends MessagingListener {
@Override
synchronized public void synchronizeMailboxFinished(long accountId, long mailboxId,
- int totalMessagesInMailbox, int numNewMessages) {
+ int totalMessagesInMailbox, int numNewMessages, ArrayList<Long> addedMessages) {
for (MessagingListener l : mListeners) {
l.synchronizeMailboxFinished(accountId, mailboxId,
- totalMessagesInMailbox, numNewMessages);
+ totalMessagesInMailbox, numNewMessages, addedMessages);
}
}
diff --git a/src/com/android/email/MessagingController.java b/src/com/android/email/MessagingController.java
index 4aea7bf15..f46a1d27d 100644
--- a/src/com/android/email/MessagingController.java
+++ b/src/com/android/email/MessagingController.java
@@ -18,7 +18,6 @@ package com.android.email;
import com.android.email.mail.Sender;
import com.android.email.mail.Store;
-import com.android.email.mail.StoreSynchronizer;
import com.android.emailcommon.Logging;
import com.android.emailcommon.internet.MimeBodyPart;
import com.android.emailcommon.internet.MimeHeader;
@@ -330,20 +329,19 @@ public class MessagingController implements Runnable {
mListeners.synchronizeMailboxStarted(account.mId, folder.mId);
if ((folder.mFlags & Mailbox.FLAG_HOLDS_MAIL) == 0) {
// We don't hold messages, so, nothing to synchronize
- mListeners.synchronizeMailboxFinished(account.mId, folder.mId, 0, 0);
+ mListeners.synchronizeMailboxFinished(account.mId, folder.mId, 0, 0, null);
return;
}
NotificationController nc = NotificationController.getInstance(mContext);
try {
processPendingActionsSynchronous(account);
- StoreSynchronizer.SyncResults results;
-
// Select generic sync or store-specific sync
- results = synchronizeMailboxGeneric(account, folder);
+ SyncResults results = synchronizeMailboxGeneric(account, folder);
mListeners.synchronizeMailboxFinished(account.mId, folder.mId,
results.mTotalMessages,
- results.mNewMessages);
+ results.mAddedMessages.size(),
+ results.mAddedMessages);
// Clear authentication notification for this account
nc.cancelLoginFailedNotification(account.mId);
} catch (MessagingException e) {
@@ -409,17 +407,23 @@ public class MessagingController implements Runnable {
* @return results of the sync pass
* @throws MessagingException
*/
- private StoreSynchronizer.SyncResults synchronizeMailboxGeneric(
+ private SyncResults synchronizeMailboxGeneric(
final EmailContent.Account account, final EmailContent.Mailbox folder)
throws MessagingException {
+ /*
+ * A list of IDs for messages that were downloaded and did not have the seen flag set.
+ * This serves as the "true" new message count reported to the user via notification.
+ */
+ final ArrayList<Long> unseenMessages = new ArrayList<Long>();
+
Log.d(Logging.LOG_TAG, "*** synchronizeMailboxGeneric ***");
ContentResolver resolver = mContext.getContentResolver();
// 0. We do not ever sync DRAFTS or OUTBOX (down or up)
if (folder.mType == Mailbox.TYPE_DRAFTS || folder.mType == Mailbox.TYPE_OUTBOX) {
int totalMessages = EmailContent.count(mContext, folder.getUri(), null, null);
- return new StoreSynchronizer.SyncResults(totalMessages, 0);
+ return new SyncResults(totalMessages, unseenMessages);
}
// 1. Get the message list from the local store and create an index of the uids
@@ -474,7 +478,7 @@ public class MessagingController implements Runnable {
|| folder.mType == Mailbox.TYPE_DRAFTS) {
if (!remoteFolder.exists()) {
if (!remoteFolder.create(FolderType.HOLDS_MESSAGES)) {
- return new StoreSynchronizer.SyncResults(0, 0);
+ return new SyncResults(0, unseenMessages);
}
}
}
@@ -542,13 +546,6 @@ public class MessagingController implements Runnable {
// 8. Download basic info about the new/unloaded messages (if any)
/*
- * A list of messages that were downloaded and which did not have the Seen flag set.
- * This will serve to indicate the true "new" message count that will be reported to
- * the user via notification.
- */
- final ArrayList<Message> newMessages = new ArrayList<Message>();
-
- /*
* Fetch the flags and envelope only of the new messages. This is intended to get us
* critical data as fast as possible, and then we'll fill in the details.
*/
@@ -584,7 +581,7 @@ public class MessagingController implements Runnable {
saveOrUpdate(localMessage, mContext);
// Track the "new" ness of the downloaded message
if (!message.isSet(Flag.SEEN)) {
- newMessages.add(message);
+ unseenMessages.add(localMessage.mId);
}
} catch (MessagingException me) {
Log.e(Logging.LOG_TAG,
@@ -753,7 +750,7 @@ public class MessagingController implements Runnable {
// 14. Clean up and report results
remoteFolder.close(false);
- return new StoreSynchronizer.SyncResults(remoteMessageCount, newMessages.size());
+ return new SyncResults(remoteMessageCount, unseenMessages);
}
/**
@@ -1960,4 +1957,20 @@ public class MessagingController implements Runnable {
return description;
}
}
+
+ /** Results of the latest synchronization. */
+ private static class SyncResults {
+ /** The total # of messages in the folder */
+ public final int mTotalMessages;
+ /** A list of new message IDs; must not be {@code null} */
+ public final ArrayList<Long> mAddedMessages;
+
+ public SyncResults(int totalMessages, ArrayList<Long> addedMessages) {
+ if (addedMessages == null) {
+ throw new IllegalArgumentException("addedMessages must not be null");
+ }
+ mTotalMessages = totalMessages;
+ mAddedMessages = addedMessages;
+ }
+ }
}
diff --git a/src/com/android/email/MessagingListener.java b/src/com/android/email/MessagingListener.java
index 8b5573f7d..3371614a8 100644
--- a/src/com/android/email/MessagingListener.java
+++ b/src/com/android/email/MessagingListener.java
@@ -20,6 +20,8 @@ import com.android.emailcommon.mail.MessagingException;
import android.content.Context;
+import java.util.ArrayList;
+
/**
* Defines the interface that MessagingController will use to callback to requesters. This class
* is defined as non-abstract so that someone who wants to receive only a few messages can
@@ -40,16 +42,25 @@ public class MessagingListener {
public void listFoldersFinished(long accountId) {
}
- public void synchronizeMailboxStarted(long accountId, long mailboxId)
- {
+ public void synchronizeMailboxStarted(long accountId, long mailboxId) {
}
- public void synchronizeMailboxFinished(long accountId,
- long mailboxId, int totalMessagesInMailbox, int numNewMessages) {
+ /**
+ * Synchronization of the mailbox finished. The mailbox and/or message databases have been
+ * updated accordingly.
+ *
+ * @param accountId The account that was synchronized
+ * @param mailboxId The mailbox that was synchronized
+ * @param totalMessagesInMailbox The total number of messages in the mailbox
+ * @param numNewMessages The number of new messages
+ * @param addedMessages Message IDs of messages that were added during the synchronization.
+ * These are new, unread messages. Messages that were previously read are not in this list.
+ */
+ public void synchronizeMailboxFinished(long accountId, long mailboxId,
+ int totalMessagesInMailbox, int numNewMessages, ArrayList<Long> addedMessages) {
}
- public void synchronizeMailboxFailed(long accountId, long mailboxId,
- Exception e) {
+ public void synchronizeMailboxFailed(long accountId, long mailboxId, Exception e) {
}
public void loadMessageForViewStarted(long messageId) {
diff --git a/src/com/android/email/NotificationController.java b/src/com/android/email/NotificationController.java
index 7a8c23d09..08e1aa1f8 100644
--- a/src/com/android/email/NotificationController.java
+++ b/src/com/android/email/NotificationController.java
@@ -23,6 +23,7 @@ import com.android.email.activity.setup.AccountSettingsXL;
import com.android.emailcommon.mail.Address;
import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.provider.EmailContent.Account;
+import com.android.emailcommon.provider.EmailContent.AccountColumns;
import com.android.emailcommon.provider.EmailContent.Attachment;
import com.android.emailcommon.provider.EmailContent.Message;
import com.android.emailcommon.utility.EmailAsyncTask;
@@ -32,16 +33,26 @@ import com.google.common.annotations.VisibleForTesting;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
+import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.AudioManager;
import android.net.Uri;
+import android.os.Handler;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.TextAppearanceSpan;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.NoSuchElementException;
+
/**
* Class that manages notifications.
*/
@@ -63,6 +74,12 @@ public class NotificationController {
private final AudioManager mAudioManager;
private final Bitmap mGenericSenderIcon;
private final Clock mClock;
+ // TODO The service context used to create and manage the notification controller is NOT
+ // guaranteed to live forever. As such, we may lose the data in this structure. We should
+ // save / restore this data upon service termination / start. We'd also want to define
+ // the behaviour after a restart.
+ /** Maps account id to the message data */
+ private final HashMap<Long, MessageData> mNotificationMap;
/** Constructor */
@VisibleForTesting
@@ -74,6 +91,7 @@ public class NotificationController {
mGenericSenderIcon = BitmapFactory.decodeResource(mContext.getResources(),
R.drawable.ic_contact_picture);
mClock = clock;
+ mNotificationMap = new HashMap<Long, MessageData>();
}
/** Singleton access */
@@ -97,11 +115,13 @@ public class NotificationController {
* @param largeIcon A large icon. May be {@code null}
* @param number A number to display using {@link Notification.Builder#setNumber(int)}. May
* be {@code null}.
+ * @param enableAudio If {@code false}, do not play any sound. Otherwise, play sound according
+ * to the settings for the given account.
* @return A {@link Notification} that can be sent to the notification service.
*/
private Notification createAccountNotification(Account account, String ticker,
CharSequence title, String contentText, Intent intent, Bitmap largeIcon,
- Integer number) {
+ Integer number, boolean enableAudio) {
// Pending Intent
PendingIntent pending = null;
if (intent != null) {
@@ -119,7 +139,10 @@ public class NotificationController {
.setSmallIcon(R.drawable.stat_notify_email_generic)
.setWhen(mClock.getTime())
.setTicker(ticker);
- setupSoundAndVibration(builder, account);
+
+ if (enableAudio) {
+ setupSoundAndVibration(builder, account);
+ }
Notification notification = builder.getNotification();
return notification;
@@ -137,8 +160,8 @@ public class NotificationController {
*/
private void showAccountNotification(Account account, String ticker, String title,
String contentText, Intent intent, int notificationId) {
- Notification notification = //nb.getNotification();
- createAccountNotification(account, ticker, title, contentText, intent, null, null);
+ Notification notification = createAccountNotification(account, ticker, title, contentText,
+ intent, null, null, true);
mNotificationManager.notify(notificationId, notification);
}
@@ -165,16 +188,35 @@ public class NotificationController {
* @param accountId The ID of the account to cancel for. If {@code -1}, "new message"
* notifications for all accounts will be canceled.
*/
- public void cancelNewMessageNotification(long accountId) {
+ public void cancelNewMessageNotification(final long accountId) {
if (accountId == -1) {
- new Utility.ForEachAccount(mContext) {
- @Override
- protected void performAction(long accountId) {
- cancelNewMessageNotification(accountId);
- }
- }.execute();
+ for (long id : mNotificationMap.keySet()) {
+ cancelNewMessageNotification(id);
+ }
} else {
+ MessageData data = mNotificationMap.remove(accountId);
+ if (data == null) {
+ // Not in map; nothing to do here
+ return;
+ }
+ // ensure we don't accidentally double-cancel a notification
+ final ContentObserver myObserver = data.mObserver;
+ data.mObserver = null;
mNotificationManager.cancel(getNewMessageNotificationId(accountId));
+
+ // now do the database work
+ EmailAsyncTask.runAsyncParallel(new Runnable() {
+ @Override
+ public void run() {
+ ContentResolver resolver = mContext.getContentResolver();
+ if (myObserver != null) {
+ resolver.unregisterContentObserver(myObserver);
+ }
+ Uri uri = Account.RESET_NEW_MESSAGE_COUNT_URI;
+ uri = ContentUris.withAppendedId(uri, accountId);
+ resolver.update(uri, null, null, null);
+ }
+ });
}
}
@@ -182,17 +224,58 @@ public class NotificationController {
* Show (or update) a "new message" notification for the given account.
*
* @param accountId The ID of the account to display a notification for.
- * @param unseenMessageCount The number of messages in the account that are unseen.
+ * @param addedMessages A list of new message IDs added to the given account.
*/
- public void showNewMessageNotification(final long accountId, final int unseenMessageCount,
- final int justFetchedCount) {
+ public void showNewMessageNotification(final long accountId,
+ final ArrayList<Long> addedMessages) {
+ if (addedMessages == null || addedMessages.size() == 0) {
+ // No messages added; nothing to do here
+ return;
+ }
+ MessageData data = mNotificationMap.get(accountId);
+ if (data == null) {
+ data = new MessageData();
+ mNotificationMap.put(accountId, data);
+ }
+ final HashSet<Long> idSet = data.mMessageList;
+ synchronized (idSet) {
+ idSet.addAll(addedMessages);
+ }
+ // Pick a message to observe
+ final long messageId = idSet.iterator().next();
+ final ContentObserver myObserver;
+ if (data.mObserver == null) {
+ myObserver = new MessageContentObserver(Utility.getMainThreadHandler(), mContext,
+ accountId, messageId);
+ data.mObserver = myObserver;
+ } else {
+ myObserver = data.mObserver;
+ }
+
EmailAsyncTask.runAsyncParallel(new Runnable() {
@Override
public void run() {
- Notification n = createNewMessageNotification(accountId, unseenMessageCount);
+ ContentResolver resolver = mContext.getContentResolver();
+ // Atomically update the unseen count
+ ContentValues cv = new ContentValues();
+ cv.put(EmailContent.FIELD_COLUMN_NAME, AccountColumns.NEW_MESSAGE_COUNT);
+ cv.put(EmailContent.ADD_COLUMN_NAME, addedMessages.size());
+ Uri uri = ContentUris.withAppendedId(Account.ADD_TO_FIELD_URI, accountId);
+ resolver.update(uri, cv, null, null);
+ // Get the unseen count
+ uri = ContentUris.withAppendedId(Account.CONTENT_URI, accountId);
+ int unseenMessageCount = Utility.getFirstRowInt(mContext, uri,
+ new String[] { AccountColumns.NEW_MESSAGE_COUNT }, null /*selection*/,
+ null /*selectionArgs*/, null /*sortOrder*/, 0 /*column*/, 0 /*default*/);
+ // Create the notification
+ Notification n = createNewMessageNotification(accountId, unseenMessageCount, true);
if (n == null) {
return;
}
+ // Register a content observer with one of the messages
+ uri = ContentUris.withAppendedId(EmailContent.Message.CONTENT_URI, messageId);
+ resolver.registerContentObserver(uri, false, myObserver);
+ // Make the notification visible
mNotificationManager.notify(getNewMessageNotificationId(accountId), n);
}
});
@@ -222,7 +305,8 @@ public class NotificationController {
* NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS)
*/
@VisibleForTesting
- Notification createNewMessageNotification(long accountId, int unseenMessageCount) {
+ Notification createNewMessageNotification(long accountId, int unseenMessageCount,
+ boolean enableAudio) {
final Account account = Account.restoreAccountWithId(mContext, accountId);
if (account == null) {
return null;
@@ -244,8 +328,8 @@ public class NotificationController {
final Bitmap largeIcon = senderPhoto != null ? senderPhoto : mGenericSenderIcon;
final Integer number = unseenMessageCount > 1 ? unseenMessageCount : null;
- Notification notification =
- createAccountNotification(account, null, title, subject, intent, largeIcon, number);
+ Notification notification = createAccountNotification(account, null, title, subject,
+ intent, largeIcon, number, enableAudio);
return notification;
}
@@ -416,4 +500,119 @@ public class NotificationController {
public void cancelSecurityNeededNotification() {
cancelNotification(NOTIFICATION_ID_SECURITY_NEEDED);
}
+
+ /**
+ * Observer invoked whenever a message we're notifying the user about changes.
+ */
+ private static class MessageContentObserver extends ContentObserver {
+ /** The account this observer is attached to */
+ private final long mAccountId;
+ /** A singular message ID to notify on */
+ private final long mMessageId;
+ /** The context */
+ private final Context mContext;
+ /** The handler we will be invoked on */
+ private final Handler mHandler;
+
+ MessageContentObserver(Handler handler, Context context, long accountId,
+ long messageId) {
+ super (handler);
+ mHandler = handler;
+ mContext = context;
+ mAccountId = accountId;
+ mMessageId = messageId;
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ super.onChange(selfChange);
+ final MessageData data = sInstance.mNotificationMap.get(mAccountId);
+ // If this account had been removed from the set of notifications or if the observer
+ // has been updated, make sure we don't get called again
+ if (data == null || data.mObserver != this) {
+ mContext.getContentResolver().unregisterContentObserver(this);
+ return;
+ }
+
+ // Ensure we're only handling one change at a time
+ EmailAsyncTask.runAsyncSerial(new Runnable() {
+ @Override
+ public void run() {
+ handleChange(data);
+ }
+ });
+ }
+
+ /**
+ * Performs any database operations to handle an observed change.
+ *
+ * NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS)
+ * @param data Message data for the observed account
+ */
+ private void handleChange(MessageData data) {
+ Message message = Message.restoreMessageWithId(mContext, mMessageId);
+ if (message != null && !message.mFlagRead) {
+ // do nothing; wait until this message is modified
+ return;
+ }
+
+ // message removed or read; get another one in the list and update the notification
+ // Remove ourselves from the set of notifiers
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.unregisterContentObserver(this);
+ synchronized (data.mMessageList) {
+ data.mMessageList.remove(mMessageId);
+ }
+ try {
+ for (;;) {
+ long nextMessageId = data.mMessageList.iterator().next();
+ Message nextMessage = Message.restoreMessageWithId(mContext, nextMessageId);
+ if ((nextMessage == null) || (nextMessage.mFlagRead)) {
+ synchronized (data.mMessageList) {
+ data.mMessageList.remove(nextMessageId);
+ }
+ continue;
+ }
+ data.mObserver = new MessageContentObserver(mHandler, mContext, mAccountId,
+ nextMessageId);
+ Uri uri = ContentUris.withAppendedId(
+ EmailContent.Message.CONTENT_URI, nextMessageId);
+ resolver.registerContentObserver(uri, false, data.mObserver);
+
+ // Update the new message count
+ int unseenMessageCount = data.mMessageList.size();
+ ContentValues cv = new ContentValues();
+
+ cv.put(EmailContent.SET_COLUMN_NAME, unseenMessageCount);
+ uri = ContentUris.withAppendedId(
+ Account.RESET_NEW_MESSAGE_COUNT_URI, mAccountId);
+ resolver.update(uri, cv, null, null);
+
+ // Re-display the notification w/o audio
+ Notification n = sInstance.createNewMessageNotification(mAccountId,
+ unseenMessageCount, false);
+ sInstance.mNotificationManager.notify(
+ sInstance.getNewMessageNotificationId(mAccountId), n);
+ break;
+ }
+ } catch (NoSuchElementException e) {
+ // this is not an error; it means the list is empty, so, hide the notification
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ // make sure we're on the UI thread to cancel the notification
+ sInstance.cancelNewMessageNotification(mAccountId);
+ }
+ });
+ }
+ }
+ }
+
+ /**
+ * Information about the message(s) we're notifying the user about.
+ */
+ private static class MessageData {
+ final HashSet<Long> mMessageList = new HashSet<Long>();
+ ContentObserver mObserver;
+ }
}
diff --git a/src/com/android/email/RefreshManager.java b/src/com/android/email/RefreshManager.java
index 3f20d9d9e..986a55dbd 100644
--- a/src/com/android/email/RefreshManager.java
+++ b/src/com/android/email/RefreshManager.java
@@ -385,7 +385,8 @@ public class RefreshManager {
*/
@Override
public void updateMailboxCallback(MessagingException exception, long accountId,
- long mailboxId, int progress, int dontUseNumNewMessages) {
+ long mailboxId, int progress, int dontUseNumNewMessages,
+ ArrayList<Long> addedMessages) {
if (LOG_ENABLED) {
Log.d(Logging.LOG_TAG, "updateMailboxCallback " + accountId + ", "
+ mailboxId + ", " + progress + ", " + exceptionToString(exception));
diff --git a/src/com/android/email/activity/AccountFolderList.java b/src/com/android/email/activity/AccountFolderList.java
index 218af0f01..a30912e33 100644
--- a/src/com/android/email/activity/AccountFolderList.java
+++ b/src/com/android/email/activity/AccountFolderList.java
@@ -28,6 +28,8 @@ import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.provider.EmailContent.Account;
import com.android.emailcommon.provider.EmailContent.Mailbox;
+import java.util.ArrayList;
+
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
@@ -286,7 +288,8 @@ public class AccountFolderList extends Activity implements AccountFolderListFrag
@Override
public void updateMailboxCallback(MessagingException result, long accountKey,
- long mailboxKey, int progress, int numNewMessages) {
+ long mailboxKey, int progress, int numNewMessages,
+ ArrayList<Long> addedMessages) {
updateProgress(result, progress);
}
diff --git a/src/com/android/email/activity/AccountFolderListFragment.java b/src/com/android/email/activity/AccountFolderListFragment.java
index 79537f19e..6fb28dc70 100644
--- a/src/com/android/email/activity/AccountFolderListFragment.java
+++ b/src/com/android/email/activity/AccountFolderListFragment.java
@@ -28,6 +28,8 @@ import com.android.emailcommon.provider.EmailContent.Message;
import com.android.emailcommon.provider.EmailContent.MessageColumns;
import com.android.emailcommon.utility.Utility;
+import java.util.ArrayList;
+
import android.app.Activity;
import android.app.ListFragment;
import android.content.Context;
@@ -448,7 +450,8 @@ public class AccountFolderListFragment extends ListFragment
private class ControllerResults extends Controller.Result {
@Override
public void updateMailboxCallback(MessagingException result, long accountKey,
- long mailboxKey, int progress, int numNewMessages) {
+ long mailboxKey, int progress, int numNewMessages,
+ ArrayList<Long> addedMessages) {
if (progress == 100) {
updateAccounts();
}
diff --git a/src/com/android/email/activity/EmailActivity.java b/src/com/android/email/activity/EmailActivity.java
index cd05b326d..c3fdee4b3 100644
--- a/src/com/android/email/activity/EmailActivity.java
+++ b/src/com/android/email/activity/EmailActivity.java
@@ -20,6 +20,7 @@ import com.android.email.Controller;
import com.android.email.ControllerResultUiThreadWrapper;
import com.android.email.Email;
import com.android.email.MessagingExceptionStrings;
+import com.android.email.NotificationController;
import com.android.email.R;
import com.android.emailcommon.Logging;
import com.android.emailcommon.mail.MessagingException;
@@ -51,6 +52,7 @@ import android.view.View;
import android.widget.TextView;
import java.security.InvalidParameterException;
+import java.util.ArrayList;
/**
* The main Email activity, which is used on both the tablet and the phone.
@@ -480,7 +482,7 @@ public class EmailActivity extends Activity implements View.OnClickListener {
@Override
public void updateMailboxCallback(MessagingException result, long accountId, long mailboxId,
- int progress, int numNewMessages) {
+ int progress, int numNewMessages, ArrayList<Long> addedMessages) {
handleError(result, accountId, progress);
}
diff --git a/src/com/android/email/activity/MailboxList.java b/src/com/android/email/activity/MailboxList.java
index e206447f4..829daf197 100644
--- a/src/com/android/email/activity/MailboxList.java
+++ b/src/com/android/email/activity/MailboxList.java
@@ -28,6 +28,8 @@ import com.android.emailcommon.provider.EmailContent.Account;
import com.android.emailcommon.provider.EmailContent.AccountColumns;
import com.android.emailcommon.utility.Utility;
+import java.util.ArrayList;
+
import android.app.ActionBar;
import android.app.Activity;
import android.content.ContentUris;
@@ -288,7 +290,7 @@ public class MailboxList extends Activity implements MailboxListFragment.Callbac
@Override
public void updateMailboxCallback(MessagingException result, long accountKey,
- long mailboxKey, int progress, int numNewMessages) {
+ long mailboxKey, int progress, int numNewMessages, ArrayList<Long> addedMessages) {
if (accountKey == mAccountId) {
updateBanner(result, progress);
updateProgress(result, progress);
diff --git a/src/com/android/email/mail/Store.java b/src/com/android/email/mail/Store.java
index cb6d0f49e..e84cdcafd 100644
--- a/src/com/android/email/mail/Store.java
+++ b/src/com/android/email/mail/Store.java
@@ -243,14 +243,6 @@ public abstract class Store {
}
/**
- * Get class of sync'er for this Store class
- * @return Message Sync controller, or null to use default
- */
- public StoreSynchronizer getMessageSynchronizer() {
- return null;
- }
-
- /**
* Some stores cannot download a message based only on the uid, and need the message structure
* to be preloaded and provided to them. This method allows a remote store to signal this
* requirement. Most stores do not need this and do not need to overload this method, which
diff --git a/src/com/android/email/mail/StoreSynchronizer.java b/src/com/android/email/mail/StoreSynchronizer.java
deleted file mode 100644
index bf7746de3..000000000
--- a/src/com/android/email/mail/StoreSynchronizer.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.email.mail;
-
-import com.android.email.MessagingListener;
-import com.android.emailcommon.mail.MessagingException;
-import com.android.emailcommon.provider.EmailContent;
-import com.android.email.GroupMessagingListener;
-
-import android.content.Context;
-
-/**
- * This interface allows a store to define a completely different synchronizer algorithm,
- * as necessary.
- */
-public interface StoreSynchronizer {
-
- /**
- * An object of this class is returned by SynchronizeMessagesSynchronous to report
- * the results of the sync run.
- */
- public static class SyncResults {
- /**
- * The total # of messages in the folder
- */
- public int mTotalMessages;
- /**
- * The # of new messages in the folder
- */
- public int mNewMessages;
-
- public SyncResults(int totalMessages, int newMessages) {
- mTotalMessages = totalMessages;
- mNewMessages = newMessages;
- }
- }
-
- /**
- * The job of this method is to synchronize messages between a remote folder and the
- * corresponding local folder.
- *
- * The following callbacks should be called during this operation:
- * {@link MessagingListener#synchronizeMailboxNewMessage(Account, String, Message)}
- * {@link MessagingListener#synchronizeMailboxRemovedMessage(Account, String, Message)}
- *
- * Callbacks (through listeners) *must* be synchronized on the listeners object, e.g.
- * synchronized (listeners) {
- * for(MessagingListener listener : listeners) {
- * listener.synchronizeMailboxNewMessage(account, folder, message);
- * }
- * }
- *
- * @param account The account to synchronize
- * @param folder The folder to synchronize
- * @param listeners callbacks to make during sync operation
- * @param context if needed for making system calls
- * @return an object describing the sync results
- */
- public SyncResults SynchronizeMessagesSynchronous(
- EmailContent.Account account, EmailContent.Mailbox folder,
- GroupMessagingListener listeners, Context context) throws MessagingException;
-
-}
diff --git a/src/com/android/email/mail/store/ExchangeStore.java b/src/com/android/email/mail/store/ExchangeStore.java
index ef118f3e3..15c55df1b 100644
--- a/src/com/android/email/mail/store/ExchangeStore.java
+++ b/src/com/android/email/mail/store/ExchangeStore.java
@@ -18,7 +18,6 @@ package com.android.email.mail.store;
import com.android.email.ExchangeUtils;
import com.android.email.mail.Store;
-import com.android.email.mail.StoreSynchronizer;
import com.android.emailcommon.mail.Folder;
import com.android.emailcommon.mail.MessagingException;
import com.android.emailcommon.provider.EmailContent.Account;
@@ -84,18 +83,6 @@ public class ExchangeStore extends Store {
}
/**
- * Get class of sync'er for this Store class. Because exchange Sync rules are so different
- * than IMAP or POP3, it's likely that an Exchange implementation will need its own sync
- * controller. If so, this function must return a non-null value.
- *
- * @return Message Sync controller, or null to use default
- */
- @Override
- public StoreSynchronizer getMessageSynchronizer() {
- return null;
- }
-
- /**
* Inform MessagingController that this store requires message structures to be prefetched
* before it can fetch message bodies (this is due to EAS protocol restrictions.)
* @return always true for EAS
diff --git a/src/com/android/email/provider/EmailProvider.java b/src/com/android/email/provider/EmailProvider.java
index 67d0aad6d..e68514a82 100644
--- a/src/com/android/email/provider/EmailProvider.java
+++ b/src/com/android/email/provider/EmailProvider.java
@@ -1664,8 +1664,16 @@ public class EmailProvider extends ContentProvider {
if (cache != null) {
cache.lock(id);
}
+ ContentValues newMessageCount = CONTENT_VALUES_RESET_NEW_MESSAGE_COUNT;
+ if (values != null) {
+ Long set = values.getAsLong(EmailContent.SET_COLUMN_NAME);
+ if (set != null) {
+ newMessageCount = new ContentValues();
+ newMessageCount.put(Account.NEW_MESSAGE_COUNT, set);
+ }
+ }
try {
- result = db.update(tableName, CONTENT_VALUES_RESET_NEW_MESSAGE_COUNT,
+ result = db.update(tableName, newMessageCount,
whereWithId(id, selection), selectionArgs);
} finally {
if (cache != null) {
diff --git a/src/com/android/email/service/AccountService.java b/src/com/android/email/service/AccountService.java
index 42725c276..1778c318a 100644
--- a/src/com/android/email/service/AccountService.java
+++ b/src/com/android/email/service/AccountService.java
@@ -25,16 +25,16 @@ import com.android.email.VendorPolicyLoader;
import com.android.emailcommon.Configuration;
import com.android.emailcommon.Device;
import com.android.emailcommon.service.IAccountService;
-import com.android.emailcommon.utility.Utility;
+import com.android.emailcommon.utility.EmailAsyncTask;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
-import android.os.RemoteException;
import java.io.IOException;
+import java.util.List;
public class AccountService extends Service {
@@ -44,37 +44,38 @@ public class AccountService extends Service {
private final IAccountService.Stub mBinder = new IAccountService.Stub() {
@Override
- public void notifyLoginFailed(long accountId) throws RemoteException {
+ public void notifyLoginFailed(long accountId) {
NotificationController.getInstance(mContext).showLoginFailedNotification(accountId);
}
@Override
- public void notifyLoginSucceeded(long accountId) throws RemoteException {
+ public void notifyLoginSucceeded(long accountId) {
NotificationController.getInstance(mContext).cancelLoginFailedNotification(accountId);
}
@Override
- public void notifyNewMessages(long accountId) throws RemoteException {
- MailService.actionNotifyNewMessages(mContext, accountId);
+ @SuppressWarnings("unchecked")
+ public void notifyNewMessages(long accountId, List messageIdList) {
+ MailService.actionNotifyNewMessages(mContext, accountId, messageIdList);
}
@Override
- public void restoreAccountsIfNeeded() throws RemoteException {
+ public void restoreAccountsIfNeeded() {
AccountBackupRestore.restoreAccountsIfNeeded(mContext);
}
@Override
- public void accountDeleted() throws RemoteException {
+ public void accountDeleted() {
MailService.accountDeleted(mContext);
}
@Override
- public int getAccountColor(long accountId) throws RemoteException {
+ public int getAccountColor(long accountId) {
return ResourceHelper.getInstance(mContext).getAccountColor(accountId);
}
@Override
- public Bundle getConfigurationData(String accountType) throws RemoteException {
+ public Bundle getConfigurationData(String accountType) {
Bundle bundle = new Bundle();
bundle.putBoolean(Configuration.EXCHANGE_CONFIGURATION_USE_ALTERNATE_STRINGS,
VendorPolicyLoader.getInstance(mContext).useAlternateExchangeStrings());
@@ -82,9 +83,9 @@ public class AccountService extends Service {
}
@Override
- public String getDeviceId() throws RemoteException {
+ public String getDeviceId() {
try {
- Utility.runAsync(new Runnable() {
+ EmailAsyncTask.runAsyncSerial(new Runnable() {
@Override
public void run() {
// Make sure the service is properly running (re: lifecycle)
diff --git a/src/com/android/email/service/MailService.java b/src/com/android/email/service/MailService.java
index d751f37e5..b9960bb5b 100644
--- a/src/com/android/email/service/MailService.java
+++ b/src/com/android/email/service/MailService.java
@@ -27,11 +27,11 @@ import com.android.emailcommon.AccountManagerTypes;
import com.android.emailcommon.mail.MessagingException;
import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.provider.EmailContent.Account;
-import com.android.emailcommon.provider.EmailContent.AccountColumns;
import com.android.emailcommon.provider.EmailContent.HostAuth;
import com.android.emailcommon.provider.EmailContent.Mailbox;
import com.android.emailcommon.utility.AccountReconciler;
-import com.android.emailcommon.utility.Utility;
+import com.android.emailcommon.utility.EmailAsyncTask;
+import com.google.common.annotations.VisibleForTesting;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
@@ -47,7 +47,6 @@ import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Handler;
import android.os.IBinder;
import android.os.SystemClock;
import android.text.TextUtils;
@@ -56,6 +55,7 @@ import android.util.Log;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.logging.Logger;
/**
* Background service for refreshing non-push email accounts.
@@ -82,24 +82,25 @@ public class MailService extends Service {
private static final String EXTRA_ACCOUNT = "com.android.email.intent.extra.ACCOUNT";
private static final String EXTRA_ACCOUNT_INFO = "com.android.email.intent.extra.ACCOUNT_INFO";
private static final String EXTRA_DEBUG_WATCHDOG = "com.android.email.intent.extra.WATCHDOG";
+ private static final String EXTRA_MESSAGE_ID_COUNT =
+ "com.android.email.intent.extra.MESSAGE_ID_COUNT";
+ private static final String EXTRA_MESSAGE_ID_PREFIX =
+ "com.android.email.intent.extra.MESSAGE_ID_";
- private static final int WATCHDOG_DELAY = 10 * 60 * 1000; // 10 minutes
+ /** Time between watchdog checks; in milliseconds */
+ private static final long WATCHDOG_DELAY = 10 * 60 * 1000; // 10 minutes
// Sentinel value asking to update mSyncReports if it's currently empty
/*package*/ static final int SYNC_REPORTS_ALL_ACCOUNTS_IF_EMPTY = -1;
// Sentinel value asking that mSyncReports be rebuilt
/*package*/ static final int SYNC_REPORTS_RESET = -2;
- private static final String[] NEW_MESSAGE_COUNT_PROJECTION =
- new String[] {AccountColumns.NEW_MESSAGE_COUNT};
-
private static MailService sMailService;
/*package*/ Controller mController;
private final Controller.Result mControllerCallback = new ControllerResults();
private ContentResolver mContentResolver;
private Context mContext;
- private Handler mHandler = new Handler();
private int mStartId;
@@ -150,28 +151,7 @@ public class MailService extends Service {
* @param accountId account to clear, or -1 for all accounts
*/
public static void resetNewMessageCount(final Context context, final long accountId) {
- synchronized (mSyncReports) {
- for (AccountSyncReport report : mSyncReports.values()) {
- if (accountId == -1 || accountId == report.accountId) {
- report.unseenMessageCount = 0;
- report.lastUnseenMessageCount = 0;
- }
- }
- }
- // Clear notification
NotificationController.getInstance(context).cancelNewMessageNotification(accountId);
-
- // now do the database - all accounts, or just one of them
- Utility.runAsync(new Runnable() {
- @Override
- public void run() {
- Uri uri = Account.RESET_NEW_MESSAGE_COUNT_URI;
- if (accountId != -1) {
- uri = ContentUris.withAppendedId(uri, accountId);
- }
- context.getContentResolver().update(uri, null, null, null);
- }
- });
}
/**
@@ -182,10 +162,21 @@ public class MailService extends Service {
* @param context a context
* @param accountId the id of the account that is reporting new messages
*/
- public static void actionNotifyNewMessages(Context context, long accountId) {
+ @SuppressWarnings("unchecked")
+ public static void actionNotifyNewMessages(
+ Context context, long accountId, List messageIdList) {
Intent i = new Intent(ACTION_NOTIFY_MAIL);
i.setClass(context, MailService.class);
i.putExtra(EXTRA_ACCOUNT, accountId);
+ int listSize = 0;
+ if (messageIdList != null) {
+ listSize = messageIdList.size();
+ for (int j = 0; j < listSize; j++) {
+ long messageId = (Long) messageIdList.get(j);
+ i.putExtra(EXTRA_MESSAGE_ID_PREFIX + j, messageId);
+ }
+ }
+ i.putExtra(EXTRA_MESSAGE_ID_COUNT, listSize);
context.startService(i);
}
@@ -203,7 +194,7 @@ public class MailService extends Service {
// Restore accounts, if it has not happened already
AccountBackupRestore.restoreAccountsIfNeeded(this);
- Utility.runAsync(new Runnable() {
+ EmailAsyncTask.runAsyncParallel(new Runnable() {
@Override
public void run() {
reconcilePopImapAccountsSync(MailService.this);
@@ -224,7 +215,7 @@ public class MailService extends Service {
if (ACTION_CHECK_MAIL.equals(action)) {
// DB access required to satisfy this intent, so offload from UI thread
- Utility.runAsync(new Runnable() {
+ EmailAsyncTask.runAsyncParallel(new Runnable() {
@Override
public void run() {
// If we have the data, restore the last-sync-times for each account
@@ -280,7 +271,7 @@ public class MailService extends Service {
if (Email.DEBUG) {
Log.d(LOG_TAG, "action: delete exchange accounts");
}
- Utility.runAsync(new Runnable() {
+ EmailAsyncTask.runAsyncParallel(new Runnable() {
public void run() {
Cursor c = mContentResolver.query(Account.CONTENT_URI, Account.ID_PROJECTION,
null, null, null);
@@ -304,7 +295,7 @@ public class MailService extends Service {
if (Email.DEBUG) {
Log.d(LOG_TAG, "action: send pending mail");
}
- Utility.runAsync(new Runnable() {
+ EmailAsyncTask.runAsyncParallel(new Runnable() {
public void run() {
mController.sendPendingMessages(accountId);
}
@@ -315,17 +306,14 @@ public class MailService extends Service {
if (Email.DEBUG) {
Log.d(LOG_TAG, "action: reschedule");
}
- final NotificationController nc = NotificationController.getInstance(this);
// DB access required to satisfy this intent, so offload from UI thread
- Utility.runAsync(new Runnable() {
+ EmailAsyncTask.runAsyncParallel(new Runnable() {
@Override
public void run() {
// Clear all notifications, in case account list has changed.
- //
- // TODO Clear notifications for non-existing accounts. Now that we have
- // separate notifications for each account, NotificationController should be
- // able to do that.
- nc.cancelNewMessageNotification(-1);
+ NotificationController
+ .getInstance(MailService.this)
+ .cancelNewMessageNotification(-1);
// When called externally, we refresh the sync reports table to pick up
// any changes in the account list or account settings
@@ -337,23 +325,23 @@ public class MailService extends Service {
});
} else if (ACTION_NOTIFY_MAIL.equals(action)) {
// DB access required to satisfy this intent, so offload from UI thread
- Utility.runAsync(new Runnable() {
+ EmailAsyncTask.runAsyncParallel(new Runnable() {
@Override
public void run() {
- // Get the current new message count
- Cursor c = mContentResolver.query(
- ContentUris.withAppendedId(Account.CONTENT_URI, accountId),
- NEW_MESSAGE_COUNT_PROJECTION, null, null, null);
- int newMessageCount = 0;
- try {
- if (c.moveToFirst()) {
- newMessageCount = c.getInt(0);
- updateAccountReport(accountId, newMessageCount);
- notifyNewMessages(accountId);
+ int newMessageCount = intent.getIntExtra(EXTRA_MESSAGE_ID_COUNT, 0);
+ ArrayList<Long> messageIdList = new ArrayList<Long>();
+ for (int i = 0; i < newMessageCount; i++) {
+ final long messageId =
+ intent.getLongExtra(EXTRA_MESSAGE_ID_PREFIX + i, -1L);
+ if (messageId <= 0) {
+ // What else to do here?? This should never happen ...
+ Log.w(LOG_TAG, "invalid message id in notification; id: " + messageId);
+ continue;
}
- } finally {
- c.close();
+ messageIdList.add(messageId);
}
+ updateAccountReport(accountId, newMessageCount);
+ notifyNewMessages(accountId, messageIdList);
if (Email.DEBUG) {
Log.d(LOG_TAG, "notify accountId=" + Long.toString(accountId)
+ " count=" + newMessageCount);
@@ -404,10 +392,7 @@ public class MailService extends Service {
AccountSyncReport oldReport = oldSyncReports.get(newReport.accountId);
if (oldReport != null) {
newReport.prevSyncTime = oldReport.prevSyncTime;
- if (newReport.syncInterval > 0 && newReport.prevSyncTime != 0) {
- newReport.nextSyncTime =
- newReport.prevSyncTime + (newReport.syncInterval * 1000 * 60);
- }
+ newReport.setNextSyncTime();
}
}
}
@@ -533,38 +518,32 @@ public class MailService extends Service {
* TODO: Look more closely at syncEnabled and see if we can simply coalesce it into
* syncInterval (e.g. if !syncEnabled, set syncInterval to -1).
*/
- /*package*/ static class AccountSyncReport {
+ @VisibleForTesting
+ static class AccountSyncReport {
long accountId;
- long prevSyncTime; // 0 == unknown
- long nextSyncTime; // 0 == ASAP -1 == don't sync
-
- /** # of "unseen" messages to show in notification */
- int unseenMessageCount;
-
- /**
- * # of unseen, the value shown on the last notification. Used to
- * calculate "the number of messages that have just been fetched".
- *
- * TODO It's a sort of cheating. Should we use the "real" number? The only difference
- * is the first notification after reboot / process restart.
- */
- int lastUnseenMessageCount;
-
+ /** The time of the last sync, or, {@code 0}, the last sync time is unknown. */
+ long prevSyncTime;
+ /** The time of the next sync. If {@code 0}, sync ASAP. If {@code 1}, don't sync. */
+ long nextSyncTime;
+ /** Minimum time between syncs; in minutes. */
int syncInterval;
+ /** If {@code true}, show system notifications. */
boolean notify;
+ /** If {@code true}, auto sync is enabled. */
+ boolean syncEnabled;
- boolean syncEnabled; // whether auto sync is enabled for this account
-
- /** # of messages that have just been fetched */
- int getJustFetchedMessageCount() {
- return unseenMessageCount - lastUnseenMessageCount;
+ /**
+ * Sets the next sync time using the previous sync time and sync interval.
+ */
+ void setNextSyncTime() {
+ if (syncInterval > 0 && prevSyncTime != 0) {
+ nextSyncTime = prevSyncTime + (syncInterval * 1000 * 60);
+ }
}
@Override
public String toString() {
- return "id=" + accountId
- + " prevSync=" + prevSyncTime + " nextSync=" + nextSyncTime + " numUnseen="
- + unseenMessageCount;
+ return "id=" + accountId + " prevSync=" + prevSyncTime + " nextSync=" + nextSyncTime;
}
}
@@ -644,8 +623,6 @@ public class MailService extends Service {
report.accountId = account.mId;
report.prevSyncTime = 0;
report.nextSyncTime = (syncInterval > 0) ? 0 : -1; // 0 == ASAP -1 == no sync
- report.unseenMessageCount = 0;
- report.lastUnseenMessageCount = 0;
report.syncInterval = syncInterval;
report.notify = (account.mFlags & Account.FLAGS_NOTIFY_NEW_MAIL) != 0;
@@ -685,12 +662,7 @@ public class MailService extends Service {
// 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.unseenMessageCount = newCount;
- }
+ report.setNextSyncTime();
if (Email.DEBUG) {
Log.d(LOG_TAG, "update account " + report.toString());
}
@@ -723,10 +695,7 @@ public class MailService extends Service {
if (report != null) {
if (report.prevSyncTime == 0) {
report.prevSyncTime = prevSync;
- if (report.syncInterval > 0 && report.prevSyncTime != 0) {
- report.nextSyncTime =
- report.prevSyncTime + (report.syncInterval * 1000 * 60);
- }
+ report.setNextSyncTime();
}
}
}
@@ -736,7 +705,8 @@ public class MailService extends Service {
class ControllerResults extends Controller.Result {
@Override
public void updateMailboxCallback(MessagingException result, long accountId,
- long mailboxId, int progress, int numNewMessages) {
+ long mailboxId, int progress, int numNewMessages,
+ ArrayList<Long> addedMessages) {
// First, look for authentication failures and notify
//checkAuthenticationStatus(result, accountId);
if (result != null || progress == 100) {
@@ -747,7 +717,7 @@ public class MailService extends Service {
if (progress == 100) {
updateAccountReport(accountId, numNewMessages);
if (numNewMessages > 0) {
- notifyNewMessages(accountId);
+ notifyNewMessages(accountId, addedMessages);
}
} else {
updateAccountReport(accountId, -1);
@@ -779,21 +749,16 @@ public class MailService extends Service {
/**
* Show "new message" notification for an account. (Notification is shown per account.)
*/
- private void notifyNewMessages(final long accountId) {
- final int unseenMessageCount;
- final int justFetchedCount;
+ private void notifyNewMessages(final long accountId, ArrayList<Long> addedMessages) {
synchronized (mSyncReports) {
AccountSyncReport report = mSyncReports.get(accountId);
- if (report == null || report.unseenMessageCount == 0 || !report.notify) {
+ if (report == null || !report.notify) {
return;
}
- unseenMessageCount = report.unseenMessageCount;
- justFetchedCount = report.getJustFetchedMessageCount();
- report.lastUnseenMessageCount = report.unseenMessageCount;
}
- NotificationController.getInstance(this).showNewMessageNotification(accountId,
- unseenMessageCount, justFetchedCount);
+ NotificationController.getInstance(this)
+ .showNewMessageNotification(accountId, addedMessages);
}
/**
@@ -874,7 +839,8 @@ public class MailService extends Service {
* @param blockExternalChanges FOR TESTING ONLY - block backups, security changes, etc.
* @param resolver the content resolver for making provider updates (injected for testability)
*/
- /* package */ public static void reconcileAccountsWithAccountManager(Context context,
+ @VisibleForTesting
+ public static void reconcileAccountsWithAccountManager(Context context,
List<Account> emailProviderAccounts, android.accounts.Account[] accountManagerAccounts,
boolean blockExternalChanges, ContentResolver resolver) {
boolean accountsDeleted = AccountReconciler.reconcileAccounts(context,
diff --git a/tests/src/com/android/email/NotificationControllerTest.java b/tests/src/com/android/email/NotificationControllerTest.java
index 2152362de..895c34dfc 100644
--- a/tests/src/com/android/email/NotificationControllerTest.java
+++ b/tests/src/com/android/email/NotificationControllerTest.java
@@ -213,7 +213,7 @@ public class NotificationControllerTest extends AndroidTestCase {
Mailbox b1 = ProviderTestUtils.setupMailbox("inbox", a1.mId, true, c, Mailbox.TYPE_INBOX);
Message m1 = ProviderTestUtils.setupMessage("message", a1.mId, b1.mId, true, true, c);
- n = mTarget.createNewMessageNotification(a1.mId, 1);
+ n = mTarget.createNewMessageNotification(a1.mId, 1, true);
assertEquals(R.drawable.stat_notify_email_generic, n.icon);
assertEquals(mMockClock.mTime, n.when);
@@ -223,7 +223,7 @@ public class NotificationControllerTest extends AndroidTestCase {
// TODO Check content -- how?
// Case 2: 1 account, 2 unseen message
- n = mTarget.createNewMessageNotification(a1.mId, 2);
+ n = mTarget.createNewMessageNotification(a1.mId, 2, true);
assertEquals(R.drawable.stat_notify_email_generic, n.icon);
assertEquals(mMockClock.mTime, n.when);
@@ -247,7 +247,7 @@ public class NotificationControllerTest extends AndroidTestCase {
m1.save(c);
// This shouldn't crash.
- n = mTarget.createNewMessageNotification(a1.mId, 1);
+ n = mTarget.createNewMessageNotification(a1.mId, 1, true);
// Minimum test for the result
assertEquals(R.drawable.stat_notify_email_generic, n.icon);
diff --git a/tests/src/com/android/email/RefreshManagerTest.java b/tests/src/com/android/email/RefreshManagerTest.java
index 30fdd8c51..694a6e493 100644
--- a/tests/src/com/android/email/RefreshManagerTest.java
+++ b/tests/src/com/android/email/RefreshManagerTest.java
@@ -260,7 +260,7 @@ public class RefreshManagerTest extends InstrumentationTestCase {
assertTrue(mTarget.isRefreshingAnyMessageListForTest());
// Refreshing mailbox 1...
- mController.mListener.updateMailboxCallback(null, ACCOUNT_1, MAILBOX_1, 0, 0);
+ mController.mListener.updateMailboxCallback(null, ACCOUNT_1, MAILBOX_1, 0, 0, null);
assertTrue(mListener.mCalledOnRefreshStatusChanged);
assertFalse(mListener.mCalledOnConnectionError);
@@ -272,7 +272,7 @@ public class RefreshManagerTest extends InstrumentationTestCase {
// Done.
Log.w(Logging.LOG_TAG, "" + mController.mListener.getClass());
- mController.mListener.updateMailboxCallback(null, ACCOUNT_1, MAILBOX_1, 100, 0);
+ mController.mListener.updateMailboxCallback(null, ACCOUNT_1, MAILBOX_1, 100, 0, null);
assertTrue(mListener.mCalledOnRefreshStatusChanged);
assertFalse(mListener.mCalledOnConnectionError);
@@ -289,7 +289,7 @@ public class RefreshManagerTest extends InstrumentationTestCase {
// Refreshing mailbox 2...
mClock.advance();
- mController.mListener.updateMailboxCallback(null, ACCOUNT_2, MAILBOX_2, 0, 0);
+ mController.mListener.updateMailboxCallback(null, ACCOUNT_2, MAILBOX_2, 0, 0, null);
assertTrue(mListener.mCalledOnRefreshStatusChanged);
assertFalse(mListener.mCalledOnConnectionError);
@@ -300,7 +300,7 @@ public class RefreshManagerTest extends InstrumentationTestCase {
assertEquals(0, mTarget.getMessageListStatusForTest(MAILBOX_2).getLastRefreshTime());
// Done with exception.
- mController.mListener.updateMailboxCallback(EXCEPTION, ACCOUNT_2, MAILBOX_2, 0, 0);
+ mController.mListener.updateMailboxCallback(EXCEPTION, ACCOUNT_2, MAILBOX_2, 0, 0, null);
assertTrue(mListener.mCalledOnRefreshStatusChanged);
assertTrue(mListener.mCalledOnConnectionError);