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

Commit 78262c81 authored by Ioana Alexandru's avatar Ioana Alexandru Committed by Android (Google) Code Review
Browse files

Merge changes I3f3ac808,Ifc38554e,I0224cd0c into main

* changes:
  Check StatusBarState in isImportantForAccessibility.
  Check StatusBarState in shouldShowEmptyShadeView.
  Bind silent section clear listener in the viewbinder.
parents 14eb3248 9c728245
Loading
Loading
Loading
Loading
+237 −0
Original line number Original line Diff line number Diff line
@@ -14,44 +14,38 @@
 * limitations under the License.
 * limitations under the License.
 */
 */


@file:OptIn(ExperimentalCoroutinesApi::class)

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


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


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


    @Component(modules = [SysUITestModule::class])
    private val underTest = kosmos.activeNotificationsInteractor
    @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)


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


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


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


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


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


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


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


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


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


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


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


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


    @Test
    @Test
    fun testHasClearableNotifications_whenHasNoClearableNotifs() =
    fun testHasClearableNotifications_whenHasNoNotifs() =
        testComponent.runTest {
        testScope.runTest {
            val hasClearable by collectLastValue(underTest.hasClearableNotifications)
            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 =
            activeNotificationListRepository.notifStats.value =
                NotifStats(
                NotifStats(
                    numActiveNotifs = 2,
                    numActiveNotifs = 2,
                    hasNonClearableAlertingNotifs = false,
                    hasNonClearableAlertingNotifs = false,
                    hasClearableAlertingNotifs = false,
                    hasClearableAlertingNotifs = false,
                    hasNonClearableSilentNotifs = 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,
                    hasClearableSilentNotifs = false,
                )
                )
            runCurrent()
            runCurrent()


            assertThat(hasClearable).isFalse()
            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 Original line Diff line number Diff line
@@ -55,10 +55,9 @@ internal constructor(
            val notifStats = calculateNotifStats(entries)
            val notifStats = calculateNotifStats(entries)
            if (FooterViewRefactor.isEnabled) {
            if (FooterViewRefactor.isEnabled) {
                activeNotificationsInteractor.setNotifStats(notifStats)
                activeNotificationsInteractor.setNotifStats(notifStats)
            }
            } else {
            // 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.
                controller.setNotifStats(notifStats)
                controller.setNotifStats(notifStats)
            }
            if (NotificationIconContainerRefactor.isEnabled || FooterViewRefactor.isEnabled) {
            if (NotificationIconContainerRefactor.isEnabled || FooterViewRefactor.isEnabled) {
                renderListInteractor.setRenderedList(entries)
                renderListInteractor.setRenderedList(entries)
            }
            }
+12 −0
Original line number Original line Diff line number Diff line
@@ -102,6 +102,18 @@ constructor(
            .distinctUntilChanged()
            .distinctUntilChanged()
            .flowOn(backgroundDispatcher)
            .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) {
    fun setNotifStats(notifStats: NotifStats) {
        repository.notifStats.value = notifStats
        repository.notifStats.value = notifStats
    }
    }
+35 −25
Original line number Original line Diff line number Diff line
@@ -5377,23 +5377,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
                && (!hasClipBounds || mTmpRect.height() > 0);
                && (!hasClipBounds || mTmpRect.height() > 0);
    }
    }


    private boolean shouldHideParent(View view, @SelectedRows int selection) {
    /** Whether the group is expanded to show the child notifications, and they are visible. */
        final boolean silentSectionWillBeGone =
    private boolean areChildrenVisible(ExpandableNotificationRow parent) {
                !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) {
        List<ExpandableNotificationRow> children = parent.getAttachedChildren();
        List<ExpandableNotificationRow> children = parent.getAttachedChildren();
        return isVisible(parent)
        return isVisible(parent)
                && children != null
                && children != null
@@ -5401,18 +5386,27 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
    }
    }


    // Similar to #getRowsToDismissInBackend, but filters for visible views.
    // 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 int viewCount = getChildCount();
        final ArrayList<View> viewsToHide = new ArrayList<>(viewCount);
        final ArrayList<View> viewsToHide = new ArrayList<>(viewCount);


        for (int i = 0; i < viewCount; i++) {
        for (int i = 0; i < viewCount; i++) {
            final View view = getChildAt(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);
                    viewsToHide.add(view);
                }
                }
            }

            if (view instanceof ExpandableNotificationRow parent) {
            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()) {
                    for (ExpandableNotificationRow child : parent.getAttachedChildren()) {
                        if (isVisible(child) && includeChildInClearAll(child, selection)) {
                        if (isVisible(child) && includeChildInClearAll(child, selection)) {
                            viewsToHide.add(child);
                            viewsToHide.add(child);
@@ -5450,17 +5444,33 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
    }
    }


    /** Clear all clearable notifications when the user requests it. */
    /** Clear all clearable notifications when the user requests it. */
    public void clearAllNotifications() {
    public void clearAllNotifications(boolean hideSilentSection) {
        clearNotifications(ROWS_ALL, /* closeShade = */ true);
        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
     * 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.
     * 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
        // 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 =
        final ArrayList<ExpandableNotificationRow> rowsToDismissInBackend =
                getRowsToDismissInBackend(selection);
                getRowsToDismissInBackend(selection);
        if (mClearAllListener != null) {
        if (mClearAllListener != null) {
+7 −7
Original line number Original line Diff line number Diff line
@@ -916,7 +916,9 @@ public class NotificationStackScrollLayoutController implements Dumpable {
            mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
            mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
        }
        }
        mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
        mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
        if (!FooterViewRefactor.isEnabled()) {
            mSilentHeaderController.setOnClearSectionClickListener(v -> clearSilentNotifications());
            mSilentHeaderController.setOnClearSectionClickListener(v -> clearSilentNotifications());
        }


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


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


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


            if (!FooterViewRefactor.isEnabled()) {
            if (!FooterViewRefactor.isEnabled()) {
Loading