From a29e6b2215fcab6024345e156d30ea1a931120d3 Mon Sep 17 00:00:00 2001 From: Danny Baumann Date: Fri, 15 Jan 2016 12:21:47 +0100 Subject: Improve notification coalescence algorithm. Instead of coalescing for 15 seconds after the first change notification, coalesce until change notifications have been idle for at least 2 seconds. This avoids long update delays, which is especially jarring when using notifications on a wearable and the initial notification didn't yet include the message body. Also skip coalescence entirely for deletions; update immediately in that case. Change-Id: I67bed9a1af7b023020b0fd5429495eb45000e858 --- .../android/email/EmailNotificationController.java | 101 +++++++++++---------- 1 file changed, 55 insertions(+), 46 deletions(-) diff --git a/src/com/android/email/EmailNotificationController.java b/src/com/android/email/EmailNotificationController.java index e57b41833..2d22776d8 100644 --- a/src/com/android/email/EmailNotificationController.java +++ b/src/com/android/email/EmailNotificationController.java @@ -56,6 +56,7 @@ import com.android.mail.utils.NotificationUtils; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -213,9 +214,7 @@ public class EmailNotificationController implements NotificationController { */ private static final int NOTIFICATION_DELAYED_MESSAGE = 0; - private static final long NOTIFICATION_DELAY = 15 * DateUtils.SECOND_IN_MILLIS; - // True if we're coalescing notification updates - private static boolean sNotificationDelayedMessagePending; + private static final long NOTIFICATION_DELAY = 2 * DateUtils.SECOND_IN_MILLIS; // True if accounts have changed and we need to refresh everything private static boolean sRefreshAllNeeded; // Set of accounts we need to regenerate notifications for @@ -228,28 +227,30 @@ public class EmailNotificationController implements NotificationController { sNotificationThread = new NotificationThread(); sNotificationHandler = new Handler(sNotificationThread.getLooper(), new Handler.Callback() { - @Override - public boolean handleMessage(final android.os.Message message) { - /** - * To reduce spamming the notifications, we quiesce updates for a few - * seconds to batch them up, then handle them here. - */ - LogUtils.d(LOG_TAG, "Delayed notification processing"); - synchronized (sNotificationDelayedMessageLock) { - sNotificationDelayedMessagePending = false; - final Context context = (Context)message.obj; - if (sRefreshAllNeeded) { - sRefreshAllNeeded = false; - refreshAllNotificationsInternal(context); - } + @Override + public boolean handleMessage(final android.os.Message message) { + /** + * To reduce spamming the notifications, we quiesce updates for a few + * seconds to batch them up, then handle them here. + */ + if (message.arg1 != 0) { + LogUtils.d(LOG_TAG, "Delayed notification processing"); + synchronized (sNotificationDelayedMessageLock) { + final Context context = (Context)message.obj; + if (sRefreshAllNeeded) { + sRefreshAllNeeded = false; + refreshAllNotificationsInternal(context); + } else { for (final Long accountId : sRefreshAccountSet) { refreshNotificationsForAccountInternal(context, accountId); } - sRefreshAccountSet.clear(); } - return true; + sRefreshAccountSet.clear(); } - }); + } + return true; + } + }); } } @@ -590,19 +591,32 @@ public class EmailNotificationController implements NotificationController { notificationManager.cancel((int) (NOTIFICATION_ID_BASE_SECURITY_CHANGED + account.mId)); } + private static void scheduleNotificationUpdateLocked(final Context context, + boolean immediate) { + ensureHandlerExists(); + android.os.Message msg = android.os.Message.obtain(sNotificationHandler, + NOTIFICATION_DELAYED_MESSAGE, 1, 0, context); + boolean isInDelayWindow = sNotificationHandler.hasMessages(NOTIFICATION_DELAYED_MESSAGE); + + sNotificationHandler.removeMessages(NOTIFICATION_DELAYED_MESSAGE); + if (!immediate && isInDelayWindow) { + // we're in the delay window, restart timer + sNotificationHandler.sendMessageDelayed(msg, NOTIFICATION_DELAY); + } else { + // no refreshes happened in the last delay window; refresh immediately + // and send a dummy message to ensure the delay window is respected + // when the next update comes in + sNotificationHandler.sendMessage(msg); + sNotificationHandler.sendEmptyMessageDelayed(NOTIFICATION_DELAYED_MESSAGE, + NOTIFICATION_DELAY); + } + } + private static void refreshNotificationsForAccount(final Context context, - final long accountId) { + final long accountId, boolean immediate) { synchronized (sNotificationDelayedMessageLock) { - if (sNotificationDelayedMessagePending) { - sRefreshAccountSet.add(accountId); - } else { - ensureHandlerExists(); - sNotificationHandler.sendMessageDelayed( - android.os.Message.obtain(sNotificationHandler, - NOTIFICATION_DELAYED_MESSAGE, context), NOTIFICATION_DELAY); - sNotificationDelayedMessagePending = true; - refreshNotificationsForAccountInternal(context, accountId); - } + sRefreshAccountSet.add(accountId); + scheduleNotificationUpdateLocked(context, immediate); } } @@ -722,18 +736,10 @@ public class EmailNotificationController implements NotificationController { account, folder, true /* getAttention */); } - private static void refreshAllNotifications(final Context context) { + private static void refreshAllNotifications(final Context context, boolean immediate) { synchronized (sNotificationDelayedMessageLock) { - if (sNotificationDelayedMessagePending) { - sRefreshAllNeeded = true; - } else { - ensureHandlerExists(); - sNotificationHandler.sendMessageDelayed( - android.os.Message.obtain(sNotificationHandler, - NOTIFICATION_DELAYED_MESSAGE, context), NOTIFICATION_DELAY); - sNotificationDelayedMessagePending = true; - refreshAllNotificationsInternal(context); - } + sRefreshAllNeeded = true; + scheduleNotificationUpdateLocked(context, immediate); } } @@ -757,8 +763,11 @@ public class EmailNotificationController implements NotificationController { } @Override - public void onChange(final boolean selfChange) { - refreshNotificationsForAccount(mContext, mAccountId); + public void onChange(final boolean selfChange, Uri uri) { + List segments = uri != null ? uri.getPathSegments() : null; + boolean isDelete = segments != null && segments.size() >= 2 + ? EmailProvider.NOTIFICATION_OP_DELETE.equals(segments.get(1)) : false; + refreshNotificationsForAccount(mContext, mAccountId, isDelete); } } @@ -774,7 +783,7 @@ public class EmailNotificationController implements NotificationController { } @Override - public void onChange(final boolean selfChange) { + public void onChange(final boolean selfChange, Uri uri) { final ContentResolver resolver = mContext.getContentResolver(); final Cursor c = resolver.query(Account.CONTENT_URI, EmailContent.ID_PROJECTION, null, null, null); @@ -811,7 +820,7 @@ public class EmailNotificationController implements NotificationController { sInstance.unregisterMessageNotification(accountId); } - refreshAllNotifications(mContext); + refreshAllNotifications(mContext, !removedAccountList.isEmpty()); } } -- cgit v1.2.3