1 /* 2 * Copyright (C) 2019 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 android.app.NotificationManager.IMPORTANCE_DEFAULT; 20 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; 21 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST; 22 23 import static org.junit.Assert.assertEquals; 24 import static org.junit.Assert.assertFalse; 25 import static org.junit.Assert.assertTrue; 26 import static org.mockito.ArgumentMatchers.any; 27 import static org.mockito.ArgumentMatchers.eq; 28 import static org.mockito.Mockito.never; 29 import static org.mockito.Mockito.reset; 30 import static org.mockito.Mockito.spy; 31 import static org.mockito.Mockito.times; 32 import static org.mockito.Mockito.verify; 33 import static org.mockito.Mockito.when; 34 35 import android.app.Notification; 36 import android.app.NotificationChannel; 37 import android.app.NotificationManager; 38 import android.testing.AndroidTestingRunner; 39 40 import androidx.annotation.Nullable; 41 import androidx.test.filters.SmallTest; 42 43 import com.android.systemui.SysuiTestCase; 44 import com.android.systemui.plugins.statusbar.StatusBarStateController; 45 import com.android.systemui.statusbar.RankingBuilder; 46 import com.android.systemui.statusbar.SbnBuilder; 47 import com.android.systemui.statusbar.notification.collection.ListEntry; 48 import com.android.systemui.statusbar.notification.collection.NotifPipeline; 49 import com.android.systemui.statusbar.notification.collection.NotificationEntry; 50 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; 51 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; 52 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner; 53 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable; 54 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; 55 import com.android.systemui.statusbar.notification.collection.render.NodeController; 56 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController; 57 58 import org.junit.Before; 59 import org.junit.Test; 60 import org.junit.runner.RunWith; 61 import org.mockito.ArgumentCaptor; 62 import org.mockito.Captor; 63 import org.mockito.Mock; 64 import org.mockito.MockitoAnnotations; 65 66 import java.util.ArrayList; 67 import java.util.Arrays; 68 69 @SmallTest 70 @RunWith(AndroidTestingRunner.class) 71 public class RankingCoordinatorTest extends SysuiTestCase { 72 73 @Mock private StatusBarStateController mStatusBarStateController; 74 @Mock private HighPriorityProvider mHighPriorityProvider; 75 @Mock private NotifPipeline mNotifPipeline; 76 @Mock private NodeController mAlertingHeaderController; 77 @Mock private NodeController mSilentNodeController; 78 @Mock private SectionHeaderController mSilentHeaderController; 79 @Mock private Pluggable.PluggableListener<NotifFilter> mInvalidationListener; 80 81 @Captor private ArgumentCaptor<NotifFilter> mNotifFilterCaptor; 82 @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerCaptor; 83 84 private NotificationEntry mEntry; 85 private NotifFilter mCapturedSuspendedFilter; 86 private NotifFilter mCapturedDozingFilter; 87 private StatusBarStateController.StateListener mStatusBarStateCallback; 88 private RankingCoordinator mRankingCoordinator; 89 90 private NotifSectioner mAlertingSectioner; 91 private NotifSectioner mSilentSectioner; 92 private NotifSectioner mMinimizedSectioner; 93 private ArrayList<NotifSectioner> mSections = new ArrayList<>(3); 94 95 @Before setup()96 public void setup() { 97 MockitoAnnotations.initMocks(this); 98 mRankingCoordinator = new RankingCoordinator( 99 mStatusBarStateController, 100 mHighPriorityProvider, 101 mAlertingHeaderController, 102 mSilentHeaderController, 103 mSilentNodeController); 104 mEntry = spy(new NotificationEntryBuilder().build()); 105 mEntry.setRanking(getRankingForUnfilteredNotif().build()); 106 107 mRankingCoordinator.attach(mNotifPipeline); 108 verify(mNotifPipeline, times(2)).addPreGroupFilter(mNotifFilterCaptor.capture()); 109 mCapturedSuspendedFilter = mNotifFilterCaptor.getAllValues().get(0); 110 mCapturedDozingFilter = mNotifFilterCaptor.getAllValues().get(1); 111 mCapturedDozingFilter.setInvalidationListener(mInvalidationListener); 112 113 verify(mStatusBarStateController, times(1)).addCallback(mStateListenerCaptor.capture()); 114 mStatusBarStateCallback = mStateListenerCaptor.getAllValues().get(0); 115 116 mAlertingSectioner = mRankingCoordinator.getAlertingSectioner(); 117 mSilentSectioner = mRankingCoordinator.getSilentSectioner(); 118 mMinimizedSectioner = mRankingCoordinator.getMinimizedSectioner(); 119 mSections.addAll(Arrays.asList(mAlertingSectioner, mSilentSectioner, mMinimizedSectioner)); 120 } 121 122 @Test testSilentHeaderClearableChildrenUpdate()123 public void testSilentHeaderClearableChildrenUpdate() { 124 ListEntry listEntry = new ListEntry(mEntry.getKey(), 0L) { 125 @Nullable 126 @Override 127 public NotificationEntry getRepresentativeEntry() { 128 return mEntry; 129 } 130 }; 131 setRankingAmbient(false); 132 setSbnClearable(true); 133 mSilentSectioner.onEntriesUpdated(Arrays.asList(listEntry)); 134 verify(mSilentHeaderController).setClearSectionEnabled(eq(true)); 135 136 setSbnClearable(false); 137 mSilentSectioner.onEntriesUpdated(Arrays.asList(listEntry)); 138 verify(mSilentHeaderController).setClearSectionEnabled(eq(false)); 139 } 140 141 @Test testUnfilteredState()142 public void testUnfilteredState() { 143 // GIVEN no suppressed visual effects + app not suspended 144 mEntry.setRanking(getRankingForUnfilteredNotif().build()); 145 146 // THEN don't filter out the notification 147 assertFalse(mCapturedSuspendedFilter.shouldFilterOut(mEntry, 0)); 148 } 149 150 @Test filterSuspended()151 public void filterSuspended() { 152 // GIVEN the notification's app is suspended 153 mEntry.setRanking(getRankingForUnfilteredNotif() 154 .setSuspended(true) 155 .build()); 156 157 // THEN filter out the notification 158 assertTrue(mCapturedSuspendedFilter.shouldFilterOut(mEntry, 0)); 159 } 160 161 @Test filterDozingSuppressAmbient()162 public void filterDozingSuppressAmbient() { 163 // GIVEN should suppress ambient 164 mEntry.setRanking(getRankingForUnfilteredNotif() 165 .setSuppressedVisualEffects(SUPPRESSED_EFFECT_AMBIENT) 166 .build()); 167 168 // WHEN it's dozing (on ambient display) 169 when(mStatusBarStateController.isDozing()).thenReturn(true); 170 171 // THEN filter out the notification 172 assertTrue(mCapturedDozingFilter.shouldFilterOut(mEntry, 0)); 173 174 // WHEN it's not dozing (showing the notification list) 175 when(mStatusBarStateController.isDozing()).thenReturn(false); 176 177 // THEN don't filter out the notification 178 assertFalse(mCapturedDozingFilter.shouldFilterOut(mEntry, 0)); 179 180 // WHEN it's not dozing and doze amount is 1 181 when(mStatusBarStateController.isDozing()).thenReturn(false); 182 when(mStatusBarStateController.getDozeAmount()).thenReturn(1f); 183 184 // THEN filter out the notification 185 assertTrue(mCapturedDozingFilter.shouldFilterOut(mEntry, 0)); 186 } 187 188 @Test filterDozingSuppressNotificationList()189 public void filterDozingSuppressNotificationList() { 190 // GIVEN should suppress from the notification list 191 mEntry.setRanking(getRankingForUnfilteredNotif() 192 .setSuppressedVisualEffects(SUPPRESSED_EFFECT_NOTIFICATION_LIST) 193 .build()); 194 195 // WHEN it's dozing (on ambient display) 196 when(mStatusBarStateController.isDozing()).thenReturn(true); 197 198 // THEN don't filter out the notification 199 assertFalse(mCapturedDozingFilter.shouldFilterOut(mEntry, 0)); 200 201 // WHEN it's not dozing (showing the notification list) 202 when(mStatusBarStateController.isDozing()).thenReturn(false); 203 204 // THEN filter out the notification 205 assertTrue(mCapturedDozingFilter.shouldFilterOut(mEntry, 0)); 206 } 207 208 @Test testIncludeInSectionAlerting()209 public void testIncludeInSectionAlerting() { 210 // GIVEN the entry is high priority 211 when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(true); 212 213 // THEN entry is in the alerting section 214 assertTrue(mAlertingSectioner.isInSection(mEntry)); 215 assertFalse(mSilentSectioner.isInSection(mEntry)); 216 } 217 218 @Test testIncludeInSectionSilent()219 public void testIncludeInSectionSilent() { 220 // GIVEN the entry isn't high priority 221 when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false); 222 setRankingAmbient(false); 223 224 // THEN entry is in the silent section 225 assertFalse(mAlertingSectioner.isInSection(mEntry)); 226 assertTrue(mSilentSectioner.isInSection(mEntry)); 227 } 228 229 @Test testMinSection()230 public void testMinSection() { 231 when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false); 232 setRankingAmbient(true); 233 assertInSection(mEntry, mMinimizedSectioner); 234 } 235 236 @Test testSilentSection()237 public void testSilentSection() { 238 when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false); 239 setRankingAmbient(false); 240 assertInSection(mEntry, mSilentSectioner); 241 } 242 243 @Test testClearableSilentSection()244 public void testClearableSilentSection() { 245 when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false); 246 setSbnClearable(true); 247 setRankingAmbient(false); 248 mSilentSectioner.onEntriesUpdated(Arrays.asList(mEntry)); 249 verify(mSilentHeaderController).setClearSectionEnabled(eq(true)); 250 } 251 252 @Test testClearableMinimizedSection()253 public void testClearableMinimizedSection() { 254 when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false); 255 setSbnClearable(true); 256 setRankingAmbient(true); 257 mMinimizedSectioner.onEntriesUpdated(Arrays.asList(mEntry)); 258 verify(mSilentHeaderController).setClearSectionEnabled(eq(true)); 259 } 260 261 @Test testNotClearableSilentSection()262 public void testNotClearableSilentSection() { 263 setSbnClearable(false); 264 when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false); 265 setRankingAmbient(false); 266 mSilentSectioner.onEntriesUpdated(Arrays.asList(mEntry)); 267 mMinimizedSectioner.onEntriesUpdated(Arrays.asList(mEntry)); 268 mAlertingSectioner.onEntriesUpdated(Arrays.asList(mEntry)); 269 verify(mSilentHeaderController, times(2)).setClearSectionEnabled(eq(false)); 270 } 271 272 @Test testNotClearableMinimizedSection()273 public void testNotClearableMinimizedSection() { 274 setSbnClearable(false); 275 when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false); 276 setRankingAmbient(true); 277 mSilentSectioner.onEntriesUpdated(Arrays.asList(mEntry)); 278 mMinimizedSectioner.onEntriesUpdated(Arrays.asList(mEntry)); 279 mAlertingSectioner.onEntriesUpdated(Arrays.asList(mEntry)); 280 verify(mSilentHeaderController, times(2)).setClearSectionEnabled(eq(false)); 281 } 282 283 @Test statusBarStateCallbackTest()284 public void statusBarStateCallbackTest() { 285 mStatusBarStateCallback.onDozeAmountChanged(1f, 1f); 286 verify(mInvalidationListener, times(1)) 287 .onPluggableInvalidated(mCapturedDozingFilter, "dozeAmount changed to one"); 288 reset(mInvalidationListener); 289 290 mStatusBarStateCallback.onDozeAmountChanged(1f, 1f); 291 verify(mInvalidationListener, never()).onPluggableInvalidated(any(), any()); 292 reset(mInvalidationListener); 293 294 mStatusBarStateCallback.onDozeAmountChanged(0.6f, 0.6f); 295 verify(mInvalidationListener, times(1)) 296 .onPluggableInvalidated(mCapturedDozingFilter, "dozeAmount changed to not one"); 297 reset(mInvalidationListener); 298 299 mStatusBarStateCallback.onDozeAmountChanged(0f, 0f); 300 verify(mInvalidationListener, never()).onPluggableInvalidated(any(), any()); 301 reset(mInvalidationListener); 302 } 303 assertInSection(NotificationEntry entry, NotifSectioner section)304 private void assertInSection(NotificationEntry entry, NotifSectioner section) { 305 for (NotifSectioner current: mSections) { 306 if (current == section) { 307 assertTrue(current.isInSection(entry)); 308 } else { 309 assertFalse(current.isInSection(entry)); 310 } 311 } 312 } 313 getRankingForUnfilteredNotif()314 private RankingBuilder getRankingForUnfilteredNotif() { 315 return new RankingBuilder(mEntry.getRanking()) 316 .setChannel(new NotificationChannel("id", null, IMPORTANCE_DEFAULT)) 317 .setSuppressedVisualEffects(0) 318 .setSuspended(false); 319 } 320 setSbnClearable(boolean clearable)321 private void setSbnClearable(boolean clearable) { 322 mEntry.setSbn(new SbnBuilder(mEntry.getSbn()) 323 .setFlag(mContext, Notification.FLAG_NO_CLEAR, !clearable) 324 .build()); 325 assertEquals(clearable, mEntry.getSbn().isClearable()); 326 } 327 setRankingAmbient(boolean ambient)328 private void setRankingAmbient(boolean ambient) { 329 mEntry.setRanking(new RankingBuilder(mEntry.getRanking()) 330 .setImportance(ambient 331 ? NotificationManager.IMPORTANCE_MIN 332 : IMPORTANCE_DEFAULT) 333 .build()); 334 assertEquals(ambient, mEntry.getRanking().isAmbient()); 335 } 336 } 337