blob: 9787a9446019c712b26e3a894d3ccb89c4d53974 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.statusbar.phone;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Notification;
import android.os.SystemClock;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dependency;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.NotificationGroup;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
import com.android.systemui.statusbar.notification.row.RowContentBindParams;
import com.android.systemui.statusbar.notification.row.RowContentBindStage;
import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* A helper class dealing with the alert interactions between {@link NotificationGroupManagerLegacy}
* and {@link HeadsUpManager}. In particular, this class deals with keeping
* the correct notification in a group alerting based off the group suppression and alertOverride.
*/
public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedListener,
StateListener {
private static final long ALERT_TRANSFER_TIMEOUT = 300;
private static final String TAG = "NotifGroupAlertTransfer";
private static final boolean DEBUG = StatusBar.DEBUG;
private static final boolean SPEW = StatusBar.SPEW;
/**
* The list of entries containing group alert metadata for each group. Keyed by group key.
*/
private final ArrayMap<String, GroupAlertEntry> mGroupAlertEntries = new ArrayMap<>();
/**
* The list of entries currently inflating that should alert after inflation. Keyed by
* notification key.
*/
private final ArrayMap<String, PendingAlertInfo> mPendingAlerts = new ArrayMap<>();
private HeadsUpManager mHeadsUpManager;
private final RowContentBindStage mRowContentBindStage;
private final NotificationGroupManagerLegacy mGroupManager =
Dependency.get(NotificationGroupManagerLegacy.class);
private NotificationEntryManager mEntryManager;
private boolean mIsDozing;
/**
* Injected constructor. See {@link StatusBarPhoneModule}.
*/
public NotificationGroupAlertTransferHelper(RowContentBindStage bindStage) {
Dependency.get(StatusBarStateController.class).addCallback(this);
mRowContentBindStage = bindStage;
}
/** Causes the TransferHelper to register itself as a listener to the appropriate classes. */
public void bind(NotificationEntryManager entryManager,
NotificationGroupManagerLegacy groupManager) {
if (mEntryManager != null) {
throw new IllegalStateException("Already bound.");
}
// TODO(b/119637830): It would be good if GroupManager already had all pending notifications
// as normal children (i.e. add notifications to GroupManager before inflation) so that we
// don't have to have this dependency. We'd also have to worry less about the suppression
// not being up to date.
mEntryManager = entryManager;
mEntryManager.addNotificationEntryListener(mNotificationEntryListener);
groupManager.registerGroupChangeListener(mOnGroupChangeListener);
}
/**
* Whether or not a notification has transferred its alert state to the notification and
* the notification should alert after inflating.
*
* @param entry notification to check
* @return true if the entry was transferred to and should inflate + alert
*/
public boolean isAlertTransferPending(@NonNull NotificationEntry entry) {
PendingAlertInfo alertInfo = mPendingAlerts.get(entry.getKey());
return alertInfo != null && alertInfo.isStillValid();
}
public void setHeadsUpManager(HeadsUpManager headsUpManager) {
mHeadsUpManager = headsUpManager;
}
@Override
public void onStateChanged(int newState) {}
@Override
public void onDozingChanged(boolean isDozing) {
if (mIsDozing != isDozing) {
for (GroupAlertEntry groupAlertEntry : mGroupAlertEntries.values()) {
groupAlertEntry.mLastAlertTransferTime = 0;
groupAlertEntry.mAlertSummaryOnNextAddition = false;
}
}
mIsDozing = isDozing;
}
private final NotificationGroupManagerLegacy.OnGroupChangeListener mOnGroupChangeListener =
new NotificationGroupManagerLegacy.OnGroupChangeListener() {
@Override
public void onGroupCreated(NotificationGroup group, String groupKey) {
mGroupAlertEntries.put(groupKey, new GroupAlertEntry(group));
}
@Override
public void onGroupRemoved(NotificationGroup group, String groupKey) {
mGroupAlertEntries.remove(groupKey);
}
@Override
public void onGroupSuppressionChanged(NotificationGroup group, boolean suppressed) {
if (DEBUG) {
Log.d(TAG, "!! onGroupSuppressionChanged: group.summary=" + group.summary
+ " suppressed=" + suppressed);
}
NotificationEntry oldAlertOverride = group.alertOverride;
onGroupChanged(group, oldAlertOverride);
}
@Override
public void onGroupAlertOverrideChanged(NotificationGroup group,
@Nullable NotificationEntry oldAlertOverride,
@Nullable NotificationEntry newAlertOverride) {
if (DEBUG) {
Log.d(TAG, "!! onGroupAlertOverrideChanged: group.summary=" + group.summary
+ " oldAlertOverride=" + oldAlertOverride
+ " newAlertOverride=" + newAlertOverride);
}
onGroupChanged(group, oldAlertOverride);
}
};
/**
* Called when either the suppressed or alertOverride fields of the group changed
*
* @param group the group which changed
* @param oldAlertOverride the previous value of group.alertOverride
*/
private void onGroupChanged(NotificationGroup group,
NotificationEntry oldAlertOverride) {
// Group summary can be null if we are no longer suppressed because the summary was
// removed. In that case, we don't need to alert the summary.
if (group.summary == null) {
if (DEBUG) {
Log.d(TAG, "onGroupChanged: summary is null");
}
return;
}
if (group.suppressed || group.alertOverride != null) {
checkForForwardAlertTransfer(group.summary, oldAlertOverride);
} else {
if (DEBUG) {
Log.d(TAG, "onGroupChanged: maybe transfer back");
}
GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(mGroupManager.getGroupKey(
group.summary.getSbn()));
// Group is no longer suppressed or overridden.
// We should check if we need to transfer the alert back to the summary.
if (groupAlertEntry.mAlertSummaryOnNextAddition) {
if (!mHeadsUpManager.isAlerting(group.summary.getKey())) {
alertNotificationWhenPossible(group.summary);
}
groupAlertEntry.mAlertSummaryOnNextAddition = false;
} else {
checkShouldTransferBack(groupAlertEntry);
}
}
}
@Override
public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
if (DEBUG) {
Log.d(TAG, "!! onHeadsUpStateChanged: entry=" + entry + " isHeadsUp=" + isHeadsUp);
}
if (isHeadsUp && entry.getSbn().getNotification().isGroupSummary()) {
// a group summary is alerting; trigger the forward transfer checks
checkForForwardAlertTransfer(entry, /* oldAlertOverride */ null);
}
}
/**
* Handles changes in a group's suppression or alertOverride, but where at least one of those
* conditions is still true (either the group is suppressed, the group has an alertOverride,
* or both). The method determined which kind of child needs to receive the alert, finds the
* entry currently alerting, and makes the transfer.
*
* Internally, this is handled with two main cases: the override needs the alert, or there is
* no override but the summary is suppressed (so an isolated child needs the alert).
*
* @param summary the notification entry of the summary of the logical group.
* @param oldAlertOverride the former value of group.alertOverride, before whatever event
* required us to check for for a transfer condition.
*/
private void checkForForwardAlertTransfer(NotificationEntry summary,
NotificationEntry oldAlertOverride) {
if (DEBUG) {
Log.d(TAG, "checkForForwardAlertTransfer: enter");
}
NotificationGroup group = mGroupManager.getGroupForSummary(summary.getSbn());
if (group != null && group.alertOverride != null) {
handleOverriddenSummaryAlerted(summary);
} else if (mGroupManager.isSummaryOfSuppressedGroup(summary.getSbn())) {
handleSuppressedSummaryAlerted(summary, oldAlertOverride);
}
}
private final NotificationEntryListener mNotificationEntryListener =
new NotificationEntryListener() {
// Called when a new notification has been posted but is not inflated yet. We use this to
// see as early as we can if we need to abort a transfer.
@Override
public void onPendingEntryAdded(NotificationEntry entry) {
if (DEBUG) {
Log.d(TAG, "!! onPendingEntryAdded: entry=" + entry);
}
String groupKey = mGroupManager.getGroupKey(entry.getSbn());
GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(groupKey);
if (groupAlertEntry != null && groupAlertEntry.mGroup.alertOverride == null) {
// new pending group entries require us to transfer back from the child to the
// group, but alertOverrides are only present in very limited circumstances, so
// while it's possible the group should ALSO alert, the previous detection which set
// this alertOverride won't be invalidated by this notification added to this group.
checkShouldTransferBack(groupAlertEntry);
}
}
@Override
public void onEntryRemoved(
@Nullable NotificationEntry entry,
NotificationVisibility visibility,
boolean removedByUser,
int reason) {
// Removes any alerts pending on this entry. Note that this will not stop any inflation
// tasks started by a transfer, so this should only be used as clean-up for when
// inflation is stopped and the pending alert no longer needs to happen.
mPendingAlerts.remove(entry.getKey());
}
};
/**
* Gets the number of new notifications pending inflation that will be added to the group
* but currently aren't and should not alert.
*
* @param group group to check
* @return the number of new notifications that will be added to the group
*/
private int getPendingChildrenNotAlerting(@NonNull NotificationGroup group) {
if (mEntryManager == null) {
return 0;
}
int number = 0;
Iterable<NotificationEntry> values = mEntryManager.getPendingNotificationsIterator();
for (NotificationEntry entry : values) {
if (isPendingNotificationInGroup(entry, group) && onlySummaryAlerts(entry)) {
number++;
}
}
return number;
}
/**
* Checks if the pending inflations will add children to this group.
*
* @param group group to check
* @return true if a pending notification will add to this group
*/
private boolean pendingInflationsWillAddChildren(@NonNull NotificationGroup group) {
if (mEntryManager == null) {
return false;
}
Iterable<NotificationEntry> values = mEntryManager.getPendingNotificationsIterator();
for (NotificationEntry entry : values) {
if (isPendingNotificationInGroup(entry, group)) {
return true;
}
}
return false;
}
/**
* Checks if a new pending notification will be added to the group.
*
* @param entry pending notification
* @param group group to check
* @return true if the notification will add to the group, false o/w
*/
private boolean isPendingNotificationInGroup(@NonNull NotificationEntry entry,
@NonNull NotificationGroup group) {
String groupKey = mGroupManager.getGroupKey(group.summary.getSbn());
return mGroupManager.isGroupChild(entry.getSbn())
&& Objects.equals(mGroupManager.getGroupKey(entry.getSbn()), groupKey)
&& !group.children.containsKey(entry.getKey());
}
/**
* Handles the scenario where a summary that has been suppressed is itself, or has a former
* alertOverride (in the form of an isolated logical child) which was alerted. A suppressed
* summary should for all intents and purposes be invisible to the user and as a result should
* not alert. When this is the case, it is our responsibility to pass the alert to the
* appropriate child which will be the representative notification alerting for the group.
*
* @param summary the summary that is suppressed and (potentially) alerting
* @param oldAlertOverride the alertOverride before whatever event triggered this method. If
* the alert override was removed, this will be the entry that should
* be transferred back from.
*/
private void handleSuppressedSummaryAlerted(@NonNull NotificationEntry summary,
NotificationEntry oldAlertOverride) {
if (DEBUG) {
Log.d(TAG, "handleSuppressedSummaryAlerted: summary=" + summary);
}
GroupAlertEntry groupAlertEntry =
mGroupAlertEntries.get(mGroupManager.getGroupKey(summary.getSbn()));
if (!mGroupManager.isSummaryOfSuppressedGroup(summary.getSbn())
|| groupAlertEntry == null) {
if (DEBUG) {
Log.d(TAG, "handleSuppressedSummaryAlerted: invalid state");
}
return;
}
boolean summaryIsAlerting = mHeadsUpManager.isAlerting(summary.getKey());
boolean priorityIsAlerting = oldAlertOverride != null
&& mHeadsUpManager.isAlerting(oldAlertOverride.getKey());
if (!summaryIsAlerting && !priorityIsAlerting) {
if (DEBUG) {
Log.d(TAG, "handleSuppressedSummaryAlerted: no summary or override alerting");
}
return;
}
if (pendingInflationsWillAddChildren(groupAlertEntry.mGroup)) {
// New children will actually be added to this group, let's not transfer the alert.
if (DEBUG) {
Log.d(TAG, "handleSuppressedSummaryAlerted: pending inflations");
}
return;
}
NotificationEntry child =
mGroupManager.getLogicalChildren(summary.getSbn()).iterator().next();
if (summaryIsAlerting) {
if (DEBUG) {
Log.d(TAG, "handleSuppressedSummaryAlerted: transfer summary -> child");
}
tryTransferAlertState(summary, /*from*/ summary, /*to*/ child, groupAlertEntry);
return;
}
// Summary didn't have the alert, so we're in "transfer back" territory. First, make sure
// it's not too late to transfer back, then transfer the alert from the oldAlertOverride to
// the isolated child which should receive the alert.
if (!canStillTransferBack(groupAlertEntry)) {
if (DEBUG) {
Log.d(TAG, "handleSuppressedSummaryAlerted: transfer from override: too late");
}
return;
}
if (DEBUG) {
Log.d(TAG, "handleSuppressedSummaryAlerted: transfer override -> child");
}
tryTransferAlertState(summary, /*from*/ oldAlertOverride, /*to*/ child, groupAlertEntry);
}
/**
* Checks for and handles the scenario where the given entry is the summary of a group which
* has an alertOverride, and either the summary itself or one of its logical isolated children
* is currently alerting (which happens if the summary is suppressed).
*/
private void handleOverriddenSummaryAlerted(NotificationEntry summary) {
if (DEBUG) {
Log.d(TAG, "handleOverriddenSummaryAlerted: summary=" + summary);
}
GroupAlertEntry groupAlertEntry =
mGroupAlertEntries.get(mGroupManager.getGroupKey(summary.getSbn()));
NotificationGroup group = mGroupManager.getGroupForSummary(summary.getSbn());
if (group == null || group.alertOverride == null || groupAlertEntry == null) {
if (DEBUG) {
Log.d(TAG, "handleOverriddenSummaryAlerted: invalid state");
}
return;
}
boolean summaryIsAlerting = mHeadsUpManager.isAlerting(summary.getKey());
if (summaryIsAlerting) {
if (DEBUG) {
Log.d(TAG, "handleOverriddenSummaryAlerted: transfer summary -> override");
}
tryTransferAlertState(summary, /*from*/ summary, group.alertOverride, groupAlertEntry);
return;
}
// Summary didn't have the alert, so we're in "transfer back" territory. First, make sure
// it's not too late to transfer back, then remove the alert from any of the logical
// children, and if one of them was alerting, we can alert the override.
if (!canStillTransferBack(groupAlertEntry)) {
if (DEBUG) {
Log.d(TAG, "handleOverriddenSummaryAlerted: transfer from child: too late");
}
return;
}
List<NotificationEntry> children = mGroupManager.getLogicalChildren(summary.getSbn());
if (children == null) {
if (DEBUG) {
Log.d(TAG, "handleOverriddenSummaryAlerted: no children");
}
return;
}
children.remove(group.alertOverride); // do not release the alert on our desired destination
boolean releasedChild = releaseChildAlerts(children);
if (releasedChild) {
if (DEBUG) {
Log.d(TAG, "handleOverriddenSummaryAlerted: transfer child -> override");
}
tryTransferAlertState(summary, /*from*/ null, group.alertOverride, groupAlertEntry);
} else {
if (DEBUG) {
Log.d(TAG, "handleOverriddenSummaryAlerted: no child alert released");
}
}
}
/**
* Transfers the alert state one entry to another. We remove the alert from the first entry
* immediately to have the incorrect one up as short as possible. The second should alert
* when possible.
*
* @param summary entry of the summary
* @param fromEntry entry to transfer alert from
* @param toEntry entry to transfer to
*/
private void tryTransferAlertState(
NotificationEntry summary,
NotificationEntry fromEntry,
NotificationEntry toEntry,
GroupAlertEntry groupAlertEntry) {
if (toEntry != null) {
if (toEntry.getRow().keepInParent()
|| toEntry.isRowRemoved()
|| toEntry.isRowDismissed()) {
// The notification is actually already removed. No need to alert it.
return;
}
if (!mHeadsUpManager.isAlerting(toEntry.getKey()) && onlySummaryAlerts(summary)) {
groupAlertEntry.mLastAlertTransferTime = SystemClock.elapsedRealtime();
}
if (DEBUG) {
Log.d(TAG, "transferAlertState: fromEntry=" + fromEntry + " toEntry=" + toEntry);
}
transferAlertState(fromEntry, toEntry);
}
}
private void transferAlertState(@Nullable NotificationEntry fromEntry,
@NonNull NotificationEntry toEntry) {
if (fromEntry != null) {
mHeadsUpManager.removeNotification(fromEntry.getKey(), true /* releaseImmediately */);
}
alertNotificationWhenPossible(toEntry);
}
/**
* Determines if we need to transfer the alert back to the summary from the child and does
* so if needed.
*
* This can happen since notification groups are not delivered as a whole unit and it is
* possible we erroneously transfer the alert from the summary to the child even though
* more children are coming. Thus, if a child is added within a certain timeframe after we
* transfer, we back out and alert the summary again.
*
* An alert can only transfer back within a small window of time after a transfer away from the
* summary to a child happened.
*
* @param groupAlertEntry group alert entry to check
*/
private void checkShouldTransferBack(@NonNull GroupAlertEntry groupAlertEntry) {
if (canStillTransferBack(groupAlertEntry)) {
NotificationEntry summary = groupAlertEntry.mGroup.summary;
if (!onlySummaryAlerts(summary)) {
return;
}
ArrayList<NotificationEntry> children = mGroupManager.getLogicalChildren(
summary.getSbn());
int numActiveChildren = children.size();
int numPendingChildren = getPendingChildrenNotAlerting(groupAlertEntry.mGroup);
int numChildren = numActiveChildren + numPendingChildren;
if (numChildren <= 1) {
return;
}
boolean releasedChild = releaseChildAlerts(children);
if (releasedChild && !mHeadsUpManager.isAlerting(summary.getKey())) {
boolean notifyImmediately = numActiveChildren > 1;
if (notifyImmediately) {
alertNotificationWhenPossible(summary);
} else {
// Should wait until the pending child inflates before alerting.
groupAlertEntry.mAlertSummaryOnNextAddition = true;
}
groupAlertEntry.mLastAlertTransferTime = 0;
}
}
}
private boolean canStillTransferBack(@NonNull GroupAlertEntry groupAlertEntry) {
return SystemClock.elapsedRealtime() - groupAlertEntry.mLastAlertTransferTime
< ALERT_TRANSFER_TIMEOUT;
}
private boolean releaseChildAlerts(List<NotificationEntry> children) {
boolean releasedChild = false;
if (SPEW) {
Log.d(TAG, "releaseChildAlerts: numChildren=" + children.size());
}
for (int i = 0; i < children.size(); i++) {
NotificationEntry entry = children.get(i);
if (SPEW) {
Log.d(TAG, "releaseChildAlerts: checking i=" + i + " entry=" + entry
+ " onlySummaryAlerts=" + onlySummaryAlerts(entry)
+ " isAlerting=" + mHeadsUpManager.isAlerting(entry.getKey())
+ " isPendingAlert=" + mPendingAlerts.containsKey(entry.getKey()));
}
if (onlySummaryAlerts(entry) && mHeadsUpManager.isAlerting(entry.getKey())) {
releasedChild = true;
mHeadsUpManager.removeNotification(
entry.getKey(), true /* releaseImmediately */);
}
if (mPendingAlerts.containsKey(entry.getKey())) {
// This is the child that would've been removed if it was inflated.
releasedChild = true;
mPendingAlerts.get(entry.getKey()).mAbortOnInflation = true;
}
}
if (SPEW) {
Log.d(TAG, "releaseChildAlerts: didRelease=" + releasedChild);
}
return releasedChild;
}
/**
* Tries to alert the notification. If its content view is not inflated, we inflate and continue
* when the entry finishes inflating the view.
*
* @param entry entry to show
*/
private void alertNotificationWhenPossible(@NonNull NotificationEntry entry) {
@InflationFlag int contentFlag = mHeadsUpManager.getContentFlag();
final RowContentBindParams params = mRowContentBindStage.getStageParams(entry);
if ((params.getContentViews() & contentFlag) == 0) {
if (DEBUG) {
Log.d(TAG, "alertNotificationWhenPossible: async requestRebind entry=" + entry);
}
mPendingAlerts.put(entry.getKey(), new PendingAlertInfo(entry));
params.requireContentViews(contentFlag);
mRowContentBindStage.requestRebind(entry, en -> {
PendingAlertInfo alertInfo = mPendingAlerts.remove(entry.getKey());
if (alertInfo != null) {
if (alertInfo.isStillValid()) {
alertNotificationWhenPossible(entry);
} else {
// The transfer is no longer valid. Free the content.
mRowContentBindStage.getStageParams(entry).markContentViewsFreeable(
contentFlag);
mRowContentBindStage.requestRebind(entry, null);
}
}
});
return;
}
if (mHeadsUpManager.isAlerting(entry.getKey())) {
if (DEBUG) {
Log.d(TAG, "alertNotificationWhenPossible: continue alerting entry=" + entry);
}
mHeadsUpManager.updateNotification(entry.getKey(), true /* alert */);
} else {
if (DEBUG) {
Log.d(TAG, "alertNotificationWhenPossible: start alerting entry=" + entry);
}
mHeadsUpManager.showNotification(entry);
}
}
private boolean onlySummaryAlerts(NotificationEntry entry) {
return entry.getSbn().getNotification().getGroupAlertBehavior()
== Notification.GROUP_ALERT_SUMMARY;
}
/**
* Information about a pending alert used to determine if the alert is still needed when
* inflation completes.
*/
private class PendingAlertInfo {
/**
* The original notification when the transfer is initiated. This is used to determine if
* the transfer is still valid if the notification is updated.
*/
final StatusBarNotification mOriginalNotification;
final NotificationEntry mEntry;
/**
* The notification is still pending inflation but we've decided that we no longer need
* the content view (e.g. suppression might have changed and we decided we need to transfer
* back).
*
* TODO: Replace this entire structure with {@link RowContentBindStage#requestRebind)}.
*/
boolean mAbortOnInflation;
PendingAlertInfo(NotificationEntry entry) {
mOriginalNotification = entry.getSbn();
mEntry = entry;
}
/**
* Whether or not the pending alert is still valid and should still alert after inflation.
*
* @return true if the pending alert should still occur, false o/w
*/
private boolean isStillValid() {
if (mAbortOnInflation) {
// Notification is aborted due to the transfer being explicitly cancelled
return false;
}
if (mEntry.getSbn().getGroupKey() != mOriginalNotification.getGroupKey()) {
// Groups have changed
return false;
}
if (mEntry.getSbn().getNotification().isGroupSummary()
!= mOriginalNotification.getNotification().isGroupSummary()) {
// Notification has changed from group summary to not or vice versa
return false;
}
return true;
}
}
/**
* Contains alert metadata for the notification group used to determine when/how the alert
* should be transferred.
*/
private static class GroupAlertEntry {
/**
* The time when the last alert transfer from summary to child happened.
*/
long mLastAlertTransferTime;
boolean mAlertSummaryOnNextAddition;
final NotificationGroup mGroup;
GroupAlertEntry(NotificationGroup group) {
this.mGroup = group;
}
}
}