1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.statusbar.notification.collection.coordinator; 18 19 import static org.junit.Assert.assertFalse; 20 import static org.junit.Assert.assertTrue; 21 import static org.mockito.ArgumentMatchers.any; 22 import static org.mockito.ArgumentMatchers.eq; 23 import static org.mockito.Mockito.never; 24 import static org.mockito.Mockito.verify; 25 import static org.mockito.Mockito.when; 26 27 import android.testing.AndroidTestingRunner; 28 import android.testing.TestableLooper; 29 30 import androidx.test.filters.SmallTest; 31 32 import com.android.systemui.SysuiTestCase; 33 import com.android.systemui.statusbar.NotificationRemoteInputManager; 34 import com.android.systemui.statusbar.notification.collection.NotifPipeline; 35 import com.android.systemui.statusbar.notification.collection.NotificationEntry; 36 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; 37 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; 38 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner; 39 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; 40 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; 41 import com.android.systemui.statusbar.notification.collection.render.NodeController; 42 import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder; 43 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; 44 import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback; 45 import com.android.systemui.statusbar.policy.HeadsUpManager; 46 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; 47 48 import org.junit.Before; 49 import org.junit.Test; 50 import org.junit.runner.RunWith; 51 import org.mockito.ArgumentCaptor; 52 import org.mockito.Mock; 53 import org.mockito.MockitoAnnotations; 54 55 @SmallTest 56 @RunWith(AndroidTestingRunner.class) 57 @TestableLooper.RunWithLooper 58 public class HeadsUpCoordinatorTest extends SysuiTestCase { 59 60 private HeadsUpCoordinator mCoordinator; 61 62 // captured listeners and pluggables: 63 private NotifCollectionListener mCollectionListener; 64 private NotifPromoter mNotifPromoter; 65 private NotifLifetimeExtender mNotifLifetimeExtender; 66 private OnHeadsUpChangedListener mOnHeadsUpChangedListener; 67 private NotifSectioner mNotifSectioner; 68 69 @Mock private NotifPipeline mNotifPipeline; 70 @Mock private HeadsUpManager mHeadsUpManager; 71 @Mock private HeadsUpViewBinder mHeadsUpViewBinder; 72 @Mock private NotificationInterruptStateProvider mNotificationInterruptStateProvider; 73 @Mock private NotificationRemoteInputManager mRemoteInputManager; 74 @Mock private NotifLifetimeExtender.OnEndLifetimeExtensionCallback mEndLifetimeExtension; 75 @Mock private NodeController mHeaderController; 76 77 private NotificationEntry mEntry; 78 79 @Before setUp()80 public void setUp() { 81 MockitoAnnotations.initMocks(this); 82 83 mCoordinator = new HeadsUpCoordinator( 84 mHeadsUpManager, 85 mHeadsUpViewBinder, 86 mNotificationInterruptStateProvider, 87 mRemoteInputManager, 88 mHeaderController); 89 90 mCoordinator.attach(mNotifPipeline); 91 92 // capture arguments: 93 ArgumentCaptor<NotifCollectionListener> notifCollectionCaptor = 94 ArgumentCaptor.forClass(NotifCollectionListener.class); 95 ArgumentCaptor<NotifPromoter> notifPromoterCaptor = 96 ArgumentCaptor.forClass(NotifPromoter.class); 97 ArgumentCaptor<NotifLifetimeExtender> notifLifetimeExtenderCaptor = 98 ArgumentCaptor.forClass(NotifLifetimeExtender.class); 99 ArgumentCaptor<OnHeadsUpChangedListener> headsUpChangedListenerCaptor = 100 ArgumentCaptor.forClass(OnHeadsUpChangedListener.class); 101 102 verify(mNotifPipeline).addCollectionListener(notifCollectionCaptor.capture()); 103 verify(mNotifPipeline).addPromoter(notifPromoterCaptor.capture()); 104 verify(mNotifPipeline).addNotificationLifetimeExtender( 105 notifLifetimeExtenderCaptor.capture()); 106 verify(mHeadsUpManager).addListener(headsUpChangedListenerCaptor.capture()); 107 108 mCollectionListener = notifCollectionCaptor.getValue(); 109 mNotifPromoter = notifPromoterCaptor.getValue(); 110 mNotifLifetimeExtender = notifLifetimeExtenderCaptor.getValue(); 111 mOnHeadsUpChangedListener = headsUpChangedListenerCaptor.getValue(); 112 113 mNotifSectioner = mCoordinator.getSectioner(); 114 mNotifLifetimeExtender.setCallback(mEndLifetimeExtension); 115 mEntry = new NotificationEntryBuilder().build(); 116 } 117 118 @Test testPromotesCurrentHUN()119 public void testPromotesCurrentHUN() { 120 // GIVEN the current HUN is set to mEntry 121 setCurrentHUN(mEntry); 122 123 // THEN only promote the current HUN, mEntry 124 assertTrue(mNotifPromoter.shouldPromoteToTopLevel(mEntry)); 125 assertFalse(mNotifPromoter.shouldPromoteToTopLevel(new NotificationEntryBuilder().build())); 126 } 127 128 @Test testIncludeInSectionCurrentHUN()129 public void testIncludeInSectionCurrentHUN() { 130 // GIVEN the current HUN is set to mEntry 131 setCurrentHUN(mEntry); 132 133 // THEN only section the current HUN, mEntry 134 assertTrue(mNotifSectioner.isInSection(mEntry)); 135 assertFalse(mNotifSectioner.isInSection(new NotificationEntryBuilder().build())); 136 } 137 138 @Test testLifetimeExtendsCurrentHUN()139 public void testLifetimeExtendsCurrentHUN() { 140 // GIVEN there is a HUN, mEntry 141 setCurrentHUN(mEntry); 142 143 // THEN only the current HUN, mEntry, should be lifetimeExtended 144 assertTrue(mNotifLifetimeExtender.shouldExtendLifetime(mEntry, /* cancellationReason */ 0)); 145 assertFalse(mNotifLifetimeExtender.shouldExtendLifetime( 146 new NotificationEntryBuilder().build(), /* cancellationReason */ 0)); 147 } 148 149 @Test testLifetimeExtensionEndsOnNewHUN()150 public void testLifetimeExtensionEndsOnNewHUN() { 151 // GIVEN there was a HUN that was lifetime extended 152 setCurrentHUN(mEntry); 153 assertTrue(mNotifLifetimeExtender.shouldExtendLifetime( 154 mEntry, /* cancellation reason */ 0)); 155 156 // WHEN there's a new HUN 157 NotificationEntry newHUN = new NotificationEntryBuilder().build(); 158 setCurrentHUN(newHUN); 159 160 // THEN the old entry's lifetime extension should be cancelled 161 verify(mEndLifetimeExtension).onEndLifetimeExtension(mNotifLifetimeExtender, mEntry); 162 } 163 164 @Test testLifetimeExtensionEndsOnNoHUNs()165 public void testLifetimeExtensionEndsOnNoHUNs() { 166 // GIVEN there was a HUN that was lifetime extended 167 setCurrentHUN(mEntry); 168 assertTrue(mNotifLifetimeExtender.shouldExtendLifetime( 169 mEntry, /* cancellation reason */ 0)); 170 171 // WHEN there's no longer a HUN 172 setCurrentHUN(null); 173 174 // THEN the old entry's lifetime extension should be cancelled 175 verify(mEndLifetimeExtension).onEndLifetimeExtension(mNotifLifetimeExtender, mEntry); 176 } 177 178 @Test testShowHUNOnInflationFinished()179 public void testShowHUNOnInflationFinished() { 180 // WHEN a notification should HUN and its inflation is finished 181 when(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true); 182 183 ArgumentCaptor<BindCallback> bindCallbackCaptor = 184 ArgumentCaptor.forClass(BindCallback.class); 185 mCollectionListener.onEntryAdded(mEntry); 186 verify(mHeadsUpViewBinder).bindHeadsUpView(eq(mEntry), bindCallbackCaptor.capture()); 187 188 bindCallbackCaptor.getValue().onBindFinished(mEntry); 189 190 // THEN we tell the HeadsUpManager to show the notification 191 verify(mHeadsUpManager).showNotification(mEntry); 192 } 193 194 @Test testNoHUNOnInflationFinished()195 public void testNoHUNOnInflationFinished() { 196 // WHEN a notification shouldn't HUN and its inflation is finished 197 when(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(false); 198 ArgumentCaptor<BindCallback> bindCallbackCaptor = 199 ArgumentCaptor.forClass(BindCallback.class); 200 mCollectionListener.onEntryAdded(mEntry); 201 202 // THEN we never bind the heads up view or tell HeadsUpManager to show the notification 203 verify(mHeadsUpViewBinder, never()).bindHeadsUpView( 204 eq(mEntry), bindCallbackCaptor.capture()); 205 verify(mHeadsUpManager, never()).showNotification(mEntry); 206 } 207 208 @Test testOnEntryRemovedRemovesHeadsUpNotification()209 public void testOnEntryRemovedRemovesHeadsUpNotification() { 210 // GIVEN the current HUN is mEntry 211 setCurrentHUN(mEntry); 212 213 // WHEN mEntry is removed from the notification collection 214 mCollectionListener.onEntryRemoved(mEntry, /* cancellation reason */ 0); 215 when(mRemoteInputManager.isSpinning(any())).thenReturn(false); 216 217 // THEN heads up manager should remove the entry 218 verify(mHeadsUpManager).removeNotification(mEntry.getKey(), false); 219 } 220 setCurrentHUN(NotificationEntry entry)221 private void setCurrentHUN(NotificationEntry entry) { 222 when(mHeadsUpManager.getTopEntry()).thenReturn(entry); 223 when(mHeadsUpManager.isAlerting(any())).thenReturn(false); 224 if (entry != null) { 225 when(mHeadsUpManager.isAlerting(entry.getKey())).thenReturn(true); 226 } 227 mOnHeadsUpChangedListener.onHeadsUpStateChanged(entry, entry != null); 228 } 229 } 230