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

Commit b4ecc576 authored by Jay Aliomer's avatar Jay Aliomer
Browse files

Properly end lifetimeExtention for ongoing notifications

Fixes: 209816888
Test: HeadsupCoordinatiorTest
Change-Id: I620ec940ec3a836c36d71af327bd74a2640bf76f
parent 1d35d293
Loading
Loading
Loading
Loading
+24 −0
Original line number Diff line number Diff line
@@ -257,6 +257,30 @@ public abstract class AlertingNotificationManager implements NotificationLifetim
        return !canRemoveImmediately(entry.getKey());
    }

    /**
     * @param key
     * @return true if the entry is pinned
     */
    public boolean isSticky(String key) {
        AlertEntry alerting = mAlertEntries.get(key);
        if (alerting != null) {
            return alerting.isSticky();
        }
        return false;
    }

    /**
     * @param key
     * @return When a HUN entry should be removed in milliseconds from now
     */
    public long getEarliestRemovalTime(String key) {
        AlertEntry alerting = mAlertEntries.get(key);
        if (alerting != null) {
            return Math.max(0, alerting.mEarliestRemovaltime - mClock.currentTimeMillis());
        }
        return 0;
    }

    @Override
    public void setShouldManageLifetime(NotificationEntry entry, boolean shouldExtend) {
        if (shouldExtend) {
+33 −23
Original line number Diff line number Diff line
@@ -19,9 +19,12 @@ package com.android.systemui.statusbar.notification.collection.coordinator;
import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY;
import static com.android.systemui.statusbar.notification.interruption.HeadsUpController.alertAgain;

import android.util.ArraySet;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -38,8 +41,7 @@ import com.android.systemui.statusbar.notification.interruption.NotificationInte
import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;

import java.util.Objects;
import com.android.systemui.util.concurrency.DelayableExecutor;

import javax.inject.Inject;

@@ -66,12 +68,11 @@ public class HeadsUpCoordinator implements Coordinator {
    private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
    private final NotificationRemoteInputManager mRemoteInputManager;
    private final NodeController mIncomingHeaderController;

    // tracks the current HeadUpNotification reported by HeadsUpManager
    private @Nullable NotificationEntry mCurrentHun;
    private final DelayableExecutor mExecutor;

    private NotifLifetimeExtender.OnEndLifetimeExtensionCallback mEndLifetimeExtension;
    private NotificationEntry mNotifExtendingLifetime; // notif we've extended the lifetime for
    // notifs we've extended the lifetime for
    private final ArraySet<NotificationEntry> mNotifsExtendingLifetime = new ArraySet<>();

    @Inject
    public HeadsUpCoordinator(
@@ -79,12 +80,14 @@ public class HeadsUpCoordinator implements Coordinator {
            HeadsUpViewBinder headsUpViewBinder,
            NotificationInterruptStateProvider notificationInterruptStateProvider,
            NotificationRemoteInputManager remoteInputManager,
            @IncomingHeader NodeController incomingHeaderController) {
            @IncomingHeader NodeController incomingHeaderController,
            @Main DelayableExecutor executor) {
        mHeadsUpManager = headsUpManager;
        mHeadsUpViewBinder = headsUpViewBinder;
        mNotificationInterruptStateProvider = notificationInterruptStateProvider;
        mRemoteInputManager = remoteInputManager;
        mIncomingHeaderController = incomingHeaderController;
        mExecutor = executor;
    }

    @Override
@@ -178,16 +181,26 @@ public class HeadsUpCoordinator implements Coordinator {
        public boolean shouldExtendLifetime(@NonNull NotificationEntry entry, int reason) {
            boolean isShowingHun = isCurrentlyShowingHun(entry);
            if (isShowingHun) {
                mNotifExtendingLifetime = entry;
                if (isSticky(entry)) {
                    long removeAfterMillis = mHeadsUpManager.getEarliestRemovalTime(entry.getKey());
                    if (removeAfterMillis <= 0) return false;
                    mExecutor.executeDelayed(() -> {
                        // make sure that the entry was not updated
                        long removeAfterMillis2 =
                                mHeadsUpManager.getEarliestRemovalTime(entry.getKey());
                        if (mNotifsExtendingLifetime.contains(entry) && removeAfterMillis2 <= 0) {
                            mHeadsUpManager.removeNotification(entry.getKey(), true);
                        }
                    }, removeAfterMillis);
                }
                mNotifsExtendingLifetime.add(entry);
            }
            return isShowingHun;
        }

        @Override
        public void cancelLifetimeExtension(@NonNull NotificationEntry entry) {
            if (Objects.equals(mNotifExtendingLifetime, entry)) {
                mNotifExtendingLifetime = null;
            }
            mNotifsExtendingLifetime.remove(entry);
        }
    };

@@ -220,27 +233,24 @@ public class HeadsUpCoordinator implements Coordinator {
            new OnHeadsUpChangedListener() {
        @Override
        public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
            NotificationEntry newHUN = mHeadsUpManager.getTopEntry();
            if (!Objects.equals(mCurrentHun, newHUN)) {
                mCurrentHun = newHUN;
                endNotifLifetimeExtension();
            }
            if (!isHeadsUp) {
                mHeadsUpViewBinder.unbindHeadsUpView(entry);
                endNotifLifetimeExtensionIfExtended(entry);
            }
        }
    };

    private boolean isSticky(NotificationEntry entry) {
        return mHeadsUpManager.isSticky(entry.getKey());
    }

    private boolean isCurrentlyShowingHun(ListEntry entry) {
        return mCurrentHun == entry.getRepresentativeEntry();
        return mHeadsUpManager.isAlerting(entry.getKey());
    }

    private void endNotifLifetimeExtension() {
        if (mNotifExtendingLifetime != null) {
            mEndLifetimeExtension.onEndLifetimeExtension(
                    mLifetimeExtender,
                    mNotifExtendingLifetime);
            mNotifExtendingLifetime = null;
    private void endNotifLifetimeExtensionIfExtended(NotificationEntry entry) {
        if (mNotifsExtendingLifetime.remove(entry)) {
            mEndLifetimeExtension.onEndLifetimeExtension(mLifetimeExtender, entry);
        }
    }
}
+61 −42
Original line number Diff line number Diff line
@@ -19,8 +19,11 @@ package com.android.systemui.statusbar.notification.collection.coordinator;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@@ -44,6 +47,8 @@ import com.android.systemui.statusbar.notification.interruption.NotificationInte
import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;

import org.junit.Before;
import org.junit.Test;
@@ -52,6 +57,8 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.ArrayList;

@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@@ -75,6 +82,9 @@ public class HeadsUpCoordinatorTest extends SysuiTestCase {
    @Mock private NodeController mHeaderController;

    private NotificationEntry mEntry;
    private final FakeSystemClock mClock = new FakeSystemClock();
    private final FakeExecutor mExecutor = new FakeExecutor(mClock);
    private final ArrayList<NotificationEntry> mHuns = new ArrayList();

    @Before
    public void setUp() {
@@ -85,7 +95,8 @@ public class HeadsUpCoordinatorTest extends SysuiTestCase {
                mHeadsUpViewBinder,
                mNotificationInterruptStateProvider,
                mRemoteInputManager,
                mHeaderController);
                mHeaderController,
                mExecutor);

        mCoordinator.attach(mNotifPipeline);

@@ -105,6 +116,16 @@ public class HeadsUpCoordinatorTest extends SysuiTestCase {
                notifLifetimeExtenderCaptor.capture());
        verify(mHeadsUpManager).addListener(headsUpChangedListenerCaptor.capture());

        given(mHeadsUpManager.getAllEntries()).willAnswer(i -> mHuns.stream());
        given(mHeadsUpManager.isAlerting(anyString())).willAnswer(i -> {
            String key = i.getArgument(0);
            for (NotificationEntry entry : mHuns) {
                if (entry.getKey().equals(key)) return true;
            }
            return false;
        });
        when(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L);

        mCollectionListener = notifCollectionCaptor.getValue();
        mNotifPromoter = notifPromoterCaptor.getValue();
        mNotifLifetimeExtender = notifLifetimeExtenderCaptor.getValue();
@@ -115,64 +136,65 @@ public class HeadsUpCoordinatorTest extends SysuiTestCase {
        mEntry = new NotificationEntryBuilder().build();
    }

    @Test
    public void testCancelStickyNotification() {
        when(mHeadsUpManager.isSticky(anyString())).thenReturn(true);
        addHUN(mEntry);
        when(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 0L);
        assertTrue(mNotifLifetimeExtender.shouldExtendLifetime(mEntry, 0));
        mClock.advanceTime(1000L);
        mExecutor.runAllReady();
        verify(mHeadsUpManager, times(1))
                .removeNotification(anyString(), eq(true));
    }

    @Test
    public void testCancelUpdatedStickyNotification() {
        when(mHeadsUpManager.isSticky(anyString())).thenReturn(true);
        addHUN(mEntry);
        when(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 500L);
        assertTrue(mNotifLifetimeExtender.shouldExtendLifetime(mEntry, 0));
        mClock.advanceTime(1000L);
        mExecutor.runAllReady();
        verify(mHeadsUpManager, times(0))
                .removeNotification(anyString(), eq(true));
    }

    @Test
    public void testPromotesCurrentHUN() {
        // GIVEN the current HUN is set to mEntry
        setCurrentHUN(mEntry);
        addHUN(mEntry);

        // THEN only promote the current HUN, mEntry
        assertTrue(mNotifPromoter.shouldPromoteToTopLevel(mEntry));
        assertFalse(mNotifPromoter.shouldPromoteToTopLevel(new NotificationEntryBuilder().build()));
        assertFalse(mNotifPromoter.shouldPromoteToTopLevel(new NotificationEntryBuilder()
                .setPkg("test-package2")
                .build()));
    }

    @Test
    public void testIncludeInSectionCurrentHUN() {
        // GIVEN the current HUN is set to mEntry
        setCurrentHUN(mEntry);
        addHUN(mEntry);

        // THEN only section the current HUN, mEntry
        assertTrue(mNotifSectioner.isInSection(mEntry));
        assertFalse(mNotifSectioner.isInSection(new NotificationEntryBuilder().build()));
        assertFalse(mNotifSectioner.isInSection(new NotificationEntryBuilder()
                .setPkg("test-package")
                .build()));
    }

    @Test
    public void testLifetimeExtendsCurrentHUN() {
        // GIVEN there is a HUN, mEntry
        setCurrentHUN(mEntry);
        addHUN(mEntry);

        // THEN only the current HUN, mEntry, should be lifetimeExtended
        assertTrue(mNotifLifetimeExtender.shouldExtendLifetime(mEntry, /* cancellationReason */ 0));
        assertFalse(mNotifLifetimeExtender.shouldExtendLifetime(
                new NotificationEntryBuilder().build(), /* cancellationReason */ 0));
    }

    @Test
    public void testLifetimeExtensionEndsOnNewHUN() {
        // GIVEN there was a HUN that was lifetime extended
        setCurrentHUN(mEntry);
        assertTrue(mNotifLifetimeExtender.shouldExtendLifetime(
                mEntry, /* cancellation reason */ 0));

        // WHEN there's a new HUN
        NotificationEntry newHUN = new NotificationEntryBuilder().build();
        setCurrentHUN(newHUN);

        // THEN the old entry's lifetime extension should be cancelled
        verify(mEndLifetimeExtension).onEndLifetimeExtension(mNotifLifetimeExtender, mEntry);
    }

    @Test
    public void testLifetimeExtensionEndsOnNoHUNs() {
        // GIVEN there was a HUN that was lifetime extended
        setCurrentHUN(mEntry);
        assertTrue(mNotifLifetimeExtender.shouldExtendLifetime(
                mEntry, /* cancellation reason */ 0));

        // WHEN there's no longer a HUN
        setCurrentHUN(null);

        // THEN the old entry's lifetime extension should be cancelled
        verify(mEndLifetimeExtension).onEndLifetimeExtension(mNotifLifetimeExtender, mEntry);
                new NotificationEntryBuilder()
                        .setPkg("test-package")
                        .build(), /* cancellationReason */ 0));
    }

    @Test
@@ -208,7 +230,7 @@ public class HeadsUpCoordinatorTest extends SysuiTestCase {
    @Test
    public void testOnEntryRemovedRemovesHeadsUpNotification() {
        // GIVEN the current HUN is mEntry
        setCurrentHUN(mEntry);
        addHUN(mEntry);

        // WHEN mEntry is removed from the notification collection
        mCollectionListener.onEntryRemoved(mEntry, /* cancellation reason */ 0);
@@ -218,12 +240,9 @@ public class HeadsUpCoordinatorTest extends SysuiTestCase {
        verify(mHeadsUpManager).removeNotification(mEntry.getKey(), false);
    }

    private void setCurrentHUN(NotificationEntry entry) {
    private void addHUN(NotificationEntry entry) {
        mHuns.add(entry);
        when(mHeadsUpManager.getTopEntry()).thenReturn(entry);
        when(mHeadsUpManager.isAlerting(any())).thenReturn(false);
        if (entry != null) {
            when(mHeadsUpManager.isAlerting(entry.getKey())).thenReturn(true);
        }
        mOnHeadsUpChangedListener.onHeadsUpStateChanged(entry, entry != null);
    }
}