Loading packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +4 −0 Original line number Diff line number Diff line Loading @@ -2768,6 +2768,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mNotificationViewState; } public NotificationViewState getViewState() { return mNotificationViewState; } @Override public boolean isAboveShelf() { return !isOnKeyguard() Loading packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java +2 −0 Original line number Diff line number Diff line Loading @@ -229,6 +229,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. ServiceManager.getService(Context.STATUS_BAR_SERVICE)); mMessagingUtil = new NotificationMessagingUtil(context); mSystemServicesProxy = SystemServicesProxy.getInstance(mContext); mGroupManager.setPendingEntries(mPendingNotifications); } public void setUpWithPresenter(NotificationPresenter presenter, Loading Loading @@ -739,6 +740,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. mNotificationData.getImportance(key)); mPendingNotifications.put(key, shadeEntry); mGroupManager.onPendingEntryAdded(shadeEntry); } @VisibleForTesting Loading packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java +153 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.phone; import android.app.Notification; import android.os.SystemClock; import android.service.notification.StatusBarNotification; import android.support.annotation.Nullable; import android.util.Log; Loading @@ -29,9 +31,11 @@ import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Objects; /** * A class to handle notifications and their corresponding groups. Loading @@ -39,12 +43,14 @@ import java.util.Map; public class NotificationGroupManager implements OnHeadsUpChangedListener { private static final String TAG = "NotificationGroupManager"; private static final long HEADS_UP_TRANSFER_TIMEOUT = 300; private final HashMap<String, NotificationGroup> mGroupMap = new HashMap<>(); private OnGroupChangeListener mListener; private int mBarState = -1; private HashMap<String, StatusBarNotification> mIsolatedEntries = new HashMap<>(); private HeadsUpManager mHeadsUpManager; private boolean mIsUpdatingUnchangedGroup; private HashMap<String, NotificationData.Entry> mPendingNotifications; public void setOnGroupChangeListener(OnGroupChangeListener listener) { mListener = listener; Loading Loading @@ -147,6 +153,103 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener { mListener.onGroupCreatedFromChildren(group); } } cleanUpHeadsUpStatesOnAdd(group, false /* addIsPending */); } public void onPendingEntryAdded(NotificationData.Entry shadeEntry) { String groupKey = getGroupKey(shadeEntry.notification); NotificationGroup group = mGroupMap.get(groupKey); if (group != null) { cleanUpHeadsUpStatesOnAdd(group, true /* addIsPending */); } } /** * Clean up the heads up states when a new child was added. * @param group The group where a view was added or will be added. * @param addIsPending True if is the addition still pending or false has it already been added. */ private void cleanUpHeadsUpStatesOnAdd(NotificationGroup group, boolean addIsPending) { if (!addIsPending && group.hunSummaryOnNextAddition) { if (!mHeadsUpManager.isHeadsUp(group.summary.key)) { mHeadsUpManager.showNotification(group.summary); } group.hunSummaryOnNextAddition = false; } // Because notification groups are not delivered as a whole unit, it may happen that a // group child gets added quite a bit after the summary got posted. Our guidance is, that // apps should always post the group summary as well and we'll hide it for them if the child // is the only child in a group. Because of this, we also have to transfer heads up to the // child, otherwise the invisible summary would be heads-upped. // This transfer to the child is not always correct in case the app has just posted another // child in addition to the existing one, but it hasn't arrived in systemUI yet. In such // a scenario we would transfer the heads up to the old child and the wrong notification // would be heads-upped. In oder to avoid this, we'll recover from this issue and hun the // summary again instead of the old child if it's within a certain timeout. if (SystemClock.elapsedRealtime() - group.lastHeadsUpTransfer < HEADS_UP_TRANSFER_TIMEOUT) { if (!onlySummaryAlerts(group.summary)) { return; } int numChildren = group.children.size(); NotificationData.Entry isolatedChild = getIsolatedChild(getGroupKey( group.summary.notification)); int numPendingChildren = getPendingChildrenNotAlerting(group); numChildren += numPendingChildren; if (isolatedChild != null) { numChildren++; } if (numChildren <= 1) { return; } boolean releasedChild = false; ArrayList<NotificationData.Entry> children = new ArrayList<>(group.children.values()); int size = children.size(); for (int i = 0; i < size; i++) { NotificationData.Entry entry = children.get(i); if (onlySummaryAlerts(entry) && entry.row.isHeadsUp()) { releasedChild = true; mHeadsUpManager.releaseImmediately(entry.key); } } if (isolatedChild != null && onlySummaryAlerts(isolatedChild) && isolatedChild.row.isHeadsUp()) { releasedChild = true; mHeadsUpManager.releaseImmediately(isolatedChild.key); } if (releasedChild && !mHeadsUpManager.isHeadsUp(group.summary.key)) { boolean notifyImmediately = (numChildren - numPendingChildren) > 1; if (notifyImmediately) { mHeadsUpManager.showNotification(group.summary); } else { group.hunSummaryOnNextAddition = true; } group.lastHeadsUpTransfer = 0; } } } private int getPendingChildrenNotAlerting(NotificationGroup group) { if (mPendingNotifications == null) { return 0; } int number = 0; String groupKey = getGroupKey(group.summary.notification); Collection<NotificationData.Entry> values = mPendingNotifications.values(); for (NotificationData.Entry entry : values) { if (!isGroupChild(entry.notification)) { continue; } if (!Objects.equals(getGroupKey(entry.notification), groupKey)) { continue; } if (group.children.containsKey(entry.key)) { continue; } if (onlySummaryAlerts(entry)) { number++; } } return number; } private void onEntryBecomingChild(NotificationData.Entry entry) { Loading Loading @@ -421,8 +524,16 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener { || !entry.row.isHeadsUp()) { return; } // The parent of a suppressed group got huned, lets hun the child! NotificationGroup notificationGroup = mGroupMap.get(sbn.getGroupKey()); if (pendingInflationsWillAddChildren(notificationGroup)) { // New children will actually be added to this group, let's not transfer the heads // up return; } if (notificationGroup != null) { Iterator<NotificationData.Entry> iterator = notificationGroup.children.values().iterator(); Loading @@ -438,6 +549,9 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener { if (mHeadsUpManager.isHeadsUp(child.key)) { mHeadsUpManager.updateNotification(child, true); } else { if (onlySummaryAlerts(entry)) { notificationGroup.lastHeadsUpTransfer = SystemClock.elapsedRealtime(); } mHeadsUpManager.showNotification(child); } } Loading @@ -445,6 +559,35 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener { mHeadsUpManager.releaseImmediately(entry.key); } private boolean onlySummaryAlerts(NotificationData.Entry entry) { return entry.notification.getNotification().getGroupAlertBehavior() == Notification.GROUP_ALERT_SUMMARY; } /** * Check if the pending inflations will add children to this group. * @param group The group to check. */ private boolean pendingInflationsWillAddChildren(NotificationGroup group) { if (mPendingNotifications == null) { return false; } Collection<NotificationData.Entry> values = mPendingNotifications.values(); String groupKey = getGroupKey(group.summary.notification); for (NotificationData.Entry entry : values) { if (!isGroupChild(entry.notification)) { continue; } if (!Objects.equals(getGroupKey(entry.notification), groupKey)) { continue; } if (!group.children.containsKey(entry.key)) { return true; } } return false; } private boolean shouldIsolate(StatusBarNotification sbn) { NotificationGroup notificationGroup = mGroupMap.get(sbn.getGroupKey()); return (sbn.isGroup() && !sbn.getNotification().isGroupSummary()) Loading Loading @@ -477,6 +620,10 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener { } } public void setPendingEntries(HashMap<String, NotificationData.Entry> pendingNotifications) { mPendingNotifications = pendingNotifications; } public static class NotificationGroup { public final HashMap<String, NotificationData.Entry> children = new HashMap<>(); public NotificationData.Entry summary; Loading @@ -485,6 +632,12 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener { * Is this notification group suppressed, i.e its summary is hidden */ public boolean suppressed; /** * The time when the last heads transfer from group to child happened, while the summary * has the flags to heads up on its own. */ public long lastHeadsUpTransfer; public boolean hunSummaryOnNextAddition; @Override public String toString() { Loading packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java +6 −0 Original line number Diff line number Diff line Loading @@ -269,6 +269,12 @@ public class NotificationChildrenContainer extends ViewGroup { updateGroupOverflow(); row.setContentTransformationAmount(0, false /* isLastChild */); // It doesn't make sense to keep old animations around, lets cancel them! ExpandableNotificationRow.NotificationViewState viewState = row.getViewState(); if (viewState != null) { viewState.cancelAnimations(row); row.cancelAppearDrawing(); } } public void removeNotification(ExpandableNotificationRow row) { Loading packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +1 −0 Original line number Diff line number Diff line Loading @@ -3204,6 +3204,7 @@ public class NotificationStackScrollLayout extends ViewGroup if (row.isChildInGroup()) { // We can otherwise get stuck in there if it was just isolated row.setHeadsUpAnimatingAway(false); continue; } } else { ExpandableViewState viewState = mCurrentStackScrollState.getViewStateForView(row); Loading Loading
packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +4 −0 Original line number Diff line number Diff line Loading @@ -2768,6 +2768,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mNotificationViewState; } public NotificationViewState getViewState() { return mNotificationViewState; } @Override public boolean isAboveShelf() { return !isOnKeyguard() Loading
packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java +2 −0 Original line number Diff line number Diff line Loading @@ -229,6 +229,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. ServiceManager.getService(Context.STATUS_BAR_SERVICE)); mMessagingUtil = new NotificationMessagingUtil(context); mSystemServicesProxy = SystemServicesProxy.getInstance(mContext); mGroupManager.setPendingEntries(mPendingNotifications); } public void setUpWithPresenter(NotificationPresenter presenter, Loading Loading @@ -739,6 +740,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater. mNotificationData.getImportance(key)); mPendingNotifications.put(key, shadeEntry); mGroupManager.onPendingEntryAdded(shadeEntry); } @VisibleForTesting Loading
packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java +153 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.phone; import android.app.Notification; import android.os.SystemClock; import android.service.notification.StatusBarNotification; import android.support.annotation.Nullable; import android.util.Log; Loading @@ -29,9 +31,11 @@ import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Objects; /** * A class to handle notifications and their corresponding groups. Loading @@ -39,12 +43,14 @@ import java.util.Map; public class NotificationGroupManager implements OnHeadsUpChangedListener { private static final String TAG = "NotificationGroupManager"; private static final long HEADS_UP_TRANSFER_TIMEOUT = 300; private final HashMap<String, NotificationGroup> mGroupMap = new HashMap<>(); private OnGroupChangeListener mListener; private int mBarState = -1; private HashMap<String, StatusBarNotification> mIsolatedEntries = new HashMap<>(); private HeadsUpManager mHeadsUpManager; private boolean mIsUpdatingUnchangedGroup; private HashMap<String, NotificationData.Entry> mPendingNotifications; public void setOnGroupChangeListener(OnGroupChangeListener listener) { mListener = listener; Loading Loading @@ -147,6 +153,103 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener { mListener.onGroupCreatedFromChildren(group); } } cleanUpHeadsUpStatesOnAdd(group, false /* addIsPending */); } public void onPendingEntryAdded(NotificationData.Entry shadeEntry) { String groupKey = getGroupKey(shadeEntry.notification); NotificationGroup group = mGroupMap.get(groupKey); if (group != null) { cleanUpHeadsUpStatesOnAdd(group, true /* addIsPending */); } } /** * Clean up the heads up states when a new child was added. * @param group The group where a view was added or will be added. * @param addIsPending True if is the addition still pending or false has it already been added. */ private void cleanUpHeadsUpStatesOnAdd(NotificationGroup group, boolean addIsPending) { if (!addIsPending && group.hunSummaryOnNextAddition) { if (!mHeadsUpManager.isHeadsUp(group.summary.key)) { mHeadsUpManager.showNotification(group.summary); } group.hunSummaryOnNextAddition = false; } // Because notification groups are not delivered as a whole unit, it may happen that a // group child gets added quite a bit after the summary got posted. Our guidance is, that // apps should always post the group summary as well and we'll hide it for them if the child // is the only child in a group. Because of this, we also have to transfer heads up to the // child, otherwise the invisible summary would be heads-upped. // This transfer to the child is not always correct in case the app has just posted another // child in addition to the existing one, but it hasn't arrived in systemUI yet. In such // a scenario we would transfer the heads up to the old child and the wrong notification // would be heads-upped. In oder to avoid this, we'll recover from this issue and hun the // summary again instead of the old child if it's within a certain timeout. if (SystemClock.elapsedRealtime() - group.lastHeadsUpTransfer < HEADS_UP_TRANSFER_TIMEOUT) { if (!onlySummaryAlerts(group.summary)) { return; } int numChildren = group.children.size(); NotificationData.Entry isolatedChild = getIsolatedChild(getGroupKey( group.summary.notification)); int numPendingChildren = getPendingChildrenNotAlerting(group); numChildren += numPendingChildren; if (isolatedChild != null) { numChildren++; } if (numChildren <= 1) { return; } boolean releasedChild = false; ArrayList<NotificationData.Entry> children = new ArrayList<>(group.children.values()); int size = children.size(); for (int i = 0; i < size; i++) { NotificationData.Entry entry = children.get(i); if (onlySummaryAlerts(entry) && entry.row.isHeadsUp()) { releasedChild = true; mHeadsUpManager.releaseImmediately(entry.key); } } if (isolatedChild != null && onlySummaryAlerts(isolatedChild) && isolatedChild.row.isHeadsUp()) { releasedChild = true; mHeadsUpManager.releaseImmediately(isolatedChild.key); } if (releasedChild && !mHeadsUpManager.isHeadsUp(group.summary.key)) { boolean notifyImmediately = (numChildren - numPendingChildren) > 1; if (notifyImmediately) { mHeadsUpManager.showNotification(group.summary); } else { group.hunSummaryOnNextAddition = true; } group.lastHeadsUpTransfer = 0; } } } private int getPendingChildrenNotAlerting(NotificationGroup group) { if (mPendingNotifications == null) { return 0; } int number = 0; String groupKey = getGroupKey(group.summary.notification); Collection<NotificationData.Entry> values = mPendingNotifications.values(); for (NotificationData.Entry entry : values) { if (!isGroupChild(entry.notification)) { continue; } if (!Objects.equals(getGroupKey(entry.notification), groupKey)) { continue; } if (group.children.containsKey(entry.key)) { continue; } if (onlySummaryAlerts(entry)) { number++; } } return number; } private void onEntryBecomingChild(NotificationData.Entry entry) { Loading Loading @@ -421,8 +524,16 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener { || !entry.row.isHeadsUp()) { return; } // The parent of a suppressed group got huned, lets hun the child! NotificationGroup notificationGroup = mGroupMap.get(sbn.getGroupKey()); if (pendingInflationsWillAddChildren(notificationGroup)) { // New children will actually be added to this group, let's not transfer the heads // up return; } if (notificationGroup != null) { Iterator<NotificationData.Entry> iterator = notificationGroup.children.values().iterator(); Loading @@ -438,6 +549,9 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener { if (mHeadsUpManager.isHeadsUp(child.key)) { mHeadsUpManager.updateNotification(child, true); } else { if (onlySummaryAlerts(entry)) { notificationGroup.lastHeadsUpTransfer = SystemClock.elapsedRealtime(); } mHeadsUpManager.showNotification(child); } } Loading @@ -445,6 +559,35 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener { mHeadsUpManager.releaseImmediately(entry.key); } private boolean onlySummaryAlerts(NotificationData.Entry entry) { return entry.notification.getNotification().getGroupAlertBehavior() == Notification.GROUP_ALERT_SUMMARY; } /** * Check if the pending inflations will add children to this group. * @param group The group to check. */ private boolean pendingInflationsWillAddChildren(NotificationGroup group) { if (mPendingNotifications == null) { return false; } Collection<NotificationData.Entry> values = mPendingNotifications.values(); String groupKey = getGroupKey(group.summary.notification); for (NotificationData.Entry entry : values) { if (!isGroupChild(entry.notification)) { continue; } if (!Objects.equals(getGroupKey(entry.notification), groupKey)) { continue; } if (!group.children.containsKey(entry.key)) { return true; } } return false; } private boolean shouldIsolate(StatusBarNotification sbn) { NotificationGroup notificationGroup = mGroupMap.get(sbn.getGroupKey()); return (sbn.isGroup() && !sbn.getNotification().isGroupSummary()) Loading Loading @@ -477,6 +620,10 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener { } } public void setPendingEntries(HashMap<String, NotificationData.Entry> pendingNotifications) { mPendingNotifications = pendingNotifications; } public static class NotificationGroup { public final HashMap<String, NotificationData.Entry> children = new HashMap<>(); public NotificationData.Entry summary; Loading @@ -485,6 +632,12 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener { * Is this notification group suppressed, i.e its summary is hidden */ public boolean suppressed; /** * The time when the last heads transfer from group to child happened, while the summary * has the flags to heads up on its own. */ public long lastHeadsUpTransfer; public boolean hunSummaryOnNextAddition; @Override public String toString() { Loading
packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java +6 −0 Original line number Diff line number Diff line Loading @@ -269,6 +269,12 @@ public class NotificationChildrenContainer extends ViewGroup { updateGroupOverflow(); row.setContentTransformationAmount(0, false /* isLastChild */); // It doesn't make sense to keep old animations around, lets cancel them! ExpandableNotificationRow.NotificationViewState viewState = row.getViewState(); if (viewState != null) { viewState.cancelAnimations(row); row.cancelAppearDrawing(); } } public void removeNotification(ExpandableNotificationRow row) { Loading
packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +1 −0 Original line number Diff line number Diff line Loading @@ -3204,6 +3204,7 @@ public class NotificationStackScrollLayout extends ViewGroup if (row.isChildInGroup()) { // We can otherwise get stuck in there if it was just isolated row.setHeadsUpAnimatingAway(false); continue; } } else { ExpandableViewState viewState = mCurrentStackScrollState.getViewStateForView(row); Loading