Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +33 −7 Original line number Diff line number Diff line Loading @@ -47,6 +47,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * NotificationEntryManager is responsible for the adding, removing, and updating of notifications. Loading @@ -64,6 +65,9 @@ public class NotificationEntryManager implements @VisibleForTesting protected final HashMap<String, NotificationEntry> mPendingNotifications = new HashMap<>(); private final Map<NotificationEntry, NotificationLifetimeExtender> mRetainedNotifications = new ArrayMap<>(); // Lazily retrieved dependencies private NotificationRemoteInputManager mRemoteInputManager; private NotificationRowBinder mNotificationRowBinder; Loading @@ -89,6 +93,16 @@ public class NotificationEntryManager implements pw.println(entry.notification); } } pw.println(" Lifetime-extended notifications:"); if (mRetainedNotifications.isEmpty()) { pw.println(" None"); } else { for (Map.Entry<NotificationEntry, NotificationLifetimeExtender> entry : mRetainedNotifications.entrySet()) { pw.println(" " + entry.getKey().notification + " retained by " + entry.getValue().getClass().getName()); } } } public NotificationEntryManager(Context context) { Loading Loading @@ -244,7 +258,7 @@ public class NotificationEntryManager implements for (NotificationLifetimeExtender extender : mNotificationLifetimeExtenders) { if (extender.shouldExtendLifetime(entry)) { mLatestRankingMap = ranking; extender.setShouldManageLifetime(entry, true /* shouldManage */); extendLifetime(entry, extender); lifetimeExtended = true; break; } Loading @@ -255,9 +269,7 @@ public class NotificationEntryManager implements // At this point, we are guaranteed the notification will be removed // Ensure any managers keeping the lifetime extended stop managing the entry for (NotificationLifetimeExtender extender : mNotificationLifetimeExtenders) { extender.setShouldManageLifetime(entry, false /* shouldManage */); } cancelLifetimeExtension(entry); if (entry.rowExists()) { entry.removeRow(); Loading Loading @@ -368,9 +380,7 @@ public class NotificationEntryManager implements // Notification is updated so it is essentially re-added and thus alive again. Don't need // to keep its lifetime extended. for (NotificationLifetimeExtender extender : mNotificationLifetimeExtenders) { extender.setShouldManageLifetime(entry, false /* shouldManage */); } cancelLifetimeExtension(entry); mNotificationData.update(entry, ranking, notification); Loading Loading @@ -464,4 +474,20 @@ public class NotificationEntryManager implements public Iterable<NotificationEntry> getPendingNotificationsIterator() { return mPendingNotifications.values(); } private void extendLifetime(NotificationEntry entry, NotificationLifetimeExtender extender) { NotificationLifetimeExtender activeExtender = mRetainedNotifications.get(entry); if (activeExtender != null && activeExtender != extender) { activeExtender.setShouldManageLifetime(entry, false); } mRetainedNotifications.put(entry, extender); extender.setShouldManageLifetime(entry, true); } private void cancelLifetimeExtension(NotificationEntry entry) { NotificationLifetimeExtender activeExtender = mRetainedNotifications.remove(entry); if (activeExtender != null) { activeExtender.setShouldManageLifetime(entry, false); } } } packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java +136 −22 Original line number Diff line number Diff line Loading @@ -45,8 +45,11 @@ import android.service.notification.StatusBarNotification; import android.support.test.filters.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.ArraySet; import android.widget.FrameLayout; import androidx.annotation.NonNull; import com.android.internal.logging.MetricsLogger; import com.android.systemui.Dependency; import com.android.systemui.ForegroundServiceController; Loading Loading @@ -84,6 +87,7 @@ import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.Arrays; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; Loading Loading @@ -347,28 +351,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase { assertNull(mEntryManager.getNotificationData().get(mSbn.getKey())); } @Test public void testRemoveNotification_blockedByLifetimeExtender() { com.android.systemui.util.Assert.isNotMainThread(); NotificationLifetimeExtender extender = mock(NotificationLifetimeExtender.class); when(extender.shouldExtendLifetime(mEntry)).thenReturn(true); ArrayList<NotificationLifetimeExtender> extenders = mEntryManager.getLifetimeExtenders(); extenders.clear(); extenders.add(extender); mEntry.setRow(mRow); mEntryManager.getNotificationData().add(mEntry); mEntryManager.removeNotification(mSbn.getKey(), mRankingMap); assertNotNull(mEntryManager.getNotificationData().get(mSbn.getKey())); verify(extender).setShouldManageLifetime(mEntry, true /* shouldManage */); verify(mEntryListener, never()).onEntryRemoved( mEntry, null, false /* removedByUser */); } @Test public void testRemoveNotification_onEntryRemoveNotFiredIfEntryDoesntExist() { com.android.systemui.util.Assert.isNotMainThread(); Loading Loading @@ -453,10 +435,142 @@ public class NotificationEntryManagerTest extends SysuiTestCase { assertEquals("action", mEntry.systemGeneratedSmartActions.get(0).title); } @Test public void testLifetimeExtenders_ifNotificationIsRetainedItIsntRemoved() { // GIVEN an entry manager with a notification mEntryManager.setRowBinder(mMockedRowBinder); mEntryManager.getNotificationData().add(mEntry); // GIVEN a lifetime extender that always tries to extend lifetime NotificationLifetimeExtender extender = mock(NotificationLifetimeExtender.class); when(extender.shouldExtendLifetime(mEntry)).thenReturn(true); mEntryManager.addNotificationLifetimeExtender(extender); // WHEN the notification is removed mEntryManager.removeNotification(mEntry.key, mRankingMap); // THEN the extender is asked to manage the lifetime verify(extender).setShouldManageLifetime(mEntry, true); // THEN the notification is retained assertNotNull(mEntryManager.getNotificationData().get(mSbn.getKey())); verify(mEntryListener, never()).onEntryRemoved(mEntry, null, false); } @Test public void testLifetimeExtenders_whenRetentionEndsNotificationIsRemoved() { // GIVEN an entry manager with a notification whose life has been extended mEntryManager.setRowBinder(mMockedRowBinder); mEntryManager.getNotificationData().add(mEntry); final FakeNotificationLifetimeExtender extender = new FakeNotificationLifetimeExtender(); mEntryManager.addNotificationLifetimeExtender(extender); mEntryManager.removeNotification(mEntry.key, mRankingMap); assertTrue(extender.isManaging(mEntry.key)); // WHEN the extender finishes its extension extender.setExtendLifetimes(false); extender.getCallback().onSafeToRemove(mEntry.key); // THEN the notification is removed assertNull(mEntryManager.getNotificationData().get(mSbn.getKey())); verify(mEntryListener).onEntryRemoved(mEntry, null, false); } @Test public void testLifetimeExtenders_whenNotificationUpdatedRetainersAreCanceled() { // GIVEN an entry manager with a notification whose life has been extended mEntryManager.setRowBinder(mMockedRowBinder); mEntryManager.getNotificationData().add(mEntry); NotificationLifetimeExtender extender = mock(NotificationLifetimeExtender.class); when(extender.shouldExtendLifetime(mEntry)).thenReturn(true); mEntryManager.addNotificationLifetimeExtender(extender); mEntryManager.removeNotification(mEntry.key, mRankingMap); // WHEN the notification is updated mEntryManager.updateNotification(mEntry.notification, mRankingMap); // THEN the lifetime extension is canceled verify(extender).setShouldManageLifetime(mEntry, false); } @Test public void testLifetimeExtenders_whenNewExtenderTakesPrecedenceOldExtenderIsCanceled() { // GIVEN an entry manager with a notification mEntryManager.setRowBinder(mMockedRowBinder); mEntryManager.getNotificationData().add(mEntry); // GIVEN two lifetime extenders, the first which never extends and the second which // always extends NotificationLifetimeExtender extender1 = mock(NotificationLifetimeExtender.class); when(extender1.shouldExtendLifetime(mEntry)).thenReturn(false); NotificationLifetimeExtender extender2 = mock(NotificationLifetimeExtender.class); when(extender2.shouldExtendLifetime(mEntry)).thenReturn(true); mEntryManager.addNotificationLifetimeExtender(extender1); mEntryManager.addNotificationLifetimeExtender(extender2); // GIVEN a notification was lifetime-extended and extender2 is managing it mEntryManager.removeNotification(mEntry.key, mRankingMap); verify(extender1, never()).setShouldManageLifetime(mEntry, true); verify(extender2).setShouldManageLifetime(mEntry, true); // WHEN the extender1 changes its mind and wants to extend the lifetime of the notif when(extender1.shouldExtendLifetime(mEntry)).thenReturn(true); mEntryManager.removeNotification(mEntry.key, mRankingMap); // THEN extender2 stops managing the notif and extender1 starts managing it verify(extender1).setShouldManageLifetime(mEntry, true); verify(extender2).setShouldManageLifetime(mEntry, false); } private Notification.Action createAction() { return new Notification.Action.Builder( Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon), "action", PendingIntent.getBroadcast(getContext(), 0, new Intent("Action"), 0)).build(); } private static class FakeNotificationLifetimeExtender implements NotificationLifetimeExtender { private NotificationSafeToRemoveCallback mCallback; private boolean mExtendLifetimes = true; private Set<String> mManagedNotifs = new ArraySet<>(); @Override public void setCallback(@NonNull NotificationSafeToRemoveCallback callback) { mCallback = callback; } @Override public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) { return mExtendLifetimes; } @Override public void setShouldManageLifetime( @NonNull NotificationEntry entry, boolean shouldManage) { final boolean hasEntry = mManagedNotifs.contains(entry.key); if (shouldManage) { if (hasEntry) { throw new RuntimeException("Already managing this entry: " + entry.key); } mManagedNotifs.add(entry.key); } else { if (!hasEntry) { throw new RuntimeException("Not managing this entry: " + entry.key); } mManagedNotifs.remove(entry.key); } } public void setExtendLifetimes(boolean extendLifetimes) { mExtendLifetimes = extendLifetimes; } public NotificationSafeToRemoveCallback getCallback() { return mCallback; } public boolean isManaging(String notificationKey) { return mManagedNotifs.contains(notificationKey); } } } Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +33 −7 Original line number Diff line number Diff line Loading @@ -47,6 +47,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * NotificationEntryManager is responsible for the adding, removing, and updating of notifications. Loading @@ -64,6 +65,9 @@ public class NotificationEntryManager implements @VisibleForTesting protected final HashMap<String, NotificationEntry> mPendingNotifications = new HashMap<>(); private final Map<NotificationEntry, NotificationLifetimeExtender> mRetainedNotifications = new ArrayMap<>(); // Lazily retrieved dependencies private NotificationRemoteInputManager mRemoteInputManager; private NotificationRowBinder mNotificationRowBinder; Loading @@ -89,6 +93,16 @@ public class NotificationEntryManager implements pw.println(entry.notification); } } pw.println(" Lifetime-extended notifications:"); if (mRetainedNotifications.isEmpty()) { pw.println(" None"); } else { for (Map.Entry<NotificationEntry, NotificationLifetimeExtender> entry : mRetainedNotifications.entrySet()) { pw.println(" " + entry.getKey().notification + " retained by " + entry.getValue().getClass().getName()); } } } public NotificationEntryManager(Context context) { Loading Loading @@ -244,7 +258,7 @@ public class NotificationEntryManager implements for (NotificationLifetimeExtender extender : mNotificationLifetimeExtenders) { if (extender.shouldExtendLifetime(entry)) { mLatestRankingMap = ranking; extender.setShouldManageLifetime(entry, true /* shouldManage */); extendLifetime(entry, extender); lifetimeExtended = true; break; } Loading @@ -255,9 +269,7 @@ public class NotificationEntryManager implements // At this point, we are guaranteed the notification will be removed // Ensure any managers keeping the lifetime extended stop managing the entry for (NotificationLifetimeExtender extender : mNotificationLifetimeExtenders) { extender.setShouldManageLifetime(entry, false /* shouldManage */); } cancelLifetimeExtension(entry); if (entry.rowExists()) { entry.removeRow(); Loading Loading @@ -368,9 +380,7 @@ public class NotificationEntryManager implements // Notification is updated so it is essentially re-added and thus alive again. Don't need // to keep its lifetime extended. for (NotificationLifetimeExtender extender : mNotificationLifetimeExtenders) { extender.setShouldManageLifetime(entry, false /* shouldManage */); } cancelLifetimeExtension(entry); mNotificationData.update(entry, ranking, notification); Loading Loading @@ -464,4 +474,20 @@ public class NotificationEntryManager implements public Iterable<NotificationEntry> getPendingNotificationsIterator() { return mPendingNotifications.values(); } private void extendLifetime(NotificationEntry entry, NotificationLifetimeExtender extender) { NotificationLifetimeExtender activeExtender = mRetainedNotifications.get(entry); if (activeExtender != null && activeExtender != extender) { activeExtender.setShouldManageLifetime(entry, false); } mRetainedNotifications.put(entry, extender); extender.setShouldManageLifetime(entry, true); } private void cancelLifetimeExtension(NotificationEntry entry) { NotificationLifetimeExtender activeExtender = mRetainedNotifications.remove(entry); if (activeExtender != null) { activeExtender.setShouldManageLifetime(entry, false); } } }
packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java +136 −22 Original line number Diff line number Diff line Loading @@ -45,8 +45,11 @@ import android.service.notification.StatusBarNotification; import android.support.test.filters.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.ArraySet; import android.widget.FrameLayout; import androidx.annotation.NonNull; import com.android.internal.logging.MetricsLogger; import com.android.systemui.Dependency; import com.android.systemui.ForegroundServiceController; Loading Loading @@ -84,6 +87,7 @@ import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.Arrays; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; Loading Loading @@ -347,28 +351,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase { assertNull(mEntryManager.getNotificationData().get(mSbn.getKey())); } @Test public void testRemoveNotification_blockedByLifetimeExtender() { com.android.systemui.util.Assert.isNotMainThread(); NotificationLifetimeExtender extender = mock(NotificationLifetimeExtender.class); when(extender.shouldExtendLifetime(mEntry)).thenReturn(true); ArrayList<NotificationLifetimeExtender> extenders = mEntryManager.getLifetimeExtenders(); extenders.clear(); extenders.add(extender); mEntry.setRow(mRow); mEntryManager.getNotificationData().add(mEntry); mEntryManager.removeNotification(mSbn.getKey(), mRankingMap); assertNotNull(mEntryManager.getNotificationData().get(mSbn.getKey())); verify(extender).setShouldManageLifetime(mEntry, true /* shouldManage */); verify(mEntryListener, never()).onEntryRemoved( mEntry, null, false /* removedByUser */); } @Test public void testRemoveNotification_onEntryRemoveNotFiredIfEntryDoesntExist() { com.android.systemui.util.Assert.isNotMainThread(); Loading Loading @@ -453,10 +435,142 @@ public class NotificationEntryManagerTest extends SysuiTestCase { assertEquals("action", mEntry.systemGeneratedSmartActions.get(0).title); } @Test public void testLifetimeExtenders_ifNotificationIsRetainedItIsntRemoved() { // GIVEN an entry manager with a notification mEntryManager.setRowBinder(mMockedRowBinder); mEntryManager.getNotificationData().add(mEntry); // GIVEN a lifetime extender that always tries to extend lifetime NotificationLifetimeExtender extender = mock(NotificationLifetimeExtender.class); when(extender.shouldExtendLifetime(mEntry)).thenReturn(true); mEntryManager.addNotificationLifetimeExtender(extender); // WHEN the notification is removed mEntryManager.removeNotification(mEntry.key, mRankingMap); // THEN the extender is asked to manage the lifetime verify(extender).setShouldManageLifetime(mEntry, true); // THEN the notification is retained assertNotNull(mEntryManager.getNotificationData().get(mSbn.getKey())); verify(mEntryListener, never()).onEntryRemoved(mEntry, null, false); } @Test public void testLifetimeExtenders_whenRetentionEndsNotificationIsRemoved() { // GIVEN an entry manager with a notification whose life has been extended mEntryManager.setRowBinder(mMockedRowBinder); mEntryManager.getNotificationData().add(mEntry); final FakeNotificationLifetimeExtender extender = new FakeNotificationLifetimeExtender(); mEntryManager.addNotificationLifetimeExtender(extender); mEntryManager.removeNotification(mEntry.key, mRankingMap); assertTrue(extender.isManaging(mEntry.key)); // WHEN the extender finishes its extension extender.setExtendLifetimes(false); extender.getCallback().onSafeToRemove(mEntry.key); // THEN the notification is removed assertNull(mEntryManager.getNotificationData().get(mSbn.getKey())); verify(mEntryListener).onEntryRemoved(mEntry, null, false); } @Test public void testLifetimeExtenders_whenNotificationUpdatedRetainersAreCanceled() { // GIVEN an entry manager with a notification whose life has been extended mEntryManager.setRowBinder(mMockedRowBinder); mEntryManager.getNotificationData().add(mEntry); NotificationLifetimeExtender extender = mock(NotificationLifetimeExtender.class); when(extender.shouldExtendLifetime(mEntry)).thenReturn(true); mEntryManager.addNotificationLifetimeExtender(extender); mEntryManager.removeNotification(mEntry.key, mRankingMap); // WHEN the notification is updated mEntryManager.updateNotification(mEntry.notification, mRankingMap); // THEN the lifetime extension is canceled verify(extender).setShouldManageLifetime(mEntry, false); } @Test public void testLifetimeExtenders_whenNewExtenderTakesPrecedenceOldExtenderIsCanceled() { // GIVEN an entry manager with a notification mEntryManager.setRowBinder(mMockedRowBinder); mEntryManager.getNotificationData().add(mEntry); // GIVEN two lifetime extenders, the first which never extends and the second which // always extends NotificationLifetimeExtender extender1 = mock(NotificationLifetimeExtender.class); when(extender1.shouldExtendLifetime(mEntry)).thenReturn(false); NotificationLifetimeExtender extender2 = mock(NotificationLifetimeExtender.class); when(extender2.shouldExtendLifetime(mEntry)).thenReturn(true); mEntryManager.addNotificationLifetimeExtender(extender1); mEntryManager.addNotificationLifetimeExtender(extender2); // GIVEN a notification was lifetime-extended and extender2 is managing it mEntryManager.removeNotification(mEntry.key, mRankingMap); verify(extender1, never()).setShouldManageLifetime(mEntry, true); verify(extender2).setShouldManageLifetime(mEntry, true); // WHEN the extender1 changes its mind and wants to extend the lifetime of the notif when(extender1.shouldExtendLifetime(mEntry)).thenReturn(true); mEntryManager.removeNotification(mEntry.key, mRankingMap); // THEN extender2 stops managing the notif and extender1 starts managing it verify(extender1).setShouldManageLifetime(mEntry, true); verify(extender2).setShouldManageLifetime(mEntry, false); } private Notification.Action createAction() { return new Notification.Action.Builder( Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon), "action", PendingIntent.getBroadcast(getContext(), 0, new Intent("Action"), 0)).build(); } private static class FakeNotificationLifetimeExtender implements NotificationLifetimeExtender { private NotificationSafeToRemoveCallback mCallback; private boolean mExtendLifetimes = true; private Set<String> mManagedNotifs = new ArraySet<>(); @Override public void setCallback(@NonNull NotificationSafeToRemoveCallback callback) { mCallback = callback; } @Override public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) { return mExtendLifetimes; } @Override public void setShouldManageLifetime( @NonNull NotificationEntry entry, boolean shouldManage) { final boolean hasEntry = mManagedNotifs.contains(entry.key); if (shouldManage) { if (hasEntry) { throw new RuntimeException("Already managing this entry: " + entry.key); } mManagedNotifs.add(entry.key); } else { if (!hasEntry) { throw new RuntimeException("Not managing this entry: " + entry.key); } mManagedNotifs.remove(entry.key); } } public void setExtendLifetimes(boolean extendLifetimes) { mExtendLifetimes = extendLifetimes; } public NotificationSafeToRemoveCallback getCallback() { return mCallback; } public boolean isManaging(String notificationKey) { return mManagedNotifs.contains(notificationKey); } } }