diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index ba6f993a4..a7073c8fe 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -172,6 +172,14 @@
android:configChanges="locale"
>
+
+
+
+
Konto \"%s\" wiederherstellen
Neue Nachricht
+ %d neue Nachrichten
%d Ungelesen (%s)
+ und %d weitere (%s)
+
+ Antworten
+ Gelesen
+ Löschen
Auf neue Nachrichten prüfen: %s:%s
Auf neue Nachrichten prüfen
@@ -320,12 +326,19 @@ Um Fehler zu melden, neue Funktionen vorzuschlagen oder Fragen zu stellen, besuc
Löschen (nur in Nachrichtenansicht)
Sternmarkierte Löschen (nur in Nachrichtenansicht)
Spam
+ Löschen (aus Benachrichtigung)
Betreff in Benachrichtigungen verbergen
Niemals
Wenn der Bildschirm gesperrt ist
Immer
+ Löschen erlauben
+ Nie
+ Für einzelne Nachricht
+ Immer
+ Der Benachrichtigung eine Schaltfläche zum Löschen der Nachrichten hinzufügen
+
Gruppenoperationen-Schaltflächen
Zeige folgende Schaltflächen in der Nachrichtenliste an
Als (un)gelesen markieren
@@ -945,6 +958,10 @@ Um Fehler zu melden, neue Funktionen vorzuschlagen oder Fragen zu stellen, besuc
Löschen bestätigen
Wollen Sie diese Nachricht löschen?
+
+ - Wollen Sie diese Nachricht wirklich löschen?
+ - Wollen Sie wirklich %1$d Nachrichten löschen?
+
Löschen
Nicht löschen
diff --git a/res/values-v11/styles.xml b/res/values-v11/styles.xml
new file mode 100644
index 000000000..9893fa580
--- /dev/null
+++ b/res/values-v11/styles.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index 03675d8ed..d2b1d226e 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -701,4 +701,16 @@
- ALWAYS
+
+ - @string/global_settings_notification_quick_delete_never
+ - @string/global_settings_notification_quick_delete_when_single_msg
+ - @string/global_settings_notification_quick_delete_always
+
+
+
+ - NEVER
+ - FOR_SINGLE_MSG
+ - ALWAYS
+
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index a29718797..44e80e7ad 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -214,7 +214,13 @@ Please submit bug reports, contribute new features and ask questions at
Recreating account \"%s\"
New mail
+ %d new messages
%d Unread (%s)
+ + %d more on %s
+
+ Reply
+ Read
+ Delete
Checking mail: %s:%s
Checking mail
@@ -329,12 +335,19 @@ Please submit bug reports, contribute new features and ask questions at
Delete (in message view)
Delete Starred (in message view)
Spam
+ Delete (from notification)
Hide subject in notifications
Never
When device is locked
Always
+ Show \'Delete\' button
+ Never
+ For single message notification
+ Always
+ Show a button in the notification that allows quick message deletion
+
Batch buttons
Configure message list batch buttons
Mark read/unread
@@ -957,6 +970,10 @@ Please submit bug reports, contribute new features and ask questions at
Confirm deletion
Do you want to delete this message?
+
+ - Do you really want to delete this message?
+ - Do you really want to delete %1$d messages?
+
Yes
No
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 86a4c2eeb..02012079b 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -18,5 +18,8 @@
- @android:color/primary_text_light
+
diff --git a/res/values/themes.xml b/res/values/themes.xml
index 5d4e8ad6b..f92c80518 100644
--- a/res/values/themes.xml
+++ b/res/values/themes.xml
@@ -113,4 +113,21 @@
+
+
+
diff --git a/res/xml/global_preferences.xml b/res/xml/global_preferences.xml
index 51bf29284..75efa5598 100644
--- a/res/xml/global_preferences.xml
+++ b/res/xml/global_preferences.xml
@@ -294,6 +294,17 @@
android:dialogTitle="@string/quiet_time_ends"
android:title="@string/quiet_time_ends"
/>
+
+
+
CREATOR = new Creator() {
@Override
public MessageReference createFromParcel(Parcel source) {
diff --git a/src/com/fsck/k9/activity/MessageView.java b/src/com/fsck/k9/activity/MessageView.java
index 760e37fac..1e3e663bf 100644
--- a/src/com/fsck/k9/activity/MessageView.java
+++ b/src/com/fsck/k9/activity/MessageView.java
@@ -14,6 +14,7 @@ import com.fsck.k9.Preferences;
import com.fsck.k9.R;
import com.fsck.k9.activity.misc.SwipeGestureDetector;
import com.fsck.k9.activity.misc.SwipeGestureDetector.OnSwipeGestureListener;
+import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.crypto.PgpData;
import com.fsck.k9.fragment.MessageViewFragment;
import com.fsck.k9.fragment.MessageViewFragment.MessageViewFragmentListener;
@@ -41,7 +42,7 @@ public class MessageView extends K9FragmentActivity implements MessageViewFragme
private static final String EXTRA_MESSAGE_REFERENCE = "com.fsck.k9.MessageView_messageReference";
private static final String EXTRA_MESSAGE_REFERENCES = "com.fsck.k9.MessageView_messageReferences";
- private static final String EXTRA_MESSAGE_LIST_EXTRAS = "com.fsck.k9.MessageView_messageListExtras";
+ private static final String EXTRA_FROM_NOTIFICATION ="com.fsck.k9.MessageView_fromNotification";
/**
* @see #mLastDirection
@@ -50,16 +51,21 @@ public class MessageView extends K9FragmentActivity implements MessageViewFragme
private static final int NEXT = 2;
- public static void actionView(Context context, MessageReference messRef,
- ArrayList messReferences, Bundle messageListExtras) {
+ public static Intent actionViewIntent(Context context, MessageReference messRef,
+ ArrayList messReferences) {
Intent i = new Intent(context, MessageView.class);
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- i.putExtra(EXTRA_MESSAGE_LIST_EXTRAS, messageListExtras);
i.putExtra(EXTRA_MESSAGE_REFERENCE, messRef);
i.putParcelableArrayListExtra(EXTRA_MESSAGE_REFERENCES, messReferences);
- context.startActivity(i);
+ return i;
}
+ public static Intent actionHandleNotificationIntent(Context context, MessageReference ref) {
+ Intent i = actionViewIntent(context, ref, null);
+ i.setFlags(i.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
+ i.putExtra(EXTRA_FROM_NOTIFICATION, true);
+ return i;
+ }
private StorageManager.StorageListener mStorageListener = new StorageListenerImplementation();
private Account mAccount;
@@ -165,6 +171,9 @@ public class MessageView extends K9FragmentActivity implements MessageViewFragme
onAccountUnavailable();
return;
}
+ if (getIntent().getBooleanExtra(EXTRA_FROM_NOTIFICATION, false)) {
+ MessagingController.getInstance(getApplication()).notifyAccountCancel(this, mAccount);
+ }
StorageManager.getInstance(getApplication()).addListener(mStorageListener);
}
@@ -438,7 +447,9 @@ public class MessageView extends K9FragmentActivity implements MessageViewFragme
private void showNextMessage() {
findSurroundingMessagesUid();
- mMessageReferences.remove(mMessageReference);
+ if (mMessageReferences == null) {
+ mMessageReferences.remove(mMessageReference);
+ }
if (mLastDirection == NEXT && mNextMessage != null) {
onNext();
} else if (mLastDirection == PREVIOUS && mPreviousMessage != null) {
@@ -491,6 +502,10 @@ public class MessageView extends K9FragmentActivity implements MessageViewFragme
private void findSurroundingMessagesUid() {
mNextMessage = mPreviousMessage = null;
+ if (mMessageReferences == null) {
+ return;
+ }
+
int i = mMessageReferences.indexOf(mMessageReference);
if (i < 0) {
return;
diff --git a/src/com/fsck/k9/activity/NotificationDeleteConfirmation.java b/src/com/fsck/k9/activity/NotificationDeleteConfirmation.java
new file mode 100644
index 000000000..aeca92b8b
--- /dev/null
+++ b/src/com/fsck/k9/activity/NotificationDeleteConfirmation.java
@@ -0,0 +1,103 @@
+package com.fsck.k9.activity;
+
+import java.util.ArrayList;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.fsck.k9.Account;
+import com.fsck.k9.K9;
+import com.fsck.k9.Preferences;
+import com.fsck.k9.R;
+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 int DIALOG_CONFIRM = 1;
+
+ private Account mAccount;
+ private ArrayList mMessageRefs;
+
+ public static PendingIntent getIntent(Context context, final Account account, final ArrayList refs) {
+ Intent i = new Intent(context, NotificationDeleteConfirmation.class);
+ i.putExtra(EXTRA_ACCOUNT, account.getUuid());
+ i.putExtra(EXTRA_MESSAGE_LIST, refs);
+ i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+
+ return PendingIntent.getActivity(context, account.getAccountNumber(), i, PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ setTheme(K9.getK9Theme() == K9.THEME_LIGHT ?
+ R.style.Theme_K9_Dialog_Translucent_Light : R.style.Theme_K9_Dialog_Translucent_Dark);
+
+ final Preferences preferences = Preferences.getPreferences(this);
+ final Intent intent = getIntent();
+
+ mAccount = preferences.getAccount(intent.getStringExtra(EXTRA_ACCOUNT));
+ mMessageRefs = (ArrayList) intent.getSerializableExtra(EXTRA_MESSAGE_LIST);
+
+ if (mAccount == null || mMessageRefs == null || mMessageRefs.isEmpty()) {
+ finish();
+ } else if (!K9.confirmDeleteFromNotification()) {
+ triggerDelete();
+ finish();
+ } else {
+ showDialog(DIALOG_CONFIRM);
+ }
+ }
+
+ @Override
+ public Dialog onCreateDialog(int id) {
+ switch (id) {
+ case DIALOG_CONFIRM:
+ return ConfirmationDialog.create(this, id,
+ R.string.dialog_confirm_delete_title, "",
+ R.string.dialog_confirm_delete_confirm_button,
+ R.string.dialog_confirm_delete_cancel_button,
+ new Runnable() {
+ @Override
+ public void run() {
+ triggerDelete();
+ finish();
+ }
+ },
+ new Runnable() {
+ @Override
+ public void run() {
+ finish();
+ }
+ });
+ }
+
+ return super.onCreateDialog(id);
+ }
+
+ @Override
+ public void onPrepareDialog(int id, Dialog d) {
+ AlertDialog alert = (AlertDialog) d;
+ switch (id) {
+ case DIALOG_CONFIRM:
+ alert.setMessage(getResources().getQuantityString(
+ R.plurals.dialog_confirm_delete_message, mMessageRefs.size()));
+ break;
+ }
+
+ super.onPrepareDialog(id, d);
+ }
+
+ private void triggerDelete() {
+ Intent i = NotificationActionService.getDeleteAllMessagesIntent(this, mAccount, mMessageRefs);
+ startService(i);
+ }
+}
diff --git a/src/com/fsck/k9/activity/setup/Prefs.java b/src/com/fsck/k9/activity/setup/Prefs.java
index 5c413e6c7..8a760e3dc 100644
--- a/src/com/fsck/k9/activity/setup/Prefs.java
+++ b/src/com/fsck/k9/activity/setup/Prefs.java
@@ -17,15 +17,18 @@ import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
+import android.preference.PreferenceScreen;
import android.widget.Toast;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.K9.NotificationHideSubject;
+import com.fsck.k9.K9.NotificationQuickDelete;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
import com.fsck.k9.activity.ColorPickerDialog;
import com.fsck.k9.activity.K9PreferenceActivity;
+import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.helper.DateFormatter;
import com.fsck.k9.helper.FileBrowserHelper;
import com.fsck.k9.helper.FileBrowserHelper.FileBrowserFailOverCallback;
@@ -72,6 +75,7 @@ public class Prefs extends K9PreferenceActivity {
private static final String PREFERENCE_QUIET_TIME_ENABLED = "quiet_time_enabled";
private static final String PREFERENCE_QUIET_TIME_STARTS = "quiet_time_starts";
private static final String PREFERENCE_QUIET_TIME_ENDS = "quiet_time_ends";
+ private static final String PREFERENCE_NOTIF_QUICK_DELETE = "notification_quick_delete";
private static final String PREFERENCE_BATCH_BUTTONS_MARK_READ = "batch_buttons_mark_read";
private static final String PREFERENCE_BATCH_BUTTONS_DELETE = "batch_buttons_delete";
private static final String PREFERENCE_BATCH_BUTTONS_ARCHIVE = "batch_buttons_archive";
@@ -122,6 +126,7 @@ public class Prefs extends K9PreferenceActivity {
private CheckBoxPreference mQuietTimeEnabled;
private com.fsck.k9.preferences.TimePickerPreference mQuietTimeStarts;
private com.fsck.k9.preferences.TimePickerPreference mQuietTimeEnds;
+ private ListPreference mNotificationQuickDelete;
private Preference mAttachmentPathPreference;
private CheckBoxPreference mBatchButtonsMarkRead;
@@ -196,16 +201,25 @@ public class Prefs extends K9PreferenceActivity {
mStartIntegratedInbox.setChecked(K9.startIntegratedInbox());
mConfirmActions = (CheckBoxListPreference) findPreference(PREFERENCE_CONFIRM_ACTIONS);
- mConfirmActions.setItems(new CharSequence[] {
- getString(R.string.global_settings_confirm_action_delete),
- getString(R.string.global_settings_confirm_action_delete_starred),
- getString(R.string.global_settings_confirm_action_spam),
- });
- mConfirmActions.setCheckedItems(new boolean[] {
- K9.confirmDelete(),
- K9.confirmDeleteStarred(),
- K9.confirmSpam(),
- });
+
+ boolean canDeleteFromNotification = MessagingController.platformSupportsExtendedNotifications();
+ CharSequence[] confirmActionEntries = new CharSequence[canDeleteFromNotification ? 4 : 3];
+ boolean[] confirmActionValues = new boolean[canDeleteFromNotification ? 4 : 3];
+ int index = 0;
+
+ confirmActionEntries[index] = getString(R.string.global_settings_confirm_action_delete);
+ confirmActionValues[index++] = K9.confirmDelete();
+ confirmActionEntries[index] = getString(R.string.global_settings_confirm_action_delete_starred);
+ confirmActionValues[index++] = K9.confirmDeleteStarred();
+ if (canDeleteFromNotification) {
+ confirmActionEntries[index] = getString(R.string.global_settings_confirm_action_delete_notif);
+ confirmActionValues[index++] = K9.confirmDeleteFromNotification();
+ }
+ confirmActionEntries[index] = getString(R.string.global_settings_confirm_action_spam);
+ confirmActionValues[index++] = K9.confirmSpam();
+
+ mConfirmActions.setItems(confirmActionEntries);
+ mConfirmActions.setCheckedItems(confirmActionValues);
mNotificationHideSubject = setupListPreference(PREFERENCE_NOTIFICATION_HIDE_SUBJECT,
K9.getNotificationHideSubject().toString());
@@ -305,8 +319,13 @@ public class Prefs extends K9PreferenceActivity {
}
});
-
-
+ mNotificationQuickDelete = setupListPreference(PREFERENCE_NOTIF_QUICK_DELETE,
+ K9.getNotificationQuickDeleteBehaviour().toString());
+ if (!MessagingController.platformSupportsExtendedNotifications()) {
+ PreferenceScreen prefs = (PreferenceScreen) findPreference("notification_preferences");
+ prefs.removePreference(mNotificationQuickDelete);
+ mNotificationQuickDelete = null;
+ }
mBackgroundOps = setupListPreference(PREFERENCE_BACKGROUND_OPS, K9.getBackgroundOps().toString());
// In ICS+ there is no 'background data' setting that apps can chose to ignore anymore. So
@@ -420,11 +439,16 @@ public class Prefs extends K9PreferenceActivity {
K9.setUseVolumeKeysForNavigation(mVolumeNavigation.getCheckedItems()[0]);
K9.setUseVolumeKeysForListNavigation(mVolumeNavigation.getCheckedItems()[1]);
K9.setStartIntegratedInbox(!mHideSpecialAccounts.isChecked() && mStartIntegratedInbox.isChecked());
- K9.setConfirmDelete(mConfirmActions.getCheckedItems()[0]);
- K9.setConfirmDeleteStarred(mConfirmActions.getCheckedItems()[1]);
- K9.setConfirmSpam(mConfirmActions.getCheckedItems()[2]);
K9.setNotificationHideSubject(NotificationHideSubject.valueOf(mNotificationHideSubject.getValue()));
+ int index = 0;
+ K9.setConfirmDelete(mConfirmActions.getCheckedItems()[index++]);
+ K9.setConfirmDeleteStarred(mConfirmActions.getCheckedItems()[index++]);
+ if (MessagingController.platformSupportsExtendedNotifications()) {
+ K9.setConfirmDeleteFromNotification(mConfirmActions.getCheckedItems()[index++]);
+ }
+ K9.setConfirmSpam(mConfirmActions.getCheckedItems()[index++]);
+
K9.setMeasureAccounts(mMeasureAccounts.isChecked());
K9.setCountSearchMessages(mCountSearch.isChecked());
K9.setHideSpecialAccounts(mHideSpecialAccounts.isChecked());
@@ -445,6 +469,11 @@ public class Prefs extends K9PreferenceActivity {
K9.setQuietTimeStarts(mQuietTimeStarts.getTime());
K9.setQuietTimeEnds(mQuietTimeEnds.getTime());
+ if (mNotificationQuickDelete != null) {
+ K9.setNotificationQuickDeleteBehaviour(
+ NotificationQuickDelete.valueOf(mNotificationQuickDelete.getValue()));
+ }
+
K9.setBatchButtonsMarkRead(mBatchButtonsMarkRead.isChecked());
K9.setBatchButtonsDelete(mBatchButtonsDelete.isChecked());
K9.setBatchButtonsArchive(mBatchButtonsArchive.isChecked());
diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java
index 64a56d9b5..febe4b540 100644
--- a/src/com/fsck/k9/controller/MessagingController.java
+++ b/src/com/fsck/k9/controller/MessagingController.java
@@ -32,7 +32,10 @@ import android.net.Uri;
import android.os.Build;
import android.os.PowerManager;
import android.os.Process;
+import android.support.v4.app.NotificationCompat;
+import android.text.SpannableStringBuilder;
import android.text.TextUtils;
+import android.text.style.TextAppearanceSpan;
import android.util.Log;
import com.fsck.k9.Account;
@@ -40,11 +43,17 @@ import com.fsck.k9.AccountStats;
import com.fsck.k9.K9;
import com.fsck.k9.K9.NotificationHideSubject;
import com.fsck.k9.K9.Intents;
+import com.fsck.k9.K9.NotificationQuickDelete;
import com.fsck.k9.NotificationSetting;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
import com.fsck.k9.activity.FolderList;
import com.fsck.k9.activity.MessageList;
+import com.fsck.k9.activity.MessageReference;
+import com.fsck.k9.activity.MessageView;
+import com.fsck.k9.activity.NotificationDeleteConfirmation;
+import com.fsck.k9.helper.Contacts;
+import com.fsck.k9.helper.HtmlConverter;
import com.fsck.k9.helper.NotificationBuilder;
import com.fsck.k9.helper.StringUtils;
import com.fsck.k9.helper.power.TracingPowerManager;
@@ -79,6 +88,7 @@ import com.fsck.k9.search.LocalSearch;
import com.fsck.k9.search.SearchAccount;
import com.fsck.k9.search.SearchSpecification;
import com.fsck.k9.search.SqlQueryBuilder;
+import com.fsck.k9.service.NotificationActionService;
/**
@@ -193,6 +203,49 @@ public class MessagingController implements Runnable {
// Key is accountUuid:folderName:messageUid , value is unimportant
private ConcurrentHashMap deletedUids = new ConcurrentHashMap();
+ private static class NotificationData {
+ int unreadBeforeNotification;
+ LinkedList messages; // newest one first
+ LinkedList droppedMessages; // newest one first
+
+ // There's no point in storing more than 5 messages for the notification, as a single notification
+ // can't display more than that anyway.
+ private final static int MAX_MESSAGES = 5;
+
+ @SuppressWarnings("serial")
+ public NotificationData(int unread) {
+ unreadBeforeNotification = unread;
+ droppedMessages = new LinkedList();
+ messages = new LinkedList() {
+ @Override
+ public boolean add(Message m) {
+ while (size() >= MAX_MESSAGES) {
+ Message dropped = super.removeLast();
+ droppedMessages.add(0, dropped.makeMessageReference());
+ }
+ super.add(0, m);
+ return true;
+ }
+ };
+ }
+
+ public ArrayList getAllMessageRefs() {
+ ArrayList refs = new ArrayList();
+ for (Message m : messages) {
+ refs.add(m.makeMessageReference());
+ }
+ refs.addAll(droppedMessages);
+ return refs;
+ }
+
+ public int getNewMessageCount() {
+ return messages.size() + droppedMessages.size();
+ }
+ };
+
+ // Key is accountNumber
+ private ConcurrentHashMap notificationData = new ConcurrentHashMap();
+
private static final Flag[] SYNC_FLAGS = new Flag[] { Flag.SEEN, Flag.FLAGGED, Flag.ANSWERED, Flag.FORWARDED };
private String createMessageKey(Account account, String folder, Message message) {
@@ -1508,7 +1561,7 @@ public class MessagingController implements Runnable {
// Send a notification of this message
if (shouldNotifyForMessage(account, localFolder, message)) {
- notifyAccount(mApplication, account, message, unreadBeforeStart, newMessages);
+ notifyAccount(mApplication, account, message, unreadBeforeStart);
}
} catch (MessagingException me) {
@@ -1644,7 +1697,7 @@ public class MessagingController implements Runnable {
// Send a notification of this message
if (shouldNotifyForMessage(account, localFolder, message)) {
- notifyAccount(mApplication, account, message, unreadBeforeStart, newMessages);
+ notifyAccount(mApplication, account, message, unreadBeforeStart);
}
}//for large messages
@@ -1681,6 +1734,7 @@ public class MessagingController implements Runnable {
Message localMessage = localFolder.getMessage(remoteMessage.getUid());
boolean messageChanged = syncFlags(localMessage, remoteMessage);
if (messageChanged) {
+ boolean shouldBeNotifiedOf = false;
if (localMessage.isSet(Flag.DELETED) || isMessageSuppressed(account, folder, localMessage)) {
for (MessagingListener l : getListeners()) {
l.synchronizeMailboxRemovedMessage(account, folder, localMessage);
@@ -1689,8 +1743,38 @@ public class MessagingController implements Runnable {
for (MessagingListener l : getListeners()) {
l.synchronizeMailboxAddOrUpdateMessage(account, folder, localMessage);
}
+ if (shouldNotifyForMessage(account, localFolder, localMessage)) {
+ shouldBeNotifiedOf = true;
+ }
}
+ NotificationData data = getNotificationData(account, -1);
+ if (data != null) {
+ synchronized (data) {
+ boolean needUpdateNotification = false;
+ String uid = localMessage.getUid();
+
+ for (Message m : data.messages) {
+ if (uid.equals(m.getUid()) && !shouldBeNotifiedOf) {
+ data.messages.remove(m);
+ needUpdateNotification = true;
+ break;
+ }
+ }
+ if (!needUpdateNotification) {
+ for (MessageReference dropped : data.droppedMessages) {
+ if (uid.equals(dropped.uid) && !shouldBeNotifiedOf) {
+ data.droppedMessages.remove(dropped.uid);
+ needUpdateNotification = true;
+ break;
+ }
+ }
+ }
+ if (needUpdateNotification) {
+ notifyAccountWithDataLocked(mApplication, account, null, data);
+ }
+ }
+ }
}
progress.incrementAndGet();
for (MessagingListener l : getListeners()) {
@@ -3076,7 +3160,7 @@ public class MessagingController implements Runnable {
NotificationManager notifMgr =
(NotificationManager) mApplication.getSystemService(Context.NOTIFICATION_SERVICE);
- NotificationBuilder builder = NotificationBuilder.createInstance(mApplication);
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(mApplication);
builder.setSmallIcon(R.drawable.ic_menu_refresh);
builder.setWhen(System.currentTimeMillis());
builder.setOngoing(true);
@@ -3101,7 +3185,7 @@ public class MessagingController implements Runnable {
}
notifMgr.notify(K9.FETCHING_EMAIL_NOTIFICATION - account.getAccountNumber(),
- builder.getNotification());
+ builder.build());
}
private void notifySendTempFailed(Account account, Exception lastFailure) {
@@ -3126,7 +3210,7 @@ public class MessagingController implements Runnable {
NotificationManager notifMgr =
(NotificationManager) mApplication.getSystemService(Context.NOTIFICATION_SERVICE);
- NotificationBuilder builder = NotificationBuilder.createInstance(mApplication);
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(mApplication);
builder.setSmallIcon(R.drawable.stat_notify_email_generic);
builder.setWhen(System.currentTimeMillis());
builder.setAutoCancel(true);
@@ -3142,7 +3226,7 @@ public class MessagingController implements Runnable {
K9.NOTIFICATION_LED_BLINK_FAST, true);
notifMgr.notify(K9.SEND_FAILED_NOTIFICATION - account.getAccountNumber(),
- builder.getNotification());
+ builder.build());
}
/**
@@ -3161,7 +3245,7 @@ public class MessagingController implements Runnable {
final NotificationManager notifMgr =
(NotificationManager) mApplication.getSystemService(Context.NOTIFICATION_SERVICE);
- NotificationBuilder builder = NotificationBuilder.createInstance(mApplication);
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(mApplication);
builder.setSmallIcon(R.drawable.ic_menu_refresh);
builder.setWhen(System.currentTimeMillis());
builder.setOngoing(true);
@@ -3187,7 +3271,7 @@ public class MessagingController implements Runnable {
}
notifMgr.notify(K9.FETCHING_EMAIL_NOTIFICATION - account.getAccountNumber(),
- builder.getNotification());
+ builder.build());
}
private void notifyFetchingMailCancel(final Account account) {
@@ -4371,46 +4455,159 @@ public class MessagingController implements Runnable {
return true;
}
+ private NotificationData getNotificationData(Account account, int previousUnreadMessageCount) {
+ NotificationData data;
+ synchronized (notificationData) {
+ data = notificationData.get(account.getAccountNumber());
+ if (data == null && previousUnreadMessageCount >= 0) {
+ data = new NotificationData(previousUnreadMessageCount);
+ notificationData.put(account.getAccountNumber(), data);
+ }
+ }
+
+ return data;
+ }
+
+ private CharSequence getMessageSender(Context context, Account account, Message message) {
+ try {
+ boolean isSelf = false;
+ final Contacts contacts = K9.showContactName() ? Contacts.getInstance(context) : null;
+ final Address[] fromAddrs = message.getFrom();
+
+ if (fromAddrs != null) {
+ isSelf = account.isAnIdentity(fromAddrs);
+ if (!isSelf && fromAddrs.length > 0) {
+ return fromAddrs[0].toFriendly(contacts).toString();
+ }
+ }
+
+ if (isSelf) {
+ // show To: if the message was sent from me
+ Address[] rcpts = message.getRecipients(Message.RecipientType.TO);
+
+ if (rcpts != null && rcpts.length > 0) {
+ return context.getString(R.string.message_to_fmt,
+ rcpts[0].toFriendly(contacts).toString());
+ }
+
+ return context.getString(R.string.general_no_sender);
+ }
+ } catch (MessagingException e) {
+ Log.e(K9.LOG_TAG, "Unable to get sender information for notification.", e);
+ }
+
+ return null;
+ }
+
+ private CharSequence getMessageSubject(Context context, Message message) {
+ String subject = message.getSubject();
+ if (!TextUtils.isEmpty(subject)) {
+ return subject;
+ }
+
+ return context.getString(R.string.general_no_subject);
+ }
+
+ private static TextAppearanceSpan sEmphasizedSpan;
+ private TextAppearanceSpan getEmphasizedSpan(Context context) {
+ if (sEmphasizedSpan == null) {
+ sEmphasizedSpan = new TextAppearanceSpan(context,
+ R.style.TextAppearance_StatusBar_EventContent_Emphasized);
+ }
+ return sEmphasizedSpan;
+ }
+
+ private CharSequence getMessagePreview(Context context, Message message) {
+ CharSequence subject = getMessageSubject(context, message);
+ String snippet = message.getPreview();
+
+ if (TextUtils.isEmpty(subject)) {
+ return snippet;
+ } else if (TextUtils.isEmpty(snippet)) {
+ return subject;
+ }
+
+ SpannableStringBuilder preview = new SpannableStringBuilder();
+ preview.append(subject);
+ preview.append('\n');
+ preview.append(snippet);
+
+ preview.setSpan(getEmphasizedSpan(context), 0, subject.length(), 0);
+
+ return preview;
+ }
+
+ private CharSequence buildMessageSummary(Context context, CharSequence sender, CharSequence subject) {
+ if (sender == null) {
+ return subject;
+ }
+
+ SpannableStringBuilder summary = new SpannableStringBuilder();
+ summary.append(sender);
+ summary.append(" ");
+ summary.append(subject);
+
+ summary.setSpan(getEmphasizedSpan(context), 0, sender.length(), 0);
+
+ return summary;
+ }
+
+ private static final boolean platformShowsNumberInNotification() {
+ // Honeycomb and newer don't show the number as overlay on the notification icon.
+ // However, the number will appear in the detailed notification view.
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
+ }
+
+ public static final boolean platformSupportsExtendedNotifications() {
+ // supported in Jellybean
+ // TODO: use constant once target SDK is set to >= 16
+ return Build.VERSION.SDK_INT >= 16;
+ }
+
+ private Message findNewestMessageForNotificationLocked(Context context,
+ Account account, NotificationData data) {
+ if (!data.messages.isEmpty()) {
+ return data.messages.get(0);
+ }
+
+ if (!data.droppedMessages.isEmpty()) {
+ return data.droppedMessages.get(0).restoreToLocalMessage(context);
+ }
+
+ return null;
+ }
/**
* Creates a notification of a newly received message.
*/
- private void notifyAccount(Context context, Account account, Message message,
- int previousUnreadMessageCount, AtomicInteger newMessageCount) {
-
- // If we have a message, set the notification to ": "
- StringBuilder messageNotice = new StringBuilder();
- final KeyguardManager keyguardService = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
- try {
- if (message.getFrom() != null) {
- Address[] fromAddrs = message.getFrom();
- String from = fromAddrs.length > 0 ? fromAddrs[0].toFriendly().toString() : null;
- String subject = message.getSubject();
- if (subject == null) {
- subject = context.getString(R.string.general_no_subject);
- }
-
- if (from != null) {
- // Show From: address by default
- if (!account.isAnIdentity(fromAddrs)) {
- messageNotice.append(from).append(": ").append(subject);
- }
- // show To: if the message was sent from me
- else {
- Address[] rcpts = message.getRecipients(Message.RecipientType.TO);
- String to = rcpts.length > 0 ? rcpts[0].toFriendly().toString() : null;
- if (to != null) {
- messageNotice.append(String.format(context.getString(R.string.message_to_fmt), to)).append(": ").append(subject);
- } else {
- messageNotice.append(context.getString(R.string.general_no_sender)).append(": ").append(subject);
- }
- }
- }
- }
- } catch (MessagingException e) {
- Log.e(K9.LOG_TAG, "Unable to get message information for notification.", e);
+ private void notifyAccount(Context context, Account account,
+ Message message, int previousUnreadMessageCount) {
+ final NotificationData data = getNotificationData(account, previousUnreadMessageCount);
+ synchronized (data) {
+ notifyAccountWithDataLocked(context, account, message, data);
}
+ }
+
+ private void notifyAccountWithDataLocked(Context context, Account account,
+ Message message, NotificationData data) {
+ boolean updateSilently = false;
+
+ if (message == null) {
+ /* this can happen if a message we previously notified for is read or deleted remotely */
+ message = findNewestMessageForNotificationLocked(context, account, data);
+ updateSilently = true;
+ if (message == null) {
+ return;
+ }
+ } else {
+ data.messages.add(message);
+ }
+
+ final KeyguardManager keyguardService = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
+ final CharSequence sender = getMessageSender(context, account, message);
+ final CharSequence subject = getMessageSubject(context, message);
+ CharSequence summary = buildMessageSummary(context, sender, subject);
// If privacy mode active and keyguard active
// OR
@@ -4420,41 +4617,130 @@ public class MessagingController implements Runnable {
if ((K9.getNotificationHideSubject() == NotificationHideSubject.WHEN_LOCKED &&
keyguardService.inKeyguardRestrictedInputMode()) ||
(K9.getNotificationHideSubject() == NotificationHideSubject.ALWAYS) ||
- messageNotice.length() == 0) {
- messageNotice = new StringBuilder(context.getString(R.string.notification_new_title));
+ summary.length() == 0) {
+ summary = context.getString(R.string.notification_new_title);
}
NotificationManager notifMgr =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
- NotificationBuilder builder = NotificationBuilder.createInstance(context);
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
builder.setSmallIcon(R.drawable.stat_notify_email_generic);
builder.setWhen(System.currentTimeMillis());
- builder.setTicker(messageNotice);
+ if (!updateSilently) {
+ builder.setTicker(summary);
+ }
- final int unreadCount = previousUnreadMessageCount + newMessageCount.get();
- if (account.isNotificationShowsUnreadCount() ||
- // Honeycomb and newer don't show the number as overlay on the notification icon.
- // However, the number will appear in the detailed notification view.
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ final int newMessages = data.getNewMessageCount();
+ final int unreadCount = data.unreadBeforeNotification + newMessages;
+
+ if (account.isNotificationShowsUnreadCount() || platformShowsNumberInNotification()) {
builder.setNumber(unreadCount);
}
String accountDescr = (account.getDescription() != null) ?
account.getDescription() : account.getEmail();
- String accountNotice = context.getString(R.string.notification_new_one_account_fmt,
- unreadCount, accountDescr);
- builder.setContentTitle(accountNotice);
- builder.setContentText(messageNotice);
+ final ArrayList allRefs = data.getAllMessageRefs();
- Intent i = FolderList.actionHandleNotification(context, account,
- message.getFolder().getName());
- PendingIntent pi = PendingIntent.getActivity(context, 0, i, 0);
+ if (platformSupportsExtendedNotifications()) {
+ if (newMessages > 1) {
+ // 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)));
+ }
+ if (!data.droppedMessages.isEmpty()) {
+ style.setSummaryText(context.getString(R.string.notification_additional_messages,
+ data.droppedMessages.size(), accountDescr));
+ }
+ String title = context.getString(R.string.notification_new_messages_title, newMessages);
+ style.setBigContentTitle(title);
+ builder.setContentTitle(title);
+ builder.setSubText(accountDescr);
+ 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);
+
+ builder.addAction(R.drawable.ic_action_single_message_options_dark,
+ context.getString(R.string.notification_action_reply),
+ NotificationActionService.getReplyIntent(context, account, message.makeMessageReference()));
+ }
+
+ builder.addAction(R.drawable.ic_action_mark_as_read_dark,
+ context.getString(R.string.notification_action_read),
+ NotificationActionService.getReadAllMessagesIntent(context, account, allRefs));
+
+ NotificationQuickDelete deleteOption = K9.getNotificationQuickDeleteBehaviour();
+ boolean showDeleteAction = deleteOption == NotificationQuickDelete.ALWAYS ||
+ (deleteOption == NotificationQuickDelete.FOR_SINGLE_MSG && newMessages == 1);
+
+ 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)
+ builder.addAction(R.drawable.ic_action_delete_dark,
+ context.getString(R.string.notification_action_delete),
+ NotificationDeleteConfirmation.getIntent(context, account, allRefs));
+ }
+ } else {
+ String accountNotice = context.getString(R.string.notification_new_one_account_fmt,
+ unreadCount, accountDescr);
+ builder.setContentTitle(accountNotice);
+ builder.setContentText(summary);
+ }
+
+ for (Message m : data.messages) {
+ if (m.isSet(Flag.FLAGGED)) {
+ builder.setPriority(NotificationCompat.PRIORITY_HIGH);
+ break;
+ }
+ }
+
+ Intent targetIntent;
+ boolean treatAsSingleMessageNotification;
+
+ if (platformSupportsExtendedNotifications()) {
+ // in the new-style notifications, we focus on the new messages, not the unread ones
+ treatAsSingleMessageNotification = newMessages == 1;
+ } else {
+ // in the old-style notifications, we focus on unread messages, as we don't have a
+ // good way to express the new message count
+ treatAsSingleMessageNotification = unreadCount == 1;
+ }
+
+ if (treatAsSingleMessageNotification) {
+ targetIntent = MessageView.actionHandleNotificationIntent(
+ context, message.makeMessageReference());
+ } else {
+ String initialFolder = message.getFolder().getName();
+ /* only go to folder if all messages are in the same folder, else go to folder list */
+ for (MessageReference ref : allRefs) {
+ if (!TextUtils.equals(initialFolder, ref.folderName)) {
+ initialFolder = null;
+ break;
+ }
+ }
+
+ targetIntent = FolderList.actionHandleNotification(context, account, initialFolder);
+ }
+
+ PendingIntent pi = PendingIntent.getActivity(context, 0, targetIntent, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(pi);
+ 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 (!account.isRingNotified()) {
+ if (!updateSilently && !account.isRingNotified()) {
account.setRingNotified(true);
ringAndVibrate = true;
}
@@ -4469,7 +4755,7 @@ public class MessagingController implements Runnable {
K9.NOTIFICATION_LED_BLINK_SLOW,
ringAndVibrate);
- notifMgr.notify(account.getAccountNumber(), builder.getNotification());
+ notifMgr.notify(account.getAccountNumber(), builder.build());
}
/**
@@ -4490,7 +4776,7 @@ public class MessagingController implements Runnable {
* @param ringAndVibrate
* {@code true}, if ringtone/vibration are allowed. {@code false}, otherwise.
*/
- private void configureNotification(NotificationBuilder builder, String ringtone,
+ private void configureNotification(NotificationCompat.Builder builder, String ringtone,
long[] vibrationPattern, Integer ledColor, int ledSpeed, boolean ringAndVibrate) {
// if it's quiet time, then we shouldn't be ringing, buzzing or flashing
@@ -4529,6 +4815,7 @@ public class MessagingController implements Runnable {
(NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
notifMgr.cancel(account.getAccountNumber());
notifMgr.cancel(-1000 - account.getAccountNumber());
+ notificationData.remove(account.getAccountNumber());
}
/**
diff --git a/src/com/fsck/k9/helper/NotificationBuilder.java b/src/com/fsck/k9/helper/NotificationBuilder.java
deleted file mode 100644
index c637cb96d..000000000
--- a/src/com/fsck/k9/helper/NotificationBuilder.java
+++ /dev/null
@@ -1,164 +0,0 @@
-package com.fsck.k9.helper;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Vibrator;
-
-/**
- * Helper class to create system notifications
- *
- * @see NotificationBuilderApi1
- * @see NotificationBuilderApi11
- */
-public abstract class NotificationBuilder {
-
- /**
- * Create instance of an API-specific {@code NotificationBuilder} subclass.
- *
- * @param context
- * A {@link Context} instance.
- *
- * @return Appropriate {@link NotificationBuilder} instance for this device.
- */
- public static NotificationBuilder createInstance(Context context) {
- Context appContext = context.getApplicationContext();
-
- NotificationBuilder instance;
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
- instance = new NotificationBuilderApi1(appContext);
- } else {
- instance = new NotificationBuilderApi11(appContext);
- }
-
- return instance;
- }
-
-
- protected Context mContext;
-
- /**
- * Constructor
- *
- * @param context
- * A {@link Context} instance.
- */
- protected NotificationBuilder(Context context) {
- mContext = context;
- }
-
- /**
- * Set the small icon to use in the notification layouts.
- *
- * @param icon
- * A resource ID in the application's package of the drawble to use.
- */
- public abstract void setSmallIcon(int icon);
-
- /**
- * Set the time that the event occurred.
- *
- * @param when
- * Timestamp of when the event occurred.
- */
- public abstract void setWhen(long when);
-
- /**
- * Set the text that is displayed in the status bar when the notification first arrives.
- *
- * @param tickerText
- * The text to display.
- */
- public abstract void setTicker(CharSequence tickerText);
-
- /**
- * Set the title (first row) of the notification, in a standard notification.
- *
- * @param title
- * The text to display as notification title.
- */
- public abstract void setContentTitle(CharSequence title);
-
- /**
- * Set the text (second row) of the notification, in a standard notification.
- *
- * @param text
- * The text to display.
- */
- public abstract void setContentText(CharSequence text);
-
- /**
- * Supply a PendingIntent to send when the notification is clicked.
- *
- * @param intent
- * The intent that will be sent when the notification was clicked.
- */
- public abstract void setContentIntent(PendingIntent intent);
-
- /**
- * Set the large number at the right-hand side of the notification.
- *
- * @param number
- * The number to display in the notification.
- */
- public abstract void setNumber(int number);
-
- /**
- * Set whether this is an ongoing notification.
- *
- * @param ongoing
- * {@code true}, if it this is an ongoing notification. {@code false}, otherwise.
- */
- public abstract void setOngoing(boolean ongoing);
-
- /**
- * Setting this flag will make it so the notification is automatically canceled when the user
- * clicks it in the panel.
- *
- * @param autoCancel
- * {@code true}, if the notification should be automatically cancelled when the user
- * clicks on it. {@code false}, otherwise.
- */
- public abstract void setAutoCancel(boolean autoCancel);
-
- /**
- * Set the sound to play.
- *
- * It will play on the notification stream.
- *
- * @param sound
- * The URI of the sound to play.
- */
- public abstract void setSound(Uri sound);
-
- /**
- * Set the vibration pattern to use.
- *
- * @param pattern
- * An array of longs of times for which to turn the vibrator on or off.
- *
- * @see Vibrator#vibrate(long[], int)
- */
- public abstract void setVibrate(long[] pattern);
-
- /**
- * Set the color that you would like the LED on the device to blink, as well as the rate.
- *
- * @param argb
- * The color the LED should blink.
- * @param onMs
- * The number of milliseconds the LED should be on.
- * @param offMs
- * The number of milliseconds the LED should be off.
- */
- public abstract void setLights(int argb, int onMs, int offMs);
-
- /**
- * Combine all of the options that have been set and return a new {@link Notification} object.
- *
- * @return A new {@code Notification} object configured by this {@link NotificationBuilder}.
- */
- public abstract Notification getNotification();
-}
diff --git a/src/com/fsck/k9/helper/NotificationBuilderApi1.java b/src/com/fsck/k9/helper/NotificationBuilderApi1.java
deleted file mode 100644
index c57a77991..000000000
--- a/src/com/fsck/k9/helper/NotificationBuilderApi1.java
+++ /dev/null
@@ -1,130 +0,0 @@
-package com.fsck.k9.helper;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.media.AudioManager;
-import android.net.Uri;
-
-/**
- * Create notifications using the now deprecated {@link Notification} constructor.
- */
-public class NotificationBuilderApi1 extends NotificationBuilder {
- private int mSmallIcon;
- private long mWhen;
- private CharSequence mTickerText;
- private CharSequence mContentText;
- private CharSequence mContentTitle;
- private PendingIntent mContentIntent;
- private int mNumber;
- private boolean mOngoing;
- private boolean mAutoCancel;
- private Uri mSoundUri;
- private long[] mVibrationPattern;
- private int mLedColor;
- private int mLedOnMS;
- private int mLedOffMS;
- private boolean mBlinkLed;
-
-
- protected NotificationBuilderApi1(Context context) {
- super(context);
- }
-
- @Override
- public void setSmallIcon(int icon) {
- mSmallIcon = icon;
- }
-
- @Override
- public void setWhen(long when) {
- mWhen = when;
- }
-
- @Override
- public void setTicker(CharSequence tickerText) {
- mTickerText = tickerText;
- }
-
- @Override
- public void setContentTitle(CharSequence title) {
- mContentTitle = title;
- }
-
- @Override
- public void setContentText(CharSequence text) {
- mContentText = text;
- }
-
- @Override
- public void setContentIntent(PendingIntent intent) {
- mContentIntent = intent;
- }
-
- @Override
- public void setNumber(int number) {
- mNumber = number;
- }
-
- @Override
- public void setOngoing(boolean ongoing) {
- mOngoing = ongoing;
- }
-
- @Override
- public void setAutoCancel(boolean autoCancel) {
- mAutoCancel = autoCancel;
- }
-
- @Override
- public void setSound(Uri sound) {
- mSoundUri = sound;
- }
-
- @Override
- public void setVibrate(long[] pattern) {
- mVibrationPattern = pattern;
- }
-
- @Override
- public void setLights(int argb, int onMs, int offMs) {
- mBlinkLed = true;
- mLedColor = argb;
- mLedOnMS = onMs;
- mLedOffMS = offMs;
- }
-
- @SuppressWarnings("deprecation")
- @Override
- public Notification getNotification() {
- Notification notification = new Notification(mSmallIcon, mTickerText, mWhen);
- notification.number = mNumber;
- notification.setLatestEventInfo(mContext, mContentTitle, mContentText, mContentIntent);
-
- if (mSoundUri != null) {
- notification.sound = mSoundUri;
- notification.audioStreamType = AudioManager.STREAM_NOTIFICATION;
- }
-
- if (mVibrationPattern != null) {
- notification.vibrate = mVibrationPattern;
- }
-
- if (mBlinkLed) {
- notification.flags |= Notification.FLAG_SHOW_LIGHTS;
- notification.ledARGB = mLedColor;
- notification.ledOnMS = mLedOnMS;
- notification.ledOffMS = mLedOffMS;
- }
-
- if (mAutoCancel) {
- notification.flags |= Notification.FLAG_AUTO_CANCEL;
- }
-
- if (mOngoing) {
- notification.flags |= Notification.FLAG_ONGOING_EVENT;
- }
-
- return notification;
- }
-}
diff --git a/src/com/fsck/k9/helper/NotificationBuilderApi11.java b/src/com/fsck/k9/helper/NotificationBuilderApi11.java
deleted file mode 100644
index 369db11ab..000000000
--- a/src/com/fsck/k9/helper/NotificationBuilderApi11.java
+++ /dev/null
@@ -1,88 +0,0 @@
-package com.fsck.k9.helper;
-
-import android.annotation.TargetApi;
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.media.AudioManager;
-import android.net.Uri;
-
-/**
- * Create notifications using the new {@link android.app.Notification.Builder} class.
- */
-@TargetApi(11)
-public class NotificationBuilderApi11 extends NotificationBuilder {
- private Notification.Builder mBuilder;
-
-
- protected NotificationBuilderApi11(Context context) {
- super(context);
- mBuilder = new Notification.Builder(context);
- }
-
- @Override
- public void setSmallIcon(int icon) {
- mBuilder.setSmallIcon(icon);
- }
-
- @Override
- public void setWhen(long when) {
- mBuilder.setWhen(when);
- }
-
- @Override
- public void setTicker(CharSequence tickerText) {
- mBuilder.setTicker(tickerText);
- }
-
- @Override
- public void setContentTitle(CharSequence title) {
- mBuilder.setContentTitle(title);
- }
-
- @Override
- public void setContentText(CharSequence text) {
- mBuilder.setContentText(text);
- }
-
- @Override
- public void setContentIntent(PendingIntent intent) {
- mBuilder.setContentIntent(intent);
- }
-
- @Override
- public void setNumber(int number) {
- mBuilder.setNumber(number);
- mBuilder.setContentInfo("" + number);
- }
-
- @Override
- public void setOngoing(boolean ongoing) {
- mBuilder.setOngoing(ongoing);
- }
-
- @Override
- public void setAutoCancel(boolean autoCancel) {
- mBuilder.setAutoCancel(autoCancel);
- }
-
- @Override
- public void setSound(Uri sound) {
- mBuilder.setSound(sound, AudioManager.STREAM_NOTIFICATION);
- }
-
- @Override
- public void setVibrate(long[] pattern) {
- mBuilder.setVibrate(pattern);
- }
-
- @Override
- public void setLights(int argb, int onMs, int offMs) {
- mBuilder.setLights(argb, onMs, offMs);
- }
-
- @Override
- public Notification getNotification() {
- return mBuilder.getNotification();
- }
-}
diff --git a/src/com/fsck/k9/mail/Message.java b/src/com/fsck/k9/mail/Message.java
index 3d31f0839..6f0a819bc 100644
--- a/src/com/fsck/k9/mail/Message.java
+++ b/src/com/fsck/k9/mail/Message.java
@@ -148,7 +148,54 @@ public abstract class Message implements Part, Body {
public abstract String getPreview();
public abstract boolean hasAttachments();
+ /*
+ * calculateContentPreview
+ * Takes a plain text message body as a string.
+ * Returns a message summary as a string suitable for showing in a message list
+ *
+ * A message summary should be about the first 160 characters
+ * of unique text written by the message sender
+ * Quoted text, "On $date" and so on will be stripped out.
+ * All newlines and whitespace will be compressed.
+ *
+ */
+ public static String calculateContentPreview(String text) {
+ if (text == null) {
+ return null;
+ }
+ // Only look at the first 8k of a message when calculating
+ // the preview. This should avoid unnecessary
+ // memory usage on large messages
+ if (text.length() > 8192) {
+ text = text.substring(0, 8192);
+ }
+
+ // Remove (correctly delimited by '-- \n') signatures
+ text = text.replaceAll("(?ms)^-- [\\r\\n]+.*", "");
+ // try to remove lines of dashes in the preview
+ text = text.replaceAll("(?m)^----.*?$", "");
+ // remove quoted text from the preview
+ text = text.replaceAll("(?m)^[#>].*$", "");
+ // Remove a common quote header from the preview
+ text = text.replaceAll("(?m)^On .*wrote.?$", "");
+ // Remove a more generic quote header from the preview
+ text = text.replaceAll("(?m)^.*\\w+:$", "");
+ // Remove horizontal rules.
+ text = text.replaceAll("\\s*([-=_]{30,}+)\\s*", " ");
+
+ // URLs in the preview should just be shown as "..." - They're not
+ // clickable and they usually overwhelm the preview
+ text = text.replaceAll("https?://\\S+", "...");
+ // Don't show newlines in the preview
+ text = text.replaceAll("(\\r|\\n)+", " ");
+ // Collapse whitespace in the preview
+ text = text.replaceAll("\\s+", " ");
+ // Remove any whitespace at the beginning and end of the string.
+ text = text.trim();
+
+ return (text.length() <= 512) ? text : text.substring(0, 512);
+ }
public void delete(String trashFolderName) throws MessagingException {}
diff --git a/src/com/fsck/k9/mail/internet/MimeMessage.java b/src/com/fsck/k9/mail/internet/MimeMessage.java
index 2777420de..004cef186 100644
--- a/src/com/fsck/k9/mail/internet/MimeMessage.java
+++ b/src/com/fsck/k9/mail/internet/MimeMessage.java
@@ -23,6 +23,10 @@ import org.apache.james.mime4j.stream.BodyDescriptor;
import org.apache.james.mime4j.stream.Field;
import org.apache.james.mime4j.stream.MimeConfig;
+import android.util.Log;
+
+import com.fsck.k9.K9;
+import com.fsck.k9.helper.HtmlConverter;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.BodyPart;
@@ -591,6 +595,32 @@ public class MimeMessage extends Message {
}
public String getPreview() {
+ String preview = null;
+
+ try {
+ Part part = MimeUtility.findFirstPartByMimeType(this, "text/html");
+ if (part != null) {
+ // We successfully found an HTML part; do the necessary character set decoding.
+ preview = MimeUtility.getTextFromPart(part);
+ if (preview != null) {
+ preview = HtmlConverter.htmlToText(preview);
+ }
+ }
+ if (preview == null) {
+ // no HTML part -> try and get a text part.
+ part = MimeUtility.findFirstPartByMimeType(this, "text/plain");
+ if (part != null) {
+ preview = MimeUtility.getTextFromPart(part);
+ }
+ }
+ } catch (MessagingException e) {
+ Log.d(K9.LOG_TAG, "Could not extract message preview", e);
+ }
+
+ if (preview != null) {
+ return calculateContentPreview(preview);
+ }
+
return "";
}
diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java
index 6e7a6afc2..4db8edd25 100644
--- a/src/com/fsck/k9/mail/store/LocalStore.java
+++ b/src/com/fsck/k9/mail/store/LocalStore.java
@@ -2403,7 +2403,7 @@ public class LocalStore extends Store implements Serializable {
html = HtmlConverter.convertEmoji2Img(container.html);
}
- String preview = calculateContentPreview(text);
+ String preview = Message.calculateContentPreview(text);
try {
ContentValues cv = new ContentValues();
@@ -2501,7 +2501,7 @@ public class LocalStore extends Store implements Serializable {
String text = container.text;
String html = HtmlConverter.convertEmoji2Img(container.html);
- String preview = calculateContentPreview(text);
+ String preview = Message.calculateContentPreview(text);
try {
db.execSQL("UPDATE messages SET "
@@ -3010,53 +3010,6 @@ public class LocalStore extends Store implements Serializable {
}
}
- /*
- * calculateContentPreview
- * Takes a plain text message body as a string.
- * Returns a message summary as a string suitable for showing in a message list
- *
- * A message summary should be about the first 160 characters
- * of unique text written by the message sender
- * Quoted text, "On $date" and so on will be stripped out.
- * All newlines and whitespace will be compressed.
- *
- */
- public String calculateContentPreview(String text) {
- if (text == null) {
- return null;
- }
-
- // Only look at the first 8k of a message when calculating
- // the preview. This should avoid unnecessary
- // memory usage on large messages
- if (text.length() > 8192) {
- text = text.substring(0, 8192);
- }
-
- // try to remove lines of dashes in the preview
- text = text.replaceAll("(?m)^----.*?$", "");
- // remove quoted text from the preview
- text = text.replaceAll("(?m)^[#>].*$", "");
- // Remove a common quote header from the preview
- text = text.replaceAll("(?m)^On .*wrote.?$", "");
- // Remove a more generic quote header from the preview
- text = text.replaceAll("(?m)^.*\\w+:$", "");
- // Remove horizontal rules.
- text = text.replaceAll("\\s*([-=_]{30,}+)\\s*", " ");
-
- // URLs in the preview should just be shown as "..." - They're not
- // clickable and they usually overwhelm the preview
- text = text.replaceAll("https?://\\S+", "...");
- // Don't show newlines in the preview
- text = text.replaceAll("(\\r|\\n)+", " ");
- // Collapse whitespace in the preview
- text = text.replaceAll("\\s+", " ");
- // Remove any whitespace at the beginning and end of the string.
- text = text.trim();
-
- return (text.length() <= 512) ? text : text.substring(0, 512);
- }
-
@Override
public boolean isInTopGroup() {
return mInTopGroup;
diff --git a/src/com/fsck/k9/service/NotificationActionService.java b/src/com/fsck/k9/service/NotificationActionService.java
new file mode 100644
index 000000000..698ff53b2
--- /dev/null
+++ b/src/com/fsck/k9/service/NotificationActionService.java
@@ -0,0 +1,129 @@
+package com.fsck.k9.service;
+
+import java.util.ArrayList;
+
+import com.fsck.k9.Account;
+import com.fsck.k9.K9;
+import com.fsck.k9.Preferences;
+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.Folder;
+import com.fsck.k9.mail.Message;
+import com.fsck.k9.mail.MessagingException;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class NotificationActionService extends CoreService {
+ private final static String REPLY_ACTION = "com.fsck.k9.service.NotificationActionService.REPLY_ACTION";
+ private final static String READ_ALL_ACTION = "com.fsck.k9.service.NotificationActionService.READ_ALL_ACTION";
+ private final static String DELETE_ALL_ACTION = "com.fsck.k9.service.NotificationActionService.DELETE_ALL_ACTION";
+ private final static String ACKNOWLEDGE_ACTION = "com.fsck.k9.service.NotificationActionService.ACKNOWLEDGE_ACTION";
+
+ private final static String EXTRA_ACCOUNT = "account";
+ private final static String EXTRA_MESSAGE = "message";
+ private final static String EXTRA_MESSAGE_LIST = "messages";
+
+ public static PendingIntent getReplyIntent(Context context, final Account account, final MessageReference ref) {
+ Intent i = new Intent(context, NotificationActionService.class);
+ i.putExtra(EXTRA_ACCOUNT, account.getUuid());
+ i.putExtra(EXTRA_MESSAGE, ref);
+ 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 ArrayList refs) {
+ Intent i = new Intent(context, NotificationActionService.class);
+ i.putExtra(EXTRA_ACCOUNT, account.getUuid());
+ i.putExtra(EXTRA_MESSAGE_LIST, refs);
+ 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) {
+ Intent i = new Intent(context, NotificationActionService.class);
+ i.putExtra(EXTRA_ACCOUNT, account.getUuid());
+ 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 ArrayList refs) {
+ Intent i = new Intent(context, NotificationActionService.class);
+ i.putExtra(EXTRA_ACCOUNT, account.getUuid());
+ i.putExtra(EXTRA_MESSAGE_LIST, refs);
+ i.setAction(DELETE_ALL_ACTION);
+
+ return i;
+ }
+
+ @Override
+ public int startService(Intent intent, int startId) {
+ if (K9.DEBUG)
+ Log.i(K9.LOG_TAG, "NotificationActionService started with startId = " + startId);
+ final Preferences preferences = Preferences.getPreferences(this);
+ final MessagingController controller = MessagingController.getInstance(getApplication());
+ final Account account = preferences.getAccount(intent.getStringExtra(EXTRA_ACCOUNT));
+ final String action = intent.getAction();
+
+ if (account != null) {
+ if (READ_ALL_ACTION.equals(action)) {
+ if (K9.DEBUG)
+ Log.i(K9.LOG_TAG, "NotificationActionService marking messages as read");
+
+ ArrayList refs = (ArrayList)
+ intent.getSerializableExtra(EXTRA_MESSAGE_LIST);
+ for (MessageReference ref : refs) {
+ controller.setFlag(account, ref.folderName, ref.uid, Flag.SEEN, true);
+ }
+ } else if (DELETE_ALL_ACTION.equals(action)) {
+ if (K9.DEBUG)
+ Log.i(K9.LOG_TAG, "NotificationActionService deleting messages");
+
+ ArrayList refs = (ArrayList)
+ intent.getSerializableExtra(EXTRA_MESSAGE_LIST);
+ ArrayList messages = new ArrayList();
+
+ for (MessageReference ref : refs) {
+ Message m = ref.restoreToLocalMessage(this);
+ if (m != null) {
+ messages.add(m);
+ }
+ }
+
+ controller.deleteMessages(messages, null);
+ } else if (REPLY_ACTION.equals(action)) {
+ if (K9.DEBUG)
+ Log.i(K9.LOG_TAG, "NotificationActionService initiating reply");
+
+ MessageReference ref = (MessageReference) intent.getParcelableExtra(EXTRA_MESSAGE);
+ Message message = ref.restoreToLocalMessage(this);
+ if (message != null) {
+ Intent i = MessageCompose.getActionReplyIntent(this, account, message, false, null);
+ i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(i);
+ } else {
+ Log.i(K9.LOG_TAG, "Could not execute reply action.");
+ }
+ } 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
+ }
+
+ /* there's no point in keeping the notification after the user clicked on it */
+ controller.notifyAccountCancel(this, account);
+ } else {
+ Log.w(K9.LOG_TAG, "Could not find account for notification action.");
+ }
+
+ return START_NOT_STICKY;
+ }
+}