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:
parent
c96cd4a848
commit
c4cdb11d24
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
||||
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);
|
||||
for (long id : mNotificationMap.keySet()) {
|
||||
cancelNewMessageNotification(id);
|
||||
}
|
||||
}.execute();
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
}
|
@ -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.)
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
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;
|
||||
}
|
||||
messageIdList.add(messageId);
|
||||
}
|
||||
updateAccountReport(accountId, newMessageCount);
|
||||
notifyNewMessages(accountId);
|
||||
}
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
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,
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user