Merge changes If9b31b3b,Iea400614,I9567aaf5,I6696029f,I3d4f434e, ...
* changes:
Don't show new groups until their children inflate
Cleanup PreparationCoordinator a bit
Add onCleanup() method to Pluggables
Introduce GroupEntryBuilder
NotificationEntryBuilder can modify existing entries
Move creationTime into ListEntry (from NotifEntry)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java
index 81494ed..0ea6857 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java
@@ -19,7 +19,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.statusbar.notification.collection.coordinator.PreparationCoordinator;
import java.util.ArrayList;
@@ -39,9 +38,8 @@
Collections.unmodifiableList(mChildren);
private int mUntruncatedChildCount;
- @VisibleForTesting
- public GroupEntry(String key) {
- super(key);
+ GroupEntry(String key, long creationTime) {
+ super(key, creationTime);
}
@Override
@@ -59,8 +57,7 @@
return mUnmodifiableChildren;
}
- @VisibleForTesting
- public void setSummary(@Nullable NotificationEntry summary) {
+ void setSummary(@Nullable NotificationEntry summary) {
mSummary = summary;
}
@@ -98,6 +95,6 @@
return mChildren;
}
- public static final GroupEntry ROOT_ENTRY = new GroupEntry("<root>");
+ public static final GroupEntry ROOT_ENTRY = new GroupEntry("<root>", 0);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
index 837374f..65f5dc4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.notification.collection;
+import android.annotation.UptimeMillisLong;
+
import androidx.annotation.Nullable;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
@@ -27,14 +29,16 @@
*/
public abstract class ListEntry {
private final String mKey;
+ private final long mCreationTime;
int mFirstAddedIteration = -1;
private final ListAttachState mPreviousAttachState = ListAttachState.create();
private final ListAttachState mAttachState = ListAttachState.create();
- ListEntry(String key) {
+ ListEntry(String key, long creationTime) {
mKey = key;
+ mCreationTime = creationTime;
}
public String getKey() {
@@ -42,6 +46,19 @@
}
/**
+ * The SystemClock.uptimeMillis() when this object was created. In general, this means the
+ * moment when NotificationManager notifies our listener about the existence of this entry.
+ *
+ * This value will not change if the notification is updated, although it will change if the
+ * notification is removed and then re-posted. It is also wholly independent from
+ * Notification#when.
+ */
+ @UptimeMillisLong
+ public long getCreationTime() {
+ return mCreationTime;
+ }
+
+ /**
* Should return the "representative entry" for this ListEntry. For NotificationEntries, its
* the entry itself. For groups, it should be the summary (but if a summary doesn't exist,
* this can return null). This method exists to interface with
@@ -79,6 +96,14 @@
}
/**
+ * True if this entry has been attached to the shade at least once in its lifetime (it may not
+ * currently be attached).
+ */
+ public boolean hasBeenAttachedBefore() {
+ return mFirstAddedIteration != -1;
+ }
+
+ /**
* Stores the current attach state into {@link #getPreviousAttachState()}} and then starts a
* fresh attach state (all entries will be null/default-initialized).
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index bd65ef0..387247e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -35,7 +35,6 @@
import static java.util.Objects.requireNonNull;
-import android.annotation.CurrentTimeMillisLong;
import android.app.Notification;
import android.app.Notification.MessagingStyle.Message;
import android.app.NotificationChannel;
@@ -96,7 +95,6 @@
private final String mKey;
private StatusBarNotification mSbn;
private Ranking mRanking;
- private long mCreationTime;
/*
* Bookkeeping members
@@ -198,11 +196,10 @@
boolean allowFgsDismissal,
long creationTime
) {
- super(requireNonNull(requireNonNull(sbn).getKey()));
+ super(requireNonNull(requireNonNull(sbn).getKey()), creationTime);
requireNonNull(ranking);
- mCreationTime = creationTime;
mKey = sbn.getKey();
setSbn(sbn);
setRanking(ranking);
@@ -255,21 +252,6 @@
}
/**
- * A timestamp of SystemClock.uptimeMillis() of when this entry was first created, regardless
- * of any changes to the data presented. It is set once on creation and will never change, and
- * allows us to know exactly how long this notification has been alive for in our listener
- * service. It is entirely unrelated to the information inside of the notification.
- *
- * This is different to Notification#when because it persists throughout updates, whereas
- * system server treats every single call to notify() as a new notification and we handle
- * updates to NotificationEntry locally.
- */
- @CurrentTimeMillisLong
- public long getCreationTime() {
- return mCreationTime;
- }
-
- /**
* Should only be called by NotificationEntryManager and friends.
* TODO: Make this package-private
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index a86ab41..6f78411 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -47,6 +47,7 @@
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
import com.android.systemui.util.Assert;
import com.android.systemui.util.time.SystemClock;
@@ -321,6 +322,7 @@
mPipelineState.incrementTo(STATE_FINALIZING);
logChanges();
freeEmptyGroups();
+ cleanupPluggables();
// Step 8: Dispatch the new list, first to any listeners and then to the view layer
dispatchOnBeforeRenderList(mReadOnlyNotifList);
@@ -421,7 +423,7 @@
GroupEntry group = mGroups.get(topLevelKey);
if (group == null) {
- group = new GroupEntry(topLevelKey);
+ group = new GroupEntry(topLevelKey, mSystemClock.uptimeMillis());
group.mFirstAddedIteration = mIterationCount;
mGroups.put(topLevelKey, group);
}
@@ -673,6 +675,20 @@
}
}
+ private void cleanupPluggables() {
+ callOnCleanup(mNotifPreGroupFilters);
+ callOnCleanup(mNotifPromoters);
+ callOnCleanup(mNotifFinalizeFilters);
+ callOnCleanup(mNotifComparators);
+ callOnCleanup(mNotifSections);
+ }
+
+ private void callOnCleanup(List<? extends Pluggable<?>> pluggables) {
+ for (int i = 0; i < pluggables.size(); i++) {
+ pluggables.get(i).onCleanup();
+ }
+ }
+
private final Comparator<ListEntry> mTopLevelComparator = (o1, o2) -> {
int cmp = Integer.compare(o1.getSection(), o2.getSection());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index 4159d43..9baaddd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -18,6 +18,8 @@
import static com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer.NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED;
+import static java.util.Objects.requireNonNull;
+
import android.annotation.IntDef;
import android.os.RemoteException;
import android.service.notification.StatusBarNotification;
@@ -28,7 +30,6 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.ListEntry;
-import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotifViewBarn;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -38,12 +39,12 @@
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager;
+import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager.NotifInflationErrorListener;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Set;
import javax.inject.Inject;
@@ -81,35 +82,48 @@
*/
private final int mChildBindCutoff;
+ /** How long we can delay a group while waiting for all children to inflate */
+ private final long mMaxGroupInflationDelay;
+
@Inject
public PreparationCoordinator(
PreparationCoordinatorLogger logger,
- NotifInflaterImpl notifInflater,
+ NotifInflater notifInflater,
NotifInflationErrorManager errorManager,
NotifViewBarn viewBarn,
IStatusBarService service) {
- this(logger, notifInflater, errorManager, viewBarn, service, CHILD_BIND_CUTOFF);
+ this(
+ logger,
+ notifInflater,
+ errorManager,
+ viewBarn,
+ service,
+ CHILD_BIND_CUTOFF,
+ MAX_GROUP_INFLATION_DELAY);
}
@VisibleForTesting
PreparationCoordinator(
PreparationCoordinatorLogger logger,
- NotifInflaterImpl notifInflater,
+ NotifInflater notifInflater,
NotifInflationErrorManager errorManager,
NotifViewBarn viewBarn,
IStatusBarService service,
- int childBindCutoff) {
+ int childBindCutoff,
+ long maxGroupInflationDelay) {
mLogger = logger;
mNotifInflater = notifInflater;
mNotifErrorManager = errorManager;
- mNotifErrorManager.addInflationErrorListener(mInflationErrorListener);
mViewBarn = viewBarn;
mStatusBarService = service;
mChildBindCutoff = childBindCutoff;
+ mMaxGroupInflationDelay = maxGroupInflationDelay;
}
@Override
public void attach(NotifPipeline pipeline) {
+ mNotifErrorManager.addInflationErrorListener(mInflationErrorListener);
+
pipeline.addCollectionListener(mNotifCollectionListener);
// Inflate after grouping/sorting since that affects what views to inflate.
pipeline.addOnBeforeFinalizeFilterListener(mOnBeforeFinalizeFilterListener);
@@ -127,7 +141,6 @@
@Override
public void onEntryUpdated(NotificationEntry entry) {
abortInflation(entry, "entryUpdated");
- mInflatingNotifs.remove(entry);
@InflationState int state = getInflationState(entry);
if (state == STATE_INFLATED) {
mInflationStates.put(entry, STATE_INFLATED_INVALID);
@@ -145,7 +158,6 @@
@Override
public void onEntryCleanUp(NotificationEntry entry) {
mInflationStates.remove(entry);
- mInflatingNotifs.remove(entry);
mViewBarn.removeViewForEntry(entry);
}
};
@@ -165,17 +177,32 @@
};
private final NotifFilter mNotifInflatingFilter = new NotifFilter(TAG + "Inflating") {
+ private final Map<GroupEntry, Boolean> mIsDelayedGroupCache = new ArrayMap<>();
+
/**
- * Filters out notifications that aren't inflated
+ * Filters out notifications that either (a) aren't inflated or (b) are part of a group
+ * that isn't completely inflated yet
*/
@Override
public boolean shouldFilterOut(NotificationEntry entry, long now) {
- return !isInflated(entry);
+ final GroupEntry parent = requireNonNull(entry.getParent());
+ Boolean isMemberOfDelayedGroup = mIsDelayedGroupCache.get(parent);
+ if (isMemberOfDelayedGroup == null) {
+ isMemberOfDelayedGroup = shouldWaitForGroupToInflate(parent, now);
+ mIsDelayedGroupCache.put(parent, isMemberOfDelayedGroup);
+ }
+
+ return !isInflated(entry) || isMemberOfDelayedGroup;
+ }
+
+ @Override
+ public void onCleanup() {
+ mIsDelayedGroupCache.clear();
}
};
- private final NotifInflationErrorManager.NotifInflationErrorListener mInflationErrorListener =
- new NotifInflationErrorManager.NotifInflationErrorListener() {
+ private final NotifInflationErrorListener mInflationErrorListener =
+ new NotifInflationErrorListener() {
@Override
public void onNotifInflationError(NotificationEntry entry, Exception e) {
mViewBarn.removeViewForEntry(entry);
@@ -191,8 +218,9 @@
sbn.getUid(),
sbn.getInitialPid(),
e.getMessage(),
- sbn.getUserId());
+ sbn.getUser().getIdentifier());
} catch (RemoteException ex) {
+ // System server is dead, nothing to do about that
}
mNotifInflationErrorFilter.invalidateList();
}
@@ -297,11 +325,36 @@
private @InflationState int getInflationState(NotificationEntry entry) {
Integer stateObj = mInflationStates.get(entry);
- Objects.requireNonNull(stateObj,
+ requireNonNull(stateObj,
"Asking state of a notification preparation coordinator doesn't know about");
return stateObj;
}
+ private boolean shouldWaitForGroupToInflate(GroupEntry group, long now) {
+ if (group == GroupEntry.ROOT_ENTRY || group.hasBeenAttachedBefore()) {
+ return false;
+ }
+ if (isBeyondGroupInitializationWindow(group, now)) {
+ mLogger.logGroupInflationTookTooLong(group.getKey());
+ return false;
+ }
+ if (mInflatingNotifs.contains(group.getSummary())) {
+ mLogger.logDelayingGroupRelease(group.getKey(), group.getSummary().getKey());
+ return true;
+ }
+ for (NotificationEntry child : group.getChildren()) {
+ if (mInflatingNotifs.contains(child) && !child.hasBeenAttachedBefore()) {
+ mLogger.logDelayingGroupRelease(group.getKey(), child.getKey());
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isBeyondGroupInitializationWindow(GroupEntry entry, long now) {
+ return now - entry.getCreationTime() > mMaxGroupInflationDelay;
+ }
+
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"STATE_"},
value = {STATE_UNINFLATED, STATE_INFLATED_INVALID, STATE_INFLATED, STATE_ERROR})
@@ -328,6 +381,8 @@
*/
private static final int EXTRA_VIEW_BUFFER_COUNT = 1;
+ private static final long MAX_GROUP_INFLATION_DELAY = 500;
+
private static final int CHILD_BIND_CUTOFF =
NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED + EXTRA_VIEW_BUFFER_COUNT;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
index 75e7bc9..dd4794f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
@@ -40,6 +40,23 @@
"NOTIF INFLATION ABORTED $str1 reason=$str2"
})
}
+
+ fun logGroupInflationTookTooLong(groupKey: String) {
+ buffer.log(TAG, LogLevel.WARNING, {
+ str1 = groupKey
+ }, {
+ "Group inflation took too long far $str1, releasing children early"
+ })
+ }
+
+ fun logDelayingGroupRelease(groupKey: String, childKey: String) {
+ buffer.log(TAG, LogLevel.DEBUG, {
+ str1 = groupKey
+ str2 = childKey
+ }, {
+ "Delaying release of group $str1 because child $str2 is still inflating"
+ })
+ }
}
private const val TAG = "PreparationCoordinator"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java
index 4270408..8e4fb75 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java
@@ -55,11 +55,17 @@
}
/** Set a listener to be notified when a pluggable is invalidated. */
- public void setInvalidationListener(PluggableListener<This> listener) {
+ public final void setInvalidationListener(PluggableListener<This> listener) {
mListener = listener;
}
/**
+ * Called on the pluggable once at the end of every pipeline run. Override this method to
+ * perform any necessary cleanup.
+ */
+ public void onCleanup() { }
+
+ /**
* Listener interface for when pluggables are invalidated.
*
* @param <T> The type of pluggable that is being listened to.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index d661b5e..ee471c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -40,8 +40,10 @@
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
+import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater;
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
@@ -221,4 +223,8 @@
@Binds
NotificationInterruptStateProvider bindNotificationInterruptStateProvider(
NotificationInterruptStateProviderImpl notificationInterruptStateProviderImpl);
+
+ /** */
+ @Binds
+ NotifInflater bindNotifInflater(NotifInflaterImpl notifInflaterImpl);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt
new file mode 100644
index 0000000..62667bc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 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.notification.collection
+
+/**
+ * Modifies a NotificationEntry
+ *
+ * The [modifier] function will be passed an instance of a NotificationEntryBuilder. Any
+ * modifications made to the builder will be applied to the [entry].
+ */
+inline fun modifyEntry(
+ entry: NotificationEntry,
+ crossinline modifier: NotificationEntryBuilder.() -> Unit
+) {
+ val builder = NotificationEntryBuilder(entry)
+ modifier(builder)
+ builder.apply(entry)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
new file mode 100644
index 0000000..2971c05
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2020 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.notification.collection;
+
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Builder to construct instances of {@link GroupEntry} for tests.
+ */
+public class GroupEntryBuilder {
+ private String mKey = "test_group_key";
+ private long mCreationTime = 0;
+ @Nullable private GroupEntry mParent = GroupEntry.ROOT_ENTRY;
+ private NotificationEntry mSummary = null;
+ private List<NotificationEntry> mChildren = new ArrayList<>();
+
+ /** Builds a new instance of GroupEntry */
+ public GroupEntry build() {
+ GroupEntry ge = new GroupEntry(mKey, mCreationTime);
+ ge.setParent(mParent);
+
+ ge.setSummary(mSummary);
+ mSummary.setParent(ge);
+
+ for (NotificationEntry child : mChildren) {
+ ge.addChild(child);
+ child.setParent(ge);
+ }
+ return ge;
+ }
+
+ public GroupEntryBuilder setKey(String key) {
+ mKey = key;
+ return this;
+ }
+
+ public GroupEntryBuilder setCreationTime(long creationTime) {
+ mCreationTime = creationTime;
+ return this;
+ }
+
+ public GroupEntryBuilder setParent(@Nullable GroupEntry entry) {
+ mParent = entry;
+ return this;
+ }
+
+ public GroupEntryBuilder setSummary(
+ NotificationEntry summary) {
+ mSummary = summary;
+ return this;
+ }
+
+ public GroupEntryBuilder setChildren(List<NotificationEntry> children) {
+ mChildren.clear();
+ mChildren.addAll(children);
+ return this;
+ }
+
+ /** Adds a child to the existing list of children */
+ public GroupEntryBuilder addChild(NotificationEntry entry) {
+ mChildren.add(entry);
+ return this;
+ }
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryHelper.java
deleted file mode 100644
index 038dd17..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryHelper.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2020 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.notification.collection;
-
-import java.util.List;
-
-/**
- * Helper class to provide methods for test classes that need {@link GroupEntry}'s for their tests.
- */
-public class GroupEntryHelper {
- /**
- * Create a group entry for testing purposes.
- * @param groupKey group key for the group and all its entries
- * @param summary summary notification for group
- * @param children group's children notifications
- */
- public static final GroupEntry createGroup(
- String groupKey,
- NotificationEntry summary,
- List<NotificationEntry> children) {
- GroupEntry groupEntry = new GroupEntry(groupKey);
- groupEntry.setSummary(summary);
- for (NotificationEntry child : children) {
- groupEntry.addChild(child);
- }
- return groupEntry;
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
index 2b12c22..386c866c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
@@ -191,9 +191,10 @@
@Test
public void testIsHighPriority_summaryUpdated() {
// GIVEN a GroupEntry with a lowPrioritySummary and no children
- final GroupEntry parentEntry = new GroupEntry("test_group_key");
final NotificationEntry lowPrioritySummary = createNotifEntry(false);
- setSummary(parentEntry, lowPrioritySummary);
+ final GroupEntry parentEntry = new GroupEntryBuilder()
+ .setSummary(lowPrioritySummary)
+ .build();
assertFalse(mHighPriorityProvider.isHighPriority(parentEntry));
// WHEN the summary changes to high priority
@@ -214,10 +215,11 @@
// GroupEntry = parentEntry, summary = lowPrioritySummary
// NotificationEntry = lowPriorityChild
// NotificationEntry = highPriorityChild
- final GroupEntry parentEntry = new GroupEntry("test_group_key");
- setSummary(parentEntry, createNotifEntry(false));
- addChild(parentEntry, createNotifEntry(false));
- addChild(parentEntry, createNotifEntry(true));
+ final GroupEntry parentEntry = new GroupEntryBuilder()
+ .setSummary(createNotifEntry(false))
+ .addChild(createNotifEntry(false))
+ .addChild(createNotifEntry(true))
+ .build();
// THEN the GroupEntry parentEntry is high priority since it has a high priority child
assertTrue(mHighPriorityProvider.isHighPriority(parentEntry));
@@ -228,10 +230,11 @@
// GIVEN:
// GroupEntry = parentEntry, summary = lowPrioritySummary
// NotificationEntry = lowPriorityChild
- final GroupEntry parentEntry = new GroupEntry("test_group_key");
final NotificationEntry lowPriorityChild = createNotifEntry(false);
- setSummary(parentEntry, createNotifEntry(false));
- addChild(parentEntry, lowPriorityChild);
+ final GroupEntry parentEntry = new GroupEntryBuilder()
+ .setSummary(createNotifEntry(false))
+ .addChild(lowPriorityChild)
+ .build();
// WHEN the child entry ranking changes to high priority
lowPriorityChild.setRanking(
@@ -250,14 +253,4 @@
.setImportance(highPriority ? IMPORTANCE_HIGH : IMPORTANCE_MIN)
.build();
}
-
- private void setSummary(GroupEntry parent, NotificationEntry summary) {
- parent.setSummary(summary);
- summary.setParent(parent);
- }
-
- private void addChild(GroupEntry parent, NotificationEntry child) {
- parent.addChild(child);
- child.setParent(parent);
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
index 4a7c6c6..1523653 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.content.pm.ShortcutInfo;
import android.os.UserHandle;
+import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
@@ -33,6 +34,8 @@
import java.util.ArrayList;
+import kotlin.Unit;
+
/**
* Combined builder for constructing a NotificationEntry and its associated StatusBarNotification
* and Ranking. Is largely a proxy for the SBN and Ranking builders, but does a little extra magic
@@ -43,8 +46,8 @@
* Only for use in tests.
*/
public class NotificationEntryBuilder {
- private final SbnBuilder mSbnBuilder = new SbnBuilder();
- private final RankingBuilder mRankingBuilder = new RankingBuilder();
+ private final SbnBuilder mSbnBuilder;
+ private final RankingBuilder mRankingBuilder;
private final FakeSystemClock mClock = new FakeSystemClock();
private StatusBarNotification mSbn = null;
@@ -55,12 +58,49 @@
/* If set, use this creation time instead of mClock.uptimeMillis */
private long mCreationTime = -1;
+ public NotificationEntryBuilder() {
+ mSbnBuilder = new SbnBuilder();
+ mRankingBuilder = new RankingBuilder();
+ }
+
+ public NotificationEntryBuilder(NotificationEntry source) {
+ mSbnBuilder = new SbnBuilder(source.getSbn());
+ mRankingBuilder = new RankingBuilder(source.getRanking());
+
+ mParent = source.getParent();
+ mSection = source.getSection();
+ mCreationTime = source.getCreationTime();
+ }
+
+ /** Build a new instance of NotificationEntry */
public NotificationEntry build() {
- StatusBarNotification sbn = mSbn != null ? mSbn : mSbnBuilder.build();
- mRankingBuilder.setKey(sbn.getKey());
- long creationTime = mCreationTime != -1 ? mCreationTime : mClock.uptimeMillis();
- final NotificationEntry entry = new NotificationEntry(
- sbn, mRankingBuilder.build(), mClock.uptimeMillis());
+ return buildOrApply(null);
+ }
+
+ /** Modifies [target] to match the contents of this builder */
+ public void apply(NotificationEntry target) {
+ buildOrApply(target);
+ }
+
+ /** Convenience method for Kotlin callbacks that are passed a builder and need to return Unit */
+ public Unit done() {
+ return Unit.INSTANCE;
+ }
+
+ private NotificationEntry buildOrApply(NotificationEntry target) {
+ final StatusBarNotification sbn = mSbn != null ? mSbn : mSbnBuilder.build();
+ final Ranking ranking = mRankingBuilder.setKey(sbn.getKey()).build();
+ final long creationTime = mCreationTime != -1 ? mCreationTime : mClock.uptimeMillis();
+
+ final NotificationEntry entry;
+ if (target == null) {
+ entry = new NotificationEntry(sbn, ranking, creationTime);
+ } else {
+ entry = target;
+ entry.setSbn(sbn);
+ entry.setRanking(ranking);
+ // Note: we can't modify the creation time as it's immutable
+ }
/* ListEntry properties */
entry.setParent(mParent);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
index f54252e..917c049 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
@@ -21,9 +21,10 @@
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_MIN;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
+import static com.android.systemui.statusbar.notification.collection.EntryUtilKt.modifyEntry;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -41,6 +42,7 @@
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -71,13 +73,12 @@
@Mock private NotifPipeline mNotifPipeline;
private NotificationEntry mEntry;
- private KeyguardCoordinator mKeyguardCoordinator;
private NotifFilter mKeyguardFilter;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mKeyguardCoordinator = new KeyguardCoordinator(
+ KeyguardCoordinator keyguardCoordinator = new KeyguardCoordinator(
mContext, mMainHandler, mKeyguardStateController, mLockscreenUserManager,
mBroadcastDispatcher, mStatusBarStateController,
mKeyguardUpdateMonitor, mHighPriorityProvider);
@@ -87,7 +88,7 @@
.build();
ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
- mKeyguardCoordinator.attach(mNotifPipeline);
+ keyguardCoordinator.attach(mNotifPipeline);
verify(mNotifPipeline, times(1)).addFinalizeFilter(filterCaptor.capture());
mKeyguardFilter = filterCaptor.getValue();
}
@@ -186,12 +187,18 @@
public void summaryExceedsThresholdToShow() {
// GIVEN the notification doesn't exceed the threshold to show on the lockscreen
// but it's part of a group (has a parent)
- final GroupEntry parent = new GroupEntry("test_group_key");
final NotificationEntry entryWithParent = new NotificationEntryBuilder()
- .setParent(parent)
.setUser(new UserHandle(NOTIF_USER_ID))
.build();
+ final GroupEntry parent = new GroupEntryBuilder()
+ .setKey("test_group_key")
+ .setSummary(new NotificationEntryBuilder()
+ .setImportance(IMPORTANCE_HIGH)
+ .build())
+ .addChild(entryWithParent)
+ .build();
+
setupUnfilteredState(entryWithParent);
entryWithParent.setRanking(new RankingBuilder()
.setKey(entryWithParent.getKey())
@@ -200,18 +207,15 @@
// WHEN its parent does exceed threshold tot show on the lockscreen
when(mHighPriorityProvider.isHighPriority(parent)).thenReturn(true);
- parent.setSummary(new NotificationEntryBuilder()
- .setImportance(IMPORTANCE_HIGH)
- .build());
// THEN don't filter out the entry
assertFalse(mKeyguardFilter.shouldFilterOut(entryWithParent, 0));
// WHEN its parent doesn't exceed threshold to show on lockscreen
when(mHighPriorityProvider.isHighPriority(parent)).thenReturn(false);
- parent.setSummary(new NotificationEntryBuilder()
+ modifyEntry(parent.getSummary(), builder -> builder
.setImportance(IMPORTANCE_MIN)
- .build());
+ .done());
// THEN filter out the entry
assertTrue(mKeyguardFilter.shouldFilterOut(entryWithParent, 0));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
index faf9da3..37561c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
+import static com.android.systemui.statusbar.notification.collection.GroupEntry.ROOT_ENTRY;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -25,6 +27,8 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static java.util.Objects.requireNonNull;
+
import android.os.RemoteException;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -34,12 +38,13 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
-import com.android.systemui.statusbar.notification.collection.GroupEntryHelper;
-import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotifViewBarn;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
@@ -52,19 +57,17 @@
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class PreparationCoordinatorTest extends SysuiTestCase {
- private static final String TEST_MESSAGE = "TEST_MESSAGE";
- private static final String TEST_GROUP_KEY = "TEST_GROUP_KEY";
- private static final int TEST_CHILD_BIND_CUTOFF = 9;
-
- private PreparationCoordinator mCoordinator;
private NotifCollectionListener mCollectionListener;
private OnBeforeFinalizeFilterListener mBeforeFilterListener;
private NotifFilter mUninflatedFilter;
@@ -75,30 +78,31 @@
@Captor private ArgumentCaptor<NotifCollectionListener> mCollectionListenerCaptor;
@Captor private ArgumentCaptor<OnBeforeFinalizeFilterListener> mBeforeFilterListenerCaptor;
- @Captor private ArgumentCaptor<NotifInflaterImpl.InflationCallback> mCallbackCaptor;
+ @Captor private ArgumentCaptor<NotifInflater.InflationCallback> mCallbackCaptor;
@Mock private NotifPipeline mNotifPipeline;
@Mock private IStatusBarService mService;
- @Mock private NotifInflaterImpl mNotifInflater;
+ @Spy private FakeNotifInflater mNotifInflater = new FakeNotifInflater();
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mEntry = new NotificationEntryBuilder().build();
+ mEntry = new NotificationEntryBuilder().setParent(ROOT_ENTRY).build();
mInflationError = new Exception(TEST_MESSAGE);
mErrorManager = new NotifInflationErrorManager();
- mCoordinator = new PreparationCoordinator(
+ PreparationCoordinator coordinator = new PreparationCoordinator(
mock(PreparationCoordinatorLogger.class),
mNotifInflater,
mErrorManager,
mock(NotifViewBarn.class),
mService,
- TEST_CHILD_BIND_CUTOFF);
+ TEST_CHILD_BIND_CUTOFF,
+ TEST_MAX_GROUP_DELAY);
ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
- mCoordinator.attach(mNotifPipeline);
+ coordinator.attach(mNotifPipeline);
verify(mNotifPipeline, times(2)).addFinalizeFilter(filterCaptor.capture());
List<NotifFilter> filters = filterCaptor.getAllValues();
mInflationErrorFilter = filters.get(0);
@@ -127,7 +131,7 @@
eq(mEntry.getSbn().getUid()),
eq(mEntry.getSbn().getInitialPid()),
eq(mInflationError.getMessage()),
- eq(mEntry.getSbn().getUserId()));
+ eq(mEntry.getSbn().getUser().getIdentifier()));
}
@Test
@@ -199,7 +203,11 @@
.build();
children.add(child);
}
- GroupEntry groupEntry = GroupEntryHelper.createGroup(TEST_GROUP_KEY, summary, children);
+ GroupEntry groupEntry = new GroupEntryBuilder()
+ .setKey(TEST_GROUP_KEY)
+ .setSummary(summary)
+ .setChildren(children)
+ .build();
mCollectionListener.onEntryInit(summary);
for (NotificationEntry entry : children) {
@@ -222,4 +230,139 @@
}
}
}
+
+ @Test
+ public void testPartiallyInflatedGroupsAreFilteredOut() {
+ // GIVEN a newly-posted group with a summary and two children
+ final GroupEntry group = new GroupEntryBuilder()
+ .setCreationTime(400)
+ .setSummary(new NotificationEntryBuilder().setId(1).build())
+ .addChild(new NotificationEntryBuilder().setId(2).build())
+ .addChild(new NotificationEntryBuilder().setId(3).build())
+ .build();
+ fireAddEvents(List.of(group));
+ final NotificationEntry child0 = group.getChildren().get(0);
+ mBeforeFilterListener.onBeforeFinalizeFilter(List.of(group));
+
+ // WHEN one of this children finishes inflating
+ mNotifInflater.getInflateCallback(child0).onInflationFinished(child0);
+
+ // THEN the inflated child is still filtered out
+ assertTrue(mUninflatedFilter.shouldFilterOut(child0, 401));
+ }
+
+ @Test
+ public void testPartiallyInflatedGroupsAreFilteredOutSummaryVersion() {
+ // GIVEN a newly-posted group with a summary and two children
+ final GroupEntry group = new GroupEntryBuilder()
+ .setCreationTime(400)
+ .setSummary(new NotificationEntryBuilder().setId(1).build())
+ .addChild(new NotificationEntryBuilder().setId(2).build())
+ .addChild(new NotificationEntryBuilder().setId(3).build())
+ .build();
+ fireAddEvents(List.of(group));
+ final NotificationEntry summary = group.getSummary();
+ final NotificationEntry child0 = group.getChildren().get(0);
+ final NotificationEntry child1 = group.getChildren().get(1);
+ mBeforeFilterListener.onBeforeFinalizeFilter(List.of(group));
+
+ // WHEN all of the children (but not the summary) finish inflating
+ mNotifInflater.getInflateCallback(child0).onInflationFinished(child0);
+ mNotifInflater.getInflateCallback(child1).onInflationFinished(child1);
+
+ // THEN the entire group is still filtered out
+ assertTrue(mUninflatedFilter.shouldFilterOut(summary, 401));
+ assertTrue(mUninflatedFilter.shouldFilterOut(child0, 401));
+ assertTrue(mUninflatedFilter.shouldFilterOut(child1, 401));
+ }
+
+ @Test
+ public void testCompletedInflatedGroupsAreReleased() {
+ // GIVEN a newly-posted group with a summary and two children
+ final GroupEntry group = new GroupEntryBuilder()
+ .setCreationTime(400)
+ .setSummary(new NotificationEntryBuilder().setId(1).build())
+ .addChild(new NotificationEntryBuilder().setId(2).build())
+ .addChild(new NotificationEntryBuilder().setId(3).build())
+ .build();
+ fireAddEvents(List.of(group));
+ final NotificationEntry summary = group.getSummary();
+ final NotificationEntry child0 = group.getChildren().get(0);
+ final NotificationEntry child1 = group.getChildren().get(1);
+ mBeforeFilterListener.onBeforeFinalizeFilter(List.of(group));
+
+ // WHEN all of the children (and the summary) finish inflating
+ mNotifInflater.getInflateCallback(child0).onInflationFinished(child0);
+ mNotifInflater.getInflateCallback(child1).onInflationFinished(child1);
+ mNotifInflater.getInflateCallback(summary).onInflationFinished(summary);
+
+ // THEN the entire group is still filtered out
+ assertFalse(mUninflatedFilter.shouldFilterOut(summary, 401));
+ assertFalse(mUninflatedFilter.shouldFilterOut(child0, 401));
+ assertFalse(mUninflatedFilter.shouldFilterOut(child1, 401));
+ }
+
+ @Test
+ public void testPartiallyInflatedGroupsAreReleasedAfterTimeout() {
+ // GIVEN a newly-posted group with a summary and two children
+ final GroupEntry group = new GroupEntryBuilder()
+ .setCreationTime(400)
+ .setSummary(new NotificationEntryBuilder().setId(1).build())
+ .addChild(new NotificationEntryBuilder().setId(2).build())
+ .addChild(new NotificationEntryBuilder().setId(3).build())
+ .build();
+ fireAddEvents(List.of(group));
+ final NotificationEntry child0 = group.getChildren().get(0);
+ mBeforeFilterListener.onBeforeFinalizeFilter(List.of(group));
+
+ // WHEN one of this children finishes inflating and enough time passes
+ mNotifInflater.getInflateCallback(child0).onInflationFinished(child0);
+
+ // THEN the inflated child is not filtered out even though the rest of the group hasn't
+ // finished inflating yet
+ assertTrue(mUninflatedFilter.shouldFilterOut(child0, TEST_MAX_GROUP_DELAY + 1));
+ }
+
+ private static class FakeNotifInflater implements NotifInflater {
+ private Map<NotificationEntry, InflationCallback> mInflateCallbacks = new HashMap<>();
+
+ @Override
+ public void inflateViews(NotificationEntry entry, InflationCallback callback) {
+ mInflateCallbacks.put(entry, callback);
+ }
+
+ @Override
+ public void rebindViews(NotificationEntry entry, InflationCallback callback) {
+ }
+
+ @Override
+ public void abortInflation(NotificationEntry entry) {
+ }
+
+ public InflationCallback getInflateCallback(NotificationEntry entry) {
+ return requireNonNull(mInflateCallbacks.get(entry));
+ }
+ }
+
+ private void fireAddEvents(List<? extends ListEntry> entries) {
+ for (ListEntry entry : entries) {
+ if (entry instanceof GroupEntry) {
+ GroupEntry ge = (GroupEntry) entry;
+ fireAddEvents(ge.getSummary());
+ fireAddEvents(ge.getChildren());
+ } else {
+ fireAddEvents((NotificationEntry) entry);
+ }
+ }
+ }
+
+ private void fireAddEvents(NotificationEntry entry) {
+ mCollectionListener.onEntryInit(entry);
+ mCollectionListener.onEntryAdded(entry);
+ }
+
+ private static final String TEST_MESSAGE = "TEST_MESSAGE";
+ private static final String TEST_GROUP_KEY = "TEST_GROUP_KEY";
+ private static final int TEST_CHILD_BIND_CUTOFF = 9;
+ private static final int TEST_MAX_GROUP_DELAY = 100;
}