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
This commit is contained in:
Danny Baumann 2016-01-15 12:21:47 +01:00
parent 34fb128e43
commit a29e6b2215
1 changed files with 55 additions and 46 deletions

View File

@ -56,6 +56,7 @@ import com.android.mail.utils.NotificationUtils;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -213,9 +214,7 @@ public class EmailNotificationController implements NotificationController {
*/ */
private static final int NOTIFICATION_DELAYED_MESSAGE = 0; private static final int NOTIFICATION_DELAYED_MESSAGE = 0;
private static final long NOTIFICATION_DELAY = 15 * DateUtils.SECOND_IN_MILLIS; private static final long NOTIFICATION_DELAY = 2 * DateUtils.SECOND_IN_MILLIS;
// True if we're coalescing notification updates
private static boolean sNotificationDelayedMessagePending;
// True if accounts have changed and we need to refresh everything // True if accounts have changed and we need to refresh everything
private static boolean sRefreshAllNeeded; private static boolean sRefreshAllNeeded;
// Set of accounts we need to regenerate notifications for // Set of accounts we need to regenerate notifications for
@ -228,28 +227,30 @@ public class EmailNotificationController implements NotificationController {
sNotificationThread = new NotificationThread(); sNotificationThread = new NotificationThread();
sNotificationHandler = new Handler(sNotificationThread.getLooper(), sNotificationHandler = new Handler(sNotificationThread.getLooper(),
new Handler.Callback() { new Handler.Callback() {
@Override @Override
public boolean handleMessage(final android.os.Message message) { public boolean handleMessage(final android.os.Message message) {
/** /**
* To reduce spamming the notifications, we quiesce updates for a few * To reduce spamming the notifications, we quiesce updates for a few
* seconds to batch them up, then handle them here. * seconds to batch them up, then handle them here.
*/ */
LogUtils.d(LOG_TAG, "Delayed notification processing"); if (message.arg1 != 0) {
synchronized (sNotificationDelayedMessageLock) { LogUtils.d(LOG_TAG, "Delayed notification processing");
sNotificationDelayedMessagePending = false; synchronized (sNotificationDelayedMessageLock) {
final Context context = (Context)message.obj; final Context context = (Context)message.obj;
if (sRefreshAllNeeded) { if (sRefreshAllNeeded) {
sRefreshAllNeeded = false; sRefreshAllNeeded = false;
refreshAllNotificationsInternal(context); refreshAllNotificationsInternal(context);
} } else {
for (final Long accountId : sRefreshAccountSet) { for (final Long accountId : sRefreshAccountSet) {
refreshNotificationsForAccountInternal(context, accountId); 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)); 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, private static void refreshNotificationsForAccount(final Context context,
final long accountId) { final long accountId, boolean immediate) {
synchronized (sNotificationDelayedMessageLock) { synchronized (sNotificationDelayedMessageLock) {
if (sNotificationDelayedMessagePending) { sRefreshAccountSet.add(accountId);
sRefreshAccountSet.add(accountId); scheduleNotificationUpdateLocked(context, immediate);
} else {
ensureHandlerExists();
sNotificationHandler.sendMessageDelayed(
android.os.Message.obtain(sNotificationHandler,
NOTIFICATION_DELAYED_MESSAGE, context), NOTIFICATION_DELAY);
sNotificationDelayedMessagePending = true;
refreshNotificationsForAccountInternal(context, accountId);
}
} }
} }
@ -722,18 +736,10 @@ public class EmailNotificationController implements NotificationController {
account, folder, true /* getAttention */); account, folder, true /* getAttention */);
} }
private static void refreshAllNotifications(final Context context) { private static void refreshAllNotifications(final Context context, boolean immediate) {
synchronized (sNotificationDelayedMessageLock) { synchronized (sNotificationDelayedMessageLock) {
if (sNotificationDelayedMessagePending) { sRefreshAllNeeded = true;
sRefreshAllNeeded = true; scheduleNotificationUpdateLocked(context, immediate);
} else {
ensureHandlerExists();
sNotificationHandler.sendMessageDelayed(
android.os.Message.obtain(sNotificationHandler,
NOTIFICATION_DELAYED_MESSAGE, context), NOTIFICATION_DELAY);
sNotificationDelayedMessagePending = true;
refreshAllNotificationsInternal(context);
}
} }
} }
@ -757,8 +763,11 @@ public class EmailNotificationController implements NotificationController {
} }
@Override @Override
public void onChange(final boolean selfChange) { public void onChange(final boolean selfChange, Uri uri) {
refreshNotificationsForAccount(mContext, mAccountId); List<String> 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 @Override
public void onChange(final boolean selfChange) { public void onChange(final boolean selfChange, Uri uri) {
final ContentResolver resolver = mContext.getContentResolver(); final ContentResolver resolver = mContext.getContentResolver();
final Cursor c = resolver.query(Account.CONTENT_URI, EmailContent.ID_PROJECTION, final Cursor c = resolver.query(Account.CONTENT_URI, EmailContent.ID_PROJECTION,
null, null, null); null, null, null);
@ -811,7 +820,7 @@ public class EmailNotificationController implements NotificationController {
sInstance.unregisterMessageNotification(accountId); sInstance.unregisterMessageNotification(accountId);
} }
refreshAllNotifications(mContext); refreshAllNotifications(mContext, !removedAccountList.isEmpty());
} }
} }