Properly end lifetimeExtention for ongoing notifications

Fixes: 209816888
Test: HeadsupCoordinatiorTest
Change-Id: I620ec940ec3a836c36d71af327bd74a2640bf76f
This commit is contained in:
Jay Aliomer
2021-12-15 12:04:16 -05:00
parent 1d35d293f3
commit b4ecc57678
3 changed files with 119 additions and 66 deletions

View File

@@ -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) {

View File

@@ -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 isCurrentlyShowingHun(ListEntry entry) {
return mCurrentHun == entry.getRepresentativeEntry();
private boolean isSticky(NotificationEntry entry) {
return mHeadsUpManager.isSticky(entry.getKey());
}
private void endNotifLifetimeExtension() {
if (mNotifExtendingLifetime != null) {
mEndLifetimeExtension.onEndLifetimeExtension(
mLifetimeExtender,
mNotifExtendingLifetime);
mNotifExtendingLifetime = null;
private boolean isCurrentlyShowingHun(ListEntry entry) {
return mHeadsUpManager.isAlerting(entry.getKey());
}
private void endNotifLifetimeExtensionIfExtended(NotificationEntry entry) {
if (mNotifsExtendingLifetime.remove(entry)) {
mEndLifetimeExtension.onEndLifetimeExtension(mLifetimeExtender, entry);
}
}
}

View File

@@ -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);
}
}