mirror of
https://github.com/moparisthebest/k-9
synced 2025-01-08 04:08:15 -05:00
Support for stacked notifications in
#619 "Add android wear support" No reply with voice yet (as requested in the ticket). No user-configurable actions yet, just delete+archive+spam
This commit is contained in:
parent
05934d75d8
commit
2701ffd2ac
@ -151,6 +151,10 @@ public class MessagingController implements Runnable {
|
||||
private static final String PENDING_COMMAND_APPEND = "com.fsck.k9.MessagingController.append";
|
||||
private static final String PENDING_COMMAND_MARK_ALL_AS_READ = "com.fsck.k9.MessagingController.markAllAsRead";
|
||||
private static final String PENDING_COMMAND_EXPUNGE = "com.fsck.k9.MessagingController.expunge";
|
||||
/**
|
||||
* Key to group stacked notifications on Android Wear.
|
||||
*/
|
||||
private static final String NOTIFICATION_GROUP_KEY = "com.fsck.k9.MessagingController.notificationGroup";
|
||||
|
||||
public static class UidReverseComparator implements Comparator<Message> {
|
||||
@Override
|
||||
@ -158,7 +162,7 @@ public class MessagingController implements Runnable {
|
||||
if (o1 == null || o2 == null || o1.getUid() == null || o2.getUid() == null) {
|
||||
return 0;
|
||||
}
|
||||
int id1, id2;
|
||||
int id1, id2;
|
||||
try {
|
||||
id1 = Integer.parseInt(o1.getUid());
|
||||
id2 = Integer.parseInt(o2.getUid());
|
||||
@ -4673,6 +4677,7 @@ public class MessagingController implements Runnable {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private CharSequence getMessageSubject(Context context, Message message) {
|
||||
String subject = message.getSubject();
|
||||
if (!TextUtils.isEmpty(subject)) {
|
||||
@ -4762,7 +4767,62 @@ public class MessagingController implements Runnable {
|
||||
// Maximum number of senders to display in a lock screen notification.
|
||||
private static final int NUM_SENDERS_IN_LOCK_SCREEN_NOTIFICATION = 5;
|
||||
|
||||
private void notifyAccountWithDataLocked(Context context, Account account,
|
||||
/**
|
||||
* Build the specific notification actions for a single message on Android Wear.
|
||||
*/
|
||||
private void addWearActions(final NotificationCompat.WearableExtender wearableExtender, final NotificationCompat.Builder builder, Account account, Message messages) {
|
||||
ArrayList<MessageReference> subAllRefs = new ArrayList<MessageReference>();
|
||||
subAllRefs.add(new MessageReference(account.getUuid(), messages.getFolder().getName(), messages.getUid(), messages.getFlags().size()==0?null:messages.getFlags().iterator().next()));
|
||||
LinkedList<Message> msgList = new LinkedList<Message>();
|
||||
msgList.add(messages);
|
||||
addWearActions(wearableExtender, builder, 1, account, subAllRefs, msgList);
|
||||
}
|
||||
/**
|
||||
* Build the specific notification actions for a single or multiple message on Android Wear.
|
||||
*/
|
||||
private void addWearActions(final NotificationCompat.WearableExtender wearableExtender, final NotificationCompat.Builder builder, int msgCount, Account account, ArrayList<MessageReference> allRefs, List<? extends Message> messages) {
|
||||
NotificationQuickDelete deleteOption = K9.getNotificationQuickDeleteBehaviour();
|
||||
boolean showDeleteAction = deleteOption == NotificationQuickDelete.ALWAYS ||
|
||||
(deleteOption == NotificationQuickDelete.FOR_SINGLE_MSG && msgCount == 1);
|
||||
|
||||
|
||||
if (showDeleteAction) {
|
||||
// Delete on wear only if no confirmation is required
|
||||
// because they would have to be confirmed on the phone, not the wear device
|
||||
if (!K9.confirmDeleteFromNotification()) {
|
||||
NotificationCompat.Action wearActionDelete =
|
||||
new NotificationCompat.Action.Builder(
|
||||
R.drawable.ic_action_delete_dark,
|
||||
context.getString(R.string.notification_action_delete),
|
||||
NotificationDeleteConfirmation.getIntent(context, account, allRefs))
|
||||
.build();
|
||||
builder.extend(wearableExtender.addAction(wearActionDelete));
|
||||
}
|
||||
}
|
||||
if (NotificationActionService.isArchiveAllMessagesWearAvaliable(context, account, messages)) {
|
||||
|
||||
// Archive on wear
|
||||
NotificationCompat.Action wearActionArchive =
|
||||
new NotificationCompat.Action.Builder(
|
||||
R.drawable.ic_action_delete_dark,
|
||||
context.getString(R.string.notification_action_archive),
|
||||
NotificationActionService.getArchiveAllMessagesIntent(context, account, allRefs))
|
||||
.build();
|
||||
builder.extend(wearableExtender.addAction(wearActionArchive));
|
||||
}
|
||||
if (NotificationActionService.isSpamAllMessagesWearAvaliable(context, account, messages)) {
|
||||
|
||||
// Spam on wear
|
||||
NotificationCompat.Action wearActionSpam =
|
||||
new NotificationCompat.Action.Builder(
|
||||
R.drawable.ic_action_delete_dark,
|
||||
context.getString(R.string.notification_action_spam),
|
||||
NotificationActionService.getSpamAllMessagesIntent(context, account, allRefs))
|
||||
.build();
|
||||
builder.extend(wearableExtender.addAction(wearActionSpam));
|
||||
}
|
||||
}
|
||||
private void notifyAccountWithDataLocked(Context context, final Account account,
|
||||
LocalMessage message, NotificationData data) {
|
||||
boolean updateSilently = false;
|
||||
|
||||
@ -4815,13 +4875,41 @@ public class MessagingController implements Runnable {
|
||||
data.supplyAllMessageRefs(allRefs);
|
||||
|
||||
if (platformSupportsExtendedNotifications() && !privacyModeEnabled) {
|
||||
NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender();
|
||||
if (newMessages > 1) {
|
||||
|
||||
//TODO: Stacked notifications for Android Wear
|
||||
// https://developer.android.com/training/wearables/notifications/stacks.html
|
||||
|
||||
// multiple messages pending, show inbox style
|
||||
NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle(builder);
|
||||
for (Message m : data.messages) {
|
||||
style.addLine(buildMessageSummary(context,
|
||||
getMessageSender(context, account, m),
|
||||
getMessageSubject(context, m)));
|
||||
|
||||
// build child-notifications for Android Wear,
|
||||
// so the grouped notification can be opened to
|
||||
// reveal the individual messages and their actions.
|
||||
NotificationCompat.Builder subBuilder = new NotificationCompat.Builder(context);
|
||||
subBuilder.setSmallIcon(R.drawable.ic_notify_new_mail);
|
||||
subBuilder.setWhen(System.currentTimeMillis());
|
||||
subBuilder.setGroup(NOTIFICATION_GROUP_KEY); // same group are the GroupSummary notification
|
||||
subBuilder.setGroupSummary(false); // this is not the summary
|
||||
|
||||
// set content
|
||||
setNotificationContent(context, m, getMessageSender(context, account, message), getMessageSubject(context, message), subBuilder, accountDescr);
|
||||
|
||||
// set actions
|
||||
addWearActions(wearableExtender, subBuilder, account, m);
|
||||
if (m.isSet(Flag.FLAGGED)) {
|
||||
subBuilder.setPriority(NotificationCompat.PRIORITY_HIGH);
|
||||
}
|
||||
// no sound, no vibrate, no LED because these are for the summary notification only
|
||||
// and depend on quiet time and user settings
|
||||
|
||||
// this must be done before the summary notification
|
||||
notifMgr.notify(account.getAccountNumber(), subBuilder.build());
|
||||
}
|
||||
if (!data.droppedMessages.isEmpty()) {
|
||||
style.setSummaryText(context.getString(R.string.notification_additional_messages,
|
||||
@ -4835,15 +4923,7 @@ public class MessagingController implements Runnable {
|
||||
builder.setStyle(style);
|
||||
} else {
|
||||
// single message pending, show big text
|
||||
NotificationCompat.BigTextStyle style = new NotificationCompat.BigTextStyle(builder);
|
||||
CharSequence preview = getMessagePreview(context, message);
|
||||
if (preview != null) {
|
||||
style.bigText(preview);
|
||||
}
|
||||
builder.setContentText(subject);
|
||||
builder.setSubText(accountDescr);
|
||||
builder.setContentTitle(sender);
|
||||
builder.setStyle(style);
|
||||
setNotificationContent(context, message, sender, subject, builder, accountDescr);
|
||||
|
||||
builder.addAction(
|
||||
platformSupportsLockScreenNotifications()
|
||||
@ -4865,53 +4945,29 @@ public class MessagingController implements Runnable {
|
||||
boolean showDeleteAction = deleteOption == NotificationQuickDelete.ALWAYS ||
|
||||
(deleteOption == NotificationQuickDelete.FOR_SINGLE_MSG && newMessages == 1);
|
||||
|
||||
NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender();
|
||||
// add /different) actions to show on connected Android Wear devices
|
||||
addWearActions(wearableExtender, builder, newMessages, account, allRefs, data.messages);
|
||||
|
||||
if (showDeleteAction) {
|
||||
// we need to pass the action directly to the activity, otherwise the
|
||||
// status bar won't be pulled up and we won't see the confirmation (if used)
|
||||
|
||||
// Delete on phone
|
||||
builder.addAction(
|
||||
platformSupportsLockScreenNotifications()
|
||||
? R.drawable.ic_action_delete_dark_vector
|
||||
: R.drawable.ic_action_delete_dark,
|
||||
context.getString(R.string.notification_action_delete),
|
||||
NotificationDeleteConfirmation.getIntent(context, account, allRefs));
|
||||
|
||||
// Delete on wear only if no confirmation is required
|
||||
if (!K9.confirmDeleteFromNotification()) {
|
||||
NotificationCompat.Action wearActionDelete =
|
||||
new NotificationCompat.Action.Builder(
|
||||
R.drawable.ic_action_delete_dark,
|
||||
context.getString(R.string.notification_action_delete),
|
||||
NotificationDeleteConfirmation.getIntent(context, account, allRefs))
|
||||
.build();
|
||||
builder.extend(wearableExtender.addAction(wearActionDelete));
|
||||
}
|
||||
platformSupportsLockScreenNotifications()
|
||||
? R.drawable.ic_action_delete_dark_vector
|
||||
: R.drawable.ic_action_delete_dark,
|
||||
context.getString(R.string.notification_action_delete),
|
||||
NotificationDeleteConfirmation.getIntent(context, account, allRefs));
|
||||
}
|
||||
if (NotificationActionService.isArchiveAllMessagesWearAvaliable(context, account, data.messages)) {
|
||||
// this may be a summary notification for multiple stacked notifications
|
||||
// for each individual mail, shown on Android Wear
|
||||
// The phone will only show the summary as it's the last notification given
|
||||
// to notifMgr with this account's key
|
||||
builder.setGroup(NOTIFICATION_GROUP_KEY);
|
||||
builder.setGroupSummary(true);
|
||||
|
||||
// Archive on wear
|
||||
NotificationCompat.Action wearActionArchive =
|
||||
new NotificationCompat.Action.Builder(
|
||||
R.drawable.ic_action_delete_dark,
|
||||
context.getString(R.string.notification_action_archive),
|
||||
NotificationActionService.getArchiveAllMessagesIntent(context, account, allRefs))
|
||||
.build();
|
||||
builder.extend(wearableExtender.addAction(wearActionArchive));
|
||||
}
|
||||
if (NotificationActionService.isSpamAllMessagesWearAvaliable(context, account, data.messages)) {
|
||||
|
||||
// Archive on wear
|
||||
NotificationCompat.Action wearActionSpam =
|
||||
new NotificationCompat.Action.Builder(
|
||||
R.drawable.ic_action_delete_dark,
|
||||
context.getString(R.string.notification_action_spam),
|
||||
NotificationActionService.getSpamAllMessagesIntent(context, account, allRefs))
|
||||
.build();
|
||||
builder.extend(wearableExtender.addAction(wearActionSpam));
|
||||
}
|
||||
} else {
|
||||
} else { // no extended notifications supported
|
||||
String accountNotice = context.getString(R.string.notification_new_one_account_fmt,
|
||||
unreadCount, accountDescr);
|
||||
builder.setContentTitle(accountNotice);
|
||||
@ -4925,6 +4981,49 @@ public class MessagingController implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
TaskStackBuilder stack = buildNotificationNavigationStack(context, account, message, newMessages, unreadCount, allRefs);
|
||||
|
||||
builder.setContentIntent(stack.getPendingIntent(
|
||||
account.getAccountNumber(),
|
||||
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT));
|
||||
builder.setDeleteIntent(NotificationActionService.getAcknowledgeIntent(context, account));
|
||||
|
||||
// Only ring or vibrate if we have not done so already on this account and fetch
|
||||
boolean ringAndVibrate = false;
|
||||
if (!updateSilently && !account.isRingNotified()) {
|
||||
account.setRingNotified(true);
|
||||
ringAndVibrate = true;
|
||||
}
|
||||
|
||||
NotificationSetting n = account.getNotificationSetting();
|
||||
|
||||
configureLockScreenNotification(builder, context, account, newMessages, unreadCount, accountDescr, sender, data.messages);
|
||||
|
||||
configureNotification(
|
||||
builder,
|
||||
(n.shouldRing()) ? n.getRingtone() : null,
|
||||
(n.shouldVibrate()) ? n.getVibration() : null,
|
||||
(n.isLed()) ? Integer.valueOf(n.getLedColor()) : null,
|
||||
K9.NOTIFICATION_LED_BLINK_SLOW,
|
||||
ringAndVibrate);
|
||||
|
||||
notifMgr.notify(account.getAccountNumber(), builder.build());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Builds the TaskStack of a notification using either buildMessageViewBackStack
|
||||
* or buildUnreadBackStack or buildMessageListBackStack depending on the
|
||||
* behavior we have on this device generation.
|
||||
* @param context
|
||||
* @param account
|
||||
* @param message (only used if there is only 1 new message)
|
||||
* @param newMessages (used on newer platforms)
|
||||
* @param unreadCount (used on platforms that support no extended notifications)
|
||||
* @param allRefs
|
||||
* @return
|
||||
*/
|
||||
private TaskStackBuilder buildNotificationNavigationStack(Context context, Account account, LocalMessage message, int newMessages, int unreadCount, ArrayList<MessageReference> allRefs) {
|
||||
TaskStackBuilder stack;
|
||||
boolean treatAsSingleMessageNotification;
|
||||
|
||||
@ -4953,32 +5052,30 @@ public class MessagingController implements Runnable {
|
||||
|
||||
stack = buildMessageListBackStack(context, account, initialFolder);
|
||||
}
|
||||
return stack;
|
||||
}
|
||||
|
||||
builder.setContentIntent(stack.getPendingIntent(
|
||||
account.getAccountNumber(),
|
||||
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT));
|
||||
builder.setDeleteIntent(NotificationActionService.getAcknowledgeIntent(context, account));
|
||||
|
||||
// Only ring or vibrate if we have not done so already on this account and fetch
|
||||
boolean ringAndVibrate = false;
|
||||
if (!updateSilently && !account.isRingNotified()) {
|
||||
account.setRingNotified(true);
|
||||
ringAndVibrate = true;
|
||||
/**
|
||||
* Set the content of a notification for a single message.
|
||||
* @see #getMessagePreview(android.content.Context, com.fsck.k9.mail.Message)
|
||||
* @param context
|
||||
* @param message
|
||||
* @param sender
|
||||
* @param subject
|
||||
* @param builder
|
||||
* @param accountDescr
|
||||
*/
|
||||
private NotificationCompat.Builder setNotificationContent(Context context, /*Local*/Message message, CharSequence sender, CharSequence subject, NotificationCompat.Builder builder, String accountDescr) {
|
||||
NotificationCompat.BigTextStyle style = new NotificationCompat.BigTextStyle(builder);
|
||||
CharSequence preview = getMessagePreview(context, message);
|
||||
if (preview != null) {
|
||||
style.bigText(preview);
|
||||
}
|
||||
|
||||
NotificationSetting n = account.getNotificationSetting();
|
||||
|
||||
configureLockScreenNotification(builder, context, account, newMessages, unreadCount, accountDescr, sender, data.messages);
|
||||
|
||||
configureNotification(
|
||||
builder,
|
||||
(n.shouldRing()) ? n.getRingtone() : null,
|
||||
(n.shouldVibrate()) ? n.getVibration() : null,
|
||||
(n.isLed()) ? Integer.valueOf(n.getLedColor()) : null,
|
||||
K9.NOTIFICATION_LED_BLINK_SLOW,
|
||||
ringAndVibrate);
|
||||
|
||||
notifMgr.notify(account.getAccountNumber(), builder.build());
|
||||
builder.setContentText(subject);
|
||||
builder.setSubText(accountDescr);
|
||||
builder.setContentTitle(sender);
|
||||
builder.setStyle(style);
|
||||
return builder;
|
||||
}
|
||||
|
||||
private TaskStackBuilder buildAccountsBackStack(Context context) {
|
||||
@ -5155,7 +5252,7 @@ public class MessagingController implements Runnable {
|
||||
}
|
||||
|
||||
/** Cancel a notification of new email messages */
|
||||
public void notifyAccountCancel(Context context, Account account) {
|
||||
public void notifyAccountCancel(final Context context, final Account account) {
|
||||
NotificationManager notifMgr =
|
||||
(NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notifMgr.cancel(account.getAccountNumber());
|
||||
|
@ -12,6 +12,7 @@ import com.fsck.k9.activity.MessageCompose;
|
||||
import com.fsck.k9.activity.MessageReference;
|
||||
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 android.app.PendingIntent;
|
||||
@ -80,7 +81,7 @@ public class NotificationActionService extends CoreService {
|
||||
* @param messages the messages to move to the spam folder (must be synchronized to allow true as a result)
|
||||
* @return true if the ArchiveAllMessages intent is available for the given messages
|
||||
*/
|
||||
public static boolean isArchiveAllMessagesWearAvaliable(Context context, final Account account, final LinkedList<LocalMessage> messages) {
|
||||
public static boolean isArchiveAllMessagesWearAvaliable(Context context, final Account account, final List<? extends Message> messages) {
|
||||
final MessagingController controller = MessagingController.getInstance(context);
|
||||
return (account.getArchiveFolderName() != null && !(account.getArchiveFolderName().equals(account.getSpamFolderName()) && K9.confirmSpam()) && isMovePossible(controller, account, account.getSentFolderName(), messages));
|
||||
}
|
||||
@ -105,7 +106,7 @@ public class NotificationActionService extends CoreService {
|
||||
* @param messages the messages to move to the spam folder (must be synchronized to allow true as a result)
|
||||
* @return true if the SpamAllMessages intent is available for the given messages
|
||||
*/
|
||||
public static boolean isSpamAllMessagesWearAvaliable(Context context, final Account account, final LinkedList<LocalMessage> messages) {
|
||||
public static boolean isSpamAllMessagesWearAvaliable(Context context, final Account account, final List<? extends Message> messages) {
|
||||
final MessagingController controller = MessagingController.getInstance(context);
|
||||
return (account.getSpamFolderName() != null && !K9.confirmSpam() && isMovePossible(controller, account, account.getSentFolderName(), messages));
|
||||
}
|
||||
@ -119,14 +120,14 @@ public class NotificationActionService extends CoreService {
|
||||
return PendingIntent.getService(context, account.getAccountNumber(), i, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
}
|
||||
|
||||
private static boolean isMovePossible(MessagingController controller, Account account, String dstFolder, List<LocalMessage> messages) {
|
||||
private static boolean isMovePossible(MessagingController controller, Account account, String dstFolder, List<? extends Message> messages) {
|
||||
if (!controller.isMoveCapable(account)) {
|
||||
return false;
|
||||
}
|
||||
if (K9.FOLDER_NONE.equalsIgnoreCase(dstFolder)) {
|
||||
return false;
|
||||
}
|
||||
for(LocalMessage messageToMove : messages) {
|
||||
for(Message messageToMove : messages) {
|
||||
if (!controller.isMoveCapable(messageToMove)) {
|
||||
return false;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user