mirror of
https://github.com/moparisthebest/k-9
synced 2025-01-08 04:08:15 -05:00
Add Android Wear support
This commit is contained in:
parent
ee7a95b750
commit
23c49d834d
@ -22,16 +22,38 @@ import com.fsck.k9.service.NotificationActionService;
|
||||
public class NotificationDeleteConfirmation extends Activity {
|
||||
private final static String EXTRA_ACCOUNT = "account";
|
||||
private final static String EXTRA_MESSAGE_LIST = "messages";
|
||||
private final static String EXTRA_NOTIFICATION_ID = NotificationActionService.EXTRA_NOTIFICATION_ID;
|
||||
|
||||
private final static int DIALOG_CONFIRM = 1;
|
||||
|
||||
/**
|
||||
* The account to delete the messages on.
|
||||
*/
|
||||
private Account mAccount;
|
||||
/**
|
||||
* The messages to delete.
|
||||
*/
|
||||
private ArrayList<MessageReference> mMessageRefs;
|
||||
/**
|
||||
* ID of the notification that triggered this Activity.
|
||||
* To make sure we close the correte notification afterwards because
|
||||
* there may be multiple of them due to Android Wear stacked notifications.
|
||||
*/
|
||||
private int mNotificationID;
|
||||
|
||||
public static PendingIntent getIntent(Context context, final Account account, final Serializable refs) {
|
||||
/**
|
||||
*
|
||||
* @param context context to create the PendingIntent.
|
||||
* @param account The account to delete the messages on.
|
||||
* @param refs The messages to delete.
|
||||
* @param notificationID ID of the notification that triggered this Activity.
|
||||
* @return PendingIntent that either deletes directly or shows a confirm-dialog on the phone (not on the wear device) first.
|
||||
*/
|
||||
public static PendingIntent getIntent(final Context context, final Account account, final Serializable refs, final int notificationID) {
|
||||
Intent i = new Intent(context, NotificationDeleteConfirmation.class);
|
||||
i.putExtra(EXTRA_ACCOUNT, account.getUuid());
|
||||
i.putExtra(EXTRA_MESSAGE_LIST, refs);
|
||||
i.putExtra(EXTRA_NOTIFICATION_ID, notificationID);
|
||||
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
|
||||
return PendingIntent.getActivity(context, account.getAccountNumber(), i, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
@ -49,11 +71,12 @@ public class NotificationDeleteConfirmation extends Activity {
|
||||
|
||||
mAccount = preferences.getAccount(intent.getStringExtra(EXTRA_ACCOUNT));
|
||||
mMessageRefs = intent.getParcelableArrayListExtra(EXTRA_MESSAGE_LIST);
|
||||
mNotificationID = intent.getIntExtra(EXTRA_NOTIFICATION_ID, mAccount.getAccountNumber());
|
||||
|
||||
if (mAccount == null || mMessageRefs == null || mMessageRefs.isEmpty()) {
|
||||
finish();
|
||||
} else if (!K9.confirmDeleteFromNotification()) {
|
||||
triggerDelete();
|
||||
triggerDelete(mNotificationID);
|
||||
finish();
|
||||
} else {
|
||||
showDialog(DIALOG_CONFIRM);
|
||||
@ -71,7 +94,7 @@ public class NotificationDeleteConfirmation extends Activity {
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
triggerDelete();
|
||||
triggerDelete(mNotificationID);
|
||||
finish();
|
||||
}
|
||||
},
|
||||
@ -100,8 +123,8 @@ public class NotificationDeleteConfirmation extends Activity {
|
||||
super.onPrepareDialog(id, d);
|
||||
}
|
||||
|
||||
private void triggerDelete() {
|
||||
Intent i = NotificationActionService.getDeleteAllMessagesIntent(this, mAccount, mMessageRefs);
|
||||
private void triggerDelete(final int notificationID) {
|
||||
Intent i = NotificationActionService.getDeleteAllMessagesIntent(this, mAccount, mMessageRefs, notificationID);
|
||||
startService(i);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
@ -215,6 +219,10 @@ public class MessagingController implements Runnable {
|
||||
* {@link #removeMatchingMessage(android.content.Context, com.fsck.k9.activity.MessageReference)} instead.
|
||||
*/
|
||||
LinkedList<LocalMessage> messages;
|
||||
/**
|
||||
* Stacked notifications that share this notification as ther summary-notification.
|
||||
*/
|
||||
Map<String, Integer> stackedNotifications = new HashMap<String, Integer>();
|
||||
/**
|
||||
* List of references for messages that the user is still to be notified of,
|
||||
* but which don't fit into the inbox style anymore. It's sorted from newest
|
||||
@ -257,9 +265,49 @@ public class MessagingController implements Runnable {
|
||||
messages.addFirst(m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a stacked notification that this is a summary notification for.
|
||||
* @param ref the message to add a stacked notification for
|
||||
* @param notificationId the id of the stacked notification
|
||||
*/
|
||||
public void addStackedChildNotification(final MessageReference ref, final int notificationId) {
|
||||
stackedNotifications.put(ref.getUid(), new Integer(notificationId));
|
||||
}
|
||||
/**
|
||||
* Add a stacked notification that this is a summary notification for.
|
||||
* @param msg the message to add a stacked notification for
|
||||
* @param notificationId the id of the stacked notification
|
||||
*/
|
||||
public void addStackedChildNotification(final Message msg, final int notificationId) {
|
||||
stackedNotifications.put(msg.getUid(), new Integer(notificationId));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the IDs of all stacked notifications this is a summary notification for.
|
||||
*/
|
||||
public Collection<Integer> getStackedChildNotifications() {
|
||||
return stackedNotifications.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ref the message to check for
|
||||
* @return null or the notification ID of a stacked notification for the given message
|
||||
*/
|
||||
public Integer getStackedChildNotification(final MessageReference ref) {
|
||||
return stackedNotifications.get(ref.getUid());
|
||||
}
|
||||
/**
|
||||
* @param msg the message to check for
|
||||
* @return null or the notification ID of a stacked notification for the given message
|
||||
*/
|
||||
public Integer getStackedChildNotification(final Message msg) {
|
||||
return stackedNotifications.get(msg.getUid());
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a certain message from the message list.
|
||||
*
|
||||
* @see #getStackedChildNotification(com.fsck.k9.activity.MessageReference) for stacked
|
||||
* notifications you may consider to cancel.
|
||||
* @param context A context.
|
||||
* @param ref Reference of the message to remove
|
||||
* @return true if message was found and removed, false otherwise
|
||||
@ -307,7 +355,7 @@ public class MessagingController implements Runnable {
|
||||
public int getNewMessageCount() {
|
||||
return messages.size() + droppedMessages.size();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Key is accountNumber
|
||||
private final ConcurrentMap<Integer, NotificationData> notificationData = new ConcurrentHashMap<Integer, NotificationData>();
|
||||
@ -1762,7 +1810,18 @@ public class MessagingController implements Runnable {
|
||||
synchronized (data) {
|
||||
MessageReference ref = localMessage.makeMessageReference();
|
||||
if (data.removeMatchingMessage(context, ref)) {
|
||||
notifyAccountWithDataLocked(context, account, null, data);
|
||||
synchronized (data) {
|
||||
// if we remove a single message from the notification,
|
||||
// maybe there is a stacked notification active for that one message
|
||||
Integer childNotification = data.getStackedChildNotification(ref);
|
||||
if (childNotification != null) {
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.cancel(childNotification);
|
||||
}
|
||||
// update the (summary-) notification
|
||||
notifyAccountWithDataLocked(context, account, null, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4605,6 +4664,7 @@ public class MessagingController implements Runnable {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private CharSequence getMessageSubject(Context context, Message message) {
|
||||
String subject = message.getSubject();
|
||||
if (!TextUtils.isEmpty(subject)) {
|
||||
@ -4686,6 +4746,7 @@ public class MessagingController implements Runnable {
|
||||
private void notifyAccount(Context context, Account account,
|
||||
LocalMessage message, int previousUnreadMessageCount) {
|
||||
final NotificationData data = getNotificationData(account, previousUnreadMessageCount);
|
||||
//noinspection SynchronizationOnLocalVariableOrMethodParameter
|
||||
synchronized (data) {
|
||||
notifyAccountWithDataLocked(context, account, message, data);
|
||||
}
|
||||
@ -4694,7 +4755,89 @@ 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.
|
||||
* @param builder NotificationBuilder to add actions to
|
||||
* @param totalMsgCount if this is a stacked notification, how many other messages are there?
|
||||
* @param account the account we intent to act on
|
||||
* @param message the single message we intent to act on (in a stacked notification or a summary notification about a single message)
|
||||
* @param notificationID the id of the future notification. Will be used in the intents, so afterwards the correct notification gets closed.
|
||||
*/
|
||||
private void addWearActions(final NotificationCompat.Builder builder, final int totalMsgCount, final Account account, final Message message, final int notificationID) {
|
||||
ArrayList<MessageReference> subAllRefs = new ArrayList<MessageReference>();
|
||||
subAllRefs.add(new MessageReference(account.getUuid(), message.getFolder().getName(), message.getUid(), message.getFlags().size()==0?null:message.getFlags().iterator().next()));
|
||||
LinkedList<Message> msgList = new LinkedList<Message>();
|
||||
msgList.add(message);
|
||||
addWearActions(builder, totalMsgCount, 1, account, subAllRefs, msgList, notificationID);
|
||||
}
|
||||
/**
|
||||
* Build the specific notification actions for a single or multiple message on Android Wear.
|
||||
* @param builder NotificationBuilder to add actions to
|
||||
* @param totalMsgCount total message count (may be different from msgCount if this is a stacked notification)
|
||||
* @param msgCount message count to be handled in this (stacked or summary) notification
|
||||
* @param account the account we intent to act on
|
||||
* @param allRefs the messages we intent to act on
|
||||
* @param messages the messages we intent to act on
|
||||
* @param notificationID the id of the future notification. Will be used in the intents, so afterwards the correct notification gets closed.
|
||||
*/
|
||||
private void addWearActions(final NotificationCompat.Builder builder, final int totalMsgCount, final int msgCount, final Account account, final ArrayList<MessageReference> allRefs, final List<? extends Message> messages, final int notificationID) {
|
||||
// we need a new wearableExtender for each notification
|
||||
final NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender();
|
||||
|
||||
NotificationQuickDelete deleteOption = K9.getNotificationQuickDeleteBehaviour();
|
||||
boolean showDeleteAction = deleteOption == NotificationQuickDelete.ALWAYS ||
|
||||
(deleteOption == NotificationQuickDelete.FOR_SINGLE_MSG && msgCount == 1);
|
||||
|
||||
// note: while we are limited to 3 actions on the phone,
|
||||
// this does not seem to be a limit on Android Wear devices.
|
||||
// Tested on Moto 360, 8 actions seem to be no problem.
|
||||
|
||||
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, notificationID))
|
||||
.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_archive_dark,
|
||||
context.getString(R.string.notification_action_archive),
|
||||
NotificationActionService.getArchiveAllMessagesIntent(context, account, allRefs, totalMsgCount > msgCount, notificationID))
|
||||
.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, totalMsgCount > msgCount, notificationID))
|
||||
.build();
|
||||
builder.extend(wearableExtender.addAction(wearActionSpam));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create/Upate and show notifications about new messages
|
||||
* or that there suddenly are no longer any new messages on an account
|
||||
* @param context used to create the notification and it's intents
|
||||
* @param account the account that has new messages
|
||||
* @param message the message (if it's just one)
|
||||
* @param data all the details
|
||||
*/
|
||||
private void notifyAccountWithDataLocked(Context context, final Account account,
|
||||
LocalMessage message, NotificationData data) {
|
||||
boolean updateSilently = false;
|
||||
|
||||
@ -4748,13 +4891,63 @@ public class MessagingController implements Runnable {
|
||||
|
||||
if (platformSupportsExtendedNotifications() && !privacyModeEnabled) {
|
||||
if (newMessages > 1) {
|
||||
|
||||
// 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);
|
||||
int nID = account.getAccountNumber();
|
||||
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 as summary
|
||||
subBuilder.setAutoCancel(true); // summary closes all, stacked only itself
|
||||
|
||||
nID = 1000 + nID;
|
||||
// reuse existing notification IDs if some of the stacked messages
|
||||
// are already shown on the wear device.
|
||||
Integer realnID = data.getStackedChildNotification(m);
|
||||
if (realnID == null) {
|
||||
realnID = nID;
|
||||
}
|
||||
|
||||
// set content
|
||||
setNotificationContent(context, m, getMessageSender(context, account, m), getMessageSubject(context, m), subBuilder, accountDescr);
|
||||
|
||||
|
||||
// set actions
|
||||
addWearActions(subBuilder, newMessages, account, m, realnID);
|
||||
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(realnID, subBuilder.build());
|
||||
data.addStackedChildNotification(m, realnID);
|
||||
}
|
||||
// go on configuring the summary notification on the phone
|
||||
// The phone will only show the summary
|
||||
// the wear device will show the stacked notifications
|
||||
builder.setGroup(NOTIFICATION_GROUP_KEY);
|
||||
builder.setGroupSummary(true);
|
||||
|
||||
//do not set summary notification to localOnly.
|
||||
//Wear devices use the vibrate pattern of the summary
|
||||
//despite not displaying the summary
|
||||
builder.setLocalOnly(true);
|
||||
|
||||
if (!data.droppedMessages.isEmpty()) {
|
||||
style.setSummaryText(context.getString(R.string.notification_additional_messages,
|
||||
data.droppedMessages.size(), accountDescr));
|
||||
@ -4767,22 +4960,19 @@ 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()
|
||||
? R.drawable.ic_action_single_message_options_dark_vector
|
||||
: R.drawable.ic_action_single_message_options_dark,
|
||||
context.getString(R.string.notification_action_reply),
|
||||
NotificationActionService.getReplyIntent(context, account, message.makeMessageReference()));
|
||||
NotificationActionService.getReplyIntent(context, account, message.makeMessageReference(), account.getAccountNumber()));
|
||||
|
||||
// add /different) actions to show on connected Android Wear devices
|
||||
// do not add these to the a summary notification or they will affect all stacked
|
||||
// notifications
|
||||
addWearActions(builder, newMessages, newMessages, account, allRefs, data.messages, account.getAccountNumber());
|
||||
}
|
||||
|
||||
// Mark Read on phone
|
||||
@ -4791,59 +4981,27 @@ public class MessagingController implements Runnable {
|
||||
? R.drawable.ic_action_mark_as_read_dark_vector
|
||||
: R.drawable.ic_action_mark_as_read_dark,
|
||||
context.getString(R.string.notification_action_mark_as_read),
|
||||
NotificationActionService.getReadAllMessagesIntent(context, account, allRefs));
|
||||
NotificationActionService.getReadAllMessagesIntent(context, account, allRefs, account.getAccountNumber()));
|
||||
|
||||
NotificationQuickDelete deleteOption = K9.getNotificationQuickDeleteBehaviour();
|
||||
boolean showDeleteAction = deleteOption == NotificationQuickDelete.ALWAYS ||
|
||||
(deleteOption == NotificationQuickDelete.FOR_SINGLE_MSG && newMessages == 1);
|
||||
|
||||
NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender();
|
||||
|
||||
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, account.getAccountNumber()));
|
||||
}
|
||||
if (NotificationActionService.isArchiveAllMessagesWearAvaliable(context, account, data.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, 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);
|
||||
@ -4857,6 +5015,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, account.getAccountNumber()));
|
||||
|
||||
// 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;
|
||||
|
||||
@ -4885,32 +5086,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(final Context context, final Message message, final CharSequence sender, final CharSequence subject, final NotificationCompat.Builder builder, final 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) {
|
||||
@ -5086,12 +5285,16 @@ public class MessagingController implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
/** Cancel a notification of new email messages */
|
||||
public void notifyAccountCancel(Context context, Account account) {
|
||||
NotificationManager notifMgr =
|
||||
/**
|
||||
* Cancel a notification of new email messages
|
||||
* @param account all notifications for this account will be canceled and removed
|
||||
*/
|
||||
public void notifyAccountCancel(final Context context, final Account account) {
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notifMgr.cancel(account.getAccountNumber());
|
||||
notifMgr.cancel(-1000 - account.getAccountNumber());
|
||||
notificationManager.cancel(account.getAccountNumber());
|
||||
notificationManager.cancel(-1000 - account.getAccountNumber());
|
||||
|
||||
notificationData.remove(account.getAccountNumber());
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@ package com.fsck.k9.service;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import com.fsck.k9.Account;
|
||||
@ -12,8 +11,10 @@ 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.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@ -34,37 +35,83 @@ public class NotificationActionService extends CoreService {
|
||||
private final static String EXTRA_ACCOUNT = "account";
|
||||
private final static String EXTRA_MESSAGE = "message";
|
||||
private final static String EXTRA_MESSAGE_LIST = "messages";
|
||||
private final static String EXTRA_DONTCANCEL = "dontcancel";
|
||||
/**
|
||||
* ID of the notification that triggered an intent.
|
||||
* Used to cancel exactly that one notification because due to
|
||||
* Android Wear there may be multiple notifications per account.
|
||||
*/
|
||||
public final static String EXTRA_NOTIFICATION_ID = "notificationid";
|
||||
|
||||
public static PendingIntent getReplyIntent(Context context, final Account account, final MessageReference ref) {
|
||||
/**
|
||||
*
|
||||
* @param context context to use for creating the {@link Intent}
|
||||
* @param account the account we intent to act on
|
||||
* @param ref the message we intent to act on
|
||||
* @param notificationID ID of the notification, this intent is for.
|
||||
* @see #EXTRA_NOTIFICATION_ID
|
||||
* @return the requested intent. To be used in a Notification.
|
||||
*/
|
||||
public static PendingIntent getReplyIntent(Context context, final Account account, final MessageReference ref, final int notificationID) {
|
||||
Intent i = new Intent(context, NotificationActionService.class);
|
||||
i.putExtra(EXTRA_ACCOUNT, account.getUuid());
|
||||
i.putExtra(EXTRA_MESSAGE, ref);
|
||||
i.putExtra(EXTRA_NOTIFICATION_ID, notificationID);
|
||||
i.setAction(REPLY_ACTION);
|
||||
|
||||
return PendingIntent.getService(context, account.getAccountNumber(), i, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
}
|
||||
|
||||
public static PendingIntent getReadAllMessagesIntent(Context context, final Account account, final Serializable refs) {
|
||||
/**
|
||||
*
|
||||
* @param context context to use for creating the {@link Intent}
|
||||
* @param account the account we intent to act on
|
||||
* @param refs the messages we intent to act on
|
||||
* @param notificationID ID of the notification, this intent is for.
|
||||
* @return the requested intent. To be used in a Notification.
|
||||
* @see #EXTRA_NOTIFICATION_ID
|
||||
*/
|
||||
public static PendingIntent getReadAllMessagesIntent(Context context, final Account account, final Serializable refs, final int notificationID) {
|
||||
Intent i = new Intent(context, NotificationActionService.class);
|
||||
i.putExtra(EXTRA_ACCOUNT, account.getUuid());
|
||||
i.putExtra(EXTRA_MESSAGE_LIST, refs);
|
||||
i.putExtra(EXTRA_NOTIFICATION_ID, notificationID);
|
||||
i.setAction(READ_ALL_ACTION);
|
||||
|
||||
return PendingIntent.getService(context, account.getAccountNumber(), i, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
}
|
||||
|
||||
public static PendingIntent getAcknowledgeIntent(Context context, final Account account) {
|
||||
/**
|
||||
*
|
||||
* @param context context to use for creating the {@link Intent}
|
||||
* @param account the account for the intent to act on
|
||||
* @param notificationID ID of the notification, this intent is for.
|
||||
* @return the requested intent. To be used in a Notification.
|
||||
* @see #EXTRA_NOTIFICATION_ID
|
||||
*/
|
||||
public static PendingIntent getAcknowledgeIntent(Context context, final Account account, final int notificationID) {
|
||||
Intent i = new Intent(context, NotificationActionService.class);
|
||||
i.putExtra(EXTRA_ACCOUNT, account.getUuid());
|
||||
i.putExtra(EXTRA_NOTIFICATION_ID, notificationID);
|
||||
i.setAction(ACKNOWLEDGE_ACTION);
|
||||
|
||||
return PendingIntent.getService(context, account.getAccountNumber(), i, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
}
|
||||
|
||||
public static Intent getDeleteAllMessagesIntent(Context context, final Account account, final Serializable refs) {
|
||||
/**
|
||||
*
|
||||
* @param context context to use for creating the {@link Intent}
|
||||
* @param account the account we intent to act on
|
||||
* @param refs the messages we intent to act on
|
||||
* @param notificationID ID of the notification, this intent is for.
|
||||
* @return the requested intent. To be used in a Notification.
|
||||
* @see #EXTRA_NOTIFICATION_ID
|
||||
*/
|
||||
public static Intent getDeleteAllMessagesIntent(Context context, final Account account, final Serializable refs, final int notificationID) {
|
||||
Intent i = new Intent(context, NotificationActionService.class);
|
||||
i.putExtra(EXTRA_ACCOUNT, account.getUuid());
|
||||
i.putExtra(EXTRA_MESSAGE_LIST, refs);
|
||||
i.putExtra(EXTRA_NOTIFICATION_ID, notificationID);
|
||||
i.setAction(DELETE_ALL_ACTION);
|
||||
|
||||
return i;
|
||||
@ -74,21 +121,35 @@ public class NotificationActionService extends CoreService {
|
||||
* Check if for the given parameters the ArchiveAllMessages intent is possible for Android Wear.
|
||||
* (No confirmation on the phone required and moving these messages to the spam-folder possible)<br/>
|
||||
* Since we can not show a toast like on the phone screen, we must not offer actions that can not be performed.
|
||||
* @see #getArchiveAllMessagesIntent(android.content.Context, com.fsck.k9.Account, java.io.Serializable)
|
||||
* @see #getArchiveAllMessagesIntent(android.content.Context, com.fsck.k9.Account, java.io.Serializable, boolean, int)
|
||||
* @param context the context to get a {@link MessagingController}
|
||||
* @param account the account (must allow moving messages to allow true as a result)
|
||||
* @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));
|
||||
}
|
||||
|
||||
public static PendingIntent getArchiveAllMessagesIntent(Context context, final Account account, final Serializable refs) {
|
||||
/**
|
||||
*
|
||||
* @param context context to use for creating the {@link Intent}
|
||||
* @param account the account we intent to act on
|
||||
* @param refs the messages we intent to act on
|
||||
* @param dontCancel if true, after executing the intent, not all notifications for this account are canceled automatically
|
||||
* @param notificationID ID of the notification, this intent is for.
|
||||
* @return the requested intent. To be used in a Notification.
|
||||
* @see #EXTRA_NOTIFICATION_ID
|
||||
*/
|
||||
public static PendingIntent getArchiveAllMessagesIntent(Context context, final Account account, final Serializable refs, final boolean dontCancel, final int notificationID) {
|
||||
Intent i = new Intent(context, NotificationActionService.class);
|
||||
i.putExtra(EXTRA_ACCOUNT, account.getUuid());
|
||||
i.putExtra(EXTRA_MESSAGE_LIST, refs);
|
||||
if (dontCancel) {
|
||||
i.putExtra(EXTRA_DONTCANCEL, true);
|
||||
}
|
||||
i.putExtra(EXTRA_NOTIFICATION_ID, notificationID);
|
||||
i.setAction(ARCHIVE_ALL_ACTION);
|
||||
|
||||
return PendingIntent.getService(context, account.getAccountNumber(), i, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
@ -99,34 +160,48 @@ public class NotificationActionService extends CoreService {
|
||||
* Check if for the given parameters the SpamAllMessages intent is possible for Android Wear.
|
||||
* (No confirmation on the phone required and moving these messages to the spam-folder possible)<br/>
|
||||
* Since we can not show a toast like on the phone screen, we must not offer actions that can not be performed.
|
||||
* @see #getSpamAllMessagesIntent(android.content.Context, com.fsck.k9.Account, java.io.Serializable)
|
||||
* @see #getSpamAllMessagesIntent(android.content.Context, com.fsck.k9.Account, java.io.Serializable, boolean, int)
|
||||
* @param context the context to get a {@link MessagingController}
|
||||
* @param account the account (must allow moving messages to allow true as a result)
|
||||
* @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));
|
||||
}
|
||||
|
||||
public static PendingIntent getSpamAllMessagesIntent(Context context, final Account account, final Serializable refs) {
|
||||
/**
|
||||
*
|
||||
* @param context context to use for creating the {@link Intent}
|
||||
* @param account the account we intent to act on
|
||||
* @param refs the messages we intent to act on
|
||||
* @param dontCancel if true, after executing the intent, not all notifications for this account are canceled automatically
|
||||
* @param notificationID ID of the notification, this intent is for.
|
||||
* @return the requested intent. To be used in a Notification.
|
||||
* @see #EXTRA_NOTIFICATION_ID
|
||||
*/
|
||||
public static PendingIntent getSpamAllMessagesIntent(Context context, final Account account, final Serializable refs, final boolean dontCancel, final int notificationID) {
|
||||
Intent i = new Intent(context, NotificationActionService.class);
|
||||
i.putExtra(EXTRA_ACCOUNT, account.getUuid());
|
||||
i.putExtra(EXTRA_MESSAGE_LIST, refs);
|
||||
if (dontCancel) {
|
||||
i.putExtra(EXTRA_DONTCANCEL, true);
|
||||
}
|
||||
i.putExtra(EXTRA_NOTIFICATION_ID, notificationID);
|
||||
i.setAction(SPAM_ALL_ACTION);
|
||||
|
||||
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;
|
||||
}
|
||||
@ -237,10 +312,21 @@ public class NotificationActionService extends CoreService {
|
||||
} else if (ACKNOWLEDGE_ACTION.equals(action)) {
|
||||
// nothing to do here, we just want to cancel the notification so the list
|
||||
// of unseen messages is reset
|
||||
Log.i(K9.LOG_TAG, "notification acknowledged");
|
||||
}
|
||||
|
||||
/* there's no point in keeping the notification after the user clicked on it */
|
||||
controller.notifyAccountCancel(this, account);
|
||||
// if this was a stacked notification on Android Wear, update the summary
|
||||
// notification and keep the other stacked notifications
|
||||
if (!intent.hasExtra(EXTRA_DONTCANCEL)) {
|
||||
// there's no point in keeping the notification after the user clicked on it
|
||||
if (intent.hasExtra(EXTRA_NOTIFICATION_ID)) {
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager)this.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.cancel(intent.getIntExtra(EXTRA_NOTIFICATION_ID, account.getAccountNumber()));
|
||||
} else {
|
||||
controller.notifyAccountCancel(this, account);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.w(K9.LOG_TAG, "Could not find account for notification action.");
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user