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