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

Commit 29c2a246 authored by Ioana Alexandru's avatar Ioana Alexandru
Browse files

Bind silent section clear listener in the viewbinder.

This finally allows us to decouple the notifstats from nsslc, to keep
the source of truth in the repository/interactor.

I also removed the shouldHideParent method because it was making things
harder to follow, since the whole "parent" idea doesn't really fit for
the section header anyway. Plus, it was calling into the controller for
every single child, now we're only calling it for the silent section
(or not at all if the refactor flag is enabled). Didn't add any real
behavior changes, just folded the logic into getVisibleViewsToAnimateAway.

Bug: 293167744
Test: NotificationStackScrollLayoutTest, NotificationListViewModelTest,
ActiveNotificationsInteractorTest
Flag: ACONFIG com.systemui.notifications_footer_view_refactor STAGING

Change-Id: I0224cd0cef4326da50061e39094bbbe57fc49032
parent 890c64d2
Loading
Loading
Loading
Loading
+237 −0
Original line number Diff line number Diff line
@@ -14,44 +14,38 @@
 * limitations under the License.
 */

@file:OptIn(ExperimentalCoroutinesApi::class)

package com.android.systemui.statusbar.notification.domain.interactor

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysUITestComponent
import com.android.systemui.SysUITestModule
import com.android.systemui.SysuiTestCase
import com.android.systemui.collectLastValue
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.runCurrent
import com.android.systemui.runTest
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.notification.collection.render.NotifStats
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import dagger.BindsInstance
import dagger.Component
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
class ActiveNotificationsInteractorTest : SysuiTestCase() {
    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope
    private val activeNotificationListRepository = kosmos.activeNotificationListRepository

    @Component(modules = [SysUITestModule::class])
    @SysUISingleton
    interface TestComponent : SysUITestComponent<ActiveNotificationsInteractor> {
        val activeNotificationListRepository: ActiveNotificationListRepository

        @Component.Factory
        interface Factory {
            fun create(@BindsInstance test: SysuiTestCase): TestComponent
        }
    }

    private val testComponent: TestComponent =
        DaggerActiveNotificationsInteractorTest_TestComponent.factory().create(test = this)
    private val underTest = kosmos.activeNotificationsInteractor

    @Test
    fun testAllNotificationsCount() =
        testComponent.runTest {
        testScope.runTest {
            val count by collectLastValue(underTest.allNotificationsCount)

            activeNotificationListRepository.setActiveNotifs(5)
@@ -62,8 +56,8 @@ class ActiveNotificationsInteractorTest : SysuiTestCase() {
        }

    @Test
    fun testAreAnyNotificationsPresent_isTrue() =
        testComponent.runTest {
    fun areAnyNotificationsPresent_isTrue() =
        testScope.runTest {
            val areAnyNotificationsPresent by collectLastValue(underTest.areAnyNotificationsPresent)

            activeNotificationListRepository.setActiveNotifs(2)
@@ -74,8 +68,8 @@ class ActiveNotificationsInteractorTest : SysuiTestCase() {
        }

    @Test
    fun testAreAnyNotificationsPresent_isFalse() =
        testComponent.runTest {
    fun areAnyNotificationsPresent_isFalse() =
        testScope.runTest {
            val areAnyNotificationsPresent by collectLastValue(underTest.areAnyNotificationsPresent)

            activeNotificationListRepository.setActiveNotifs(0)
@@ -87,7 +81,7 @@ class ActiveNotificationsInteractorTest : SysuiTestCase() {

    @Test
    fun testActiveNotificationRanks_sizeMatches() {
        testComponent.runTest {
        testScope.runTest {
            val activeNotificationRanks by collectLastValue(underTest.activeNotificationRanks)

            activeNotificationListRepository.setActiveNotifs(5)
@@ -98,8 +92,8 @@ class ActiveNotificationsInteractorTest : SysuiTestCase() {
    }

    @Test
    fun testHasClearableNotifications_whenHasClearableAlertingNotifs() =
        testComponent.runTest {
    fun clearableNotifications_whenHasClearableAlertingNotifs() =
        testScope.runTest {
            val hasClearable by collectLastValue(underTest.hasClearableNotifications)

            activeNotificationListRepository.notifStats.value =
@@ -116,8 +110,8 @@ class ActiveNotificationsInteractorTest : SysuiTestCase() {
        }

    @Test
    fun testHasClearableNotifications_whenHasClearableSilentNotifs() =
        testComponent.runTest {
    fun hasClearableNotifications_whenHasClearableSilentNotifs() =
        testScope.runTest {
            val hasClearable by collectLastValue(underTest.hasClearableNotifications)

            activeNotificationListRepository.notifStats.value =
@@ -134,20 +128,110 @@ class ActiveNotificationsInteractorTest : SysuiTestCase() {
        }

    @Test
    fun testHasClearableNotifications_whenHasNoClearableNotifs() =
        testComponent.runTest {
    fun testHasClearableNotifications_whenHasNoNotifs() =
        testScope.runTest {
            val hasClearable by collectLastValue(underTest.hasClearableNotifications)

            activeNotificationListRepository.notifStats.value =
                NotifStats(
                    numActiveNotifs = 0,
                    hasNonClearableAlertingNotifs = false,
                    hasClearableAlertingNotifs = false,
                    hasNonClearableSilentNotifs = false,
                    hasClearableSilentNotifs = false,
                )
            runCurrent()

            assertThat(hasClearable).isFalse()
        }

    @Test
    fun hasClearableAlertingNotifications_whenHasClearableSilentNotifs() =
        testScope.runTest {
            val hasClearable by collectLastValue(underTest.hasClearableAlertingNotifications)

            activeNotificationListRepository.notifStats.value =
                NotifStats(
                    numActiveNotifs = 2,
                    hasNonClearableAlertingNotifs = false,
                    hasClearableAlertingNotifs = false,
                    hasNonClearableSilentNotifs = false,
                    hasClearableSilentNotifs = true,
                )
            runCurrent()

            assertThat(hasClearable).isFalse()
        }

    @Test
    fun hasClearableAlertingNotifications_whenHasNoClearableNotifs() =
        testScope.runTest {
            val hasClearable by collectLastValue(underTest.hasClearableAlertingNotifications)

            activeNotificationListRepository.notifStats.value =
                NotifStats(
                    numActiveNotifs = 2,
                    hasNonClearableAlertingNotifs = true,
                    hasClearableAlertingNotifs = false,
                    hasNonClearableSilentNotifs = true,
                    hasClearableSilentNotifs = false,
                )
            runCurrent()

            assertThat(hasClearable).isFalse()
        }

    @Test
    fun hasClearableAlertingNotifications_whenHasAlertingNotifs() =
        testScope.runTest {
            val hasClearable by collectLastValue(underTest.hasClearableAlertingNotifications)

            activeNotificationListRepository.notifStats.value =
                NotifStats(
                    numActiveNotifs = 2,
                    hasNonClearableAlertingNotifs = false,
                    hasClearableAlertingNotifs = true,
                    hasNonClearableSilentNotifs = false,
                    hasClearableSilentNotifs = false,
                )
            runCurrent()

            assertThat(hasClearable).isTrue()
        }

    @Test
    fun hasNonClearableSilentNotifications_whenHasNonClearableSilentNotifs() =
        testScope.runTest {
            val hasNonClearable by collectLastValue(underTest.hasNonClearableSilentNotifications)

            activeNotificationListRepository.notifStats.value =
                NotifStats(
                    numActiveNotifs = 2,
                    hasNonClearableAlertingNotifs = false,
                    hasClearableAlertingNotifs = false,
                    hasNonClearableSilentNotifs = true,
                    hasClearableSilentNotifs = false,
                )
            runCurrent()

            assertThat(hasNonClearable).isTrue()
        }

    @Test
    fun testHasNonClearableSilentNotifications_whenHasClearableSilentNotifs() =
        testScope.runTest {
            val hasNonClearable by collectLastValue(underTest.hasNonClearableSilentNotifications)

            activeNotificationListRepository.notifStats.value =
                NotifStats(
                    numActiveNotifs = 2,
                    hasNonClearableAlertingNotifs = false,
                    hasClearableAlertingNotifs = false,
                    hasNonClearableSilentNotifs = false,
                    hasClearableSilentNotifs = true,
                )
            runCurrent()

            assertThat(hasNonClearable).isFalse()
        }
}
+2 −3
Original line number Diff line number Diff line
@@ -55,10 +55,9 @@ internal constructor(
            val notifStats = calculateNotifStats(entries)
            if (FooterViewRefactor.isEnabled) {
                activeNotificationsInteractor.setNotifStats(notifStats)
            }
            // TODO(b/293167744): This shouldn't be done if the footer flag is on, once the silent
            //  section clear action is handled in the new stack.
            } else {
                controller.setNotifStats(notifStats)
            }
            if (NotificationIconContainerRefactor.isEnabled || FooterViewRefactor.isEnabled) {
                renderListInteractor.setRenderedList(entries)
            }
+12 −0
Original line number Diff line number Diff line
@@ -102,6 +102,18 @@ constructor(
            .distinctUntilChanged()
            .flowOn(backgroundDispatcher)

    val hasClearableAlertingNotifications: Flow<Boolean> =
        repository.notifStats
            .map { it.hasClearableAlertingNotifs }
            .distinctUntilChanged()
            .flowOn(backgroundDispatcher)

    val hasNonClearableSilentNotifications: Flow<Boolean> =
        repository.notifStats
            .map { it.hasNonClearableSilentNotifs }
            .distinctUntilChanged()
            .flowOn(backgroundDispatcher)

    fun setNotifStats(notifStats: NotifStats) {
        repository.notifStats.value = notifStats
    }
+35 −25
Original line number Diff line number Diff line
@@ -5365,23 +5365,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
                && (!hasClipBounds || mTmpRect.height() > 0);
    }

    private boolean shouldHideParent(View view, @SelectedRows int selection) {
        final boolean silentSectionWillBeGone =
                !mController.hasNotifications(ROWS_GENTLE, false /* clearable */);

        // The only SectionHeaderView we have is the silent section header.
        if (view instanceof SectionHeaderView && silentSectionWillBeGone) {
            return true;
        }
        if (view instanceof ExpandableNotificationRow row) {
            if (isVisible(row) && includeChildInClearAll(row, selection)) {
                return true;
            }
        }
        return false;
    }

    private boolean isChildrenVisible(ExpandableNotificationRow parent) {
    /** Whether the group is expanded to show the child notifications, and they are visible. */
    private boolean areChildrenVisible(ExpandableNotificationRow parent) {
        List<ExpandableNotificationRow> children = parent.getAttachedChildren();
        return isVisible(parent)
                && children != null
@@ -5389,18 +5374,27 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
    }

    // Similar to #getRowsToDismissInBackend, but filters for visible views.
    private ArrayList<View> getVisibleViewsToAnimateAway(@SelectedRows int selection) {
    private ArrayList<View> getVisibleViewsToAnimateAway(@SelectedRows int selection,
            boolean hideSilentSection) {
        final int viewCount = getChildCount();
        final ArrayList<View> viewsToHide = new ArrayList<>(viewCount);

        for (int i = 0; i < viewCount; i++) {
            final View view = getChildAt(i);

            if (shouldHideParent(view, selection)) {
            if (view instanceof SectionHeaderView) {
                // The only SectionHeaderView we have is the silent section header.
                if (hideSilentSection) {
                    viewsToHide.add(view);
                }
            }

            if (view instanceof ExpandableNotificationRow parent) {
                if (isChildrenVisible(parent)) {
                if (isVisible(parent) && includeChildInClearAll(parent, selection)) {
                    viewsToHide.add(parent);
                }

                if (areChildrenVisible(parent)) {
                    for (ExpandableNotificationRow child : parent.getAttachedChildren()) {
                        if (isVisible(child) && includeChildInClearAll(child, selection)) {
                            viewsToHide.add(child);
@@ -5438,17 +5432,33 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
    }

    /** Clear all clearable notifications when the user requests it. */
    public void clearAllNotifications() {
        clearNotifications(ROWS_ALL, /* closeShade = */ true);
    public void clearAllNotifications(boolean hideSilentSection) {
        clearNotifications(ROWS_ALL, /* closeShade = */ true, hideSilentSection);
    }

    /** Clear all clearable silent notifications when the user requests it. */
    public void clearSilentNotifications(boolean closeShade,
            boolean hideSilentSection) {
        clearNotifications(ROWS_GENTLE, closeShade, hideSilentSection);
    }

    /** Legacy version of clearNotifications below. Uses the old data source for notif stats. */
    void clearNotifications(@SelectedRows int selection, boolean closeShade) {
        FooterViewRefactor.assertInLegacyMode();
        final boolean hideSilentSection = !mController.hasNotifications(
                ROWS_GENTLE, false /* clearable */);
        clearNotifications(selection, closeShade, hideSilentSection);
    }

    /**
     * Collects a list of visible rows, and animates them away in a staggered fashion as if they
     * were dismissed. Notifications are dismissed in the backend via onClearAllAnimationsEnd.
     */
    void clearNotifications(@SelectedRows int selection, boolean closeShade) {
    void clearNotifications(@SelectedRows int selection, boolean closeShade,
            boolean hideSilentSection) {
        // Animate-swipe all dismissable notifications, then animate the shade closed
        final ArrayList<View> viewsToAnimateAway = getVisibleViewsToAnimateAway(selection);
        final ArrayList<View> viewsToAnimateAway = getVisibleViewsToAnimateAway(selection,
                hideSilentSection);
        final ArrayList<ExpandableNotificationRow> rowsToDismissInBackend =
                getRowsToDismissInBackend(selection);
        if (mClearAllListener != null) {
+7 −7
Original line number Diff line number Diff line
@@ -916,7 +916,9 @@ public class NotificationStackScrollLayoutController implements Dumpable {
            mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
        }
        mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
        if (!FooterViewRefactor.isEnabled()) {
            mSilentHeaderController.setOnClearSectionClickListener(v -> clearSilentNotifications());
        }

        mGroupExpansionManager.registerGroupExpansionChangeListener(
                (changedRow, expanded) -> mView.onGroupExpandChanged(changedRow, expanded));
@@ -1518,14 +1520,12 @@ public class NotificationStackScrollLayoutController implements Dumpable {
     * Return whether there are any clearable notifications
     */
    public boolean hasActiveClearableNotifications(@SelectedRows int selection) {
        // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the silent
        //  section clear action in the new stack.
        FooterViewRefactor.assertInLegacyMode();
        return hasNotifications(selection, true /* clearable */);
    }

    public boolean hasNotifications(@SelectedRows int selection, boolean isClearable) {
        // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the silent
        //  section clear action in the new stack.
        FooterViewRefactor.assertInLegacyMode();
        boolean hasAlertingMatchingClearable = isClearable
                ? mNotifStats.getHasClearableAlertingNotifs()
                : mNotifStats.getHasNonClearableAlertingNotifs();
@@ -1676,6 +1676,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
    }

    public void clearSilentNotifications() {
        FooterViewRefactor.assertInLegacyMode();
        // Leave the shade open if there will be other notifs left over to clear
        final boolean closeShade = !hasActiveClearableNotifications(ROWS_HIGH_PRIORITY);
        mView.clearNotifications(ROWS_GENTLE, closeShade);
@@ -2146,8 +2147,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
    private class NotifStackControllerImpl implements NotifStackController {
        @Override
        public void setNotifStats(@NonNull NotifStats notifStats) {
            // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the silent
            //  section clear action in the new stack.
            FooterViewRefactor.assertInLegacyMode();
            mNotifStats = notifStats;

            if (!FooterViewRefactor.isEnabled()) {
Loading