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

Commit 7593da44 authored by Julia Tuttle's avatar Julia Tuttle
Browse files

Dismiss HUN group summary if user taps HUN group child

If an app posts a group with a summary and 2+ children and sets
GROUP_ALERT_SUMMARY, System UI will present the summary with the
collapsed children as a single HUN.

If the user expands one of the children and taps it, the animation code
will try to remove the HUN for the child the user tapped. Unfortunately,
since the *summary* was the alerting notification, the entire group HUN
will remain visible.

This can also happen if an app posts a group with a high-priority
summary but default (or lower) priority children.

This change augments the logic in removeHun so that if the notification
itself isn't alerting, but it has a parent that *is* alerting, the
*parent's* HUN will be removed instead.

Bug: 249840069
Test: manual, using Notify.apk
Test: atest 'NotificationLaunchAnimatorControllerTest#testHunIsRemovedAndCallbackIsInvokedOnAlertingParent'
Test: atest TapChildOfHeadsUpSummary
Change-Id: Ia5b8537747def402df518d33f14ed7c8e2198585
parent 83cfc036
Loading
Loading
Loading
Loading
+15 −4
Original line number Diff line number Diff line
@@ -144,13 +144,24 @@ class NotificationLaunchAnimatorController(
        }
    }

    private fun removeHun(animate: Boolean) {
        if (!headsUpManager.isAlerting(notificationKey)) {
            return
    private val headsUpNotificationRow: ExpandableNotificationRow? get() {
        val summaryEntry = notificationEntry.parent?.summary

        return when {
            headsUpManager.isAlerting(notificationKey) -> notification
            summaryEntry == null -> null
            headsUpManager.isAlerting(summaryEntry.key) -> summaryEntry.row
            else -> null
        }
    }

    private fun removeHun(animate: Boolean) {
        val row = headsUpNotificationRow ?: return

        // TODO: b/297247841 - Call on the row we're removing, which may differ from notification.
        HeadsUpUtil.setNeedsHeadsUpDisappearAnimationAfterClick(notification, animate)
        headsUpManager.removeNotification(notificationKey, true /* releaseImmediately */, animate)

        headsUpManager.removeNotification(row.entry.key, true /* releaseImmediately */, animate)
    }

    override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) {
+36 −0
Original line number Diff line number Diff line
package com.android.systemui.statusbar.notification

import android.app.Notification.GROUP_ALERT_SUMMARY
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
@@ -7,20 +8,26 @@ import androidx.test.filters.SmallTest
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.NotificationTestHelper
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
import com.android.systemui.statusbar.policy.HeadsUpUtil
import com.android.systemui.tests.R
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import kotlinx.coroutines.test.TestScope
import org.junit.Assert.assertNotSame
import org.junit.Assert.assertSame
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnit
@@ -117,6 +124,35 @@ class NotificationLaunchAnimatorControllerTest : SysuiTestCase() {
        verify(onFinishAnimationCallback).run()
    }

    @Test
    fun testAlertingSummaryHunRemovedOnNonAlertingChildLaunch() {
        val GROUP_KEY = "test_group_key"

        val summary = NotificationEntryBuilder().setGroup(mContext, GROUP_KEY).setId(0).apply {
            modifyNotification(mContext).setSmallIcon(R.drawable.ic_person)
        }.build()
        assertNotSame(summary.key, notification.entry.key)

        notificationTestHelper.createRow(summary)

        GroupEntryBuilder().setKey(GROUP_KEY).setSummary(summary).addChild(notification.entry)
                .build()
        assertSame(summary, notification.entry.parent?.summary)

        `when`(headsUpManager.isAlerting(notificationKey)).thenReturn(false)
        `when`(headsUpManager.isAlerting(summary.key)).thenReturn(true)

        assertNotSame(GROUP_ALERT_SUMMARY, summary.sbn.notification.groupAlertBehavior)
        assertNotSame(GROUP_ALERT_SUMMARY, notification.entry.sbn.notification.groupAlertBehavior)

        controller.onLaunchAnimationEnd(isExpandingFullyAbove = true)

        verify(headsUpManager).removeNotification(
                summary.key, true /* releaseImmediately */, false /* animate */)
        verify(headsUpManager, never()).removeNotification(
                notification.entry.key, true /* releaseImmediately */, false /* animate */)
    }

    @Test
    fun testNotificationIsExpandingDuringAnimation() {
        controller.onIntentStarted(willAnimate = true)
+23 −12
Original line number Diff line number Diff line
@@ -269,6 +269,10 @@ public class NotificationTestHelper {
        return generateRow(notification, PKG, UID, USER_HANDLE, mDefaultInflationFlags);
    }

    public ExpandableNotificationRow createRow(NotificationEntry entry) throws Exception {
        return generateRow(entry, mDefaultInflationFlags);
    }

    /**
     * Create a row with the specified content views inflated in addition to the default.
     *
@@ -538,18 +542,6 @@ public class NotificationTestHelper {
            @InflationFlag int extraInflationFlags,
            int importance)
            throws Exception {
        // NOTE: This flag is read when the ExpandableNotificationRow is inflated, so it needs to be
        //  set, but we do not want to override an existing value that is needed by a specific test.
        mFeatureFlags.setDefault(Flags.IMPROVED_HUN_ANIMATIONS);

        LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
                mContext.LAYOUT_INFLATER_SERVICE);
        mRow = (ExpandableNotificationRow) inflater.inflate(
                R.layout.status_bar_notification_row,
                null /* root */,
                false /* attachToRoot */);
        ExpandableNotificationRow row = mRow;

        final NotificationChannel channel =
                new NotificationChannel(
                        notification.getChannelId(),
@@ -569,6 +561,25 @@ public class NotificationTestHelper {
                .setChannel(channel)
                .build();

        return generateRow(entry, extraInflationFlags);
    }

    private ExpandableNotificationRow generateRow(
            NotificationEntry entry,
            @InflationFlag int extraInflationFlags)
            throws Exception {
        // NOTE: This flag is read when the ExpandableNotificationRow is inflated, so it needs to be
        //  set, but we do not want to override an existing value that is needed by a specific test.
        mFeatureFlags.setDefault(Flags.IMPROVED_HUN_ANIMATIONS);

        LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
                mContext.LAYOUT_INFLATER_SERVICE);
        mRow = (ExpandableNotificationRow) inflater.inflate(
                R.layout.status_bar_notification_row,
                null /* root */,
                false /* attachToRoot */);
        ExpandableNotificationRow row = mRow;

        entry.setRow(row);
        mIconManager.createIcons(entry);