New Pipeline: Restore the ShadeListBuilder order to sort before the finalize filter.

* Fixes the bug where cleaning up incomplete groups would break section continuity by ensuring that children removed from a group are inserted into the list at the rank where the group was.
* This enables future work which allows using sort order and/or section to determine view inflation specs.

Fixes: 203233468
Fixes: 196408434
Test: atest ShadeListBuilderTest
Change-Id: If75d0d4f594b462ca49d2c19c98edfcf93892cad
This commit is contained in:
Jeff DeCew
2021-11-06 16:39:18 -04:00
parent b9df9d9653
commit 0e5706709b
4 changed files with 137 additions and 58 deletions

View File

@@ -58,12 +58,13 @@ import javax.inject.Inject
* appropriately).
* 3. OnBeforeTransformGroupListeners are fired ([.addOnBeforeTransformGroupsListener])
* 4. NotifPromoters are called on each notification with a parent ([.addPromoter])
* 5. Finalize filters are fired on each notification ([.addFinalizeFilter])
* 6. OnBeforeSortListeners are fired ([.addOnBeforeSortListener])
* 7. Top-level entries are assigned sections by NotifSections ([.setSections])
* 8. Top-level entries within the same section are sorted by NotifComparators ([.setComparators])
* 9. OnBeforeRenderListListeners are fired ([.addOnBeforeRenderListListener])
* 10. The list is handed off to the view layer to be rendered
* 5. OnBeforeSortListeners are fired ([.addOnBeforeSortListener])
* 6. Top-level entries are assigned sections by NotifSections ([.setSections])
* 7. Top-level entries within the same section are sorted by NotifComparators ([.setComparators])
* 8. OnBeforeFinalizeFilterListeners are fired ([.addOnBeforeFinalizeFilterListener])
* 9. Finalize filters are fired on each notification ([.addFinalizeFilter])
* 10. OnBeforeRenderListListeners are fired ([.addOnBeforeRenderListListener])
* 11. The list is handed off to the view layer to be rendered
*/
@SysUISingleton
class NotifPipeline @Inject constructor(

View File

@@ -365,16 +365,7 @@ public class ShadeListBuilder implements Dumpable {
mPipelineState.incrementTo(STATE_GROUP_STABILIZING);
stabilizeGroupingNotifs(mNotifList);
// Step 5: Filter out entries after pre-group filtering, grouping and promoting
// Now filters can see grouping information to determine whether to filter or not.
dispatchOnBeforeFinalizeFilter(mReadOnlyNotifList);
mPipelineState.incrementTo(STATE_FINALIZE_FILTERING);
filterNotifs(mNotifList, mNewNotifList, mNotifFinalizeFilters);
applyNewNotifList();
pruneIncompleteGroups(mNotifList);
// Step 6: Section & Sort
// Step 5: Section & Sort
// Assign each top-level entry a section, and copy to all of its children
dispatchOnBeforeSort(mReadOnlyNotifList);
mPipelineState.incrementTo(STATE_SORTING);
@@ -383,6 +374,15 @@ public class ShadeListBuilder implements Dumpable {
// Sort the list by section and then within section by our list of custom comparators
sortListAndGroups();
// Step 6: Filter out entries after pre-group filtering, grouping, promoting, and sorting
// Now filters can see grouping, sectioning, and order information to determine whether
// to filter or not.
dispatchOnBeforeFinalizeFilter(mReadOnlyNotifList);
mPipelineState.incrementTo(STATE_FINALIZE_FILTERING);
filterNotifs(mNotifList, mNewNotifList, mNotifFinalizeFilters);
applyNewNotifList();
pruneIncompleteGroups(mNotifList);
// Step 7: Lock in our group structure and log anything that's changed since the last run
mPipelineState.incrementTo(STATE_FINALIZING);
logChanges();
@@ -654,16 +654,15 @@ public class ShadeListBuilder implements Dumpable {
final List<NotificationEntry> children = group.getRawChildren();
if (group.getSummary() != null && children.size() == 0) {
shadeList.remove(i);
i--;
NotificationEntry summary = group.getSummary();
summary.setParent(ROOT_ENTRY);
shadeList.add(summary);
// The list may be sorted; replace the group with the summary, in its place
shadeList.set(i, summary);
group.setSummary(null);
annulAddition(group, shadeList);
i--; // The node we visited is gone, so be sure to visit this index again.
} else if (group.getSummary() == null
|| children.size() < MIN_CHILDREN_FOR_GROUP) {
@@ -681,7 +680,6 @@ public class ShadeListBuilder implements Dumpable {
// its children (if any) directly to top-level.
shadeList.remove(i);
i--;
if (group.getSummary() != null) {
final NotificationEntry summary = group.getSummary();
@@ -692,11 +690,14 @@ public class ShadeListBuilder implements Dumpable {
for (int j = 0; j < children.size(); j++) {
final NotificationEntry child = children.get(j);
child.setParent(ROOT_ENTRY);
shadeList.add(child);
// The list may be sorted, so add the children in order where the group was.
shadeList.add(i + j, child);
}
children.clear();
annulAddition(group, shadeList);
i--; // The node we visited is gone, so be sure to visit this index again.
}
}
}

View File

@@ -82,8 +82,8 @@ public class PipelineState {
public static final int STATE_GROUPING = 4;
public static final int STATE_TRANSFORMING = 5;
public static final int STATE_GROUP_STABILIZING = 6;
public static final int STATE_FINALIZE_FILTERING = 7;
public static final int STATE_SORTING = 8;
public static final int STATE_SORTING = 7;
public static final int STATE_FINALIZE_FILTERING = 8;
public static final int STATE_FINALIZING = 9;
@IntDef(prefix = { "STATE_" }, value = {
@@ -94,8 +94,8 @@ public class PipelineState {
STATE_GROUPING,
STATE_TRANSFORMING,
STATE_GROUP_STABILIZING,
STATE_FINALIZE_FILTERING,
STATE_SORTING,
STATE_FINALIZE_FILTERING,
STATE_FINALIZING,
})
@Retention(RetentionPolicy.SOURCE)

View File

@@ -45,6 +45,7 @@ import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.ArrayMap;
import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -861,13 +862,13 @@ public class ShadeListBuilderTest extends SysuiTestCase {
.onBeforeTransformGroups(anyList());
inOrder.verify(promoter, atLeastOnce())
.shouldPromoteToTopLevel(any(NotificationEntry.class));
inOrder.verify(mOnBeforeFinalizeFilterListener).onBeforeFinalizeFilter(anyList());
inOrder.verify(preRenderFilter, atLeastOnce())
.shouldFilterOut(any(NotificationEntry.class), anyLong());
inOrder.verify(mOnBeforeSortListener).onBeforeSort(anyList());
inOrder.verify(section, atLeastOnce()).isInSection(any(ListEntry.class));
inOrder.verify(comparator, atLeastOnce())
.compare(any(ListEntry.class), any(ListEntry.class));
inOrder.verify(mOnBeforeFinalizeFilterListener).onBeforeFinalizeFilter(anyList());
inOrder.verify(preRenderFilter, atLeastOnce())
.shouldFilterOut(any(NotificationEntry.class), anyLong());
inOrder.verify(mOnBeforeRenderListListener).onBeforeRenderList(anyList());
inOrder.verify(mOnRenderListListener).onRenderList(anyList());
}
@@ -973,13 +974,11 @@ public class ShadeListBuilderTest extends SysuiTestCase {
);
// THEN all the new notifs, including the new GroupEntry, are passed to the listener
assertEquals(
asList(
mEntrySet.get(0),
mBuiltList.get(1),
mEntrySet.get(4)),
listener.mEntriesReceived
);
assertThat(listener.mEntriesReceived).containsExactly(
mEntrySet.get(0),
mBuiltList.get(1),
mEntrySet.get(4)
).inOrder(); // Order is a bonus because this listener is before sort
}
@Test
@@ -1019,14 +1018,12 @@ public class ShadeListBuilderTest extends SysuiTestCase {
);
// THEN all the new notifs, including the new GroupEntry, are passed to the listener
assertEquals(
asList(
mEntrySet.get(0),
mBuiltList.get(2),
mEntrySet.get(7),
mEntrySet.get(1)),
listener.mEntriesReceived
);
assertThat(listener.mEntriesReceived).containsExactly(
mEntrySet.get(0),
mEntrySet.get(1),
mBuiltList.get(2),
mEntrySet.get(7)
).inOrder(); // Order is a bonus because this listener is before sort
}
@Test
@@ -1116,10 +1113,94 @@ public class ShadeListBuilderTest extends SysuiTestCase {
);
}
@Test
public void testFinalizeFilteringGroupSummaryDoesNotBreakSort() {
// GIVEN children from 3 packages, with one in the middle of the sort order being a group
addNotif(0, PACKAGE_1);
addNotif(1, PACKAGE_2);
addNotif(2, PACKAGE_3);
addNotif(3, PACKAGE_1);
addNotif(4, PACKAGE_2);
addNotif(5, PACKAGE_3);
addGroupSummary(6, PACKAGE_2, GROUP_1);
addGroupChild(7, PACKAGE_2, GROUP_1);
addGroupChild(8, PACKAGE_2, GROUP_1);
// GIVEN that they should be sorted by package
mListBuilder.setComparators(asList(
new HypeComparator(PACKAGE_1),
new HypeComparator(PACKAGE_2),
new HypeComparator(PACKAGE_3)
));
// WHEN a finalize filter removes the summary
mListBuilder.addFinalizeFilter(new NotifFilter("Test") {
@Override
public boolean shouldFilterOut(@NonNull NotificationEntry entry, long now) {
return entry == notif(6).entry;
}
});
dispatchBuild();
// THEN the notifications remain ordered by package, even though the children were promoted
verifyBuiltList(
notif(0),
notif(3),
notif(1),
notif(4),
notif(7), // promoted child
notif(8), // promoted child
notif(2),
notif(5)
);
}
@Test
public void testFinalizeFilteringGroupChildDoesNotBreakSort() {
// GIVEN children from 3 packages, with one in the middle of the sort order being a group
addNotif(0, PACKAGE_1);
addNotif(1, PACKAGE_2);
addNotif(2, PACKAGE_3);
addNotif(3, PACKAGE_1);
addNotif(4, PACKAGE_2);
addNotif(5, PACKAGE_3);
addGroupSummary(6, PACKAGE_2, GROUP_1);
addGroupChild(7, PACKAGE_2, GROUP_1);
addGroupChild(8, PACKAGE_2, GROUP_1);
// GIVEN that they should be sorted by package
mListBuilder.setComparators(asList(
new HypeComparator(PACKAGE_1),
new HypeComparator(PACKAGE_2),
new HypeComparator(PACKAGE_3)
));
// WHEN a finalize filter one of the 2 children from a group
mListBuilder.addFinalizeFilter(new NotifFilter("Test") {
@Override
public boolean shouldFilterOut(@NonNull NotificationEntry entry, long now) {
return entry == notif(7).entry;
}
});
dispatchBuild();
// THEN the notifications remain ordered by package, even though the children were promoted
verifyBuiltList(
notif(0),
notif(3),
notif(1),
notif(4),
notif(8), // promoted child
notif(2),
notif(5)
);
}
@Test
public void testBrokenGroupNotificationOrdering() {
// GIVEN two group children with different sections & without a summary yet
addGroupChild(0, PACKAGE_2, GROUP_1);
addNotif(1, PACKAGE_1);
addGroupChild(2, PACKAGE_2, GROUP_1);
@@ -1250,13 +1331,11 @@ public class ShadeListBuilderTest extends SysuiTestCase {
dispatchBuild();
// THEN all the new notifs are passed to the listener out of order
assertEquals(
asList(
mEntrySet.get(0),
mEntrySet.get(1),
mEntrySet.get(2)),
listener.mEntriesReceived
);
assertThat(listener.mEntriesReceived).containsExactly(
mEntrySet.get(0),
mEntrySet.get(1),
mEntrySet.get(2)
).inOrder(); // Checking out-of-order input to validate sorted output
// THEN the final list is in order
verifyBuiltList(
@@ -1282,13 +1361,11 @@ public class ShadeListBuilderTest extends SysuiTestCase {
dispatchBuild();
// THEN all the new notifs are passed to the listener
assertEquals(
asList(
mEntrySet.get(0),
mEntrySet.get(1),
mEntrySet.get(2)),
listener.mEntriesReceived
);
assertThat(listener.mEntriesReceived).containsExactly(
mEntrySet.get(0),
mEntrySet.get(1),
mEntrySet.get(2)
).inOrder();
}
@Test