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:
parent
34fb128e43
commit
a29e6b2215
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue