Split NotificationController into multiple classes for maintainability

This commit is contained in:
cketti 2015-07-09 07:33:22 +02:00
parent 0f696bb1c5
commit 29bd67641b
8 changed files with 1083 additions and 824 deletions

View file

@ -0,0 +1,28 @@
package com.fsck.k9.helper;
import com.fsck.k9.mail.MessagingException;
public class ExceptionHelper {
public static String getRootCauseMessage(Throwable t) {
Throwable rootCause = t;
Throwable nextCause;
do {
nextCause = rootCause.getCause();
if (nextCause != null) {
rootCause = nextCause;
}
} while (nextCause != null);
if (rootCause instanceof MessagingException) {
return rootCause.getMessage();
}
// Remove the namespace on the exception so we have a fighting chance of seeing more of the error in the
// notification.
String simpleName = rootCause.getClass().getSimpleName();
return (rootCause.getLocalizedMessage() != null) ?
simpleName + ": " + rootCause.getLocalizedMessage() : simpleName;
}
}

View file

@ -0,0 +1,72 @@
package com.fsck.k9.notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;
import com.fsck.k9.Account;
import com.fsck.k9.R;
import com.fsck.k9.activity.setup.AccountSetupIncoming;
import com.fsck.k9.activity.setup.AccountSetupOutgoing;
import static com.fsck.k9.notification.NotificationController.NOTIFICATION_LED_BLINK_FAST;
import static com.fsck.k9.notification.NotificationController.NOTIFICATION_LED_FAILURE_COLOR;
class CertificateErrorNotifications {
private final NotificationController controller;
public CertificateErrorNotifications(NotificationController controller) {
this.controller = controller;
}
public void showCertificateErrorNotification(Account account, boolean incoming) {
int notificationId = NotificationIds.getCertificateErrorNotificationId(account, incoming);
Context context = controller.getContext();
Intent editServerSettingsIntent = incoming ?
AccountSetupIncoming.intentActionEditIncomingSettings(context, account) :
AccountSetupOutgoing.intentActionEditOutgoingSettings(context, account);
PendingIntent editServerSettingsPendingIntent = PendingIntent.getActivity(context,
account.getAccountNumber(), editServerSettingsIntent, PendingIntent.FLAG_UPDATE_CURRENT);
String title = context.getString(R.string.notification_certificate_error_title, account.getDescription());
String text = context.getString(R.string.notification_certificate_error_text);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
.setSmallIcon(getCertificateErrorNotificationIcon())
.setWhen(System.currentTimeMillis())
.setAutoCancel(true)
.setTicker(title)
.setContentTitle(title)
.setContentText(text)
.setContentIntent(editServerSettingsPendingIntent)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
controller.configureNotification(builder, null, null,
NOTIFICATION_LED_FAILURE_COLOR,
NOTIFICATION_LED_BLINK_FAST, true);
getNotificationManager().notify(notificationId, builder.build());
}
public void clearCertificateErrorNotifications(Account account, boolean incoming) {
int notificationId = NotificationIds.getCertificateErrorNotificationId(account, incoming);
getNotificationManager().cancel(notificationId);
}
private int getCertificateErrorNotificationIcon() {
//TODO: Use a different icon for certificate error notifications
return controller.platformSupportsVectorDrawables() ?
R.drawable.ic_notify_new_mail_vector : R.drawable.ic_notify_new_mail;
}
private NotificationManager getNotificationManager() {
return controller.getNotificationManager();
}
}

View file

@ -0,0 +1,518 @@
package com.fsck.k9.notification;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import android.app.KeyguardManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationCompat.Builder;
import android.support.v4.app.NotificationCompat.WearableExtender;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.K9.NotificationHideSubject;
import com.fsck.k9.K9.NotificationQuickDelete;
import com.fsck.k9.NotificationSetting;
import com.fsck.k9.R;
import com.fsck.k9.activity.MessageReference;
import com.fsck.k9.activity.NotificationDeleteConfirmation;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mailstore.LocalMessage;
import static com.fsck.k9.notification.NotificationController.NOTIFICATION_LED_BLINK_SLOW;
class NewMailNotifications {
private static final String NOTIFICATION_GROUP_KEY = "newMailNotifications";
private final Context context;
private final NotificationController controller;
private final NotificationActionCreator actionCreator;
private final NotificationContentCreator contentCreator;
private final ConcurrentMap<Integer, NotificationsHolder> notificationsHolders =
new ConcurrentHashMap<Integer, NotificationsHolder>();
public NewMailNotifications(NotificationController controller, NotificationActionCreator actionCreator) {
this.controller = controller;
this.actionCreator = actionCreator;
this.context = controller.getContext();
this.contentCreator = new NotificationContentCreator(context);
}
public void addNewMailNotification(Account account, LocalMessage message, int previousUnreadMessageCount) {
NotificationsHolder notificationsHolder = getOrCreateNotificationData(account, previousUnreadMessageCount);
synchronized (notificationsHolder) {
notifyAccountWithDataLocked(account, message, notificationsHolder);
}
}
public void removeNewMailNotification(Account account, LocalMessage localMessage) {
NotificationsHolder notificationsHolder = getNotificationData(account);
if (notificationsHolder == null) {
return;
}
synchronized (notificationsHolder) {
MessageReference messageReference = localMessage.makeMessageReference();
if (notificationsHolder.removeMatchingMessage(context, messageReference)) {
Integer childNotification = notificationsHolder.getStackedChildNotification(messageReference);
if (childNotification != null) {
getNotificationManager().cancel(childNotification);
}
notifyAccountWithDataLocked(account, null, notificationsHolder);
}
}
}
public void clearNewMailNotifications(Account account) {
int newMailNotificationId = NotificationIds.getNewMailNotificationId(account);
getNotificationManager().cancel(newMailNotificationId);
notificationsHolders.remove(account.getAccountNumber());
//TODO: clear stacked notifications
}
private NotificationsHolder getOrCreateNotificationData(Account account, int previousUnreadMessageCount) {
int accountNumber = account.getAccountNumber();
NotificationsHolder notificationsHolder = notificationsHolders.get(accountNumber);
if (notificationsHolder == null) {
NotificationsHolder newNotificationHolder = new NotificationsHolder(previousUnreadMessageCount);
NotificationsHolder oldNotificationHolder =
notificationsHolders.putIfAbsent(accountNumber, newNotificationHolder);
if (oldNotificationHolder != null) {
notificationsHolder = oldNotificationHolder;
} else {
notificationsHolder = newNotificationHolder;
}
}
return notificationsHolder;
}
private NotificationsHolder getNotificationData(Account account) {
int accountNumber = account.getAccountNumber();
return notificationsHolders.get(accountNumber);
}
private void notifyAccountWithDataLocked(Account account, LocalMessage newMessage,
NotificationsHolder notificationsHolder) {
boolean updateSilently = false;
if (newMessage == null) {
newMessage = notificationsHolder.getNewestMessage(context);
updateSilently = true;
if (newMessage == null) {
clearNewMailNotifications(account);
return;
}
} else {
notificationsHolder.addMessage(newMessage);
}
int newMessagesCount = notificationsHolder.getNewMessagesCount();
int unreadMessagesCount = notificationsHolder.getUnreadMessagesCountBeforeNotification() + newMessagesCount;
CharSequence sender = contentCreator.getMessageSender(account, newMessage);
CharSequence subject = contentCreator.getMessageSubject(newMessage);
CharSequence summary = contentCreator.buildMessageSummary(sender, subject);
boolean privacyModeEnabled = isPrivacyModeEnabled();
if (privacyModeEnabled || summary.length() == 0) {
summary = context.getString(R.string.notification_new_title);
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
.setSmallIcon(getNewMailNotificationIcon())
.setColor(account.getChipColor())
.setWhen(System.currentTimeMillis())
.setNumber(unreadMessagesCount);
if (!updateSilently) {
builder.setTicker(summary);
}
String accountName = controller.getAccountName(account);
ArrayList<MessageReference> allNotificationMessageReferences =
notificationsHolder.getMessageReferencesForAllNotifications();
if (NotificationController.platformSupportsExtendedNotifications() && !privacyModeEnabled) {
boolean singleMessageNotification = (newMessagesCount == 1);
if (singleMessageNotification) {
createSingleMessageNotification(account, newMessage, sender, subject, builder,
accountName, allNotificationMessageReferences);
} else {
createInboxStyleNotification(account, notificationsHolder, builder, newMessagesCount, accountName);
}
addMarkAsReadAction(builder, account, allNotificationMessageReferences);
if (isDeleteActionEnabled(singleMessageNotification)) {
addDeleteAction(builder, account, allNotificationMessageReferences);
}
} else {
String accountNotice = context.getString(R.string.notification_new_one_account_fmt,
unreadMessagesCount, accountName);
builder.setContentTitle(accountNotice);
builder.setContentText(summary);
}
for (Message message : notificationsHolder.getMessagesForSummaryNotification()) {
if (message.isSet(Flag.FLAGGED)) {
builder.setPriority(NotificationCompat.PRIORITY_HIGH);
break;
}
}
PendingIntent notificationPendingIntent = actionCreator.createSummaryNotificationActionPendingIntent(
account, newMessage, newMessagesCount, unreadMessagesCount, allNotificationMessageReferences);
builder.setContentIntent(notificationPendingIntent);
PendingIntent deletePendingIntent = NotificationActionService.createDismissPendingIntent(
context, account, account.getAccountNumber());
builder.setDeleteIntent(deletePendingIntent);
boolean ringAndVibrate = false;
if (!updateSilently && !account.isRingNotified()) {
account.setRingNotified(true);
ringAndVibrate = true;
}
configureLockScreenNotification(builder, account, newMessagesCount, unreadMessagesCount, accountName,
sender, notificationsHolder.getMessagesForSummaryNotification());
NotificationSetting notificationSetting = account.getNotificationSetting();
controller.configureNotification(
builder,
(notificationSetting.shouldRing()) ? notificationSetting.getRingtone() : null,
(notificationSetting.shouldVibrate()) ? notificationSetting.getVibration() : null,
(notificationSetting.isLed()) ? notificationSetting.getLedColor() : null,
NOTIFICATION_LED_BLINK_SLOW,
ringAndVibrate);
int notificationId = NotificationIds.getNewMailNotificationId(account);
getNotificationManager().notify(notificationId, builder.build());
}
private void createInboxStyleNotification(Account account, NotificationsHolder notificationsHolder, Builder builder,
int newMessagesCount, String accountName) {
String title = context.getResources().getQuantityString(R.plurals.notification_new_messages_title,
newMessagesCount, newMessagesCount);
NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle(builder);
style.setBigContentTitle(title);
if (notificationsHolder.hasAdditionalMessages()) {
String summaryText = context.getString(R.string.notification_additional_messages,
notificationsHolder.getAdditionalMessagesCount(), accountName);
style.setSummaryText(summaryText);
}
int notificationOffset = 1;
for (LocalMessage message : notificationsHolder.getMessagesForSummaryNotification()) {
MessageReference messageReference = message.makeMessageReference();
CharSequence sender = contentCreator.getMessageSender(account, message);
CharSequence subject = contentCreator.getMessageSubject(message);
style.addLine(contentCreator.buildMessageSummary(sender, subject));
Builder stackedNotificationBuilder = new Builder(context)
.setSmallIcon(R.drawable.ic_notify_new_mail)
.setWhen(System.currentTimeMillis())
.setGroup(NOTIFICATION_GROUP_KEY)
.setAutoCancel(true);
int notificationId;
Integer existingNotificationId = notificationsHolder.getStackedChildNotification(messageReference);
if (existingNotificationId != null) {
notificationId = existingNotificationId;
} else {
notificationId = NotificationIds.getNewMailNotificationId(account, notificationOffset);
}
setNotificationContent(message, sender, subject, stackedNotificationBuilder, accountName);
addWearActionsForSingleMessage(stackedNotificationBuilder, account, message, notificationId);
if (message.isSet(Flag.FLAGGED)) {
stackedNotificationBuilder.setPriority(NotificationCompat.PRIORITY_HIGH);
}
notificationsHolder.addStackedChildNotification(messageReference, notificationId);
getNotificationManager().notify(notificationId, stackedNotificationBuilder.build());
notificationOffset++;
}
builder.setGroup(NOTIFICATION_GROUP_KEY)
.setGroupSummary(true)
.setLocalOnly(true)
.setContentTitle(title)
.setSubText(accountName)
.setStyle(style);
}
private void createSingleMessageNotification(Account account, LocalMessage message,
CharSequence sender, CharSequence subject, Builder builder, String accountName,
ArrayList<MessageReference> allNotificationMessageReferences) {
setNotificationContent(message, sender, subject, builder, accountName);
addReplyAction(builder, account, message);
addWearActions(builder, account, allNotificationMessageReferences,
account.getAccountNumber());
}
private NotificationCompat.Builder setNotificationContent(Message message, CharSequence sender,
CharSequence subject, Builder builder, String accountName) {
NotificationCompat.BigTextStyle style = new NotificationCompat.BigTextStyle(builder);
CharSequence preview = contentCreator.getMessagePreview(message);
if (preview != null) {
style.bigText(preview);
}
builder.setStyle(style)
.setContentTitle(sender)
.setContentText(subject)
.setSubText(accountName);
return builder;
}
private void addMarkAsReadAction(Builder builder, Account account,
ArrayList<MessageReference> allNotificationMessageReferences) {
int icon = getMarkAsReadActionIcon();
String title = context.getString(R.string.notification_action_mark_as_read);
PendingIntent readAllMessagesPendingIntent = NotificationActionService.createMarkAsReadPendingIntent(
context, account, allNotificationMessageReferences, account.getAccountNumber());
builder.addAction(icon, title, readAllMessagesPendingIntent);
}
private void addDeleteAction(Builder builder, Account account,
ArrayList<MessageReference> allNotificationMessageReferences) {
int icon = getDeleteActionIcon();
String title = context.getString(R.string.notification_action_delete);
PendingIntent confirmDeletionPendingIntent = NotificationDeleteConfirmation.getIntent(
context, account, allNotificationMessageReferences, account.getAccountNumber());
builder.addAction(icon, title, confirmDeletionPendingIntent);
}
private void addReplyAction(Builder builder, Account account, LocalMessage message) {
int icon = getReplyActionIcon();
String title = context.getString(R.string.notification_action_reply);
PendingIntent replyToMessagePendingIntent = NotificationActionService.createReplyPendingIntent(
context, account, message.makeMessageReference(), account.getAccountNumber());
builder.addAction(icon, title, replyToMessagePendingIntent);
}
private void addWearActionsForSingleMessage(Builder builder, Account account, LocalMessage message,
int notificationId) {
ArrayList<MessageReference> allNotificationMessageReferences = new ArrayList<MessageReference>(1);
allNotificationMessageReferences.add(message.makeMessageReference());
addWearActions(builder, account, allNotificationMessageReferences, notificationId);
}
private void addWearActions(Builder builder, Account account,
ArrayList<MessageReference> allNotificationMessageReferences, int notificationId) {
NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender();
if (isDeleteActionAvailableForWear()) {
addWearDeleteAction(builder, account, allNotificationMessageReferences, notificationId, wearableExtender);
}
if (isArchiveActionAvailableForWear(account)) {
addWearArchiveAction(builder, account, allNotificationMessageReferences, notificationId, wearableExtender);
}
if (isSpamActionAvailableForWear(account)) {
addWearSpamAction(builder, account, allNotificationMessageReferences, notificationId, wearableExtender);
}
}
private void addWearDeleteAction(Builder builder, Account account,
ArrayList<MessageReference> allNotificationMessageReferences, int notificationId,
WearableExtender wearableExtender) {
int icon = R.drawable.ic_action_delete_dark;
String title = context.getString(R.string.notification_action_delete);
PendingIntent deleteMessagePendingIntent = NotificationDeleteConfirmation.getIntent(
context, account, allNotificationMessageReferences, notificationId);
NotificationCompat.Action wearDeleteAction =
new NotificationCompat.Action.Builder(icon, title, deleteMessagePendingIntent).build();
builder.extend(wearableExtender.addAction(wearDeleteAction));
}
private void addWearArchiveAction(Builder builder, Account account,
ArrayList<MessageReference> allNotificationMessageReferences, int notificationId,
WearableExtender wearableExtender) {
int icon = R.drawable.ic_action_archive_dark;
String title = context.getString(R.string.notification_action_archive);
PendingIntent archiveMessagePendingIntent = NotificationActionService.createArchivePendingIntent(
context, account, allNotificationMessageReferences, notificationId);
NotificationCompat.Action wearActionArchive =
new NotificationCompat.Action.Builder(icon, title, archiveMessagePendingIntent).build();
builder.extend(wearableExtender.addAction(wearActionArchive));
}
private void addWearSpamAction(Builder builder, Account account,
ArrayList<MessageReference> allNotificationMessageReferences, int notificationId,
WearableExtender wearableExtender) {
int icon = R.drawable.ic_action_delete_dark;
String title = context.getString(R.string.notification_action_spam);
PendingIntent markAsSpamPendingIntent = NotificationActionService.createMarkAsSpamPendingIntent(
context, account, allNotificationMessageReferences, notificationId);
NotificationCompat.Action wearActionSpam =
new NotificationCompat.Action.Builder(icon, title, markAsSpamPendingIntent).build();
builder.extend(wearableExtender.addAction(wearActionSpam));
}
private boolean isDeleteActionAvailableForWear() {
return isDeleteActionEnabled(true) && !K9.confirmDeleteFromNotification();
}
private boolean isArchiveActionAvailableForWear(Account account) {
String archiveFolderName = account.getArchiveFolderName();
return archiveFolderName != null && isMovePossible(account, archiveFolderName);
}
private boolean isSpamActionAvailableForWear(Account account) {
String spamFolderName = account.getSpamFolderName();
return spamFolderName != null && !K9.confirmSpam() && isMovePossible(account, spamFolderName);
}
private boolean isMovePossible(Account account, String destinationFolderName) {
if (K9.FOLDER_NONE.equalsIgnoreCase(destinationFolderName)) {
return false;
}
MessagingController controller = MessagingController.getInstance(context);
return controller.isMoveCapable(account);
}
private void configureLockScreenNotification(Builder builder, Account account, int newMessages, int unreadCount,
CharSequence accountDescription, CharSequence formattedSender, List<LocalMessage> messages) {
if (!NotificationController.platformSupportsLockScreenNotifications()) {
return;
}
switch (K9.getLockScreenNotificationVisibility()) {
case NOTHING: {
builder.setVisibility(NotificationCompat.VISIBILITY_SECRET);
break;
}
case APP_NAME: {
builder.setVisibility(NotificationCompat.VISIBILITY_PRIVATE);
break;
}
case EVERYTHING: {
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
break;
}
case SENDERS: {
Builder publicNotification = createPublicNotification(account, newMessages, unreadCount);
if (newMessages == 1) {
publicNotification.setContentText(formattedSender);
} else {
String senderList = contentCreator.createCommaSeparatedListOfSenders(account, messages);
publicNotification.setContentText(senderList);
}
builder.setPublicVersion(publicNotification.build());
break;
}
case MESSAGE_COUNT: {
Builder publicNotification = createPublicNotification(account, newMessages, unreadCount);
publicNotification.setContentText(accountDescription);
builder.setPublicVersion(publicNotification.build());
break;
}
}
}
private Builder createPublicNotification(Account account, int newMessages, int unreadCount) {
String title = context.getResources().getQuantityString(R.plurals.notification_new_messages_title,
newMessages, newMessages);
return new Builder(context)
.setSmallIcon(R.drawable.ic_notify_new_mail_vector)
.setColor(account.getChipColor())
.setNumber(unreadCount)
.setContentTitle(title);
}
private boolean isPrivacyModeEnabled() {
KeyguardManager keyguardService = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
boolean privacyModeAlwaysEnabled = K9.getNotificationHideSubject() == NotificationHideSubject.ALWAYS;
boolean privacyModeEnabledWhenLocked = K9.getNotificationHideSubject() == NotificationHideSubject.WHEN_LOCKED;
boolean screenLocked = keyguardService.inKeyguardRestrictedInputMode();
return privacyModeAlwaysEnabled || (privacyModeEnabledWhenLocked && screenLocked);
}
private boolean isDeleteActionEnabled(boolean singleMessageNotification) {
NotificationQuickDelete deleteOption = K9.getNotificationQuickDeleteBehaviour();
return deleteOption == NotificationQuickDelete.ALWAYS ||
(deleteOption == NotificationQuickDelete.FOR_SINGLE_MSG && singleMessageNotification);
}
private int getNewMailNotificationIcon() {
return controller.platformSupportsVectorDrawables() ?
R.drawable.ic_notify_new_mail_vector : R.drawable.ic_notify_new_mail;
}
private int getMarkAsReadActionIcon() {
return controller.platformSupportsVectorDrawables() ?
R.drawable.ic_action_mark_as_read_dark_vector : R.drawable.ic_action_mark_as_read_dark;
}
private int getDeleteActionIcon() {
return NotificationController.platformSupportsLockScreenNotifications() ?
R.drawable.ic_action_delete_dark_vector : R.drawable.ic_action_delete_dark;
}
private int getReplyActionIcon() {
return controller.platformSupportsVectorDrawables() ?
R.drawable.ic_action_single_message_options_dark_vector :
R.drawable.ic_action_single_message_options_dark;
}
private NotificationManager getNotificationManager() {
return controller.getNotificationManager();
}
}

View file

@ -0,0 +1,134 @@
package com.fsck.k9.notification;
import java.util.ArrayList;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.TaskStackBuilder;
import android.text.TextUtils;
import com.fsck.k9.Account;
import com.fsck.k9.Preferences;
import com.fsck.k9.activity.Accounts;
import com.fsck.k9.activity.FolderList;
import com.fsck.k9.activity.MessageList;
import com.fsck.k9.activity.MessageReference;
import com.fsck.k9.mailstore.LocalMessage;
import com.fsck.k9.search.LocalSearch;
class NotificationActionCreator {
private final Context context;
public NotificationActionCreator(Context context) {
this.context = context;
}
public PendingIntent createSummaryNotificationActionPendingIntent(Account account, LocalMessage message,
int newMessagesCount, int unreadMessagesCount,
ArrayList<MessageReference> allNotificationMessageReferences) {
TaskStackBuilder stack = buildNotificationNavigationStack(account, message, newMessagesCount,
unreadMessagesCount, allNotificationMessageReferences);
return stack.getPendingIntent(account.getAccountNumber(),
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
}
public PendingIntent createViewInboxPendingIntent(Account account) {
String inboxFolderName = account.getInboxFolderName();
TaskStackBuilder stack = buildMessageListBackStack(account, inboxFolderName);
return stack.getPendingIntent(0, 0);
}
public PendingIntent createViewFolderListPendingIntent(Account account) {
TaskStackBuilder stack = buildFolderListBackStack(account);
return stack.getPendingIntent(0, 0);
}
private TaskStackBuilder buildNotificationNavigationStack(Account account, LocalMessage message, int newMessages,
int unreadCount, ArrayList<MessageReference> allRefs) {
TaskStackBuilder stack;
boolean treatAsSingleMessageNotification;
if (NotificationController.platformSupportsExtendedNotifications()) {
// in the new-style notifications, we focus on the new messages, not the unread ones
treatAsSingleMessageNotification = newMessages == 1;
} else {
// in the old-style notifications, we focus on unread messages, as we don't have a
// good way to express the new message count
treatAsSingleMessageNotification = unreadCount == 1;
}
if (treatAsSingleMessageNotification) {
stack = buildMessageViewBackStack(message.makeMessageReference());
} else if (account.goToUnreadMessageSearch()) {
stack = buildUnreadBackStack(account);
} else {
String initialFolder = message.getFolder().getName();
/* only go to folder if all messages are in the same folder, else go to folder list */
for (MessageReference ref : allRefs) {
if (!TextUtils.equals(initialFolder, ref.getFolderName())) {
initialFolder = null;
break;
}
}
stack = buildMessageListBackStack(account, initialFolder);
}
return stack;
}
private TaskStackBuilder buildAccountsBackStack() {
TaskStackBuilder stack = TaskStackBuilder.create(context);
if (!skipAccountsInBackStack()) {
stack.addNextIntent(new Intent(context, Accounts.class).putExtra(Accounts.EXTRA_STARTUP, false));
}
return stack;
}
private TaskStackBuilder buildFolderListBackStack(Account account) {
TaskStackBuilder stack = buildAccountsBackStack();
stack.addNextIntent(FolderList.actionHandleAccountIntent(context, account, false));
return stack;
}
private TaskStackBuilder buildUnreadBackStack(final Account account) {
TaskStackBuilder stack = buildAccountsBackStack();
LocalSearch search = Accounts.createUnreadSearch(context, account);
stack.addNextIntent(MessageList.intentDisplaySearch(context, search, true, false, false));
return stack;
}
private TaskStackBuilder buildMessageListBackStack(Account account, String folder) {
TaskStackBuilder stack = skipFolderListInBackStack(account, folder) ?
buildAccountsBackStack() : buildFolderListBackStack(account);
if (folder != null) {
LocalSearch search = new LocalSearch(folder);
search.addAllowedFolder(folder);
search.addAccountUuid(account.getUuid());
stack.addNextIntent(MessageList.intentDisplaySearch(context, search, false, true, true));
}
return stack;
}
private TaskStackBuilder buildMessageViewBackStack(MessageReference message) {
Account account = Preferences.getPreferences(context).getAccount(message.getAccountUuid());
TaskStackBuilder stack = buildMessageListBackStack(account, message.getFolderName());
stack.addNextIntent(MessageList.actionDisplayMessageIntent(context, message));
return stack;
}
private boolean skipFolderListInBackStack(Account account, String folder) {
return folder != null && folder.equals(account.getAutoExpandFolderName());
}
private boolean skipAccountsInBackStack() {
return Preferences.getPreferences(context).getAccounts().size() == 1;
}
}

View file

@ -0,0 +1,132 @@
package com.fsck.k9.notification;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import android.content.Context;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.TextAppearanceSpan;
import android.util.Log;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.R;
import com.fsck.k9.helper.Contacts;
import com.fsck.k9.helper.MessageHelper;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mailstore.LocalMessage;
class NotificationContentCreator {
private static final int MAX_NUMBER_OF_SENDERS_IN_LOCK_SCREEN_NOTIFICATION = 5;
private final Context context;
private TextAppearanceSpan emphasizedSpan;
public NotificationContentCreator(Context context) {
this.context = context;
}
public String createCommaSeparatedListOfSenders(Account account, List<LocalMessage> messages) {
// Use a LinkedHashSet so that we preserve ordering (newest to oldest), but still remove duplicates
Set<CharSequence> senders = new LinkedHashSet<CharSequence>(MAX_NUMBER_OF_SENDERS_IN_LOCK_SCREEN_NOTIFICATION);
for (Message message : messages) {
senders.add(getMessageSender(account, message));
if (senders.size() == MAX_NUMBER_OF_SENDERS_IN_LOCK_SCREEN_NOTIFICATION) {
break;
}
}
return TextUtils.join(", ", senders);
}
public CharSequence getMessagePreview(Message message) {
CharSequence subject = getMessageSubject(message);
String snippet = message.getPreview();
if (TextUtils.isEmpty(subject)) {
return snippet;
} else if (TextUtils.isEmpty(snippet)) {
return subject;
}
SpannableStringBuilder preview = new SpannableStringBuilder();
preview.append(subject);
preview.append('\n');
preview.append(snippet);
preview.setSpan(getEmphasizedSpan(), 0, subject.length(), 0);
return preview;
}
public CharSequence buildMessageSummary(CharSequence sender, CharSequence subject) {
if (sender == null) {
return subject;
}
SpannableStringBuilder summary = new SpannableStringBuilder();
summary.append(sender);
summary.append(" ");
summary.append(subject);
summary.setSpan(getEmphasizedSpan(), 0, sender.length(), 0);
return summary;
}
public CharSequence getMessageSubject(Message message) {
String subject = message.getSubject();
if (!TextUtils.isEmpty(subject)) {
return subject;
}
return context.getString(R.string.general_no_subject);
}
public CharSequence getMessageSender(Account account, Message message) {
try {
boolean isSelf = false;
final Contacts contacts = K9.showContactName() ? Contacts.getInstance(context) : null;
final Address[] fromAddresses = message.getFrom();
if (fromAddresses != null) {
isSelf = account.isAnIdentity(fromAddresses);
if (!isSelf && fromAddresses.length > 0) {
return MessageHelper.toFriendly(fromAddresses[0], contacts).toString();
}
}
if (isSelf) {
// show To: if the message was sent from me
Address[] recipients = message.getRecipients(Message.RecipientType.TO);
if (recipients != null && recipients.length > 0) {
return context.getString(R.string.message_to_fmt,
MessageHelper.toFriendly(recipients[0], contacts).toString());
}
return context.getString(R.string.general_no_sender);
}
} catch (MessagingException e) {
Log.e(K9.LOG_TAG, "Unable to get sender information for notification.", e);
}
return null;
}
private TextAppearanceSpan getEmphasizedSpan() {
if (emphasizedSpan == null) {
emphasizedSpan = new TextAppearanceSpan(context,
R.style.TextAppearance_StatusBar_EventContent_Emphasized);
}
return emphasizedSpan;
}
}

View file

@ -1,75 +1,35 @@
package com.fsck.k9.notification;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import android.app.KeyguardManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationCompat.Builder;
import android.support.v4.app.NotificationCompat.WearableExtender;
import android.support.v4.app.TaskStackBuilder;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.TextAppearanceSpan;
import android.util.Log;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.K9.NotificationHideSubject;
import com.fsck.k9.K9.NotificationQuickDelete;
import com.fsck.k9.NotificationSetting;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
import com.fsck.k9.activity.Accounts;
import com.fsck.k9.activity.FolderList;
import com.fsck.k9.activity.MessageList;
import com.fsck.k9.activity.MessageReference;
import com.fsck.k9.activity.NotificationDeleteConfirmation;
import com.fsck.k9.activity.setup.AccountSetupIncoming;
import com.fsck.k9.activity.setup.AccountSetupOutgoing;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.helper.Contacts;
import com.fsck.k9.helper.MessageHelper;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mailstore.LocalMessage;
import com.fsck.k9.search.LocalSearch;
public class NotificationController {
private static final boolean NOTIFICATION_LED_WHILE_SYNCING = false;
private static final int MAX_NUMBER_OF_SENDERS_IN_LOCK_SCREEN_NOTIFICATION = 5;
private static final String NOTIFICATION_GROUP_KEY = "newMailNotifications";
private static final int NOTIFICATION_LED_ON_TIME = 500;
private static final int NOTIFICATION_LED_OFF_TIME = 2000;
private static final int NOTIFICATION_LED_FAST_ON_TIME = 100;
private static final int NOTIFICATION_LED_FAST_OFF_TIME = 100;
private static final int NOTIFICATION_LED_BLINK_SLOW = 0;
private static final int NOTIFICATION_LED_BLINK_FAST = 1;
private static final int NOTIFICATION_LED_FAILURE_COLOR = 0xffff0000;
static final int NOTIFICATION_LED_BLINK_SLOW = 0;
static final int NOTIFICATION_LED_BLINK_FAST = 1;
static final int NOTIFICATION_LED_FAILURE_COLOR = 0xffff0000;
private final Context context;
private final NotificationManager notificationManager;
private TextAppearanceSpan emphasizedSpan;
private final ConcurrentMap<Integer, NotificationsHolder> notificationsHolders =
new ConcurrentHashMap<Integer, NotificationsHolder>();
private final CertificateErrorNotifications certificateErrorNotifications;
private final SyncNotifications syncNotifications;
private final SendFailedNotifications sendFailedNotifications;
private final NewMailNotifications newMailNotifications;
public static NotificationController newInstance(Context context) {
@ -91,524 +51,64 @@ public class NotificationController {
NotificationController(Context context, NotificationManager notificationManager) {
this.context = context;
this.notificationManager = notificationManager;
NotificationActionCreator actionBuilder = new NotificationActionCreator(context);
certificateErrorNotifications = new CertificateErrorNotifications(this);
syncNotifications = new SyncNotifications(this, actionBuilder);
sendFailedNotifications = new SendFailedNotifications(this, actionBuilder);
newMailNotifications = new NewMailNotifications(this, actionBuilder);
}
public void showCertificateErrorNotification(Account account, boolean incoming) {
int notificationId = NotificationIds.getCertificateErrorNotificationId(account, incoming);
Intent editServerSettingsIntent = incoming ?
AccountSetupIncoming.intentActionEditIncomingSettings(context, account) :
AccountSetupOutgoing.intentActionEditOutgoingSettings(context, account);
PendingIntent editServerSettingsPendingIntent = PendingIntent.getActivity(context,
account.getAccountNumber(), editServerSettingsIntent, PendingIntent.FLAG_UPDATE_CURRENT);
String title = context.getString(R.string.notification_certificate_error_title, account.getDescription());
String text = context.getString(R.string.notification_certificate_error_text);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
.setSmallIcon(getCertificateErrorNotificationIcon())
.setWhen(System.currentTimeMillis())
.setAutoCancel(true)
.setTicker(title)
.setContentTitle(title)
.setContentText(text)
.setContentIntent(editServerSettingsPendingIntent)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
configureNotification(builder, null, null,
NOTIFICATION_LED_FAILURE_COLOR,
NOTIFICATION_LED_BLINK_FAST, true);
notificationManager.notify(notificationId, builder.build());
certificateErrorNotifications.showCertificateErrorNotification(account, incoming);
}
public void clearCertificateErrorNotifications(Account account, boolean incoming) {
int notificationId = NotificationIds.getCertificateErrorNotificationId(account, incoming);
notificationManager.cancel(notificationId);
certificateErrorNotifications.clearCertificateErrorNotifications(account, incoming);
}
public void showSendingNotification(Account account) {
String accountName = getAccountName(account);
String title = context.getString(R.string.notification_bg_send_title);
String tickerText = context.getString(R.string.notification_bg_send_ticker, accountName);
String folderName = account.getInboxFolderName();
TaskStackBuilder stack = buildMessageListBackStack(account, folderName);
PendingIntent showMessageListPendingIntent = stack.getPendingIntent(0, 0);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
.setSmallIcon(R.drawable.ic_notify_check_mail)
.setWhen(System.currentTimeMillis())
.setOngoing(true)
.setTicker(tickerText)
.setContentTitle(title)
.setContentText(accountName)
.setContentIntent(showMessageListPendingIntent)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
if (NOTIFICATION_LED_WHILE_SYNCING) {
configureNotification(builder, null, null,
account.getNotificationSetting().getLedColor(),
NOTIFICATION_LED_BLINK_FAST, true);
}
int notificationId = NotificationIds.getFetchingMailNotificationId(account);
notificationManager.notify(notificationId, builder.build());
syncNotifications.showSendingNotification(account);
}
public void clearSendingNotification(Account account) {
int notificationId = NotificationIds.getFetchingMailNotificationId(account);
notificationManager.cancel(notificationId);
syncNotifications.clearSendingNotification(account);
}
public void showSendFailedNotification(Account account, Exception exception) {
String title = context.getString(R.string.send_failure_subject);
String text = getRootCauseMessage(exception);
TaskStackBuilder stack = buildFolderListBackStack(account);
PendingIntent folderListPendingIntent = stack.getPendingIntent(0, 0);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
.setSmallIcon(getSendFailedNotificationIcon())
.setWhen(System.currentTimeMillis())
.setAutoCancel(true)
.setTicker(title)
.setContentTitle(title)
.setContentText(text)
.setContentIntent(folderListPendingIntent)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
configureNotification(builder, null, null, NOTIFICATION_LED_FAILURE_COLOR,
NOTIFICATION_LED_BLINK_FAST, true);
int notificationId = NotificationIds.getSendFailedNotificationId(account);
notificationManager.notify(notificationId, builder.build());
sendFailedNotifications.showSendFailedNotification(account, exception);
}
public void clearSendFailedNotification(Account account) {
int notificationId = NotificationIds.getSendFailedNotificationId(account);
notificationManager.cancel(notificationId);
sendFailedNotifications.clearSendFailedNotification(account);
}
public void showFetchingMailNotification(Account account, Folder folder) {
String accountName = account.getDescription();
String folderName = folder.getName();
String tickerText = context.getString(R.string.notification_bg_sync_ticker, accountName, folderName);
String title = context.getString(R.string.notification_bg_sync_title);
//TODO: Use format string from resources
String text = accountName + context.getString(R.string.notification_bg_title_separator) + folderName;
TaskStackBuilder stack = buildMessageListBackStack(account,
account.getInboxFolderName());
PendingIntent showMessageListPendingIntent = stack.getPendingIntent(0, 0);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
.setSmallIcon(R.drawable.ic_notify_check_mail)
.setWhen(System.currentTimeMillis())
.setOngoing(true)
.setTicker(tickerText)
.setContentTitle(title)
.setContentText(text)
.setContentIntent(showMessageListPendingIntent)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
if (NOTIFICATION_LED_WHILE_SYNCING) {
configureNotification(builder, null, null,
account.getNotificationSetting().getLedColor(),
NOTIFICATION_LED_BLINK_FAST, true);
}
int notificationId = NotificationIds.getFetchingMailNotificationId(account);
notificationManager.notify(notificationId, builder.build());
syncNotifications.showFetchingMailNotification(account, folder);
}
public void clearFetchingMailNotification(Account account) {
int notificationId = NotificationIds.getFetchingMailNotificationId(account);
notificationManager.cancel(notificationId);
syncNotifications.clearFetchingMailNotification(account);
}
public void addNewMailNotification(Account account, LocalMessage message, int previousUnreadMessageCount) {
NotificationsHolder notificationsHolder = getOrCreateNotificationData(account, previousUnreadMessageCount);
synchronized (notificationsHolder) {
notifyAccountWithDataLocked(account, message, notificationsHolder);
}
newMailNotifications.addNewMailNotification(account, message, previousUnreadMessageCount);
}
public void removeNewMailNotification(Account account, LocalMessage localMessage) {
NotificationsHolder notificationsHolder = getNotificationData(account);
if (notificationsHolder == null) {
return;
}
synchronized (notificationsHolder) {
MessageReference messageReference = localMessage.makeMessageReference();
if (notificationsHolder.removeMatchingMessage(context, messageReference)) {
Integer childNotification = notificationsHolder.getStackedChildNotification(messageReference);
if (childNotification != null) {
notificationManager.cancel(childNotification);
}
notifyAccountWithDataLocked(account, null, notificationsHolder);
}
}
newMailNotifications.removeNewMailNotification(account, localMessage);
}
public void clearNewMailNotifications(Account account) {
int newMailNotificationId = NotificationIds.getNewMailNotificationId(account);
notificationManager.cancel(newMailNotificationId);
notificationsHolders.remove(account.getAccountNumber());
//TODO: clear stacked notifications
newMailNotifications.clearNewMailNotifications(account);
}
public void cancelNotification(int notificationId) {
notificationManager.cancel(notificationId);
}
private NotificationsHolder getOrCreateNotificationData(Account account, int previousUnreadMessageCount) {
int accountNumber = account.getAccountNumber();
NotificationsHolder notificationsHolder = notificationsHolders.get(accountNumber);
if (notificationsHolder == null) {
NotificationsHolder newNotificationHolder = new NotificationsHolder(previousUnreadMessageCount);
NotificationsHolder oldNotificationHolder =
notificationsHolders.putIfAbsent(accountNumber, newNotificationHolder);
if (oldNotificationHolder != null) {
notificationsHolder = oldNotificationHolder;
} else {
notificationsHolder = newNotificationHolder;
}
}
return notificationsHolder;
}
private NotificationsHolder getNotificationData(Account account) {
int accountNumber = account.getAccountNumber();
return notificationsHolders.get(accountNumber);
}
private void notifyAccountWithDataLocked(Account account, LocalMessage newMessage,
NotificationsHolder notificationsHolder) {
boolean updateSilently = false;
if (newMessage == null) {
newMessage = notificationsHolder.getNewestMessage(context);
updateSilently = true;
if (newMessage == null) {
clearNewMailNotifications(account);
return;
}
} else {
notificationsHolder.addMessage(newMessage);
}
int newMessagesCount = notificationsHolder.getNewMessagesCount();
int unreadMessagesCount = notificationsHolder.getUnreadMessagesCountBeforeNotification() + newMessagesCount;
CharSequence sender = getMessageSender(account, newMessage);
CharSequence subject = getMessageSubject(newMessage);
CharSequence summary = buildMessageSummary(sender, subject);
boolean privacyModeEnabled = isPrivacyModeEnabled();
if (privacyModeEnabled || summary.length() == 0) {
summary = context.getString(R.string.notification_new_title);
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
.setSmallIcon(getNewMailNotificationIcon())
.setColor(account.getChipColor())
.setWhen(System.currentTimeMillis())
.setNumber(unreadMessagesCount);
if (!updateSilently) {
builder.setTicker(summary);
}
String accountName = getAccountName(account);
ArrayList<MessageReference> allNotificationMessageReferences =
notificationsHolder.getMessageReferencesForAllNotifications();
if (platformSupportsExtendedNotifications() && !privacyModeEnabled) {
boolean singleMessageNotification = (newMessagesCount == 1);
if (singleMessageNotification) {
createSingleMessageNotification(account, newMessage, sender, subject, builder,
accountName, allNotificationMessageReferences);
} else {
createInboxStyleNotification(account, notificationsHolder, builder, newMessagesCount, accountName);
}
addMarkAsReadAction(builder, account, allNotificationMessageReferences);
if (isDeleteActionEnabled(singleMessageNotification)) {
addDeleteAction(builder, account, allNotificationMessageReferences);
}
} else {
String accountNotice = context.getString(R.string.notification_new_one_account_fmt,
unreadMessagesCount, accountName);
builder.setContentTitle(accountNotice);
builder.setContentText(summary);
}
for (Message message : notificationsHolder.getMessagesForSummaryNotification()) {
if (message.isSet(Flag.FLAGGED)) {
builder.setPriority(NotificationCompat.PRIORITY_HIGH);
break;
}
}
TaskStackBuilder stack = buildNotificationNavigationStack(account, newMessage, newMessagesCount,
unreadMessagesCount, allNotificationMessageReferences);
PendingIntent notificationPendingIntent = stack.getPendingIntent(account.getAccountNumber(),
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
builder.setContentIntent(notificationPendingIntent);
PendingIntent deletePendingIntent = NotificationActionService.createDismissPendingIntent(
context, account, account.getAccountNumber());
builder.setDeleteIntent(deletePendingIntent);
boolean ringAndVibrate = false;
if (!updateSilently && !account.isRingNotified()) {
account.setRingNotified(true);
ringAndVibrate = true;
}
configureLockScreenNotification(builder, account, newMessagesCount, unreadMessagesCount, accountName,
sender, notificationsHolder.getMessagesForSummaryNotification());
NotificationSetting notificationSetting = account.getNotificationSetting();
configureNotification(
builder,
(notificationSetting.shouldRing()) ? notificationSetting.getRingtone() : null,
(notificationSetting.shouldVibrate()) ? notificationSetting.getVibration() : null,
(notificationSetting.isLed()) ? notificationSetting.getLedColor() : null,
NOTIFICATION_LED_BLINK_SLOW,
ringAndVibrate);
int notificationId = NotificationIds.getNewMailNotificationId(account);
notificationManager.notify(notificationId, builder.build());
}
private void createInboxStyleNotification(Account account, NotificationsHolder notificationsHolder, Builder builder,
int newMessagesCount, String accountName) {
String title = context.getResources().getQuantityString(R.plurals.notification_new_messages_title,
newMessagesCount, newMessagesCount);
NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle(builder);
style.setBigContentTitle(title);
if (notificationsHolder.hasAdditionalMessages()) {
String summaryText = context.getString(R.string.notification_additional_messages,
notificationsHolder.getAdditionalMessagesCount(), accountName);
style.setSummaryText(summaryText);
}
int notificationOffset = 1;
for (LocalMessage message : notificationsHolder.getMessagesForSummaryNotification()) {
MessageReference messageReference = message.makeMessageReference();
CharSequence sender = getMessageSender(account, message);
CharSequence subject = getMessageSubject(message);
style.addLine(buildMessageSummary(sender, subject));
Builder stackedNotificationBuilder = new Builder(context)
.setSmallIcon(R.drawable.ic_notify_new_mail)
.setWhen(System.currentTimeMillis())
.setGroup(NOTIFICATION_GROUP_KEY)
.setAutoCancel(true);
int notificationId;
Integer existingNotificationId = notificationsHolder.getStackedChildNotification(messageReference);
if (existingNotificationId != null) {
notificationId = existingNotificationId;
} else {
notificationId = NotificationIds.getNewMailNotificationId(account, notificationOffset);
}
setNotificationContent(message, sender, subject, stackedNotificationBuilder, accountName);
addWearActionsForSingleMessage(stackedNotificationBuilder, account, message, notificationId);
if (message.isSet(Flag.FLAGGED)) {
stackedNotificationBuilder.setPriority(NotificationCompat.PRIORITY_HIGH);
}
notificationsHolder.addStackedChildNotification(messageReference, notificationId);
notificationManager.notify(notificationId, stackedNotificationBuilder.build());
notificationOffset++;
}
builder.setGroup(NOTIFICATION_GROUP_KEY)
.setGroupSummary(true)
.setLocalOnly(true)
.setContentTitle(title)
.setSubText(accountName)
.setStyle(style);
}
private void createSingleMessageNotification(Account account, LocalMessage message,
CharSequence sender, CharSequence subject, Builder builder, String accountName,
ArrayList<MessageReference> allNotificationMessageReferences) {
setNotificationContent(message, sender, subject, builder, accountName);
addReplyAction(builder, account, message);
addWearActions(builder, account, allNotificationMessageReferences,
account.getAccountNumber());
}
private NotificationCompat.Builder setNotificationContent(Message message, CharSequence sender,
CharSequence subject, Builder builder, String accountName) {
NotificationCompat.BigTextStyle style = new NotificationCompat.BigTextStyle(builder);
CharSequence preview = getMessagePreview(message);
if (preview != null) {
style.bigText(preview);
}
builder.setStyle(style)
.setContentTitle(sender)
.setContentText(subject)
.setSubText(accountName);
return builder;
}
private void addMarkAsReadAction(Builder builder, Account account,
ArrayList<MessageReference> allNotificationMessageReferences) {
int icon = getMarkAsReadActionIcon();
String title = context.getString(R.string.notification_action_mark_as_read);
PendingIntent readAllMessagesPendingIntent = NotificationActionService.createMarkAsReadPendingIntent(
context, account, allNotificationMessageReferences, account.getAccountNumber());
builder.addAction(icon, title, readAllMessagesPendingIntent);
}
private void addDeleteAction(Builder builder, Account account,
ArrayList<MessageReference> allNotificationMessageReferences) {
int icon = getDeleteActionIcon();
String title = context.getString(R.string.notification_action_delete);
PendingIntent confirmDeletionPendingIntent = NotificationDeleteConfirmation.getIntent(
context, account, allNotificationMessageReferences, account.getAccountNumber());
builder.addAction(icon, title, confirmDeletionPendingIntent);
}
private void addReplyAction(Builder builder, Account account, LocalMessage message) {
int icon = getReplyActionIcon();
String title = context.getString(R.string.notification_action_reply);
PendingIntent replyToMessagePendingIntent = NotificationActionService.createReplyPendingIntent(
context, account, message.makeMessageReference(), account.getAccountNumber());
builder.addAction(icon, title, replyToMessagePendingIntent);
}
private void addWearActionsForSingleMessage(Builder builder, Account account, LocalMessage message,
int notificationId) {
ArrayList<MessageReference> allNotificationMessageReferences = new ArrayList<MessageReference>(1);
allNotificationMessageReferences.add(message.makeMessageReference());
addWearActions(builder, account, allNotificationMessageReferences, notificationId);
}
private void addWearActions(Builder builder, Account account,
ArrayList<MessageReference> allNotificationMessageReferences, int notificationId) {
NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender();
if (isDeleteActionAvailableForWear()) {
addWearDeleteAction(builder, account, allNotificationMessageReferences, notificationId, wearableExtender);
}
if (isArchiveActionAvailableForWear(account)) {
addWearArchiveAction(builder, account, allNotificationMessageReferences, notificationId, wearableExtender);
}
if (isSpamActionAvailableForWear(account)) {
addWearSpamAction(builder, account, allNotificationMessageReferences, notificationId, wearableExtender);
}
}
private void addWearDeleteAction(Builder builder, Account account,
ArrayList<MessageReference> allNotificationMessageReferences, int notificationId,
WearableExtender wearableExtender) {
int icon = R.drawable.ic_action_delete_dark;
String title = context.getString(R.string.notification_action_delete);
PendingIntent deleteMessagePendingIntent = NotificationDeleteConfirmation.getIntent(
context, account, allNotificationMessageReferences, notificationId);
NotificationCompat.Action wearDeleteAction =
new NotificationCompat.Action.Builder(icon, title, deleteMessagePendingIntent).build();
builder.extend(wearableExtender.addAction(wearDeleteAction));
}
private void addWearArchiveAction(Builder builder, Account account,
ArrayList<MessageReference> allNotificationMessageReferences, int notificationId,
WearableExtender wearableExtender) {
int icon = R.drawable.ic_action_archive_dark;
String title = context.getString(R.string.notification_action_archive);
PendingIntent archiveMessagePendingIntent = NotificationActionService.createArchivePendingIntent(
context, account, allNotificationMessageReferences, notificationId);
NotificationCompat.Action wearActionArchive =
new NotificationCompat.Action.Builder(icon, title, archiveMessagePendingIntent).build();
builder.extend(wearableExtender.addAction(wearActionArchive));
}
private void addWearSpamAction(Builder builder, Account account,
ArrayList<MessageReference> allNotificationMessageReferences, int notificationId,
WearableExtender wearableExtender) {
int icon = R.drawable.ic_action_delete_dark;
String title = context.getString(R.string.notification_action_spam);
PendingIntent markAsSpamPendingIntent = NotificationActionService.createMarkAsSpamPendingIntent(
context, account, allNotificationMessageReferences, notificationId);
NotificationCompat.Action wearActionSpam =
new NotificationCompat.Action.Builder(icon, title, markAsSpamPendingIntent).build();
builder.extend(wearableExtender.addAction(wearActionSpam));
}
private boolean isDeleteActionAvailableForWear() {
return isDeleteActionEnabled(true) && !K9.confirmDeleteFromNotification();
}
private boolean isArchiveActionAvailableForWear(Account account) {
String archiveFolderName = account.getArchiveFolderName();
return archiveFolderName != null && isMovePossible(account, archiveFolderName);
}
private boolean isSpamActionAvailableForWear(Account account) {
String spamFolderName = account.getSpamFolderName();
return spamFolderName != null && !K9.confirmSpam() && isMovePossible(account, spamFolderName);
}
private boolean isMovePossible(Account account, String destinationFolderName) {
if (K9.FOLDER_NONE.equalsIgnoreCase(destinationFolderName)) {
return false;
}
MessagingController controller = MessagingController.getInstance(context);
return controller.isMoveCapable(account);
}
private void configureNotification(NotificationCompat.Builder builder, String ringtone,
long[] vibrationPattern, Integer ledColor, int ledSpeed, boolean ringAndVibrate) {
void configureNotification(NotificationCompat.Builder builder, String ringtone, long[] vibrationPattern,
Integer ledColor, int ledSpeed, boolean ringAndVibrate) {
if (K9.isQuietTime()) {
return;
@ -639,312 +139,20 @@ public class NotificationController {
}
}
private void configureLockScreenNotification(Builder builder, Account account, int newMessages, int unreadCount,
CharSequence accountDescription, CharSequence formattedSender, List<LocalMessage> messages) {
if (!platformSupportsLockScreenNotifications()) {
return;
}
switch (K9.getLockScreenNotificationVisibility()) {
case NOTHING: {
builder.setVisibility(NotificationCompat.VISIBILITY_SECRET);
break;
}
case APP_NAME: {
builder.setVisibility(NotificationCompat.VISIBILITY_PRIVATE);
break;
}
case EVERYTHING: {
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
break;
}
case SENDERS: {
Builder publicNotification = createPublicNotification(account, newMessages, unreadCount);
if (newMessages == 1) {
publicNotification.setContentText(formattedSender);
} else {
String senderList = createCommaSeparatedListOfSenders(account, messages);
publicNotification.setContentText(senderList);
}
builder.setPublicVersion(publicNotification.build());
break;
}
case MESSAGE_COUNT: {
Builder publicNotification = createPublicNotification(account, newMessages, unreadCount);
publicNotification.setContentText(accountDescription);
builder.setPublicVersion(publicNotification.build());
break;
}
}
}
private Builder createPublicNotification(Account account, int newMessages, int unreadCount) {
String title = context.getResources().getQuantityString(R.plurals.notification_new_messages_title,
newMessages, newMessages);
return new Builder(context)
.setSmallIcon(R.drawable.ic_notify_new_mail_vector)
.setColor(account.getChipColor())
.setNumber(unreadCount)
.setContentTitle(title);
}
private boolean platformSupportsVectorDrawables() {
boolean platformSupportsVectorDrawables() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
}
private int getNewMailNotificationIcon() {
return platformSupportsVectorDrawables() ? R.drawable.ic_notify_new_mail_vector : R.drawable.ic_notify_new_mail;
}
private int getMarkAsReadActionIcon() {
return platformSupportsVectorDrawables() ?
R.drawable.ic_action_mark_as_read_dark_vector : R.drawable.ic_action_mark_as_read_dark;
}
private int getDeleteActionIcon() {
return platformSupportsLockScreenNotifications() ?
R.drawable.ic_action_delete_dark_vector : R.drawable.ic_action_delete_dark;
}
private int getReplyActionIcon() {
return platformSupportsVectorDrawables() ?
R.drawable.ic_action_single_message_options_dark_vector :
R.drawable.ic_action_single_message_options_dark;
}
private int getCertificateErrorNotificationIcon() {
//TODO: Use a different icon for certificate error notifications
return getNewMailNotificationIcon();
}
private int getSendFailedNotificationIcon() {
//TODO: Use a different icon for send failure notifications
return getNewMailNotificationIcon();
}
private TaskStackBuilder buildNotificationNavigationStack(Account account, LocalMessage message, int newMessages,
int unreadCount, ArrayList<MessageReference> allRefs) {
TaskStackBuilder stack;
boolean treatAsSingleMessageNotification;
if (platformSupportsExtendedNotifications()) {
// in the new-style notifications, we focus on the new messages, not the unread ones
treatAsSingleMessageNotification = newMessages == 1;
} else {
// in the old-style notifications, we focus on unread messages, as we don't have a
// good way to express the new message count
treatAsSingleMessageNotification = unreadCount == 1;
}
if (treatAsSingleMessageNotification) {
stack = buildMessageViewBackStack(message.makeMessageReference());
} else if (account.goToUnreadMessageSearch()) {
stack = buildUnreadBackStack(account);
} else {
String initialFolder = message.getFolder().getName();
/* only go to folder if all messages are in the same folder, else go to folder list */
for (MessageReference ref : allRefs) {
if (!TextUtils.equals(initialFolder, ref.getFolderName())) {
initialFolder = null;
break;
}
}
stack = buildMessageListBackStack(account, initialFolder);
}
return stack;
}
private TaskStackBuilder buildAccountsBackStack() {
TaskStackBuilder stack = TaskStackBuilder.create(context);
if (!skipAccountsInBackStack()) {
stack.addNextIntent(new Intent(context, Accounts.class).putExtra(Accounts.EXTRA_STARTUP, false));
}
return stack;
}
private TaskStackBuilder buildFolderListBackStack(Account account) {
TaskStackBuilder stack = buildAccountsBackStack();
stack.addNextIntent(FolderList.actionHandleAccountIntent(context, account, false));
return stack;
}
private TaskStackBuilder buildUnreadBackStack(final Account account) {
TaskStackBuilder stack = buildAccountsBackStack();
LocalSearch search = Accounts.createUnreadSearch(context, account);
stack.addNextIntent(MessageList.intentDisplaySearch(context, search, true, false, false));
return stack;
}
private TaskStackBuilder buildMessageListBackStack(Account account, String folder) {
TaskStackBuilder stack = skipFolderListInBackStack(account, folder) ?
buildAccountsBackStack() : buildFolderListBackStack(account);
if (folder != null) {
LocalSearch search = new LocalSearch(folder);
search.addAllowedFolder(folder);
search.addAccountUuid(account.getUuid());
stack.addNextIntent(MessageList.intentDisplaySearch(context, search, false, true, true));
}
return stack;
}
private TaskStackBuilder buildMessageViewBackStack(MessageReference message) {
Account account = Preferences.getPreferences(context).getAccount(message.getAccountUuid());
TaskStackBuilder stack = buildMessageListBackStack(account, message.getFolderName());
stack.addNextIntent(MessageList.actionDisplayMessageIntent(context, message));
return stack;
}
private boolean skipFolderListInBackStack(Account account, String folder) {
return folder != null && folder.equals(account.getAutoExpandFolderName());
}
private boolean skipAccountsInBackStack() {
return Preferences.getPreferences(context).getAccounts().size() == 1;
}
private boolean isPrivacyModeEnabled() {
KeyguardManager keyguardService = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
boolean privacyModeAlwaysEnabled = K9.getNotificationHideSubject() == NotificationHideSubject.ALWAYS;
boolean privacyModeEnabledWhenLocked = K9.getNotificationHideSubject() == NotificationHideSubject.WHEN_LOCKED;
boolean screenLocked = keyguardService.inKeyguardRestrictedInputMode();
return privacyModeAlwaysEnabled || (privacyModeEnabledWhenLocked && screenLocked);
}
private boolean isDeleteActionEnabled(boolean singleMessageNotification) {
NotificationQuickDelete deleteOption = K9.getNotificationQuickDeleteBehaviour();
return deleteOption == NotificationQuickDelete.ALWAYS ||
(deleteOption == NotificationQuickDelete.FOR_SINGLE_MSG && singleMessageNotification);
}
private String getAccountName(Account account) {
String getAccountName(Account account) {
String accountDescription = account.getDescription();
return TextUtils.isEmpty(accountDescription) ? account.getEmail() : accountDescription;
}
private CharSequence getMessageSubject(Message message) {
String subject = message.getSubject();
if (!TextUtils.isEmpty(subject)) {
return subject;
}
return context.getString(R.string.general_no_subject);
Context getContext() {
return context;
}
private TextAppearanceSpan getEmphasizedSpan() {
if (emphasizedSpan == null) {
emphasizedSpan = new TextAppearanceSpan(context,
R.style.TextAppearance_StatusBar_EventContent_Emphasized);
}
return emphasizedSpan;
}
private CharSequence getMessageSender(Account account, Message message) {
try {
boolean isSelf = false;
final Contacts contacts = K9.showContactName() ? Contacts.getInstance(context) : null;
final Address[] fromAddresses = message.getFrom();
if (fromAddresses != null) {
isSelf = account.isAnIdentity(fromAddresses);
if (!isSelf && fromAddresses.length > 0) {
return MessageHelper.toFriendly(fromAddresses[0], contacts).toString();
}
}
if (isSelf) {
// show To: if the message was sent from me
Address[] recipients = message.getRecipients(Message.RecipientType.TO);
if (recipients != null && recipients.length > 0) {
return context.getString(R.string.message_to_fmt,
MessageHelper.toFriendly(recipients[0], contacts).toString());
}
return context.getString(R.string.general_no_sender);
}
} catch (MessagingException e) {
Log.e(K9.LOG_TAG, "Unable to get sender information for notification.", e);
}
return null;
}
private String createCommaSeparatedListOfSenders(Account account, List<LocalMessage> messages) {
// Use a LinkedHashSet so that we preserve ordering (newest to oldest), but still remove duplicates
Set<CharSequence> senders = new LinkedHashSet<CharSequence>(MAX_NUMBER_OF_SENDERS_IN_LOCK_SCREEN_NOTIFICATION);
for (Message message : messages) {
senders.add(getMessageSender(account, message));
if (senders.size() == MAX_NUMBER_OF_SENDERS_IN_LOCK_SCREEN_NOTIFICATION) {
break;
}
}
return TextUtils.join(", ", senders);
}
private CharSequence getMessagePreview(Message message) {
CharSequence subject = getMessageSubject(message);
String snippet = message.getPreview();
if (TextUtils.isEmpty(subject)) {
return snippet;
} else if (TextUtils.isEmpty(snippet)) {
return subject;
}
SpannableStringBuilder preview = new SpannableStringBuilder();
preview.append(subject);
preview.append('\n');
preview.append(snippet);
preview.setSpan(getEmphasizedSpan(), 0, subject.length(), 0);
return preview;
}
private CharSequence buildMessageSummary(CharSequence sender, CharSequence subject) {
if (sender == null) {
return subject;
}
SpannableStringBuilder summary = new SpannableStringBuilder();
summary.append(sender);
summary.append(" ");
summary.append(subject);
summary.setSpan(getEmphasizedSpan(), 0, sender.length(), 0);
return summary;
}
private String getRootCauseMessage(Throwable t) {
Throwable rootCause = t;
Throwable nextCause;
do {
nextCause = rootCause.getCause();
if (nextCause != null) {
rootCause = nextCause;
}
} while (nextCause != null);
if (rootCause instanceof MessagingException) {
return rootCause.getMessage();
}
// Remove the namespace on the exception so we have a fighting chance of seeing more of the error in the
// notification.
String simpleName = rootCause.getClass().getSimpleName();
return (rootCause.getLocalizedMessage() != null) ?
simpleName + ": " + rootCause.getLocalizedMessage() : simpleName;
NotificationManager getNotificationManager() {
return notificationManager;
}
}

View file

@ -0,0 +1,65 @@
package com.fsck.k9.notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.support.v4.app.NotificationCompat;
import com.fsck.k9.Account;
import com.fsck.k9.R;
import com.fsck.k9.helper.ExceptionHelper;
import static com.fsck.k9.notification.NotificationController.NOTIFICATION_LED_BLINK_FAST;
import static com.fsck.k9.notification.NotificationController.NOTIFICATION_LED_FAILURE_COLOR;
class SendFailedNotifications {
private final NotificationController controller;
private final NotificationActionCreator actionBuilder;
public SendFailedNotifications(NotificationController controller, NotificationActionCreator actionBuilder) {
this.controller = controller;
this.actionBuilder = actionBuilder;
}
public void showSendFailedNotification(Account account, Exception exception) {
Context context = controller.getContext();
String title = context.getString(R.string.send_failure_subject);
String text = ExceptionHelper.getRootCauseMessage(exception);
PendingIntent folderListPendingIntent = actionBuilder.createViewFolderListPendingIntent(account);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
.setSmallIcon(getSendFailedNotificationIcon())
.setWhen(System.currentTimeMillis())
.setAutoCancel(true)
.setTicker(title)
.setContentTitle(title)
.setContentText(text)
.setContentIntent(folderListPendingIntent)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
controller.configureNotification(builder, null, null, NOTIFICATION_LED_FAILURE_COLOR,
NOTIFICATION_LED_BLINK_FAST, true);
int notificationId = NotificationIds.getSendFailedNotificationId(account);
getNotificationManager().notify(notificationId, builder.build());
}
public void clearSendFailedNotification(Account account) {
int notificationId = NotificationIds.getSendFailedNotificationId(account);
getNotificationManager().cancel(notificationId);
}
private int getSendFailedNotificationIcon() {
//TODO: Use a different icon for send failure notifications
return controller.platformSupportsVectorDrawables() ?
R.drawable.ic_notify_new_mail_vector : R.drawable.ic_notify_new_mail;
}
private NotificationManager getNotificationManager() {
return controller.getNotificationManager();
}
}

View file

@ -0,0 +1,102 @@
package com.fsck.k9.notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.support.v4.app.NotificationCompat;
import com.fsck.k9.Account;
import com.fsck.k9.R;
import com.fsck.k9.mail.Folder;
import static com.fsck.k9.notification.NotificationController.NOTIFICATION_LED_BLINK_FAST;
class SyncNotifications {
private static final boolean NOTIFICATION_LED_WHILE_SYNCING = false;
private final NotificationController controller;
private final NotificationActionCreator actionBuilder;
public SyncNotifications(NotificationController controller, NotificationActionCreator actionBuilder) {
this.controller = controller;
this.actionBuilder = actionBuilder;
}
public void showSendingNotification(Account account) {
Context context = controller.getContext();
String accountName = controller.getAccountName(account);
String title = context.getString(R.string.notification_bg_send_title);
String tickerText = context.getString(R.string.notification_bg_send_ticker, accountName);
PendingIntent showMessageListPendingIntent = actionBuilder.createViewInboxPendingIntent(account);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
.setSmallIcon(R.drawable.ic_notify_check_mail)
.setWhen(System.currentTimeMillis())
.setOngoing(true)
.setTicker(tickerText)
.setContentTitle(title)
.setContentText(accountName)
.setContentIntent(showMessageListPendingIntent)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
if (NOTIFICATION_LED_WHILE_SYNCING) {
controller.configureNotification(builder, null, null,
account.getNotificationSetting().getLedColor(),
NOTIFICATION_LED_BLINK_FAST, true);
}
int notificationId = NotificationIds.getFetchingMailNotificationId(account);
getNotificationManager().notify(notificationId, builder.build());
}
public void clearSendingNotification(Account account) {
int notificationId = NotificationIds.getFetchingMailNotificationId(account);
getNotificationManager().cancel(notificationId);
}
public void showFetchingMailNotification(Account account, Folder folder) {
String accountName = account.getDescription();
String folderName = folder.getName();
Context context = controller.getContext();
String tickerText = context.getString(R.string.notification_bg_sync_ticker, accountName, folderName);
String title = context.getString(R.string.notification_bg_sync_title);
//TODO: Use format string from resources
String text = accountName + context.getString(R.string.notification_bg_title_separator) + folderName;
PendingIntent showMessageListPendingIntent = actionBuilder.createViewInboxPendingIntent(account);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
.setSmallIcon(R.drawable.ic_notify_check_mail)
.setWhen(System.currentTimeMillis())
.setOngoing(true)
.setTicker(tickerText)
.setContentTitle(title)
.setContentText(text)
.setContentIntent(showMessageListPendingIntent)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
if (NOTIFICATION_LED_WHILE_SYNCING) {
controller.configureNotification(builder, null, null,
account.getNotificationSetting().getLedColor(),
NOTIFICATION_LED_BLINK_FAST, true);
}
int notificationId = NotificationIds.getFetchingMailNotificationId(account);
getNotificationManager().notify(notificationId, builder.build());
}
public void clearFetchingMailNotification(Account account) {
int notificationId = NotificationIds.getFetchingMailNotificationId(account);
getNotificationManager().cancel(notificationId);
}
private NotificationManager getNotificationManager() {
return controller.getNotificationManager();
}
}