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