Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 35956b45 authored by Steve Elliott's avatar Steve Elliott
Browse files

Update section header placement logic

This new logic accounts for the current position of the header when
determining the new placement in the NSSL.

Fixes: 150166074
Test: atest, manual
Change-Id: I699e5dd93ae1d13fc56aa5f411825a357199ff88
parent 7b27da1d
Loading
Loading
Loading
Loading
+124 −69
Original line number Original line Diff line number Diff line
@@ -16,6 +16,8 @@


package com.android.systemui.statusbar.notification.stack;
package com.android.systemui.statusbar.notification.stack;


import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;

import static java.lang.annotation.RetentionPolicy.SOURCE;
import static java.lang.annotation.RetentionPolicy.SOURCE;


import android.annotation.IntDef;
import android.annotation.IntDef;
@@ -100,14 +102,11 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section
    private boolean mInitialized = false;
    private boolean mInitialized = false;


    private SectionHeaderView mGentleHeader;
    private SectionHeaderView mGentleHeader;
    private boolean mGentleHeaderVisible;
    @Nullable private View.OnClickListener mOnClearGentleNotifsClickListener;
    @Nullable private View.OnClickListener mOnClearGentleNotifsClickListener;


    private SectionHeaderView mAlertingHeader;
    private SectionHeaderView mAlertingHeader;
    private boolean mAlertingHeaderVisible;


    private PeopleHubView mPeopleHubView;
    private PeopleHubView mPeopleHubView;
    private boolean mPeopleHeaderVisible;
    private boolean mPeopleHubVisible = false;
    private boolean mPeopleHubVisible = false;
    @Nullable private Subscription mPeopleHubSubscription;
    @Nullable private Subscription mPeopleHubSubscription;


@@ -231,88 +230,135 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section
            return;
            return;
        }
        }


        // The overall strategy here is to iterate over the current children of mParent, looking
        // for where the sections headers are currently positioned, and where each section begins.
        // Then, once we find the start of a new section, we track that position as the "target" for
        // the section header, adjusted for the case where existing headers are in front of that
        // target, but won't be once they are moved / removed after the pass has completed.

        final boolean showHeaders = mStatusBarStateController.getState() != StatusBarState.KEYGUARD;
        final boolean showHeaders = mStatusBarStateController.getState() != StatusBarState.KEYGUARD;
        final boolean usingPeopleFiltering = mSectionsFeatureManager.isFilteringEnabled();
        final boolean usingPeopleFiltering = mSectionsFeatureManager.isFilteringEnabled();


        boolean peopleNotifsPresent = false;
        boolean peopleNotifsPresent = false;

        int currentPeopleHeaderIdx = -1;
        int peopleHeaderTarget = -1;
        int peopleHeaderTarget = -1;
        int currentAlertingHeaderIdx = -1;
        int alertingHeaderTarget = -1;
        int alertingHeaderTarget = -1;
        int currentGentleHeaderIdx = -1;
        int gentleHeaderTarget = -1;
        int gentleHeaderTarget = -1;


        int viewCount = 0;
        int lastNotifIndex = 0;


        if (showHeaders) {
        final int childCount = mParent.getChildCount();
        final int childCount = mParent.getChildCount();
        for (int i = 0; i < childCount; i++) {
        for (int i = 0; i < childCount; i++) {
            View child = mParent.getChildAt(i);
            View child = mParent.getChildAt(i);
                if (child.getVisibility() == View.GONE

                        || !(child instanceof ExpandableNotificationRow)) {
            // Track the existing positions of the headers
            if (child == mPeopleHubView) {
                currentPeopleHeaderIdx = i;
                continue;
            }
            if (child == mAlertingHeader) {
                currentAlertingHeaderIdx = i;
                continue;
            }
            if (child == mGentleHeader) {
                currentGentleHeaderIdx = i;
                continue;
                continue;
            }
            }

            if (!(child instanceof ExpandableNotificationRow)) {
                continue;
            }
            lastNotifIndex = i;
            ExpandableNotificationRow row = (ExpandableNotificationRow) child;
            ExpandableNotificationRow row = (ExpandableNotificationRow) child;
            // Once we enter a new section, calculate the target position for the header.
            switch (row.getEntry().getBucket()) {
            switch (row.getEntry().getBucket()) {
                case BUCKET_HEADS_UP:
                    break;
                case BUCKET_PEOPLE:
                case BUCKET_PEOPLE:
                        if (peopleHeaderTarget == -1) {
                    peopleNotifsPresent = true;
                    peopleNotifsPresent = true;
                            peopleHeaderTarget = viewCount;
                    if (showHeaders && peopleHeaderTarget == -1) {
                            viewCount++;
                        peopleHeaderTarget = i;
                        // Offset the target if there are other headers before this that will be
                        // moved.
                        if (currentPeopleHeaderIdx != -1) {
                            peopleHeaderTarget--;
                        }
                        if (currentAlertingHeaderIdx != -1) {
                            peopleHeaderTarget--;
                        }
                        if (currentGentleHeaderIdx != -1) {
                            peopleHeaderTarget--;
                        }
                    }
                    }
                    break;
                    break;
                case BUCKET_ALERTING:
                case BUCKET_ALERTING:
                        if (usingPeopleFiltering && alertingHeaderTarget == -1) {
                    if (showHeaders && usingPeopleFiltering && alertingHeaderTarget == -1) {
                            alertingHeaderTarget = viewCount;
                        alertingHeaderTarget = i;
                            viewCount++;
                        // Offset the target if there are other headers before this that will be
                        // moved.
                        if (currentAlertingHeaderIdx != -1) {
                            alertingHeaderTarget--;
                        }
                        if (currentGentleHeaderIdx != -1) {
                            alertingHeaderTarget--;
                        }
                    }
                    }
                    break;
                    break;
                case BUCKET_SILENT:
                case BUCKET_SILENT:
                        if (gentleHeaderTarget == -1) {
                    if (showHeaders && gentleHeaderTarget == -1) {
                            gentleHeaderTarget = viewCount;
                        gentleHeaderTarget = i;
                            viewCount++;
                        // Offset the target if there are other headers before this that will be
                        // moved.
                        if (currentGentleHeaderIdx != -1) {
                            gentleHeaderTarget--;
                        }
                    }
                    }
                    break;
                    break;
                default:
                    throw new IllegalStateException("Cannot find section bucket for view");
            }
            }
                viewCount++;
        }
        }
            if (usingPeopleFiltering && mPeopleHubVisible && peopleHeaderTarget == -1) {
        if (showHeaders && usingPeopleFiltering && mPeopleHubVisible && peopleHeaderTarget == -1) {
            // Insert the people header even if there are no people visible, in order to show
            // Insert the people header even if there are no people visible, in order to show
            // the hub. Put it directly above the next header.
            // the hub. Put it directly above the next header.
            if (alertingHeaderTarget != -1) {
            if (alertingHeaderTarget != -1) {
                peopleHeaderTarget = alertingHeaderTarget;
                peopleHeaderTarget = alertingHeaderTarget;
                    alertingHeaderTarget++;
                    gentleHeaderTarget++;
            } else if (gentleHeaderTarget != -1) {
            } else if (gentleHeaderTarget != -1) {
                peopleHeaderTarget = gentleHeaderTarget;
                peopleHeaderTarget = gentleHeaderTarget;
                    gentleHeaderTarget++;
            } else {
            } else {
                // Put it at the end of the list.
                // Put it at the end of the list.
                    peopleHeaderTarget = viewCount;
                peopleHeaderTarget = lastNotifIndex;
                }
            }
            }
        }
        }


        // Allow swiping the people header if the section is empty
        // Add headers in reverse order to preserve indices
        mPeopleHubView.setCanSwipe(mPeopleHubVisible && !peopleNotifsPresent);
        adjustHeaderVisibilityAndPosition(
                gentleHeaderTarget, mGentleHeader, currentGentleHeaderIdx);
        adjustHeaderVisibilityAndPosition(
                alertingHeaderTarget, mAlertingHeader, currentAlertingHeaderIdx);
        adjustHeaderVisibilityAndPosition(
                peopleHeaderTarget, mPeopleHubView, currentPeopleHeaderIdx);


        mPeopleHeaderVisible = adjustHeaderVisibilityAndPosition(
        // Update headers to reflect state of section contents
                peopleHeaderTarget, mPeopleHubView, mPeopleHeaderVisible);
        mGentleHeader.setAreThereDismissableGentleNotifs(
        mAlertingHeaderVisible = adjustHeaderVisibilityAndPosition(
                mParent.hasActiveClearableNotifications(ROWS_GENTLE));
                alertingHeaderTarget, mAlertingHeader, mAlertingHeaderVisible);
        mPeopleHubView.setCanSwipe(showHeaders && mPeopleHubVisible && !peopleNotifsPresent);
        mGentleHeaderVisible = adjustHeaderVisibilityAndPosition(
        if (peopleHeaderTarget != currentPeopleHeaderIdx) {
                gentleHeaderTarget, mGentleHeader, mGentleHeaderVisible);
            mPeopleHubView.resetTranslation();
        }
    }
    }


    private boolean adjustHeaderVisibilityAndPosition(
    private void adjustHeaderVisibilityAndPosition(
            int targetIndex, StackScrollerDecorView header, boolean isCurrentlyVisible) {
            int targetPosition, StackScrollerDecorView header, int currentPosition) {
        if (targetIndex == -1) {
        if (targetPosition == -1) {
            if (isCurrentlyVisible) {
            if (currentPosition != -1) {
                mParent.removeView(header);
                mParent.removeView(header);
            }
            }
            return false;
        } else {
        } else {
            if (header instanceof SwipeableView) {
            if (currentPosition == -1) {
                ((SwipeableView) header).resetTranslation();
            }
            if (!isCurrentlyVisible) {
                // If the header is animating away, it will still have a parent, so detach it first
                // If the header is animating away, it will still have a parent, so detach it first
                // TODO: We should really cancel the active animations here. This will happen
                // TODO: We should really cancel the active animations here. This will happen
                // automatically when the view's intro animation starts, but it's a fragile link.
                // automatically when the view's intro animation starts, but it's a fragile link.
@@ -321,11 +367,10 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section
                    header.setTransientContainer(null);
                    header.setTransientContainer(null);
                }
                }
                header.setContentVisible(true);
                header.setContentVisible(true);
                mParent.addView(header, targetIndex);
                mParent.addView(header, targetPosition);
            } else if (mParent.indexOfChild(header) != targetIndex) {
            } else {
                mParent.changeViewPosition(header, targetIndex);
                mParent.changeViewPosition(header, targetPosition);
            }
            }
            return true;
        }
        }
    }
    }


@@ -400,10 +445,20 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section




    @VisibleForTesting
    @VisibleForTesting
    SectionHeaderView getGentleHeaderView() {
    ExpandableView getGentleHeaderView() {
        return mGentleHeader;
        return mGentleHeader;
    }
    }


    @VisibleForTesting
    ExpandableView getAlertingHeaderView() {
        return mAlertingHeader;
    }

    @VisibleForTesting
    ExpandableView getPeopleHeaderView() {
        return mPeopleHubView;
    }

    private final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
    private final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
        @Override
        @Override
        public void onLocaleListChanged() {
        public void onLocaleListChanged() {
+129 −47
Original line number Original line Diff line number Diff line
@@ -19,6 +19,8 @@ package com.android.systemui.statusbar.notification.stack;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;


import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING;
import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING;
import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_HEADS_UP;
import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE;
import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;


import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.any;
@@ -107,7 +109,7 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
    @Test
    @Test
    public void testInsertHeader() {
    public void testInsertHeader() {
        // GIVEN a stack with HI and LO rows but no section headers
        // GIVEN a stack with HI and LO rows but no section headers
        setStackState(ChildType.HIPRI, ChildType.HIPRI, ChildType.HIPRI, ChildType.LOPRI);
        setStackState(ChildType.ALERTING, ChildType.ALERTING, ChildType.ALERTING, ChildType.GENTLE);


        // WHEN we update the section headers
        // WHEN we update the section headers
        mSectionsManager.updateSectionBoundaries();
        mSectionsManager.updateSectionBoundaries();
@@ -119,11 +121,15 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
    @Test
    @Test
    public void testRemoveHeader() {
    public void testRemoveHeader() {
        // GIVEN a stack that originally had a header between the HI and LO sections
        // GIVEN a stack that originally had a header between the HI and LO sections
        setStackState(ChildType.HIPRI, ChildType.HIPRI, ChildType.LOPRI);
        setStackState(ChildType.ALERTING, ChildType.ALERTING, ChildType.GENTLE);
        mSectionsManager.updateSectionBoundaries();
        mSectionsManager.updateSectionBoundaries();


        // WHEN the last LO row is replaced with a HI row
        // WHEN the last LO row is replaced with a HI row
        setStackState(ChildType.HIPRI, ChildType.HIPRI, ChildType.HEADER, ChildType.HIPRI);
        setStackState(
                ChildType.ALERTING,
                ChildType.ALERTING,
                ChildType.GENTLE_HEADER,
                ChildType.ALERTING);
        clearInvocations(mNssl);
        clearInvocations(mNssl);
        mSectionsManager.updateSectionBoundaries();
        mSectionsManager.updateSectionBoundaries();


@@ -134,7 +140,7 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
    @Test
    @Test
    public void testDoNothingIfHeaderAlreadyRemoved() {
    public void testDoNothingIfHeaderAlreadyRemoved() {
        // GIVEN a stack with only HI rows
        // GIVEN a stack with only HI rows
        setStackState(ChildType.HIPRI, ChildType.HIPRI, ChildType.HIPRI);
        setStackState(ChildType.ALERTING, ChildType.ALERTING, ChildType.ALERTING);


        // WHEN we update the sections headers
        // WHEN we update the sections headers
        mSectionsManager.updateSectionBoundaries();
        mSectionsManager.updateSectionBoundaries();
@@ -147,19 +153,19 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
    public void testMoveHeaderForward() {
    public void testMoveHeaderForward() {
        // GIVEN a stack that originally had a header between the HI and LO sections
        // GIVEN a stack that originally had a header between the HI and LO sections
        setStackState(
        setStackState(
                ChildType.HIPRI,
                ChildType.ALERTING,
                ChildType.HIPRI,
                ChildType.ALERTING,
                ChildType.HIPRI,
                ChildType.ALERTING,
                ChildType.LOPRI);
                ChildType.GENTLE);
        mSectionsManager.updateSectionBoundaries();
        mSectionsManager.updateSectionBoundaries();


        // WHEN the LO section moves forward
        // WHEN the LO section moves forward
        setStackState(
        setStackState(
                ChildType.HIPRI,
                ChildType.ALERTING,
                ChildType.HIPRI,
                ChildType.ALERTING,
                ChildType.LOPRI,
                ChildType.GENTLE,
                ChildType.HEADER,
                ChildType.GENTLE_HEADER,
                ChildType.LOPRI);
                ChildType.GENTLE);
        mSectionsManager.updateSectionBoundaries();
        mSectionsManager.updateSectionBoundaries();


        // THEN the LO section header is also moved forward
        // THEN the LO section header is also moved forward
@@ -170,19 +176,19 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
    public void testMoveHeaderBackward() {
    public void testMoveHeaderBackward() {
        // GIVEN a stack that originally had a header between the HI and LO sections
        // GIVEN a stack that originally had a header between the HI and LO sections
        setStackState(
        setStackState(
                ChildType.HIPRI,
                ChildType.ALERTING,
                ChildType.LOPRI,
                ChildType.GENTLE,
                ChildType.LOPRI,
                ChildType.GENTLE,
                ChildType.LOPRI);
                ChildType.GENTLE);
        mSectionsManager.updateSectionBoundaries();
        mSectionsManager.updateSectionBoundaries();


        // WHEN the LO section moves backward
        // WHEN the LO section moves backward
        setStackState(
        setStackState(
                ChildType.HIPRI,
                ChildType.ALERTING,
                ChildType.HEADER,
                ChildType.GENTLE_HEADER,
                ChildType.HIPRI,
                ChildType.ALERTING,
                ChildType.HIPRI,
                ChildType.ALERTING,
                ChildType.LOPRI);
                ChildType.GENTLE);
        mSectionsManager.updateSectionBoundaries();
        mSectionsManager.updateSectionBoundaries();


        // THEN the LO section header is also moved backward (with appropriate index shifting)
        // THEN the LO section header is also moved backward (with appropriate index shifting)
@@ -193,14 +199,14 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
    public void testHeaderRemovedFromTransientParent() {
    public void testHeaderRemovedFromTransientParent() {
        // GIVEN a stack where the header is animating away
        // GIVEN a stack where the header is animating away
        setStackState(
        setStackState(
                ChildType.HIPRI,
                ChildType.ALERTING,
                ChildType.LOPRI,
                ChildType.GENTLE,
                ChildType.LOPRI,
                ChildType.GENTLE,
                ChildType.LOPRI);
                ChildType.GENTLE);
        mSectionsManager.updateSectionBoundaries();
        mSectionsManager.updateSectionBoundaries();
        setStackState(
        setStackState(
                ChildType.HIPRI,
                ChildType.ALERTING,
                ChildType.HEADER);
                ChildType.GENTLE_HEADER);
        mSectionsManager.updateSectionBoundaries();
        mSectionsManager.updateSectionBoundaries();
        clearInvocations(mNssl);
        clearInvocations(mNssl);


@@ -209,8 +215,8 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {


        // WHEN the LO section reappears
        // WHEN the LO section reappears
        setStackState(
        setStackState(
                ChildType.HIPRI,
                ChildType.ALERTING,
                ChildType.LOPRI);
                ChildType.GENTLE);
        mSectionsManager.updateSectionBoundaries();
        mSectionsManager.updateSectionBoundaries();


        // THEN the header is first removed from the transient parent before being added to the
        // THEN the header is first removed from the transient parent before being added to the
@@ -223,7 +229,7 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
    public void testHeaderNotShownOnLockscreen() {
    public void testHeaderNotShownOnLockscreen() {
        // GIVEN a stack of HI and LO notifs on the lockscreen
        // GIVEN a stack of HI and LO notifs on the lockscreen
        when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
        when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
        setStackState(ChildType.HIPRI, ChildType.HIPRI, ChildType.HIPRI, ChildType.LOPRI);
        setStackState(ChildType.ALERTING, ChildType.ALERTING, ChildType.ALERTING, ChildType.GENTLE);


        // WHEN we update the section headers
        // WHEN we update the section headers
        mSectionsManager.updateSectionBoundaries();
        mSectionsManager.updateSectionBoundaries();
@@ -236,7 +242,7 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
    public void testHeaderShownWhenEnterLockscreen() {
    public void testHeaderShownWhenEnterLockscreen() {
        // GIVEN a stack of HI and LO notifs on the lockscreen
        // GIVEN a stack of HI and LO notifs on the lockscreen
        when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
        when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
        setStackState(ChildType.HIPRI, ChildType.HIPRI, ChildType.HIPRI, ChildType.LOPRI);
        setStackState(ChildType.ALERTING, ChildType.ALERTING, ChildType.ALERTING, ChildType.GENTLE);
        mSectionsManager.updateSectionBoundaries();
        mSectionsManager.updateSectionBoundaries();


        // WHEN we unlock
        // WHEN we unlock
@@ -250,37 +256,104 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
    @Test
    @Test
    public void testHeaderHiddenWhenEnterLockscreen() {
    public void testHeaderHiddenWhenEnterLockscreen() {
        // GIVEN a stack of HI and LO notifs on the shade
        // GIVEN a stack of HI and LO notifs on the shade
        when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE_LOCKED);
        setStackState(ChildType.ALERTING, ChildType.GENTLE_HEADER, ChildType.GENTLE);
        setStackState(ChildType.HIPRI, ChildType.HIPRI, ChildType.HIPRI, ChildType.LOPRI);
        mSectionsManager.updateSectionBoundaries();


        // WHEN we go back to the keyguard
        // WHEN we go back to the keyguard
        when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
        when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
        mSectionsManager.updateSectionBoundaries();
        mSectionsManager.updateSectionBoundaries();


        // Then the section header is removed
        // Then the section header is removed
        verify(mNssl).removeView(eq(mSectionsManager.getGentleHeaderView()));
        verify(mNssl).removeView(mSectionsManager.getGentleHeaderView());
    }

    @Test
    public void testPeopleFiltering_addHeadersFromShowingOnlyGentle() {
        enablePeopleFiltering();

        setStackState(
                ChildType.GENTLE_HEADER,
                ChildType.PERSON,
                ChildType.ALERTING,
                ChildType.GENTLE);
        mSectionsManager.updateSectionBoundaries();

        verify(mNssl).changeViewPosition(mSectionsManager.getGentleHeaderView(), 2);
        verify(mNssl).addView(mSectionsManager.getAlertingHeaderView(), 1);
        verify(mNssl).addView(mSectionsManager.getPeopleHeaderView(), 0);
    }

    @Test
    public void testPeopleFiltering_addAllHeaders() {
        enablePeopleFiltering();

        setStackState(
                ChildType.PERSON,
                ChildType.ALERTING,
                ChildType.GENTLE);
        mSectionsManager.updateSectionBoundaries();

        verify(mNssl).addView(mSectionsManager.getGentleHeaderView(), 2);
        verify(mNssl).addView(mSectionsManager.getAlertingHeaderView(), 1);
        verify(mNssl).addView(mSectionsManager.getPeopleHeaderView(), 0);
    }

    @Test
    public void testPeopleFiltering_moveAllHeaders() {
        enablePeopleFiltering();

        setStackState(
                ChildType.PEOPLE_HEADER,
                ChildType.ALERTING_HEADER,
                ChildType.GENTLE_HEADER,
                ChildType.PERSON,
                ChildType.ALERTING,
                ChildType.GENTLE);
        mSectionsManager.updateSectionBoundaries();

        verify(mNssl).changeViewPosition(mSectionsManager.getGentleHeaderView(), 4);
        verify(mNssl).changeViewPosition(mSectionsManager.getAlertingHeaderView(), 2);
        verify(mNssl).changeViewPosition(mSectionsManager.getPeopleHeaderView(), 0);
    }
    }


    private enum ChildType { HEADER, HIPRI, LOPRI }
    private void enablePeopleFiltering() {
        when(mSectionsFeatureManager.isFilteringEnabled()).thenReturn(true);
        when(mSectionsFeatureManager.getNumberOfBuckets()).thenReturn(4);
    }

    private enum ChildType {
        PEOPLE_HEADER, ALERTING_HEADER, GENTLE_HEADER, HEADS_UP, PERSON, ALERTING, GENTLE, OTHER
    }


    private void setStackState(ChildType... children) {
    private void setStackState(ChildType... children) {
        when(mNssl.getChildCount()).thenReturn(children.length);
        when(mNssl.getChildCount()).thenReturn(children.length);
        for (int i = 0; i < children.length; i++) {
        for (int i = 0; i < children.length; i++) {
            View child;
            View child;
            switch (children[i]) {
            switch (children[i]) {
                case HEADER:
                case PEOPLE_HEADER:
                    child = mSectionsManager.getPeopleHeaderView();
                    break;
                case ALERTING_HEADER:
                    child = mSectionsManager.getAlertingHeaderView();
                    break;
                case GENTLE_HEADER:
                    child = mSectionsManager.getGentleHeaderView();
                    child = mSectionsManager.getGentleHeaderView();
                    break;
                    break;
                case HIPRI:
                case HEADS_UP:
                case LOPRI:
                    child = mockNotification(BUCKET_HEADS_UP);
                    ExpandableNotificationRow notifRow = mock(ExpandableNotificationRow.class,
                    break;
                            RETURNS_DEEP_STUBS);
                case PERSON:
                    when(notifRow.getVisibility()).thenReturn(View.VISIBLE);
                    child = mockNotification(BUCKET_PEOPLE);
                    when(notifRow.getEntry().getBucket()).thenReturn(
                    break;
                            children[i] == ChildType.HIPRI ? BUCKET_ALERTING : BUCKET_SILENT);
                case ALERTING:
                    when(notifRow.getParent()).thenReturn(mNssl);
                    child = mockNotification(BUCKET_ALERTING);
                    child = notifRow;
                    break;
                case GENTLE:
                    child = mockNotification(BUCKET_SILENT);
                    break;
                case OTHER:
                    child = mock(View.class);
                    when(child.getVisibility()).thenReturn(View.VISIBLE);
                    when(child.getParent()).thenReturn(mNssl);
                    break;
                    break;
                default:
                default:
                    throw new RuntimeException("Unknown ChildType: " + children[i]);
                    throw new RuntimeException("Unknown ChildType: " + children[i]);
@@ -289,4 +362,13 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
            when(mNssl.indexOfChild(child)).thenReturn(i);
            when(mNssl.indexOfChild(child)).thenReturn(i);
        }
        }
    }
    }

    private View mockNotification(int bucket) {
        ExpandableNotificationRow notifRow = mock(ExpandableNotificationRow.class,
                RETURNS_DEEP_STUBS);
        when(notifRow.getVisibility()).thenReturn(View.VISIBLE);
        when(notifRow.getEntry().getBucket()).thenReturn(bucket);
        when(notifRow.getParent()).thenReturn(mNssl);
        return notifRow;
    }
}
}