1
0
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:
Marcus Wolschon 2015-05-01 19:56:01 +02:00 committed by cketti
parent ee7a95b750
commit 23c49d834d
3 changed files with 415 additions and 103 deletions

View File

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

View File

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

View File

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