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
This commit is contained in:
Todd Kennedy 2011-05-03 14:42:26 -07:00
parent c96cd4a848
commit c4cdb11d24
23 changed files with 413 additions and 291 deletions

View File

@ -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

View File

@ -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;

View File

@ -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();

View File

@ -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();

View File

@ -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);
}
}
}

View File

@ -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);
}
});
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}
@ -541,13 +545,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;
}
}
}

View File

@ -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) {

View File

@ -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;
}
}

View File

@ -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));

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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);

View File

@ -242,14 +242,6 @@ public abstract class Store {
return com.android.email.activity.setup.AccountSetupIncoming.class;
}
/**
* 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

View File

@ -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;
}

View File

@ -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;
@ -83,18 +82,6 @@ public class ExchangeStore extends Store {
return com.android.email.activity.setup.AccountSetupExchange.class;
}
/**
* 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.)

View File

@ -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) {

View File

@ -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)

View File

@ -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;
/** 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;
/**
* # 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.
* Sets the next sync time using the previous sync time and sync interval.
*/
int lastUnseenMessageCount;
int syncInterval;
boolean notify;
boolean syncEnabled; // whether auto sync is enabled for this account
/** # of messages that have just been fetched */
int getJustFetchedMessageCount() {
return unseenMessageCount - lastUnseenMessageCount;
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,

View File

@ -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);

View File

@ -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);